@wrongstack/plugins 0.308.6 → 0.309.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 (51) hide show
  1. package/dist/accessibility-auditor.js +26 -82
  2. package/dist/agent-handoff.js +16 -26
  3. package/dist/auto-i18n-extractor.js +19 -23
  4. package/dist/branch-guard.js +19 -111
  5. package/dist/changelog-writer.js +20 -133
  6. package/dist/code-metrics.js +26 -71
  7. package/dist/commit-validator.js +16 -14
  8. package/dist/config-validator.js +18 -22
  9. package/dist/cost-tracker.js +15 -96
  10. package/dist/dead-code-detector.js +27 -31
  11. package/dist/dependency-vulnerability-gate.js +18 -15
  12. package/dist/diff-summary.js +19 -128
  13. package/dist/doc-sync-guard.js +24 -39
  14. package/dist/duplicate-code-detector.js +30 -169
  15. package/dist/feature-flag-tracker.js +26 -71
  16. package/dist/file-watcher.js +19 -23
  17. package/dist/format-on-save.js +25 -214
  18. package/dist/import-organizer.js +31 -106
  19. package/dist/index.js +452 -1236
  20. package/dist/interface-contract-guard.js +26 -71
  21. package/dist/lint-gate.js +19 -111
  22. package/dist/migration-planner.js +28 -120
  23. package/dist/notify-hub.js +18 -28
  24. package/dist/path-guard.js +27 -102
  25. package/dist/pr-drafter.js +18 -16
  26. package/dist/prompt-firewall.js +8 -205
  27. package/dist/refactor-suggester.js +27 -166
  28. package/dist/release-notes-generator.js +6 -35
  29. package/dist/runtime/bounded-map.d.ts +2 -85
  30. package/dist/runtime/credential-patterns.d.ts +2 -41
  31. package/dist/runtime/h1-state.d.ts +2 -61
  32. package/dist/runtime/handles.d.ts +2 -45
  33. package/dist/runtime/index.d.ts +8 -180
  34. package/dist/runtime/llm.d.ts +2 -43
  35. package/dist/runtime/local-bin.d.ts +2 -119
  36. package/dist/runtime/redos-guard.d.ts +2 -68
  37. package/dist/runtime/safe-json.d.ts +2 -24
  38. package/dist/runtime/sandbox.d.ts +2 -58
  39. package/dist/runtime.js +1 -868
  40. package/dist/schema-evolution-guard.js +20 -24
  41. package/dist/secret-scanner.js +22 -146
  42. package/dist/security-hotspot-scanner.js +24 -154
  43. package/dist/session-recap.js +16 -14
  44. package/dist/spec-linker.js +19 -17
  45. package/dist/template-engine.js +22 -37
  46. package/dist/test-coverage-gate.js +19 -34
  47. package/dist/test-generator.js +6 -35
  48. package/dist/test-runner-gate.js +34 -143
  49. package/dist/todo-listener.js +17 -38
  50. package/dist/type-gate.js +23 -311
  51. package/package.json +4 -3
@@ -2,41 +2,12 @@
2
2
  import { execFile } from "node:child_process";
3
3
 
4
4
  // src/runtime/llm.ts
5
- function stripOuterMarkdownFence(text) {
6
- const trimmed = text.trim();
7
- const match = trimmed.match(/^```(?:[a-z0-9_-]+)?\s*\r?\n([\s\S]*?)\r?\n```$/i);
8
- return (match?.[1] ?? trimmed).trim();
9
- }
10
- async function runOptionalPluginLlm(request) {
11
- if (!request.requested) {
12
- return { used: false, value: null, fallbackReason: "not-requested" };
13
- }
14
- if (!request.api.llm) {
15
- return { used: false, value: null, fallbackReason: "unavailable" };
16
- }
17
- if (request.options?.signal?.aborted) {
18
- return { used: false, value: null, fallbackReason: "cancelled" };
19
- }
20
- try {
21
- const response = await request.api.llm.complete(request.prompt, request.options);
22
- const parsed = request.parse(response.text);
23
- if (parsed === null) {
24
- request.api.log.warn(`${request.label}: ignored invalid LLM response`);
25
- return { used: false, value: null, fallbackReason: "invalid-response" };
26
- }
27
- return { used: true, value: parsed, fallbackReason: null };
28
- } catch (error) {
29
- const cancelled = request.options?.signal?.aborted === true;
30
- request.api.log.warn(`${request.label}: LLM enrichment failed; using deterministic fallback`, {
31
- error: error instanceof Error ? error.message : String(error)
32
- });
33
- return {
34
- used: false,
35
- value: null,
36
- fallbackReason: cancelled ? "cancelled" : "provider-error"
37
- };
38
- }
39
- }
5
+ import {
6
+ parseLlmJsonObject,
7
+ runOptionalPluginCouncil,
8
+ runOptionalPluginLlm,
9
+ stripOuterMarkdownFence
10
+ } from "@wrongstack/plugin-sdk/runtime";
40
11
 
