@wrongstack/wrongtrace 0.313.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/gate.d.ts ADDED
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Shared WrongTrace pre-flight gate for every host process.
3
+ *
4
+ * Originally lived in `@wrongstack/cli/wiring`; moved here so the CLI
5
+ * leader, fleet subagents, AND the standalone WebUI server can all pass
6
+ * their mutating tool calls through the exact same lock gate without any
7
+ * of them importing `@wrongstack/cli` (dep direction cli -> webui-server
8
+ * forbids the reverse edge).
9
+ *
10
+ * Wraps the @wrongstack/wrongtrace client into tiny entry points a host
11
+ * can call without caring whether the daemon is running:
12
+ *
13
+ * - `preflightFileEdit(path)` -> health/lock check BEFORE a heavy edit.
14
+ * Locked files hard-block; fragile files soften the edit strategy.
15
+ * - `withFileLock(path, fn)` -> claim the daemon's lock around a heavy
16
+ * edit so peer agents don't thrash the same file; unlocks in `finally`.
17
+ *
18
+ * Design rules (kept deliberately):
19
+ * - The daemon is OPTIONAL. Every path degrades to "allow" - an offline
20
+ * WrongTrace must never block an edit (mirrors proxy-probe's soft
21
+ * -signal philosophy).
22
+ * - Discovery runs ONCE, lazily, fire-and-forget per process. Each host
23
+ * process gets its own singleton here; the CLI additionally warms it
24
+ * at boot (`void getWrongTrace()`).
25
+ * - Stale locks (TTL already elapsed) do not block; the risk fusion in
26
+ * agent-helpers already ignores them.
27
+ */
28
+ import { type WrongTraceClientInternal } from "./client.js";
29
+ import { type CrossAgentRisk } from "./agent-helpers.js";
30
+ /** Outcome the caller dispatches on. `allow` is always the safe default. */
31
+ export type PreflightVerdict = {
32
+ kind: "allow";
33
+ risk: CrossAgentRisk | null;
34
+ } | {
35
+ kind: "blocked";
36
+ risk: CrossAgentRisk;
37
+ };
38
+ /** Lazily created singleton — resolves to isAvailable:false when offline. */
39
+ export declare function getWrongTrace(): Promise<WrongTraceClientInternal>;
40
+ /** Test seam: reset the singleton between suites. */
41
+ export declare function resetWrongTraceGate(): void;
42
+ export interface PreflightOptions {
43
+ /** Caller identity stamped on locks (e.g. session id / agent name). */
44
+ owner?: string;
45
+ ownerRunId?: string;
46
+ }
47
+ /**
48
+ * Pre-flight check before a heavy edit. Fast (single file-health fetch
49
+ * behind the adapter's risk fusion). Locked by ANOTHER owner → blocked;
50
+ * everything else, including daemon-offline → allow. A lock the caller
51
+ * itself holds (`selfOwner` matching the daemon's `lock_owner`) is exempted.
52
+ *
53
+ * Verified live (2026-08-24): `/api/file/health` returns `is_locked`,
54
+ * `lock_owner`, `lock_reason`, `lock_expires_at` while a lock is held, so
55
+ * this CAN return `blocked` on the current daemon schema.
56
+ */
57
+ export declare function preflightFileEdit(path: string, selfOwner?: string): Promise<PreflightVerdict>;
58
+ /**
59
+ * Run `fn` under the daemon's file lock. Best-effort: if the daemon is
60
+ * offline, or lock acquisition fails for any transport reason, `fn` still
61
+ * runs — coordination is an optimization, not a hard dependency.
62
+ */
63
+ export declare function withFileLock<T>(path: string, reason: string, fn: () => Promise<T>, opts?: PreflightOptions): Promise<T>;
64
+ //# sourceMappingURL=gate.d.ts.map
package/dist/gate.js ADDED
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Shared WrongTrace pre-flight gate for every host process.
3
+ *
4
+ * Originally lived in `@wrongstack/cli/wiring`; moved here so the CLI
5
+ * leader, fleet subagents, AND the standalone WebUI server can all pass
6
+ * their mutating tool calls through the exact same lock gate without any
7
+ * of them importing `@wrongstack/cli` (dep direction cli -> webui-server
8
+ * forbids the reverse edge).
9
+ *
10
+ * Wraps the @wrongstack/wrongtrace client into tiny entry points a host
11
+ * can call without caring whether the daemon is running:
12
+ *
13
+ * - `preflightFileEdit(path)` -> health/lock check BEFORE a heavy edit.
14
+ * Locked files hard-block; fragile files soften the edit strategy.
15
+ * - `withFileLock(path, fn)` -> claim the daemon's lock around a heavy
16
+ * edit so peer agents don't thrash the same file; unlocks in `finally`.
17
+ *
18
+ * Design rules (kept deliberately):
19
+ * - The daemon is OPTIONAL. Every path degrades to "allow" - an offline
20
+ * WrongTrace must never block an edit (mirrors proxy-probe's soft
21
+ * -signal philosophy).
22
+ * - Discovery runs ONCE, lazily, fire-and-forget per process. Each host
23
+ * process gets its own singleton here; the CLI additionally warms it
24
+ * at boot (`void getWrongTrace()`).
25
+ * - Stale locks (TTL already elapsed) do not block; the risk fusion in
26
+ * agent-helpers already ignores them.
27
+ */
28
+ import { createWrongTraceClient } from "./client.js";
29
+ import { getCrossAgentRisk } from "./agent-helpers.js";
30
+ let clientPromise;
31
+ /** Lazily created singleton — resolves to isAvailable:false when offline. */
32
+ export function getWrongTrace() {
33
+ if (clientPromise === undefined) {
34
+ clientPromise = createWrongTraceClient().catch(() => {
35
+ // Never let a discovery rejection poison the singleton.
36
+ return { isAvailable: false };
37
+ });
38
+ }
39
+ return clientPromise;
40
+ }
41
+ /** Test seam: reset the singleton between suites. */
42
+ export function resetWrongTraceGate() {
43
+ clientPromise = undefined;
44
+ }
45
+ /**
46
+ * Pre-flight check before a heavy edit. Fast (single file-health fetch
47
+ * behind the adapter's risk fusion). Locked by ANOTHER owner → blocked;
48
+ * everything else, including daemon-offline → allow. A lock the caller
49
+ * itself holds (`selfOwner` matching the daemon's `lock_owner`) is exempted.
50
+ *
51
+ * Verified live (2026-08-24): `/api/file/health` returns `is_locked`,
52
+ * `lock_owner`, `lock_reason`, `lock_expires_at` while a lock is held, so
53
+ * this CAN return `blocked` on the current daemon schema.
54
+ */
55
+ export async function preflightFileEdit(path, selfOwner) {
56
+ const wt = await getWrongTrace();
57
+ if (!wt.isAvailable)
58
+ return { kind: "allow", risk: null };
59
+ const risk = await getCrossAgentRisk(wt, path, 50, selfOwner);
60
+ if (risk.band === "locked")
61
+ return { kind: "blocked", risk };
62
+ return { kind: "allow", risk };
63
+ }
64
+ /**
65
+ * Run `fn` under the daemon's file lock. Best-effort: if the daemon is
66
+ * offline, or lock acquisition fails for any transport reason, `fn` still
67
+ * runs — coordination is an optimization, not a hard dependency.
68
+ */
69
+ export async function withFileLock(path, reason, fn, opts = {}) {
70
+ const wt = await getWrongTrace();
71
+ if (!wt.isAvailable)
72
+ return fn();
73
+ const lockOpts = {
74
+ // Generous TTL: heavy edits can run long; the lock self-reaps if we
75
+ // crash mid-edit so a dead session can never block the file forever.
76
+ ttlSeconds: 900,
77
+ };
78
+ if (opts.owner !== undefined)
79
+ lockOpts.owner = opts.owner;
80
+ if (opts.ownerRunId !== undefined)
81
+ lockOpts.ownerRunId = opts.ownerRunId;
82
+ const res = await wt.lockFile(path, reason, lockOpts);
83
+ // Conflict body carries ok:false + owner/expires_at — do NOT steal the
84
+ // file; run unlocked rather than forcing a peer's lock away.
85
+ const acquired = res?.ok === true;
86
+ try {
87
+ return await fn();
88
+ }
89
+ finally {
90
+ if (acquired)
91
+ await wt.unlockFile(path);
92
+ }
93
+ }
94
+ //# sourceMappingURL=gate.js.map
@@ -0,0 +1,120 @@
1
+ /**
2
+ * WrongTrace guardrail hooks — shared by every host that executes tools.
3
+ *
4
+ * Originally lived in `@wrongstack/cli/wiring`; moved here so the CLI
5
+ * leader, fleet subagents, the standalone WebUI server, and runtime-package
6
+ * light subagents register the identical gate without cross-importing each
7
+ * other. These are STRUCTURAL functions: they deliberately avoid an
8
+ * `@wrongstack/core` dependency (the adapter package stays decoupled from
9
+ * every runtime inside WrongStack), so hosts register them on their own
10
+ * `HookRegistry` and the structural parameter types accept core's richer
11
+ * `HookInput`.
12
+ *
13
+ * Behaviour:
14
+ * preToolUse: resolve the target path from toolInput; run the
15
+ * WrongTrace pre-flight (health + lock state). A file
16
+ * locked by ANOTHER owner DENIES the call with the
17
+ * owner/expiry in the reason — the model sees it and can
18
+ * pick another file. A lock owned by THIS session is
19
+ * treated as available (self-owner exemption) so the
20
+ * acquire-in-pre/release-in-post pairing stays usable —
21
+ * a leaked own lock (executor denied/threw before postToolUse)
22
+ * never blocks the session's own retry. Healthy/fragile/
23
+ * offline daemons ALLOW; fragile files additionally get a
24
+ * one-line "prefer surgical edits" nudge via additionalContext.
25
+ * On allow we also ACQUIRE the daemon lock for the edit
26
+ * (owner = session id) so peers see the claim.
27
+ * postToolUse: release the lock acquired in preToolUse (path-keyed).
28
+ * The daemon TTL is the leak backstop if execution never
29
+ * completes.
30
+ *
31
+ * Concurrency: lock bookkeeping is scoped PER HOOK PAIR (per runner), not
32
+ * process-global, and reference-counted. A shared module-level map would let
33
+ * one executor's postToolUse release another executor's active lock when the
34
+ * same path is claimed by two in-process runners. `createWrongTraceHookPair`
35
+ * allocates its own counter map; it is also safe to share ONE pair across
36
+ * concurrent executors (standalone WebUI parent + SDD-wizard workers, or the
37
+ * fleet runner across subagents) — overlapping claims increment the count and
38
+ * the daemon lock is only released when the LAST finisher's postToolUse
39
+ * decrements it to zero.
40
+ *
41
+ * Failure philosophy: COORDINATION optimization, never a hard dependency.
42
+ * Daemon offline → everything allows. Any throw inside the hooks must be
43
+ * swallowed by the caller-side catch below so a slow daemon (timeout) can
44
+ * never add latency surprises to the edit path.
45
+ */
46
+ /**
47
+ * Structural subset of the host's `HookInput` this hook actually reads.
48
+ * Core's `HookInput` (and any host equivalent carrying `toolName` +
49
+ * `toolInput`) is structurally assignable to this, which is what lets the
50
+ * same factory serve every host without a framework dependency here.
51
+ */
52
+ export interface WrongTraceHookInput {
53
+ toolName?: string | undefined;
54
+ toolInput?: unknown;
55
+ }
56
+ /** Mutually-exclusive pre-flight verdict, mirroring core's contract. */
57
+ export type WrongTracePreToolUseOutcome = {
58
+ action: "allow";
59
+ additionalContext?: string | undefined;
60
+ } | {
61
+ action: "deny";
62
+ reason: string;
63
+ };
64
+ /**
65
+ * Typed gate-decision events emitted by the hooks when a host supplies an
66
+ * `emit` callback. One event per decision point — denied edit, fragile
67
+ * nudge, lock acquired / race-lost / released. Hosts map these onto their
68
+ * EventBus (e.g. `events.emit('wrongtrace.gate.decision', e)`); the
69
+ * adapter itself stays transport-agnostic.
70
+ */
71
+ export type WrongTraceGateDecisionEvent = {
72
+ kind: "deny";
73
+ path: string;
74
+ reason: string;
75
+ } | {
76
+ kind: "allow-fragile";
77
+ path: string;
78
+ reasons: readonly string[];
79
+ } | {
80
+ kind: "lock-acquired";
81
+ path: string;
82
+ owner: string;
83
+ } | {
84
+ kind: "lock-conflict-race";
85
+ path: string;
86
+ } | {
87
+ kind: "lock-released";
88
+ path: string;
89
+ };
90
+ export interface WrongTraceHookPair {
91
+ /** Pre-flight + claim. Denies when another owner holds the file. */
92
+ preToolUse: (input: WrongTraceHookInput, runtime?: unknown) => Promise<WrongTracePreToolUseOutcome | undefined>;
93
+ /** Release the lock claimed by `preToolUse` for the same path. */
94
+ postToolUse: (input: WrongTraceHookInput) => Promise<void>;
95
+ }
96
+ export interface WrongTraceHookOptions {
97
+ /** Typed gate-decision event sink (host maps it onto its EventBus). */
98
+ emit?: (event: WrongTraceGateDecisionEvent) => void;
99
+ }
100
+ /**
101
+ * Per-runner lock bookkeeping. Reference-counted: when one hook pair is
102
+ * shared across concurrent executors (standalone WebUI parent + SDD-wizard
103
+ * workers, or the fleet runner across subagents), overlapping claims on the
104
+ * same path increment the count and the daemon lock is only released when
105
+ * the LAST finisher's postToolUse decrements it to zero — a sibling finishing
106
+ * early can never unlock a path another in-flight edit still holds.
107
+ */
108
+ export declare function newWrongTraceLockCounter(): Map<string, number>;
109
+ /**
110
+ * Create a pre/post hook PAIR sharing one per-runner reference-counted lock
111
+ * map. Every host should use this when wiring both phases: the pairing
112
+ * guarantees `postToolUse` only releases locks this exact pair's
113
+ * `preToolUse` claimed (see concurrency note at the top of this file), and
114
+ * the reference count keeps the daemon lock held while any sibling executor
115
+ * sharing the pair still has the path in flight.
116
+ */
117
+ export declare function createWrongTraceHookPair(sessionId: () => string, opts?: WrongTraceHookOptions, counters?: Map<string, number>): WrongTraceHookPair;
118
+ export declare function createWrongTracePreToolUseHook(sessionId: () => string, opts?: WrongTraceHookOptions): (input: WrongTraceHookInput, runtime?: unknown) => Promise<WrongTracePreToolUseOutcome | undefined>;
119
+ export declare function createWrongTracePostToolUseHook(opts?: WrongTraceHookOptions): (input: WrongTraceHookInput) => Promise<void>;
120
+ //# sourceMappingURL=hooks.d.ts.map
package/dist/hooks.js ADDED
@@ -0,0 +1,228 @@
1
+ /**
2
+ * WrongTrace guardrail hooks — shared by every host that executes tools.
3
+ *
4
+ * Originally lived in `@wrongstack/cli/wiring`; moved here so the CLI
5
+ * leader, fleet subagents, the standalone WebUI server, and runtime-package
6
+ * light subagents register the identical gate without cross-importing each
7
+ * other. These are STRUCTURAL functions: they deliberately avoid an
8
+ * `@wrongstack/core` dependency (the adapter package stays decoupled from
9
+ * every runtime inside WrongStack), so hosts register them on their own
10
+ * `HookRegistry` and the structural parameter types accept core's richer
11
+ * `HookInput`.
12
+ *
13
+ * Behaviour:
14
+ * preToolUse: resolve the target path from toolInput; run the
15
+ * WrongTrace pre-flight (health + lock state). A file
16
+ * locked by ANOTHER owner DENIES the call with the
17
+ * owner/expiry in the reason — the model sees it and can
18
+ * pick another file. A lock owned by THIS session is
19
+ * treated as available (self-owner exemption) so the
20
+ * acquire-in-pre/release-in-post pairing stays usable —
21
+ * a leaked own lock (executor denied/threw before postToolUse)
22
+ * never blocks the session's own retry. Healthy/fragile/
23
+ * offline daemons ALLOW; fragile files additionally get a
24
+ * one-line "prefer surgical edits" nudge via additionalContext.
25
+ * On allow we also ACQUIRE the daemon lock for the edit
26
+ * (owner = session id) so peers see the claim.
27
+ * postToolUse: release the lock acquired in preToolUse (path-keyed).
28
+ * The daemon TTL is the leak backstop if execution never
29
+ * completes.
30
+ *
31
+ * Concurrency: lock bookkeeping is scoped PER HOOK PAIR (per runner), not
32
+ * process-global, and reference-counted. A shared module-level map would let
33
+ * one executor's postToolUse release another executor's active lock when the
34
+ * same path is claimed by two in-process runners. `createWrongTraceHookPair`
35
+ * allocates its own counter map; it is also safe to share ONE pair across
36
+ * concurrent executors (standalone WebUI parent + SDD-wizard workers, or the
37
+ * fleet runner across subagents) — overlapping claims increment the count and
38
+ * the daemon lock is only released when the LAST finisher's postToolUse
39
+ * decrements it to zero.
40
+ *
41
+ * Failure philosophy: COORDINATION optimization, never a hard dependency.
42
+ * Daemon offline → everything allows. Any throw inside the hooks must be
43
+ * swallowed by the caller-side catch below so a slow daemon (timeout) can
44
+ * never add latency surprises to the edit path.
45
+ */
46
+ import { getWrongTrace, preflightFileEdit } from "./gate.js";
47
+ /** Tools that mutate a single target file and must pass the gate. */
48
+ const EDIT_TOOLS = new Set([
49
+ "edit",
50
+ "write",
51
+ "replace",
52
+ "patch",
53
+ "codebase-ast-replace",
54
+ ]);
55
+ /**
56
+ * Extract the target file path from a mutating tool's input, if present.
57
+ *
58
+ * Order matters: `file` (codebase-ast-replace) MUST come before any
59
+ * selector-ish field — `target` is the body/full AST selector, never a
60
+ * path, so it must not be treated as one. Multi-file inputs resolve to
61
+ * their first concrete file; `patch` has no single derivable target (the
62
+ * diff payload names its own files), so its optional `directory` is the
63
+ * coarsest honest claim and a bare `patch` input (no directory) yields
64
+ * `undefined` → gate allows without a claim.
65
+ */
66
+ function targetPathOf(toolInput) {
67
+ if (!toolInput || typeof toolInput !== "object")
68
+ return undefined;
69
+ const t = toolInput;
70
+ // Single-file keys: edit/write → `path`, codebase-ast-replace → `file`.
71
+ for (const key of ["path", "file_path", "file"]) {
72
+ const v = t[key];
73
+ if (typeof v === "string" && v.length > 0)
74
+ return v;
75
+ }
76
+ // `replace` accepts files: string | string[].
77
+ const files = t["files"];
78
+ if (typeof files === "string" && files.length > 0)
79
+ return files;
80
+ if (Array.isArray(files) && files.length > 0) {
81
+ const first = files[0];
82
+ if (typeof first === "string" && first.length > 0)
83
+ return first;
84
+ }
85
+ // `patch` accepts an optional directory.
86
+ const dir = t["directory"];
87
+ if (typeof dir === "string" && dir.length > 0)
88
+ return dir;
89
+ return undefined;
90
+ }
91
+ /** Emit a gate-decision event without ever letting a throw escape. */
92
+ function emitSafe(emit, event) {
93
+ try {
94
+ emit?.(event);
95
+ }
96
+ catch {
97
+ // Observability must never break the edit path.
98
+ }
99
+ }
100
+ /**
101
+ * Per-runner lock bookkeeping. Reference-counted: when one hook pair is
102
+ * shared across concurrent executors (standalone WebUI parent + SDD-wizard
103
+ * workers, or the fleet runner across subagents), overlapping claims on the
104
+ * same path increment the count and the daemon lock is only released when
105
+ * the LAST finisher's postToolUse decrements it to zero — a sibling finishing
106
+ * early can never unlock a path another in-flight edit still holds.
107
+ */
108
+ export function newWrongTraceLockCounter() {
109
+ return new Map();
110
+ }
111
+ function acquireLock(counters, path) {
112
+ counters.set(path, (counters.get(path) ?? 0) + 1);
113
+ }
114
+ function releaseLock(counters, path, onZero) {
115
+ const next = (counters.get(path) ?? 1) - 1;
116
+ if (next > 0) {
117
+ counters.set(path, next);
118
+ return;
119
+ }
120
+ counters.delete(path);
121
+ onZero();
122
+ }
123
+ /**
124
+ * Create a pre/post hook PAIR sharing one per-runner reference-counted lock
125
+ * map. Every host should use this when wiring both phases: the pairing
126
+ * guarantees `postToolUse` only releases locks this exact pair's
127
+ * `preToolUse` claimed (see concurrency note at the top of this file), and
128
+ * the reference count keeps the daemon lock held while any sibling executor
129
+ * sharing the pair still has the path in flight.
130
+ */
131
+ export function createWrongTraceHookPair(sessionId, opts = {}, counters = newWrongTraceLockCounter()) {
132
+ const emit = opts.emit;
133
+ return {
134
+ async preToolUse(input, _runtime) {
135
+ if (!EDIT_TOOLS.has(input.toolName ?? ""))
136
+ return undefined;
137
+ const path = targetPathOf(input.toolInput);
138
+ if (!path)
139
+ return undefined;
140
+ try {
141
+ // Self-owner exemption: OUR OWN held lock (leaked by an interrupted
142
+ // earlier edit) must not deny this session's retry. lock_owner from
143
+ // the daemon's file-health response is compared against this pair's
144
+ // owner identity by preflightFileEdit.
145
+ const verdict = await preflightFileEdit(path, `wrongstack:${sessionId()}`);
146
+ if (verdict.kind === "blocked") {
147
+ const owner = verdict.risk.reasons.join("; ");
148
+ emitSafe(emit, { kind: "deny", path, reason: `WrongTrace lock: ${owner}` });
149
+ return { action: "deny", reason: `WrongTrace lock: ${owner}` };
150
+ }
151
+ // Allow — and claim the lock so peers see this edit in flight.
152
+ const wt = await getWrongTrace();
153
+ if (wt.isAvailable) {
154
+ const owner = `wrongstack:${sessionId()}`;
155
+ const res = await wt.lockFile(path, "WrongStack edit in progress", {
156
+ owner,
157
+ ttlSeconds: 900,
158
+ });
159
+ if (res?.ok === true) {
160
+ acquireLock(counters, path);
161
+ emitSafe(emit, { kind: "lock-acquired", path, owner });
162
+ }
163
+ else {
164
+ // Peer grabbed it between the pre-flight and the claim (or our
165
+ // own earlier leak still holds it — the exemption let us through).
166
+ // Either way the file is being edited by someone: we proceed
167
+ // without re-claiming; coordination stays advisory.
168
+ emitSafe(emit, { kind: "lock-conflict-race", path });
169
+ }
170
+ }
171
+ if (verdict.risk && verdict.risk.band === "fragile") {
172
+ emitSafe(emit, { kind: "allow-fragile", path, reasons: verdict.risk.reasons });
173
+ return {
174
+ action: "allow",
175
+ additionalContext: `WrongTrace: ${path} is fragile (${verdict.risk.reasons.join("; ")}). Prefer surgical AST diffs over rewrites.`,
176
+ };
177
+ }
178
+ return { action: "allow" };
179
+ }
180
+ catch {
181
+ // Fail-open: coordination must never break the edit path.
182
+ return undefined;
183
+ }
184
+ },
185
+ async postToolUse(input) {
186
+ if (!EDIT_TOOLS.has(input.toolName ?? ""))
187
+ return;
188
+ const path = targetPathOf(input.toolInput);
189
+ // Only release a lock THIS pair claimed — a shared (module-level) map
190
+ // would let one executor free another's active lock. Reference counts:
191
+ // the daemon unlock happens only when the LAST sibling release lands.
192
+ if (!path || !counters.has(path))
193
+ return;
194
+ let shouldUnlock = false;
195
+ releaseLock(counters, path, () => {
196
+ shouldUnlock = true;
197
+ });
198
+ if (!shouldUnlock)
199
+ return; // a sibling still holds this path
200
+ emitSafe(emit, { kind: "lock-released", path });
201
+ try {
202
+ const wt = await getWrongTrace();
203
+ if (wt.isAvailable)
204
+ await wt.unlockFile(path);
205
+ }
206
+ catch {
207
+ // TTL backstop will reap it.
208
+ }
209
+ },
210
+ };
211
+ }
212
+ // ── Legacy single-phase factories ─────────────────────────────────────────
213
+ // Old consumers register pre/post separately. They share the module-level
214
+ // reference-counted lock map, preserving the historical behaviour; new code
215
+ // should use `createWrongTraceHookPair` (per-runner scoping) instead.
216
+ //
217
+ // IMPORTANT: paired and legacy hooks use SEPARATE counters — a legacy
218
+ // postToolUse can never release a paired pair's claim (and vice versa). Do
219
+ // not mix the two styles for the same path in one process: the intended
220
+ // release would no-op and the daemon TTL would reap the lock.
221
+ const legacyLocks = newWrongTraceLockCounter();
222
+ export function createWrongTracePreToolUseHook(sessionId, opts) {
223
+ return createWrongTraceHookPair(sessionId, opts, legacyLocks).preToolUse;
224
+ }
225
+ export function createWrongTracePostToolUseHook(opts) {
226
+ return createWrongTraceHookPair(() => "", opts, legacyLocks).postToolUse;
227
+ }
228
+ //# sourceMappingURL=hooks.js.map
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Public surface of the @wrongstack/wrongtrace package.
3
+ *
4
+ * Callers do this and never worry about whether the daemon is running:
5
+ *
6
+ * import { getWrongTraceClient } from "@wrongstack/wrongtrace";
7
+ * const wt = await getWrongTraceClient();
8
+ * if (wt.isAvailable) { /* safe to use lock/lineage/telemetry APIs *\/ }
9
+ */
10
+ export type { WrongTraceAtlasFile, WrongTraceAtlasQuery, WrongTraceAtlasSummary, WrongTraceClient, WrongTraceFileHealth, WrongTraceFrictionRow, WrongTraceHealth, WrongTraceLockInfo, WrongTraceLockOwnership, WrongTraceLockRequest, WrongTraceLockResult, WrongTraceRecentEvent, WrongTraceRecentEventsQuery, WrongTraceSymbolEvent, WrongTraceTelemetryReport, WrongTraceUnlockRequest, } from "./types.js";
11
+ export { discover, defaultSocketPath } from "./discovery.js";
12
+ export type { DiscoveryOptions, DiscoveryResult } from "./discovery.js";
13
+ export { createWrongTraceClient } from "./client.js";
14
+ export type { WrongTraceClientOptions, WrongTraceClientInternal } from "./client.js";
15
+ export { getCrossAgentRisk, summarizeFriction, getRecentActivity, digestAtlas, } from "./agent-helpers.js";
16
+ export type { CrossAgentRisk, FrictionSummary, AtlasDigest, RecentActivityEntry, } from "./agent-helpers.js";
17
+ export { createIpcTransport } from "./adapters/ipc.js";
18
+ export type { IpcTransport, IpcCallResult, IpcTimeouts } from "./adapters/ipc.js";
19
+ export { createMcpTransport } from "./adapters/mcp.js";
20
+ export type { McpTransport, McpToolBag, McpToolHandler, McpToolName } from "./adapters/mcp.js";
21
+ export { getWrongTrace, preflightFileEdit, resetWrongTraceGate, withFileLock, } from "./gate.js";
22
+ export type { PreflightOptions, PreflightVerdict } from "./gate.js";
23
+ export { createWrongTraceHookPair, createWrongTracePostToolUseHook, createWrongTracePreToolUseHook, } from "./hooks.js";
24
+ export type { WrongTraceGateDecisionEvent, WrongTraceHookInput, WrongTraceHookOptions, WrongTraceHookPair, WrongTracePreToolUseOutcome, } from "./hooks.js";
25
+ export { createWrongTraceGateCounter, countersFilePath, formatGateCounterReport, loadWrongTraceGateCounters, persistWrongTraceGateCounters, recordGateDecision, resetGateDecisions, snapshotGateDecisions, } from "./gate-counters.js";
26
+ export type { WrongTraceGateCounter, WrongTraceGateCounterSnapshot, } from "./gate-counters.js";
27
+ /**
28
+ * Drop-in replacement for the legacy `getWrongTraceClient()` from the
29
+ * reference TypeScript snippet. Kept as a one-liner alias for caller
30
+ * familiarity — implementation lives in `client.ts`.
31
+ */
32
+ export declare function getWrongTraceClient(): Promise<import("./client.js").WrongTraceClientInternal>;
33
+ //# sourceMappingURL=index.d.ts.map
package/dist/index.js ADDED
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Public surface of the @wrongstack/wrongtrace package.
3
+ *
4
+ * Callers do this and never worry about whether the daemon is running:
5
+ *
6
+ * import { getWrongTraceClient } from "@wrongstack/wrongtrace";
7
+ * const wt = await getWrongTraceClient();
8
+ * if (wt.isAvailable) { /* safe to use lock/lineage/telemetry APIs *\/ }
9
+ */
10
+ export { discover, defaultSocketPath } from "./discovery.js";
11
+ export { createWrongTraceClient } from "./client.js";
12
+ export { getCrossAgentRisk, summarizeFriction, getRecentActivity, digestAtlas, } from "./agent-helpers.js";
13
+ export { createIpcTransport } from "./adapters/ipc.js";
14
+ export { createMcpTransport } from "./adapters/mcp.js";
15
+ // ── Shared guardrail gate + hooks (CLI leader, fleet subagents, WebUI server) ──
16
+ export { getWrongTrace, preflightFileEdit, resetWrongTraceGate, withFileLock, } from "./gate.js";
17
+ export { createWrongTraceHookPair, createWrongTracePostToolUseHook, createWrongTracePreToolUseHook, } from "./hooks.js";
18
+ export { createWrongTraceGateCounter, countersFilePath, formatGateCounterReport, loadWrongTraceGateCounters, persistWrongTraceGateCounters, recordGateDecision, resetGateDecisions, snapshotGateDecisions, } from "./gate-counters.js";
19
+ /**
20
+ * Drop-in replacement for the legacy `getWrongTraceClient()` from the
21
+ * reference TypeScript snippet. Kept as a one-liner alias for caller
22
+ * familiarity — implementation lives in `client.ts`.
23
+ */
24
+ export async function getWrongTraceClient() {
25
+ const { createWrongTraceClient } = await import("./client.js");
26
+ return createWrongTraceClient();
27
+ }
28
+ //# sourceMappingURL=index.js.map