@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.
- package/dist/accessibility-auditor.js +26 -82
- package/dist/agent-handoff.js +16 -26
- package/dist/auto-i18n-extractor.js +19 -23
- package/dist/branch-guard.js +19 -111
- package/dist/changelog-writer.js +20 -133
- package/dist/code-metrics.js +26 -71
- package/dist/commit-validator.js +16 -14
- package/dist/config-validator.js +18 -22
- package/dist/cost-tracker.js +15 -96
- package/dist/dead-code-detector.js +27 -31
- package/dist/dependency-vulnerability-gate.js +18 -15
- package/dist/diff-summary.js +19 -128
- package/dist/doc-sync-guard.js +24 -39
- package/dist/duplicate-code-detector.js +30 -169
- package/dist/feature-flag-tracker.js +26 -71
- package/dist/file-watcher.js +19 -23
- package/dist/format-on-save.js +25 -214
- package/dist/import-organizer.js +31 -106
- package/dist/index.js +452 -1236
- package/dist/interface-contract-guard.js +26 -71
- package/dist/lint-gate.js +19 -111
- package/dist/migration-planner.js +28 -120
- package/dist/notify-hub.js +18 -28
- package/dist/path-guard.js +27 -102
- package/dist/pr-drafter.js +18 -16
- package/dist/prompt-firewall.js +8 -205
- package/dist/refactor-suggester.js +27 -166
- package/dist/release-notes-generator.js +6 -35
- package/dist/runtime/bounded-map.d.ts +2 -85
- package/dist/runtime/credential-patterns.d.ts +2 -41
- package/dist/runtime/h1-state.d.ts +2 -61
- package/dist/runtime/handles.d.ts +2 -45
- package/dist/runtime/index.d.ts +8 -180
- package/dist/runtime/llm.d.ts +2 -43
- package/dist/runtime/local-bin.d.ts +2 -119
- package/dist/runtime/redos-guard.d.ts +2 -68
- package/dist/runtime/safe-json.d.ts +2 -24
- package/dist/runtime/sandbox.d.ts +2 -58
- package/dist/runtime.js +1 -868
- package/dist/schema-evolution-guard.js +20 -24
- package/dist/secret-scanner.js +22 -146
- package/dist/security-hotspot-scanner.js +24 -154
- package/dist/session-recap.js +16 -14
- package/dist/spec-linker.js +19 -17
- package/dist/template-engine.js +22 -37
- package/dist/test-coverage-gate.js +19 -34
- package/dist/test-generator.js +6 -35
- package/dist/test-runner-gate.js +34 -143
- package/dist/todo-listener.js +17 -38
- package/dist/type-gate.js +23 -311
- 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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
package/dist/runtime/index.d.ts
CHANGED
|
@@ -1,182 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* `
|
|
7
|
-
*
|
|
8
|
-
|
|
9
|
-
*
|
|
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
|
package/dist/runtime/llm.d.ts
CHANGED
|
@@ -1,44 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
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
|
-
|
|
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
|