41
12
  // src/release-notes-generator/index.ts
42
13
  var API_VERSION = "^0.1.10";
@@ -1,86 +1,3 @@
1
- /**
2
- * @wrongstack/plugins bounded key/value cache.
3
- *
4
- * Several plugins memoise per-path or per-model work in a module-scope
5
- * `Map` that is only ever cleared at teardown. In a short test that is
6
- * invisible; in a long session over a large repository it is a slow leak —
7
- * one entry per file touched, per branch resolved, per model seen — held
8
- * for the lifetime of the process.
9
- *
10
- * `BoundedMap` is a drop-in replacement for those maps: same `get`/`set`/
11
- * `delete`/`clear`/`size` surface, plus a hard entry cap with
12
- * least-recently-used eviction and an optional per-entry TTL. Nothing
13
- * about the call sites has to change beyond the constructor.
14
- *
15
- * Why LRU rather than insertion-order eviction: the access pattern for
16
- * these caches is "a few hot paths, a long tail of cold ones". Evicting by
17
- * insertion order throws away the hot entries first, which is exactly
18
- * backwards; re-reading a hot entry should keep it.
19
- */
20
- export interface BoundedMapOptions {
21
- /** Hard cap on retained entries. Must be a safe integer >= 1. */
22
- max: number;
23
- /**
24
- * Optional per-entry lifetime in ms. An entry older than this is treated
25
- * as absent by `get`/`has` and dropped on access. Omit for no expiry.
26
- */
27
- ttlMs?: number | undefined;
28
- /** Clock injection point — tests override this instead of faking timers. */
29
- now?: (() => number) | undefined;
30
- }
31
- export declare class BoundedMap<K, V> {
32
- private readonly map;
33
- private readonly max;
34
- private readonly ttlMs;
35
- private readonly now;
36
- /** Entries dropped to stay under `max`. Surfaced by plugin health(). */
37
- private evictions;
38
- constructor(options: BoundedMapOptions);
39
- private expired;
40
- get(key: K): V | undefined;
41
- /**
42
- * Read without promoting the key to most-recently-used. Use for
43
- * diagnostics that must not perturb the eviction order.
44
- */
45
- peek(key: K): V | undefined;
46
- has(key: K): boolean;
47
- set(key: K, value: V): this;
48
- delete(key: K): boolean;
49
- clear(): void;
50
- get size(): number;
51
- /** How many entries have been dropped to respect `max`, since the last clear. */
52
- get evictionCount(): number;
53
- /** Drop every expired entry. Cheap enough to call from a status tool. */
54
- prune(): number;
55
- /** Live (non-expired) entries, coldest first. */
56
- entries(): IterableIterator<[K, V]>;
57
- [Symbol.iterator](): IterableIterator<[K, V]>;
58
- }
59
- /**
60
- * A `Set` with the same hard cap and LRU eviction as {@link BoundedMap}.
61
- *
62
- * Plugins use module-scope sets to remember "already reported" keys so a
63
- * finding is surfaced once rather than on every scan. That set has to
64
- * outlive a single scan, which is why it lives at module scope — but
65
- * without a bound it grows one entry per distinct finding for the life of
66
- * the process.
67
- *
68
- * Eviction means a very old key may eventually be reported a second time.
69
- * That is the right trade: re-reporting a finding the user last saw
70
- * thousands of findings ago is reasonable behaviour, whereas unbounded
71
- * growth is not.
72
- */
73
- export declare class BoundedSet<T> {
74
- private readonly inner;
75
- constructor(options: BoundedMapOptions);
76
- has(value: T): boolean;
77
- add(value: T): this;
78
- delete(value: T): boolean;
79
- clear(): void;
80
- get size(): number;
81
- /** How many entries have been dropped to respect `max`, since the last clear. */
82
- get evictionCount(): number;
83
- values(): IterableIterator<T>;
84
- [Symbol.iterator](): IterableIterator<T>;
85
- }
1
+ /** Shim: implementation moved to @wrongstack/plugin-sdk/runtime. */
2
+ export { BoundedMap, BoundedSet, type BoundedMapOptions } from '@wrongstack/plugin-sdk/runtime';
86
3
  //# sourceMappingURL=bounded-map.d.ts.map
@@ -1,42 +1,3 @@
1
- /**
2
- * @wrongstack/plugins — canonical credential-pattern table.
3
- *
4
- * Two plugins detect credentials, at different points in the pipeline:
5
- *
6
- * - `secret-scanner` gates tool *input* and inspects tool *output*.
7
- * - `prompt-firewall` inspects the outgoing *provider request* (and the
8
- * response) so a credential sitting in context is not shipped to a
9
- * third party.
10
- *
11
- * They previously each carried their own pattern list. The lists drifted:
12
- * one grew GitLab, npm, SendGrid and DigitalOcean coverage while the other
13
- * did not, so which credentials were caught depended on which side of the
14
- * pipeline they crossed. A credential is a credential — the two surfaces
15
- * should never disagree about what one looks like.
16
- *
17
- * This module is the single source of truth. Adding a pattern here
18
- * strengthens both surfaces at once.
19
- *
20
- * Style notes for new entries:
21
- * - Prefer explicit lookaround boundaries — `(?<![A-Za-z0-9])` /
22
- * `(?![A-Za-z0-9])` — over ``. `` is defined against word
23
- * characters, so it silently fails next to the `+`, `/` and `=` that
24
- * base64-shaped secrets routinely end with.
25
- * - Keep every group non-capturing (`(?:…)`). The combined regex maps a
26
- * capture-group index back to the pattern that fired; an inner group
27
- * shifts that mapping. `secret-scanner` compensates for user-supplied
28
- * patterns, but built-ins should not need it.
29
- * - Avoid unbounded `.*` lookarounds: these run on every provider
30
- * request, against the full context.
31
- */
32
- /** One credential shape, with a stable machine-readable id. */
33
- export interface CredentialPattern {
34
- /** Stable id, e.g. `github_pat`. Reported to users and in metrics. */
35
- type: string;
36
- /** Global-flagged matcher. */
37
- regex: RegExp;
38
- }
39
- export declare const CREDENTIAL_PATTERNS: readonly CredentialPattern[];
40
- /** Fresh, independently-stateful copies (RegExp `lastIndex` is mutable). */
41
- export declare function cloneCredentialPatterns(): CredentialPattern[];
1
+ /** Shim: implementation moved to @wrongstack/plugin-sdk/runtime. */
2
+ export { cloneCredentialPatterns, CREDENTIAL_PATTERNS, type CredentialPattern, } from '@wrongstack/plugin-sdk/runtime';
42
3
  //# sourceMappingURL=credential-patterns.d.ts.map
@@ -1,62 +1,3 @@
1
- /**
2
- * H1 idempotent state single-slot plugin state with a registry of
3
- * releasable handles that survives `setup()` reload cycles.
4
- *
5
- * The "H1 audit pattern" (per SAGE memory T-03) is documented across
6
- * the plugin suite: a plugin's module-scope `state` object holds
7
- * counters plus a `hookUnregister` (or `extensionUnregister`) slot;
8
- * on reload, the slot MUST be released before a new one is stored.
9
- * Every plugin implements this inline with subtle variations:
10
- *
11
- * - some use `releaseHandle(state.hookUnregister)` (`accessibility-auditor`)
12
- * - some use `try { state.hookUnregister(); } catch {}` (`config-validator`)
13
- * - some use a single inline `if (state.hookUnregister) { … }` block
14
- *
15
- * The drift cost: in 4 plugins the prior handle was leaked on reload
16
- * because the inline `if` check raced with the new registration.
17
- * This helper centralises the contract.
18
- *
19
- * Contract:
20
- * `createH1State<T>(initial)` returns
21
- * {
22
- * state: T, // the user's mutable state
23
- * register: (key, unregister) => void,
24
- * release: (key) => void,
25
- * releaseAll: () => void,
26
- * }
27
- *
28
- * - `register(key, unregister)` releases any prior handle at `key`
29
- * before storing the new one.
30
- * - `release(key)` is a no-op if no handle is registered.
31
- * - `releaseAll()` releases every registered handle and clears the map.
32
- * - A throwing unregister function is swallowed (best-effort), matching
33
- * the existing `releaseHandle` semantics at `runtime/handles.ts`.
34
- *
35
- * The state object itself is NOT reset by `releaseAll` — counter
36
- * reset is the plugin's responsibility (it knows the semantics of its
37
- * counters). This helper owns the handle lifecycle only.
38
- */
39
- export type Unregister = () => void;
40
- export interface H1State<T> {
41
- /** The plugin's mutable state. Owned by the caller; never reset by this helper. */
42
- state: T;
43
- /**
44
- * Register an unregister function under `key`. Any prior handle at
45
- * `key` is released first. Throwing unregister functions are
46
- * swallowed.
47
- */
48
- register: (key: string, unregister: Unregister | null | undefined) => void;
49
- /**
50
- * Release the handle at `key` (if any). Idempotent. Throwing
51
- * unregister functions are swallowed.
52
- */
53
- release: (key: string) => void;
54
- /** Release every registered handle. Idempotent. */
55
- releaseAll: () => void;
56
- /** Number of currently registered handles. Observability for health()/status tools. */
57
- size: () => number;
58
- /** List the registered keys. Order is insertion order; useful for diagnostics. */
59
- keys: () => string[];
60
- }
61
- export declare function createH1State<T>(initial: T): H1State<T>;
1
+ /** Shim: implementation moved to @wrongstack/plugin-sdk/runtime. */
2
+ export { createH1State, type H1State } from '@wrongstack/plugin-sdk/runtime';
62
3
  //# sourceMappingURL=h1-state.d.ts.map
@@ -1,46 +1,3 @@
1
- /**
2
- * @wrongstack/plugins — unregister-handle discipline.
3
- *
4
- * Plugins keep their hook/listener unregister functions in module-scope
5
- * state, and every plugin's `setup()` is expected to be idempotent: calling
6
- * it twice must not leave two live registrations. The house style calls
7
- * this the "H1 pattern".
8
- *
9
- * The failure mode this helper exists to prevent is subtle and was present
10
- * in ~15 plugins: `setup()` reset the handle with
11
- *
12
- * ```ts
13
- * state.hookUnregister = null; // WRONG
14
- * ```
15
- *
16
- * which drops the only reference to the *previous* registration without
17
- * calling it. The old hook stays live in the registry, unreachable, and
18
- * fires alongside the new one — so counters double, warnings appear twice,
19
- * and a gate can block on a stale closure holding the previous config.
20
- *
21
- * `releaseHandle` makes the correct spelling a one-liner with the same
22
- * shape as the wrong one, so the two are hard to confuse:
23
- *
24
- * ```ts
25
- * state.hookUnregister = releaseHandle(state.hookUnregister);
26
- * ```
27
- */
28
- /** An unregister function returned by `registerHook`/`onEvent`/etc. */
29
- export type Unregister = (() => void) | null | undefined;
30
- /**
31
- * Invoke `off` if present, swallowing any error, and return `null` so the
32
- * caller can assign the result straight back to the handle.
33
- *
34
- * Unregistering is best-effort by design: a handle whose owner has already
35
- * been torn down may throw, and that must never prevent the rest of
36
- * `setup()`/`teardown()` from running.
37
- */
38
- export declare function releaseHandle(off: Unregister): null;
39
- /**
40
- * Release several handles held on one state object, clearing each in place.
41
- *
42
- * @example
43
- * releaseHandles(state, ['hookUnregister', 'postHookUnregister']);
44
- */
45
- export declare function releaseHandles<S extends object, K extends keyof S>(state: S, keys: readonly K[]): void;
1
+ /** Shim: implementation moved to @wrongstack/plugin-sdk/runtime. */
2
+ export { releaseHandle, releaseHandles, type Unregister } from '@wrongstack/plugin-sdk/runtime';
46
3
  //# sourceMappingURL=handles.d.ts.map
@@ -1,182 +1,10 @@
1
1
  /**
2
- * @wrongstack/plugins — Language-agnostic runtime helper.
3
- *
4
- * Plugins that need to invoke a build-time or test-time tool
5
- * (TypeScript's `tsc`, Node's `vitest`, Python's `pytest`, Rust's
6
- * `cargo`, Go's `go test`, etc.) share this module instead of
7
- * re-implementing argv/sandbox/allowlist logic.
8
- *
9
- * Why one module instead of three plugin-local copies?
10
- * - One audit surface for security-sensitive code: arg-splitting,
11
- * sandboxing, execFile-with-shell-false, maxBuffer, timeout.
12
- * - Plugins stay focused on their domain (linters, type-checkers,
13
- * test runners); the runtime helper owns the cross-cutting concern.
14
- * - New languages opt in by adding a `LanguageRuntime` entry; the
15
- * plugin layer keeps working unchanged.
16
- *
17
- * The public API is intentionally small:
18
- * - `LanguageRuntime`: declares which language a plugin targets.
19
- * - `resolveRunnerCommand(runtime, command, options)`: validate +
20
- * split a user-supplied command into a safe argv array.
21
- * - `sanitizeRunnerPath(value, options)`: reject paths outside
22
- * the project or that start with `-` (option smuggling).
23
- * - `runRunnerCommand(argv, options)`: spawn the resolved argv
24
- * with `shell:false`, capture stdout/stderr/code, and apply
25
- * timeout/abort.
26
- * - `probeRunner(runtime, argv, options)`: cheap availability check.
27
- *
28
- * Anything language-specific (flag tables, default commands,
29
- * output parsing) stays in the plugin that owns that language.
30
- */
31
- export { parseLlmJsonObject, runOptionalPluginCouncil, runOptionalPluginLlm, stripOuterMarkdownFence, type OptionalCouncilRequest, type OptionalLlmRequest, type OptionalLlmResult, } from './llm.js';
32
- export { BoundedMap, BoundedSet, type BoundedMapOptions } from './bounded-map.js';
33
- export { UNSERIALIZABLE, safeJsonStringify } from './safe-json.js';
34
- export { releaseHandle, releaseHandles, type Unregister } from './handles.js';
35
- export { withReDoSGuard, guardedMatcher, type ReDoSResult, type ReDoSOptions, } from './redos-guard.js';
36
- export { safePath, isInsideProject, type SafePathOptions, } from './sandbox.js';
37
- export { createH1State, type H1State, } from './h1-state.js';
38
- export { clearLocalBinCache, findOnPath, resolveExecInvocation, resolveFirstNodeBin, resolveNodeBin, resolveWin32Command, type ExecInvocation, type ResolvedNodeBin, } from './local-bin.js';
39
- export type LanguageId = 'typescript' | 'javascript' | 'python' | 'go' | 'rust' | 'shell' | 'ruby' | 'java' | 'kotlin' | 'dotnet' | 'generic';
40
- export type PackageManagerId = 'npm' | 'pnpm' | 'yarn' | 'bun' | 'pip' | 'poetry' | 'go' | 'cargo' | 'gem' | 'maven' | 'gradle' | 'dotnet' | 'none';
41
- export interface LanguageRuntime {
42
- /** Stable identifier for diagnostics and logging. */
43
- id: LanguageId;
44
- /**
45
- * Default package manager launcher if the plugin must spawn a tool
46
- * (e.g. `npx vitest` or `cargo test`). `none` means the executable
47
- * itself is invoked directly (no launcher).
48
- */
49
- packageManager: PackageManagerId;
50
- /**
51
- * Executable token that must appear as the second argv element when
52
- * a launcher is used (e.g. `vitest` after `pnpm exec`, `test` after
53
- * `cargo`). When `subcommands.length === 0` and the package manager
54
- * has no subcommand step, this is also accepted as the head token.
55
- */
56
- executable: string;
57
- /**
58
- * Allowlisted flag values, in addition to positional arguments and
59
- * file paths. `null` means "no flag allowlist enforced" — every
60
- * leading-dash token is still rejected, but no flag whitelist applies.
61
- */
62
- allowedFlags: ReadonlySet<string> | null;
63
- /**
64
- * Optional list of subcommand tokens that may follow the launcher,
65
- * e.g. `['exec']` for `pnpm exec tsc`. Empty array means the runner
66
- * executable must appear immediately as the second token (e.g.
67
- * `cargo test`, `go test`).
68
- */
69
- subcommands: readonly string[];
70
- /**
71
- * Default command spelling for `resolveRunnerCommand` when no
72
- * custom command is supplied. Plugins can override via config.
73
- */
74
- defaultCommand: string;
75
- }
76
- export interface ResolvedCommand {
77
- cmd: string;
78
- args: readonly string[];
79
- display: string;
80
- }
81
- export interface ResolveOptions {
82
- /**
83
- * Project root used to sandbox absolute executable paths. Defaults
84
- * to `process.cwd()`. Absolute executable paths must resolve inside
85
- * this directory; relative basenames must match `LanguageRuntime.executable`.
86
- */
87
- projectRoot?: string;
88
- }
89
- export interface RunOptions extends ResolveOptions {
90
- cwd: string;
91
- timeoutMs: number;
92
- signal?: AbortSignal;
93
- }
94
- export interface RunResult {
95
- code: number | null;
96
- stdout: string;
97
- stderr: string;
98
- timedOut: boolean;
99
- /** True when the executable could not be spawned (ENOENT, EPERM, …). */
100
- spawnError: boolean;
101
- }
102
- /**
103
- * Validate `value` as a sandboxed path inside the project. Returns the
104
- * canonical absolute path on success, `null` on rejection (empty,
105
- * outside the project, leading-dash, or longer than 4096 bytes).
106
- */
107
- export declare function sanitizeRunnerPath(value: string, options?: ResolveOptions): string | null;
108
- /**
109
- * Resolve a user-supplied command string into an argv-style invocation
110
- * using the language runtime's allowlist. Returns `null` if the command
111
- * fails closed (unknown launcher, unknown subcommand, disallowed flag,
112
- * metacharacters, leading-dash injection, absolute-path escape).
113
- */
114
- export declare function resolveRunnerCommand(runtime: LanguageRuntime, command: string, options?: ResolveOptions): ResolvedCommand | null;
115
- /**
116
- * Spawn the resolved argv with `shell:false`. Always uses
117
- * `execFile` (no shell, argv), captures stdout/stderr with a hard
118
- * `maxBuffer`, and respects `signal` + `timeoutMs`. Plugins can layer
119
- * language-specific output parsing on top of `RunResult`.
120
- */
121
- export declare function runRunnerCommand(argv: readonly string[], options: RunOptions): Promise<RunResult>;
122
- /**
123
- * Cheap availability check. Calls the runner's `--version`-like command
124
- * with a short timeout; returns true only when exit code is zero.
125
- */
126
- export declare function probeRunner(runtime: LanguageRuntime, probeArg: string | undefined, options: RunOptions): Promise<boolean>;
127
- /**
128
- * Check whether a file path is inside the project root. Uses
129
- * `process.cwd()` as the project boundary. Returns `true` for valid
130
- * paths inside the project, `false` for empty, too-long, outside,
131
- * or absolute paths that escape.
132
- *
133
- * This is the canonical sandbox check that every file-mutating or
134
- * file-reading plugin should call before touching a path supplied
135
- * by tool input. It replaces 27 identical copies across plugins.
136
- *
137
- * Performance: caches `process.cwd()` per call to avoid redundant
138
- * syscalls when checking multiple paths in the same tick.
139
- */
140
- export declare function withinProject(p: string): boolean;
141
- /**
142
- * Convenience: locate the runner binary on disk inside the project.
143
- * Returns the absolute path or `null`.
144
- */
145
- export declare function locateRunnerEntry(runtime: LanguageRuntime, projectRoot: string): string | null;
146
- export interface CollectOptions {
147
- /** File extensions to include (e.g. ['.ts', '.tsx', '.js']). */
148
- extensions: string[];
149
- /** Directory names to skip entirely. Default skips node_modules, dist, .git, coverage. */
150
- excludeDirs?: string[] | undefined;
151
- /** Maximum recursion depth. Unlimited when omitted. */
152
- maxDepth?: number | undefined;
153
- }
154
- /**
155
- * Recursively collect files under `root` whose extension is in
156
- * `opts.extensions`. Skips directories named in `opts.excludeDirs`
157
- * (defaulting to node_modules, dist, .git, coverage) and limits depth
158
- * when `opts.maxDepth` is set.
159
- *
160
- * Shared by 6+ plugin source-scan tools that previously duplicated
161
- * this implementation identically.
162
- *
163
- * Determinism: returns files in sorted order (locale-aware) so
164
- * scan results are reproducible across platforms and file systems.
165
- */
166
- export declare function collectSourceFiles(root: string, opts: CollectOptions): string[];
167
- /**
168
- * Async version of `collectSourceFiles` for non-blocking file collection.
169
- * Uses `fs.promises` to avoid blocking the event loop on large directory trees.
170
- *
171
- * Performance: prefer this in hooks and tools that run on every write/edit
172
- * (e.g., duplicate-code-detector) to keep the agent loop responsive during
173
- * large scans.
174
- */
175
- export declare function collectSourceFilesAsync(root: string, opts: CollectOptions): Promise<string[]>;
176
- /**
177
- * Check whether `p` has one of the given extensions (case-insensitive).
178
- * Replaces a 6-copy helper that was duplicated identically across
179
- * source-scan plugins.
180
- */
181
- export declare function matchesExtension(p: string, exts: string[]): boolean;
2
+ * Backwards-compatible re-export: the runtime helpers moved to
3
+ * `@wrongstack/plugin-sdk/runtime` so third-party plugin authors get the
4
+ * same audit-hardened helpers first-party plugins use, without depending
5
+ * on the whole @wrongstack/plugins package. This shim keeps the
6
+ * `@wrongstack/plugins/runtime` subpath (and every in-repo relative
7
+ * import) working unchanged.
8
+ */
9
+ export * from '@wrongstack/plugin-sdk/runtime';
182
10
  //# sourceMappingURL=index.d.ts.map
@@ -1,44 +1,3 @@
1
- /**
2
- * Small, shared helpers for optional plugin LLM enrichment.
3
- *
4
- * The host-owned `api.llm` facade keeps provider credentials and routing out
5
- * of plugins. These helpers standardise the other half of that contract:
6
- * bounded prompts, cancellation, defensive response parsing, and an explicit
7
- * deterministic fallback when no provider is wired or generation fails.
8
- */
9
- import type { CouncilOption, PluginAPI, PluginLLMOptions } from '@wrongstack/core/types';
10
- export interface OptionalLlmResult<T> {
11
- used: boolean;
12
- value: T | null;
13
- fallbackReason: 'not-requested' | 'unavailable' | 'cancelled' | 'provider-error' | 'invalid-response' | null;
14
- }
15
- export interface OptionalLlmRequest<T> {
16
- requested: boolean;
17
- prompt: string;
18
- options?: PluginLLMOptions | undefined;
19
- parse(text: string): T | null;
20
- api: Pick<PluginAPI, 'llm' | 'log'>;
21
- label: string;
22
- }
23
- export interface OptionalCouncilRequest<T> extends OptionalLlmRequest<T> {
24
- context?: string | undefined;
25
- profile?: string | undefined;
26
- councilOptions?: readonly CouncilOption[] | undefined;
27
- }
28
- /** Remove one outer Markdown fence without modifying inner code fences. */
29
- export declare function stripOuterMarkdownFence(text: string): string;
30
- /** Parse a JSON object from a plain or fenced provider response. */
31
- export declare function parseLlmJsonObject(text: string): Record<string, unknown> | null;
32
- /**
33
- * Run optional enrichment without turning a provider outage into a tool
34
- * failure. Abort remains observable as a fallback reason and the caller's
35
- * deterministic result remains authoritative.
36
- */
37
- export declare function runOptionalPluginLlm<T>(request: OptionalLlmRequest<T>): Promise<OptionalLlmResult<T>>;
38
- /**
39
- * Prefer the host Council for consequential analysis, then degrade through the
40
- * same One Shot helper and finally the caller's deterministic result. Council
41
- * outages therefore never turn an optional enrichment into a tool failure.
42
- */
43
- export declare function runOptionalPluginCouncil<T>(request: OptionalCouncilRequest<T>): Promise<OptionalLlmResult<T>>;
1
+ /** Shim: implementation moved to @wrongstack/plugin-sdk/runtime. */
2
+ export { parseLlmJsonObject, runOptionalPluginCouncil, runOptionalPluginLlm, stripOuterMarkdownFence, type OptionalCouncilRequest, type OptionalLlmRequest, type OptionalLlmResult, } from '@wrongstack/plugin-sdk/runtime';
44
3
  //# sourceMappingURL=llm.d.ts.map
@@ -1,120 +1,3 @@
1
- /**
2
- * @wrongstack/plugins — project-local binary resolution.
3
- *
4
- * Why this module exists
5
- * ----------------------
6
- * Several plugins need to run a Node CLI that ships with the project
7
- * (`biome`, `eslint`, `tsc`, `vitest`, …). The obvious spelling —
8
- * `execFile('npx', ['biome', …])` — is wrong in three separate ways:
9
- *
10
- * 1. **It does not run on Windows at all.** `npx`/`pnpm`/`npm` are
11
- * `.cmd` shims there, and `execFile`/`spawn` without a shell do not
12
- * consult `PATHEXT`. The call fails `ENOENT`, plugins swallow the
13
- * error as "tool not installed", and the feature silently no-ops on
14
- * every Windows machine.
15
- * 2. **It is slow.** `npx` re-resolves the package on each invocation,
16
- * and for a missing package it may try to *download* one — on a hook
17
- * that fires after every write.
18
- * 3. **It is a weaker sandbox.** The shim is a shell script; arguments
19
- * cross a `cmd.exe` boundary on Windows (the BatBadBut class).
20
- *
21
- * `lint-gate` and `test-flake-detector` already solved this locally by
22
- * resolving the package's `bin` entry through `createRequire` and running
23
- * it as `process.execPath <entry>`. That is the correct pattern: no shell,
24
- * no shim, no network, identical on every platform. This module promotes
25
- * that proven approach to a shared helper so every plugin gets it.
26
- *
27
- * Fallback: when the package is genuinely absent from the project,
28
- * `resolveNodeBin` returns `null` and the caller decides whether to
29
- * degrade gracefully or fall back to a PATH lookup via
30
- * `resolveWin32Command` (exported here for that purpose).
31
- */
32
- import { resolveWin32Command } from '@wrongstack/tools/win32';
33
- export { resolveWin32Command };
34
- /** A spawn-ready invocation, already adjusted for the host platform. */
35
- export interface ExecInvocation {
36
- cmd: string;
37
- args: string[];
38
- /** Only ever true on the Windows `.cmd`/`.bat` shim path. */
39
- windowsVerbatimArguments: boolean;
40
- }
41
- /**
42
- * Turn a `(command, args)` pair into something `execFile`/`spawn` can
43
- * actually launch on this platform.
44
- *
45
- * On Windows, `npx`/`npm`/`pnpm`/`biome`/`tsc`/… ship as `.cmd` wrappers.
46
- * `execFile` and `spawn` without a shell ignore `PATHEXT`, so a bare
47
- * `execFile('npx', …)` fails `ENOENT` — and because every plugin treats a
48
- * spawn failure as "tool not installed", the feature silently no-ops on
49
- * every Windows machine. This resolves the real path first and, for a
50
- * `.cmd`/`.bat` shim, routes through `cmd.exe` with per-argument quoting
51
- * and a metacharacter guard (the BatBadBut argument-injection class), so
52
- * a dynamic path argument still cannot chain a second command.
53
- *
54
- * On non-Windows, and for real `.exe` binaries, this is a passthrough.
55
- *
56
- * Throws when an argument carries a `cmd.exe` metacharacter on the shim
57
- * path — callers should treat that as "skip", never as "run anyway".
58
- *
59
- * Prefer {@link resolveNodeBin} when the target is a Node CLI that the
60
- * project depends on: `node <bin-entry>` needs no shim at all.
61
- */
62
- export declare function resolveExecInvocation(command: string, args?: readonly string[]): ExecInvocation;
63
- /**
64
- * Locate `cmd` on `PATH`, or return `null` if it is not there.
65
- *
66
- * This is the existence check `resolveWin32Command` deliberately does not
67
- * provide: that function returns its input unchanged when nothing matches,
68
- * so a caller cannot distinguish "found `biome`" from "gave up and handed
69
- * back the string `biome`". Treating the passthrough as success makes a
70
- * missing tool look installed — the caller then spawns it, gets `ENOENT`,
71
- * and (because plugins read a spawn failure as "not installed") silently
72
- * does nothing while reporting itself healthy.
73
- *
74
- * Returns the resolved path on success. Walks `PATH` directly, applying
75
- * `PATHEXT` suffixes on Windows for a bare name and checking the execute
76
- * bit on POSIX.
77
- */
78
- export declare function findOnPath(cmd: string): string | null;
79
- /** A resolved, directly-spawnable invocation. Never a shell or a shim. */
80
- export interface ResolvedNodeBin {
81
- /** Always `process.execPath` — the Node binary running this process. */
82
- cmd: string;
83
- /** `[binEntryPath, ...extraArgs]`. */
84
- args: string[];
85
- /** Absolute path of the package's bin entry, for diagnostics. */
86
- entry: string;
87
- }
88
- /** Drop every cached resolution. Call from `teardown()`. */
89
- export declare function clearLocalBinCache(): void;
90
- /**
91
- * Resolve a project-local Node CLI to a `process.execPath <entry>`
92
- * invocation.
93
- *
94
- * @param packageName npm package to resolve, e.g. `@biomejs/biome`.
95
- * @param binName which `bin` key to prefer when the package declares
96
- * several. Falls back to the sole/first entry.
97
- * @param cwd project root whose `package.json` anchors resolution.
98
- * @param extraArgs arguments appended after the bin entry.
99
- *
100
- * Returns `null` when the package is not installed, declares no `bin`, or
101
- * the declared entry escapes its own package directory (a tampered
102
- * `package.json` must not become an arbitrary-file execution primitive).
103
- */
104
- export declare function resolveNodeBin(packageName: string, binName: string, cwd: string, extraArgs?: readonly string[]): ResolvedNodeBin | null;
105
- /**
106
- * Resolve the first installed package from `candidates`.
107
- *
108
- * Used by plugins that accept several equivalent tools (biome *or*
109
- * eslint, vitest *or* jest) and want the first one the project actually
110
- * has, without probing each with a subprocess.
111
- */
112
- export declare function resolveFirstNodeBin(candidates: readonly {
113
- packageName: string;
114
- binName: string;
115
- args?: readonly string[];
116
- }[], cwd: string): (ResolvedNodeBin & {
117
- packageName: string;
118
- binName: string;
119
- }) | null;
1
+ /** Shim: implementation moved to @wrongstack/plugin-sdk/runtime. */
2
+ export { clearLocalBinCache, findOnPath, resolveExecInvocation, resolveFirstNodeBin, resolveNodeBin, resolveWin32Command, type ExecInvocation, type ResolvedNodeBin, } from '@wrongstack/plugin-sdk/runtime';
120
3
  //# sourceMappingURL=local-bin.d.ts.map