@kodr/aiwrangler 0.1.2 → 0.1.3

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/CHANGELOG.md CHANGED
@@ -5,6 +5,25 @@ All notable changes to this project are documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## 0.1.3 - 2026-10-20
9
+
10
+ ### Fixed
11
+
12
+ - OpenCode V2 package plugins load again: opencode resolves a
13
+ `{"package": "@kodr/aiwrangler"}` entry through the package's `./server`
14
+ export, falling back to the root. 0.1.2 exposed neither, so the plugin
15
+ failed with "Plugin must export a default definition with an id and an
16
+ effect or setup function". The package now maps `./server` (and keeps
17
+ `./plugin`) at the plugin entry.
18
+ - The V2 definition no longer needs `@opencode/plugin` at runtime. opencode's
19
+ package-plugin cache installs the package without optional peers, so
20
+ `Plugin.define` (an identity function in 2.1.1) now falls back to
21
+ pass-through when the SDK is absent; the default export always carries
22
+ `id`/`setup` plus the V1 `server()` bridge. The V1 named export is
23
+ unchanged.
24
+ - `verify:plugin` asserts the `./server` export mapping and the default
25
+ export's `id`/`setup` unconditionally.
26
+
8
27
  ## 0.1.2 - 2026-10-20
9
28
 
10
29
  ### Added
package/dist/plugin.js CHANGED
@@ -122,162 +122,161 @@ var pluginLog = (level, message) => {
122
122
  } catch {
123
123
  }
124
124
  };
125
- var v2Plugin = await import("@opencode/plugin").then(
126
- ({ Plugin }) => Plugin.define({
127
- id: "aiwrangler.nudge",
128
- async setup(ctx) {
129
- const states = /* @__PURE__ */ new Map();
130
- const lastHandled = /* @__PURE__ */ new Map();
131
- const me = randomUUID();
132
- const LEASE_KEY = "leader.lease";
133
- let leader = false;
134
- async function tryLead() {
135
- try {
136
- const cur = await ctx.storage.get(LEASE_KEY);
137
- const now = Date.now();
138
- if (!cur || cur.id === me || (cur.expires ?? 0) < now) {
139
- await ctx.storage.set(LEASE_KEY, { id: me, expires: now + 45e3 });
140
- if (!leader) pluginLog("info", `sweep lease acquired (instance ${me.slice(0, 8)})`);
141
- leader = true;
142
- } else {
143
- leader = false;
144
- }
145
- } catch {
125
+ var v2Definition = {
126
+ id: "aiwrangler.nudge",
127
+ async setup(ctx) {
128
+ const states = /* @__PURE__ */ new Map();
129
+ const lastHandled = /* @__PURE__ */ new Map();
130
+ const me = randomUUID();
131
+ const LEASE_KEY = "leader.lease";
132
+ let leader = false;
133
+ async function tryLead() {
134
+ try {
135
+ const cur = await ctx.storage.get(LEASE_KEY);
136
+ const now = Date.now();
137
+ if (!cur || cur.id === me || (cur.expires ?? 0) < now) {
138
+ await ctx.storage.set(LEASE_KEY, { id: me, expires: now + 45e3 });
139
+ if (!leader) pluginLog("info", `sweep lease acquired (instance ${me.slice(0, 8)})`);
146
140
  leader = true;
141
+ } else {
142
+ leader = false;
147
143
  }
144
+ } catch {
145
+ leader = true;
148
146
  }
149
- void tryLead();
150
- const leadTimer = setInterval(() => void tryLead(), 15e3);
151
- function stateFor(statePath) {
152
- const cached = states.get(statePath);
153
- if (cached) return cached;
154
- let loaded = { lastFingerprint: "", nudges: 0, greenAnnounced: false };
155
- try {
156
- if (existsSync(statePath)) loaded = { ...loaded, ...JSON.parse(readFileSync(statePath, "utf8")) };
157
- } catch {
158
- loaded = { lastFingerprint: "", nudges: 0, greenAnnounced: false };
159
- }
160
- states.set(statePath, loaded);
161
- return loaded;
147
+ }
148
+ void tryLead();
149
+ const leadTimer = setInterval(() => void tryLead(), 15e3);
150
+ function stateFor(statePath) {
151
+ const cached = states.get(statePath);
152
+ if (cached) return cached;
153
+ let loaded = { lastFingerprint: "", nudges: 0, greenAnnounced: false };
154
+ try {
155
+ if (existsSync(statePath)) loaded = { ...loaded, ...JSON.parse(readFileSync(statePath, "utf8")) };
156
+ } catch {
157
+ loaded = { lastFingerprint: "", nudges: 0, greenAnnounced: false };
162
158
  }
163
- const save = (statePath, state) => {
164
- mkdirSync(dirname(statePath), { recursive: true });
165
- writeFileSync(statePath, JSON.stringify(state), "utf8");
166
- };
167
- async function nudge(sessionID, text) {
168
- try {
169
- await ctx.session.prompt({ sessionID, text });
170
- } catch (err) {
171
- pluginLog("warn", `nudge failed: ${err instanceof Error ? err.message : String(err)}`);
172
- }
159
+ states.set(statePath, loaded);
160
+ return loaded;
161
+ }
162
+ const save = (statePath, state) => {
163
+ mkdirSync(dirname(statePath), { recursive: true });
164
+ writeFileSync(statePath, JSON.stringify(state), "utf8");
165
+ };
166
+ async function nudge(sessionID, text) {
167
+ try {
168
+ await ctx.session.prompt({ sessionID, text });
169
+ } catch (err) {
170
+ pluginLog("warn", `nudge failed: ${err instanceof Error ? err.message : String(err)}`);
173
171
  }
174
- async function directoryFor(sessionID, event) {
175
- const fromEvent = event.location?.directory;
176
- if (fromEvent) return fromEvent;
177
- try {
178
- const res = await ctx.session.get({ sessionID });
179
- const session = res?.data ?? res;
180
- if (session?.location?.directory) return session.location.directory;
181
- } catch {
182
- }
183
- return void 0;
172
+ }
173
+ async function directoryFor(sessionID, event) {
174
+ const fromEvent = event.location?.directory;
175
+ if (fromEvent) return fromEvent;
176
+ try {
177
+ const res = await ctx.session.get({ sessionID });
178
+ const session = res?.data ?? res;
179
+ if (session?.location?.directory) return session.location.directory;
180
+ } catch {
184
181
  }
185
- async function sweep(sessionID, event) {
186
- const now = Date.now();
187
- const at = lastHandled.get(sessionID) ?? 0;
188
- if (now - at < 5e3) return;
189
- lastHandled.set(sessionID, now);
190
- if (lastHandled.size > 1024) {
191
- for (const [key, ts] of lastHandled) if (ts < now - 6e4) lastHandled.delete(key);
192
- }
193
- const directory = await directoryFor(sessionID, event);
194
- if (!directory) return;
195
- if (!findConfigFile(directory) && !process.env.AIWRANGLER_CONFIG) return;
196
- let loaded;
197
- try {
198
- loaded = loadConfig(directory);
199
- } catch {
182
+ return void 0;
183
+ }
184
+ async function sweep(sessionID, event) {
185
+ const now = Date.now();
186
+ const at = lastHandled.get(sessionID) ?? 0;
187
+ if (now - at < 5e3) return;
188
+ lastHandled.set(sessionID, now);
189
+ if (lastHandled.size > 1024) {
190
+ for (const [key, ts] of lastHandled) if (ts < now - 6e4) lastHandled.delete(key);
191
+ }
192
+ const directory = await directoryFor(sessionID, event);
193
+ if (!directory) return;
194
+ if (!findConfigFile(directory) && !process.env.AIWRANGLER_CONFIG) return;
195
+ let loaded;
196
+ try {
197
+ loaded = loadConfig(directory);
198
+ } catch {
199
+ return;
200
+ }
201
+ if (!loaded.config.gates.session.nudge) return;
202
+ const statePath = join(loaded.root, ".aiwrangler", "watch-state.json");
203
+ const watch = stateFor(statePath);
204
+ currentRoot = loaded.root;
205
+ currentLoaded = loaded;
206
+ try {
207
+ const report = await runDeterministic(loaded.config, loaded.root, { cargo: false });
208
+ const fingerprint = diffFingerprint(
209
+ report.repoDiffs.flatMap((d) => d.files.map((f) => f.path + String(f.hunks.length))).join("|")
210
+ );
211
+ const blocking = report.findings.filter((f) => f.severity === "block" || f.severity === "human");
212
+ pluginLog(
213
+ "info",
214
+ `sweep session=${sessionID} root=${loaded.root} fingerprint=${fingerprint.slice(0, 12)} diffRepos=${report.repoDiffs.length} blocking=${blocking.length}`
215
+ );
216
+ if (fingerprint === watch.lastFingerprint) return;
217
+ watch.lastFingerprint = fingerprint;
218
+ if (blocking.length > 0 && watch.nudges < loaded.config.gates.session.maxNudges) {
219
+ watch.nudges++;
220
+ watch.greenAnnounced = false;
221
+ save(statePath, watch);
222
+ await nudge(sessionID, [
223
+ "aiwrangler gate: deterministic findings on the current dirty files. Fix these before finishing, then run the wrangler_check tool for the full battery:",
224
+ "",
225
+ formatFindings(blocking)
226
+ ].join("\n"));
200
227
  return;
201
228
  }
202
- if (!loaded.config.gates.session.nudge) return;
203
- const statePath = join(loaded.root, ".aiwrangler", "watch-state.json");
204
- const watch = stateFor(statePath);
205
- currentRoot = loaded.root;
206
- currentLoaded = loaded;
207
- try {
208
- const report = await runDeterministic(loaded.config, loaded.root, { cargo: false });
209
- const fingerprint = diffFingerprint(
210
- report.repoDiffs.flatMap((d) => d.files.map((f) => f.path + String(f.hunks.length))).join("|")
211
- );
212
- const blocking = report.findings.filter((f) => f.severity === "block" || f.severity === "human");
213
- pluginLog(
214
- "info",
215
- `sweep session=${sessionID} root=${loaded.root} fingerprint=${fingerprint.slice(0, 12)} diffRepos=${report.repoDiffs.length} blocking=${blocking.length}`
216
- );
217
- if (fingerprint === watch.lastFingerprint) return;
218
- watch.lastFingerprint = fingerprint;
219
- if (blocking.length > 0 && watch.nudges < loaded.config.gates.session.maxNudges) {
220
- watch.nudges++;
221
- watch.greenAnnounced = false;
222
- save(statePath, watch);
223
- await nudge(sessionID, [
224
- "aiwrangler gate: deterministic findings on the current dirty files. Fix these before finishing, then run the wrangler_check tool for the full battery:",
225
- "",
226
- formatFindings(blocking)
227
- ].join("\n"));
228
- return;
229
- }
230
- if (report.repoDiffs.length > 0 && blocking.length === 0 && !watch.greenAnnounced && watch.nudges > 0) {
231
- watch.greenAnnounced = true;
232
- save(statePath, watch);
233
- await nudge(sessionID, "aiwrangler gate: previous deterministic findings are resolved. Run the wrangler_check tool for the full semantic battery before declaring done.");
234
- }
235
- if (report.repoDiffs.length === 0) {
236
- watch.nudges = 0;
237
- watch.greenAnnounced = false;
238
- save(statePath, watch);
239
- }
240
- } catch (err) {
241
- pluginLog("warn", `sweep error: ${err instanceof Error ? err.message : String(err)}`);
229
+ if (report.repoDiffs.length > 0 && blocking.length === 0 && !watch.greenAnnounced && watch.nudges > 0) {
230
+ watch.greenAnnounced = true;
231
+ save(statePath, watch);
232
+ await nudge(sessionID, "aiwrangler gate: previous deterministic findings are resolved. Run the wrangler_check tool for the full semantic battery before declaring done.");
233
+ }
234
+ if (report.repoDiffs.length === 0) {
235
+ watch.nudges = 0;
236
+ watch.greenAnnounced = false;
237
+ save(statePath, watch);
242
238
  }
239
+ } catch (err) {
240
+ pluginLog("warn", `sweep error: ${err instanceof Error ? err.message : String(err)}`);
241
+ }
242
+ }
243
+ setJevWarnSink((message) => {
244
+ pluginLog("warn", message);
245
+ if (!currentRoot || !currentLoaded) return;
246
+ try {
247
+ new Ledger(currentLoaded.config, currentRoot).append({ ts: (/* @__PURE__ */ new Date()).toISOString(), kind: "notice", message });
248
+ } catch {
249
+ }
250
+ });
251
+ const controller = new AbortController();
252
+ void (async () => {
253
+ for await (const event of ctx.event.subscribe({ signal: controller.signal })) {
254
+ if (event.type !== "session.execution.succeeded" && event.type !== "session.idle") continue;
255
+ if (!leader) continue;
256
+ const props = event.data ?? event.properties ?? {};
257
+ const sessionID = String(props.sessionID ?? props.id ?? "");
258
+ if (!sessionID) continue;
259
+ await sweep(sessionID, event);
243
260
  }
244
- setJevWarnSink((message) => {
245
- pluginLog("warn", message);
246
- if (!currentRoot || !currentLoaded) return;
261
+ })().catch((err) => pluginLog("warn", `event stream ended: ${err instanceof Error ? err.message : String(err)}`));
262
+ pluginLog("info", `nudge tripwire loaded (location ${ctx.location.directory})`);
263
+ return () => {
264
+ clearInterval(leadTimer);
265
+ controller.abort();
266
+ currentRoot = null;
267
+ currentLoaded = null;
268
+ setJevWarnSink(null);
269
+ void (async () => {
247
270
  try {
248
- new Ledger(currentLoaded.config, currentRoot).append({ ts: (/* @__PURE__ */ new Date()).toISOString(), kind: "notice", message });
271
+ const cur = await ctx.storage.get(LEASE_KEY);
272
+ if (cur?.id === me) await ctx.storage.remove(LEASE_KEY);
249
273
  } catch {
250
274
  }
251
- });
252
- const controller = new AbortController();
253
- void (async () => {
254
- for await (const event of ctx.event.subscribe({ signal: controller.signal })) {
255
- if (event.type !== "session.execution.succeeded" && event.type !== "session.idle") continue;
256
- if (!leader) continue;
257
- const props = event.data ?? event.properties ?? {};
258
- const sessionID = String(props.sessionID ?? props.id ?? "");
259
- if (!sessionID) continue;
260
- await sweep(sessionID, event);
261
- }
262
- })().catch((err) => pluginLog("warn", `event stream ended: ${err instanceof Error ? err.message : String(err)}`));
263
- pluginLog("info", `nudge tripwire loaded (location ${ctx.location.directory})`);
264
- return () => {
265
- clearInterval(leadTimer);
266
- controller.abort();
267
- currentRoot = null;
268
- currentLoaded = null;
269
- setJevWarnSink(null);
270
- void (async () => {
271
- try {
272
- const cur = await ctx.storage.get(LEASE_KEY);
273
- if (cur?.id === me) await ctx.storage.remove(LEASE_KEY);
274
- } catch {
275
- }
276
- })();
277
- };
278
- }
279
- })
280
- ).catch(() => void 0);
275
+ })();
276
+ };
277
+ }
278
+ };
279
+ var v2Plugin = await import("@opencode/plugin").then(({ Plugin }) => Plugin.define(v2Definition)).catch(() => v2Definition);
281
280
  var plugin_default = {
282
281
  ...v2Plugin ?? {},
283
282
  async server(input) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/plugin.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\"\nimport { appendFileSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from \"node:fs\"\nimport { dirname, join } from \"node:path\"\nimport type { Plugin as V1Plugin } from \"@opencode-ai/plugin\"\nimport { findConfigFile, loadConfig, type LoadedConfig } from \"./config.js\"\nimport { runDeterministic, formatFindings } from \"./gates/deterministic/index.js\"\nimport { diffFingerprint } from \"./loop.js\"\nimport { setJevWarnSink } from \"./jev/client.js\"\nimport { Ledger } from \"./ledger.js\"\n\ninterface WatchState {\n lastFingerprint: string\n nudges: number\n greenAnnounced: boolean\n}\n\n// ---- V1 named export: unchanged ---------------------------------------------\n// V1 consumers re-export this by name (see README, \"OpenCode 1.x (legacy)\").\nexport const WranglerWatchPlugin: V1Plugin = async ({ client, directory }) => {\n const log = (level: \"debug\" | \"info\" | \"warn\" | \"error\", message: string) =>\n client.app.log({ body: { service: \"aiwrangler-watch\", level, message } }).catch(() => {})\n\n if (!directory || (!findConfigFile(directory) && !process.env.AIWRANGLER_CONFIG)) {\n return {}\n }\n\n let loaded\n try {\n loaded = loadConfig(directory)\n } catch (err) {\n void log(\"error\", `aiwrangler config failed to load; watch plugin inactive: ${err instanceof Error ? err.message : String(err)}`)\n return {}\n }\n const statePath = join(loaded.root, \".aiwrangler\", \"watch-state.json\")\n\n setJevWarnSink((message) => {\n void log(\"warn\", message)\n client.tui\n .showToast({ body: { title: \"aiwrangler\", message, variant: \"warning\", duration: 8000 } })\n .catch(() => {})\n try {\n new Ledger(loaded.config, loaded.root).append({ ts: new Date().toISOString(), kind: \"notice\", message })\n } catch {\n /* ledger unavailable; the app log above still captured it */\n }\n })\n let watch: WatchState = { lastFingerprint: \"\", nudges: 0, greenAnnounced: false }\n try {\n if (existsSync(statePath)) watch = { ...watch, ...(JSON.parse(readFileSync(statePath, \"utf8\")) as WatchState) }\n } catch {\n watch = { lastFingerprint: \"\", nudges: 0, greenAnnounced: false }\n }\n const save = () => {\n mkdirSync(dirname(statePath), { recursive: true })\n writeFileSync(statePath, JSON.stringify(watch), \"utf8\")\n }\n\n async function nudge(sessionID: string, text: string) {\n try {\n await client.session.promptAsync({\n path: { id: sessionID },\n body: { parts: [{ type: \"text\", text }] },\n })\n } catch (err) {\n void log(\"warn\", `nudge failed: ${err instanceof Error ? err.message : String(err)}`)\n }\n }\n\n return {\n event: async ({ event }) => {\n const e = event as { type?: string; properties?: Record<string, any> }\n if (e.type !== \"session.idle\") return\n const props = e.properties ?? {}\n const sessionID = String(props.sessionID ?? props.id ?? \"\")\n if (!sessionID) return\n\n try {\n const loaded = loadConfig(directory!)\n if (!loaded.config.gates.session.nudge) return\n const report = await runDeterministic(loaded.config, loaded.root, { cargo: false })\n const fingerprint = diffFingerprint(\n report.repoDiffs\n .flatMap((d) => d.files.map((f) => f.path + String(f.hunks.length)))\n .join(\"|\"),\n )\n if (fingerprint === watch.lastFingerprint) return\n watch.lastFingerprint = fingerprint\n\n const blocking = report.findings.filter((f) => f.severity === \"block\" || f.severity === \"human\")\n if (blocking.length > 0 && watch.nudges < loaded.config.gates.session.maxNudges) {\n watch.nudges++\n watch.greenAnnounced = false\n save()\n await nudge(\n sessionID,\n [\n \"aiwrangler gate: deterministic findings on the current dirty files. Fix these before finishing, then run the wrangler_check tool for the full battery:\",\n \"\",\n formatFindings(blocking),\n ].join(\"\\n\"),\n )\n return\n }\n\n if (report.repoDiffs.length > 0 && blocking.length === 0 && !watch.greenAnnounced && watch.nudges > 0) {\n watch.greenAnnounced = true\n save()\n await nudge(\n sessionID,\n \"aiwrangler gate: previous deterministic findings are resolved. Run the wrangler_check tool for the full semantic battery before declaring done.\",\n )\n }\n if (report.repoDiffs.length === 0) {\n watch.nudges = 0\n watch.greenAnnounced = false\n save()\n }\n } catch (err) {\n void log(\"warn\", `idle check error: ${err instanceof Error ? err.message : String(err)}`)\n }\n },\n }\n}\n\n// ---- V2 plugin ----------------------------------------------------------------\n//\n// opencode V2 loads plugins per location and every instance sees the same\n// server-wide event stream; the completion family is\n// `session.execution.succeeded` (V1's `session.idle` never fires and is kept\n// only as a deduped fallback). Event payloads live in `event.data`, and V2\n// client methods wrap their payloads (`{ location?, data: [...] }` for lists,\n// `{ data: {...} }` for singletons), so every read below unwraps defensively.\n// These shapes were verified empirically against opencode 2.1.1; where the\n// shipped types disagree, the runtime behavior wins.\n\ntype BusEvent = {\n type?: string\n data?: Record<string, unknown>\n properties?: Record<string, unknown>\n location?: { directory?: string }\n}\n\n// The Jev warn sink is process-global, but one leader instance now serves\n// multiple repos: currentRoot/currentLoaded point the sink and the per-repo\n// plugin log at the repo the current sweep is working on.\nlet currentRoot: string | null = null\nlet currentLoaded: LoadedConfig | null = null\n\n// console.error from plugin code does not reach opencode.log, and V1's\n// client.app.log / client.tui.showToast do not exist in V2; keep our own\n// trail beside the ledger, tail-truncated.\nconst pluginLog = (level: \"debug\" | \"info\" | \"warn\" | \"error\", message: string): void => {\n try {\n console.error(`[aiwrangler-nudge] ${level}: ${message}`)\n if (!currentRoot) return\n const logPath = join(currentRoot, \".aiwrangler\", \"plugin.log\")\n appendFileSync(logPath, `${new Date().toISOString()} ${level.toUpperCase()} ${message}\\n`)\n if (statSync(logPath).size > 128 * 1024) {\n writeFileSync(logPath, readFileSync(logPath, \"utf8\").split(\"\\n\").slice(-300).join(\"\\n\"))\n }\n } catch {\n /* never break on log trouble */\n }\n}\n\nconst v2Plugin = await import(\"@opencode/plugin\")\n .then(({ Plugin }) =>\n Plugin.define({\n id: \"aiwrangler.nudge\",\n async setup(ctx) {\n const states = new Map<string, WatchState>()\n const lastHandled = new Map<string, number>()\n\n // ---- single-actor lease -------------------------------------------------\n // V2 loads this plugin per location; every instance receives every\n // session's completion event. Unguarded, three loaded locations means\n // three sweeps, tripled nudges, and a burned maxNudges budget. One\n // lease holder sweeps; followers stay subscribed and take over within\n // <=15s of the leader disappearing (lease TTL 45s; the TTL is the\n // correctness mechanism, deletion in cleanup is an optimization).\n const me = randomUUID()\n const LEASE_KEY = \"leader.lease\" // plugin-scoped storage, shared across location instances\n let leader = false\n async function tryLead() {\n try {\n const cur = (await ctx.storage.get(LEASE_KEY)) as { id?: string; expires?: number } | undefined\n const now = Date.now()\n if (!cur || cur.id === me || (cur.expires ?? 0) < now) {\n await ctx.storage.set(LEASE_KEY, { id: me, expires: now + 45_000 })\n if (!leader) pluginLog(\"info\", `sweep lease acquired (instance ${me.slice(0, 8)})`)\n leader = true\n } else {\n leader = false\n }\n } catch {\n leader = true // storage unavailable: rather sweep than go silent\n }\n }\n void tryLead()\n const leadTimer = setInterval(() => void tryLead(), 15_000)\n\n function stateFor(statePath: string): WatchState {\n const cached = states.get(statePath)\n if (cached) return cached\n let loaded: WatchState = { lastFingerprint: \"\", nudges: 0, greenAnnounced: false }\n try {\n if (existsSync(statePath)) loaded = { ...loaded, ...(JSON.parse(readFileSync(statePath, \"utf8\")) as WatchState) }\n } catch {\n loaded = { lastFingerprint: \"\", nudges: 0, greenAnnounced: false }\n }\n states.set(statePath, loaded)\n return loaded\n }\n\n const save = (statePath: string, state: WatchState) => {\n mkdirSync(dirname(statePath), { recursive: true })\n writeFileSync(statePath, JSON.stringify(state), \"utf8\")\n }\n\n async function nudge(sessionID: string, text: string) {\n try {\n await ctx.session.prompt({ sessionID, text })\n } catch (err) {\n pluginLog(\"warn\", `nudge failed: ${err instanceof Error ? err.message : String(err)}`)\n }\n }\n\n // Resolve the repo directory from the event first (the session's\n // directory, not the plugin instance's), then from the session\n // record, unwrapped, because V2 wraps singleton payloads in `data`.\n async function directoryFor(sessionID: string, event: BusEvent): Promise<string | undefined> {\n const fromEvent = event.location?.directory\n if (fromEvent) return fromEvent\n try {\n const res = (await ctx.session.get({ sessionID })) as unknown as {\n data?: { location?: { directory?: string } }\n location?: { directory?: string }\n }\n const session = res?.data ?? res\n if (session?.location?.directory) return session.location.directory\n } catch {\n /* fall through */\n }\n return undefined\n }\n\n async function sweep(sessionID: string, event: BusEvent) {\n // dedupe execution.succeeded + session.idle fallback double-fire\n const now = Date.now()\n const at = lastHandled.get(sessionID) ?? 0\n if (now - at < 5_000) return\n lastHandled.set(sessionID, now)\n if (lastHandled.size > 1024) {\n for (const [key, ts] of lastHandled) if (ts < now - 60_000) lastHandled.delete(key)\n }\n\n const directory = await directoryFor(sessionID, event)\n if (!directory) return\n if (!findConfigFile(directory) && !process.env.AIWRANGLER_CONFIG) return\n\n let loaded: LoadedConfig\n try {\n loaded = loadConfig(directory)\n } catch {\n return // config problems are the MCP tools' business, not ours\n }\n if (!loaded.config.gates.session.nudge) return\n const statePath = join(loaded.root, \".aiwrangler\", \"watch-state.json\")\n const watch = stateFor(statePath)\n\n // Point the process-global Jev warn sink and the plugin log at\n // this repo for the duration of the sweep.\n currentRoot = loaded.root\n currentLoaded = loaded\n\n try {\n const report = await runDeterministic(loaded.config, loaded.root, { cargo: false })\n const fingerprint = diffFingerprint(\n report.repoDiffs.flatMap((d) => d.files.map((f) => f.path + String(f.hunks.length))).join(\"|\"),\n )\n const blocking = report.findings.filter((f) => f.severity === \"block\" || f.severity === \"human\")\n pluginLog(\n \"info\",\n `sweep session=${sessionID} root=${loaded.root} fingerprint=${fingerprint.slice(0, 12)} diffRepos=${report.repoDiffs.length} blocking=${blocking.length}`,\n )\n if (fingerprint === watch.lastFingerprint) return\n watch.lastFingerprint = fingerprint\n\n if (blocking.length > 0 && watch.nudges < loaded.config.gates.session.maxNudges) {\n watch.nudges++\n watch.greenAnnounced = false\n save(statePath, watch)\n await nudge(sessionID, [\n \"aiwrangler gate: deterministic findings on the current dirty files. Fix these before finishing, then run the wrangler_check tool for the full battery:\",\n \"\",\n formatFindings(blocking),\n ].join(\"\\n\"))\n return\n }\n if (report.repoDiffs.length > 0 && blocking.length === 0 && !watch.greenAnnounced && watch.nudges > 0) {\n watch.greenAnnounced = true\n save(statePath, watch)\n await nudge(sessionID, \"aiwrangler gate: previous deterministic findings are resolved. Run the wrangler_check tool for the full semantic battery before declaring done.\")\n }\n if (report.repoDiffs.length === 0) {\n watch.nudges = 0\n watch.greenAnnounced = false\n save(statePath, watch)\n }\n } catch (err) {\n pluginLog(\"warn\", `sweep error: ${err instanceof Error ? err.message : String(err)}`)\n }\n }\n\n // Process-global sink: log against the repo being swept and append a\n // notice to that repo's ledger. V1's client.tui.showToast is gone in\n // V2 and is not replaced.\n setJevWarnSink((message) => {\n pluginLog(\"warn\", message)\n if (!currentRoot || !currentLoaded) return\n try {\n new Ledger(currentLoaded.config, currentRoot).append({ ts: new Date().toISOString(), kind: \"notice\", message })\n } catch {\n /* ledger unavailable; the plugin log above still captured it */\n }\n })\n\n const controller = new AbortController()\n void (async () => {\n for await (const event of ctx.event.subscribe({ signal: controller.signal }) as unknown as AsyncIterable<BusEvent>) {\n if (event.type !== \"session.execution.succeeded\" && event.type !== \"session.idle\") continue\n if (!leader) continue // followers stay subscribed for seamless takeover\n const props = (event.data ?? event.properties ?? {}) as Record<string, unknown>\n const sessionID = String(props.sessionID ?? props.id ?? \"\")\n if (!sessionID) continue\n await sweep(sessionID, event)\n }\n })().catch((err) => pluginLog(\"warn\", `event stream ended: ${err instanceof Error ? err.message : String(err)}`))\n\n pluginLog(\"info\", `nudge tripwire loaded (location ${ctx.location.directory})`)\n return () => {\n clearInterval(leadTimer)\n controller.abort()\n currentRoot = null\n currentLoaded = null\n setJevWarnSink(null)\n void (async () => {\n try {\n const cur = (await ctx.storage.get(LEASE_KEY)) as { id?: string } | undefined\n if (cur?.id === me) await ctx.storage.remove(LEASE_KEY)\n } catch {\n /* best effort: the 45s lease TTL self-heals */\n }\n })()\n }\n },\n }),\n )\n .catch(() => undefined) // V1-only consumer: @opencode/plugin not installed\n\n// Dual entrypoint: V2 reads `id`/`setup` and ignores `server()`; V1 1.18.29+\n// reads `server()` on object entrypoints. When @opencode/plugin does not\n// resolve, only the V1 half survives.\nexport default {\n ...(v2Plugin ?? {}),\n async server(input: Parameters<V1Plugin>[0]) {\n return WranglerWatchPlugin(input)\n },\n}\n"],"mappings":";;;;;;;;;;;;;AAAA,SAAS,kBAAkB;AAC3B,SAAS,gBAAgB,YAAY,WAAW,cAAc,UAAU,qBAAqB;AAC7F,SAAS,SAAS,YAAY;AAgBvB,IAAM,sBAAgC,OAAO,EAAE,QAAQ,UAAU,MAAM;AAC5E,QAAM,MAAM,CAAC,OAA4C,YACvD,OAAO,IAAI,IAAI,EAAE,MAAM,EAAE,SAAS,oBAAoB,OAAO,QAAQ,EAAE,CAAC,EAAE,MAAM,MAAM;AAAA,EAAC,CAAC;AAE1F,MAAI,CAAC,aAAc,CAAC,eAAe,SAAS,KAAK,CAAC,QAAQ,IAAI,mBAAoB;AAChF,WAAO,CAAC;AAAA,EACV;AAEA,MAAI;AACJ,MAAI;AACF,aAAS,WAAW,SAAS;AAAA,EAC/B,SAAS,KAAK;AACZ,SAAK,IAAI,SAAS,4DAA4D,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC,EAAE;AAChI,WAAO,CAAC;AAAA,EACV;AACA,QAAM,YAAY,KAAK,OAAO,MAAM,eAAe,kBAAkB;AAErE,iBAAe,CAAC,YAAY;AAC1B,SAAK,IAAI,QAAQ,OAAO;AACxB,WAAO,IACJ,UAAU,EAAE,MAAM,EAAE,OAAO,cAAc,SAAS,SAAS,WAAW,UAAU,IAAK,EAAE,CAAC,EACxF,MAAM,MAAM;AAAA,IAAC,CAAC;AACjB,QAAI;AACF,UAAI,OAAO,OAAO,QAAQ,OAAO,IAAI,EAAE,OAAO,EAAE,KAAI,oBAAI,KAAK,GAAE,YAAY,GAAG,MAAM,UAAU,QAAQ,CAAC;AAAA,IACzG,QAAQ;AAAA,IAER;AAAA,EACF,CAAC;AACD,MAAI,QAAoB,EAAE,iBAAiB,IAAI,QAAQ,GAAG,gBAAgB,MAAM;AAChF,MAAI;AACF,QAAI,WAAW,SAAS,EAAG,SAAQ,EAAE,GAAG,OAAO,GAAI,KAAK,MAAM,aAAa,WAAW,MAAM,CAAC,EAAiB;AAAA,EAChH,QAAQ;AACN,YAAQ,EAAE,iBAAiB,IAAI,QAAQ,GAAG,gBAAgB,MAAM;AAAA,EAClE;AACA,QAAM,OAAO,MAAM;AACjB,cAAU,QAAQ,SAAS,GAAG,EAAE,WAAW,KAAK,CAAC;AACjD,kBAAc,WAAW,KAAK,UAAU,KAAK,GAAG,MAAM;AAAA,EACxD;AAEA,iBAAe,MAAM,WAAmB,MAAc;AACpD,QAAI;AACF,YAAM,OAAO,QAAQ,YAAY;AAAA,QAC/B,MAAM,EAAE,IAAI,UAAU;AAAA,QACtB,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,QAAQ,KAAK,CAAC,EAAE;AAAA,MAC1C,CAAC;AAAA,IACH,SAAS,KAAK;AACZ,WAAK,IAAI,QAAQ,iBAAiB,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC,EAAE;AAAA,IACtF;AAAA,EACF;AAEA,SAAO;AAAA,IACL,OAAO,OAAO,EAAE,MAAM,MAAM;AAC1B,YAAM,IAAI;AACV,UAAI,EAAE,SAAS,eAAgB;AAC/B,YAAM,QAAQ,EAAE,cAAc,CAAC;AAC/B,YAAM,YAAY,OAAO,MAAM,aAAa,MAAM,MAAM,EAAE;AAC1D,UAAI,CAAC,UAAW;AAEhB,UAAI;AACF,cAAMA,UAAS,WAAW,SAAU;AACpC,YAAI,CAACA,QAAO,OAAO,MAAM,QAAQ,MAAO;AACxC,cAAM,SAAS,MAAM,iBAAiBA,QAAO,QAAQA,QAAO,MAAM,EAAE,OAAO,MAAM,CAAC;AAClF,cAAM,cAAc;AAAA,UAClB,OAAO,UACJ,QAAQ,CAAC,MAAM,EAAE,MAAM,IAAI,CAAC,MAAM,EAAE,OAAO,OAAO,EAAE,MAAM,MAAM,CAAC,CAAC,EAClE,KAAK,GAAG;AAAA,QACb;AACA,YAAI,gBAAgB,MAAM,gBAAiB;AAC3C,cAAM,kBAAkB;AAExB,cAAM,WAAW,OAAO,SAAS,OAAO,CAAC,MAAM,EAAE,aAAa,WAAW,EAAE,aAAa,OAAO;AAC/F,YAAI,SAAS,SAAS,KAAK,MAAM,SAASA,QAAO,OAAO,MAAM,QAAQ,WAAW;AAC/E,gBAAM;AACN,gBAAM,iBAAiB;AACvB,eAAK;AACL,gBAAM;AAAA,YACJ;AAAA,YACA;AAAA,cACE;AAAA,cACA;AAAA,cACA,eAAe,QAAQ;AAAA,YACzB,EAAE,KAAK,IAAI;AAAA,UACb;AACA;AAAA,QACF;AAEA,YAAI,OAAO,UAAU,SAAS,KAAK,SAAS,WAAW,KAAK,CAAC,MAAM,kBAAkB,MAAM,SAAS,GAAG;AACrG,gBAAM,iBAAiB;AACvB,eAAK;AACL,gBAAM;AAAA,YACJ;AAAA,YACA;AAAA,UACF;AAAA,QACF;AACA,YAAI,OAAO,UAAU,WAAW,GAAG;AACjC,gBAAM,SAAS;AACf,gBAAM,iBAAiB;AACvB,eAAK;AAAA,QACP;AAAA,MACF,SAAS,KAAK;AACZ,aAAK,IAAI,QAAQ,qBAAqB,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC,EAAE;AAAA,MAC1F;AAAA,IACF;AAAA,EACF;AACF;AAuBA,IAAI,cAA6B;AACjC,IAAI,gBAAqC;AAKzC,IAAM,YAAY,CAAC,OAA4C,YAA0B;AACvF,MAAI;AACF,YAAQ,MAAM,sBAAsB,KAAK,KAAK,OAAO,EAAE;AACvD,QAAI,CAAC,YAAa;AAClB,UAAM,UAAU,KAAK,aAAa,eAAe,YAAY;AAC7D,mBAAe,SAAS,IAAG,oBAAI,KAAK,GAAE,YAAY,CAAC,IAAI,MAAM,YAAY,CAAC,IAAI,OAAO;AAAA,CAAI;AACzF,QAAI,SAAS,OAAO,EAAE,OAAO,MAAM,MAAM;AACvC,oBAAc,SAAS,aAAa,SAAS,MAAM,EAAE,MAAM,IAAI,EAAE,MAAM,IAAI,EAAE,KAAK,IAAI,CAAC;AAAA,IACzF;AAAA,EACF,QAAQ;AAAA,EAER;AACF;AAEA,IAAM,WAAW,MAAM,OAAO,kBAAkB,EAC7C;AAAA,EAAK,CAAC,EAAE,OAAO,MACd,OAAO,OAAO;AAAA,IACZ,IAAI;AAAA,IACJ,MAAM,MAAM,KAAK;AACf,YAAM,SAAS,oBAAI,IAAwB;AAC3C,YAAM,cAAc,oBAAI,IAAoB;AAS5C,YAAM,KAAK,WAAW;AACtB,YAAM,YAAY;AAClB,UAAI,SAAS;AACb,qBAAe,UAAU;AACvB,YAAI;AACF,gBAAM,MAAO,MAAM,IAAI,QAAQ,IAAI,SAAS;AAC5C,gBAAM,MAAM,KAAK,IAAI;AACrB,cAAI,CAAC,OAAO,IAAI,OAAO,OAAO,IAAI,WAAW,KAAK,KAAK;AACrD,kBAAM,IAAI,QAAQ,IAAI,WAAW,EAAE,IAAI,IAAI,SAAS,MAAM,KAAO,CAAC;AAClE,gBAAI,CAAC,OAAQ,WAAU,QAAQ,kCAAkC,GAAG,MAAM,GAAG,CAAC,CAAC,GAAG;AAClF,qBAAS;AAAA,UACX,OAAO;AACL,qBAAS;AAAA,UACX;AAAA,QACF,QAAQ;AACN,mBAAS;AAAA,QACX;AAAA,MACF;AACA,WAAK,QAAQ;AACb,YAAM,YAAY,YAAY,MAAM,KAAK,QAAQ,GAAG,IAAM;AAE1D,eAAS,SAAS,WAA+B;AAC/C,cAAM,SAAS,OAAO,IAAI,SAAS;AACnC,YAAI,OAAQ,QAAO;AACnB,YAAI,SAAqB,EAAE,iBAAiB,IAAI,QAAQ,GAAG,gBAAgB,MAAM;AACjF,YAAI;AACF,cAAI,WAAW,SAAS,EAAG,UAAS,EAAE,GAAG,QAAQ,GAAI,KAAK,MAAM,aAAa,WAAW,MAAM,CAAC,EAAiB;AAAA,QAClH,QAAQ;AACN,mBAAS,EAAE,iBAAiB,IAAI,QAAQ,GAAG,gBAAgB,MAAM;AAAA,QACnE;AACA,eAAO,IAAI,WAAW,MAAM;AAC5B,eAAO;AAAA,MACT;AAEA,YAAM,OAAO,CAAC,WAAmB,UAAsB;AACrD,kBAAU,QAAQ,SAAS,GAAG,EAAE,WAAW,KAAK,CAAC;AACjD,sBAAc,WAAW,KAAK,UAAU,KAAK,GAAG,MAAM;AAAA,MACxD;AAEA,qBAAe,MAAM,WAAmB,MAAc;AACpD,YAAI;AACF,gBAAM,IAAI,QAAQ,OAAO,EAAE,WAAW,KAAK,CAAC;AAAA,QAC9C,SAAS,KAAK;AACZ,oBAAU,QAAQ,iBAAiB,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC,EAAE;AAAA,QACvF;AAAA,MACF;AAKA,qBAAe,aAAa,WAAmB,OAA8C;AAC3F,cAAM,YAAY,MAAM,UAAU;AAClC,YAAI,UAAW,QAAO;AACtB,YAAI;AACF,gBAAM,MAAO,MAAM,IAAI,QAAQ,IAAI,EAAE,UAAU,CAAC;AAIhD,gBAAM,UAAU,KAAK,QAAQ;AAC7B,cAAI,SAAS,UAAU,UAAW,QAAO,QAAQ,SAAS;AAAA,QAC5D,QAAQ;AAAA,QAER;AACA,eAAO;AAAA,MACT;AAEA,qBAAe,MAAM,WAAmB,OAAiB;AAEvD,cAAM,MAAM,KAAK,IAAI;AACrB,cAAM,KAAK,YAAY,IAAI,SAAS,KAAK;AACzC,YAAI,MAAM,KAAK,IAAO;AACtB,oBAAY,IAAI,WAAW,GAAG;AAC9B,YAAI,YAAY,OAAO,MAAM;AAC3B,qBAAW,CAAC,KAAK,EAAE,KAAK,YAAa,KAAI,KAAK,MAAM,IAAQ,aAAY,OAAO,GAAG;AAAA,QACpF;AAEA,cAAM,YAAY,MAAM,aAAa,WAAW,KAAK;AACrD,YAAI,CAAC,UAAW;AAChB,YAAI,CAAC,eAAe,SAAS,KAAK,CAAC,QAAQ,IAAI,kBAAmB;AAElE,YAAI;AACJ,YAAI;AACF,mBAAS,WAAW,SAAS;AAAA,QAC/B,QAAQ;AACN;AAAA,QACF;AACA,YAAI,CAAC,OAAO,OAAO,MAAM,QAAQ,MAAO;AACxC,cAAM,YAAY,KAAK,OAAO,MAAM,eAAe,kBAAkB;AACrE,cAAM,QAAQ,SAAS,SAAS;AAIhC,sBAAc,OAAO;AACrB,wBAAgB;AAEhB,YAAI;AACF,gBAAM,SAAS,MAAM,iBAAiB,OAAO,QAAQ,OAAO,MAAM,EAAE,OAAO,MAAM,CAAC;AAClF,gBAAM,cAAc;AAAA,YAClB,OAAO,UAAU,QAAQ,CAAC,MAAM,EAAE,MAAM,IAAI,CAAC,MAAM,EAAE,OAAO,OAAO,EAAE,MAAM,MAAM,CAAC,CAAC,EAAE,KAAK,GAAG;AAAA,UAC/F;AACA,gBAAM,WAAW,OAAO,SAAS,OAAO,CAAC,MAAM,EAAE,aAAa,WAAW,EAAE,aAAa,OAAO;AAC/F;AAAA,YACE;AAAA,YACA,iBAAiB,SAAS,SAAS,OAAO,IAAI,gBAAgB,YAAY,MAAM,GAAG,EAAE,CAAC,cAAc,OAAO,UAAU,MAAM,aAAa,SAAS,MAAM;AAAA,UACzJ;AACA,cAAI,gBAAgB,MAAM,gBAAiB;AAC3C,gBAAM,kBAAkB;AAExB,cAAI,SAAS,SAAS,KAAK,MAAM,SAAS,OAAO,OAAO,MAAM,QAAQ,WAAW;AAC/E,kBAAM;AACN,kBAAM,iBAAiB;AACvB,iBAAK,WAAW,KAAK;AACrB,kBAAM,MAAM,WAAW;AAAA,cACrB;AAAA,cACA;AAAA,cACA,eAAe,QAAQ;AAAA,YACzB,EAAE,KAAK,IAAI,CAAC;AACZ;AAAA,UACF;AACA,cAAI,OAAO,UAAU,SAAS,KAAK,SAAS,WAAW,KAAK,CAAC,MAAM,kBAAkB,MAAM,SAAS,GAAG;AACrG,kBAAM,iBAAiB;AACvB,iBAAK,WAAW,KAAK;AACrB,kBAAM,MAAM,WAAW,iJAAiJ;AAAA,UAC1K;AACA,cAAI,OAAO,UAAU,WAAW,GAAG;AACjC,kBAAM,SAAS;AACf,kBAAM,iBAAiB;AACvB,iBAAK,WAAW,KAAK;AAAA,UACvB;AAAA,QACF,SAAS,KAAK;AACZ,oBAAU,QAAQ,gBAAgB,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC,EAAE;AAAA,QACtF;AAAA,MACF;AAKA,qBAAe,CAAC,YAAY;AAC1B,kBAAU,QAAQ,OAAO;AACzB,YAAI,CAAC,eAAe,CAAC,cAAe;AACpC,YAAI;AACF,cAAI,OAAO,cAAc,QAAQ,WAAW,EAAE,OAAO,EAAE,KAAI,oBAAI,KAAK,GAAE,YAAY,GAAG,MAAM,UAAU,QAAQ,CAAC;AAAA,QAChH,QAAQ;AAAA,QAER;AAAA,MACF,CAAC;AAED,YAAM,aAAa,IAAI,gBAAgB;AACvC,YAAM,YAAY;AAChB,yBAAiB,SAAS,IAAI,MAAM,UAAU,EAAE,QAAQ,WAAW,OAAO,CAAC,GAAyC;AAClH,cAAI,MAAM,SAAS,iCAAiC,MAAM,SAAS,eAAgB;AACnF,cAAI,CAAC,OAAQ;AACb,gBAAM,QAAS,MAAM,QAAQ,MAAM,cAAc,CAAC;AAClD,gBAAM,YAAY,OAAO,MAAM,aAAa,MAAM,MAAM,EAAE;AAC1D,cAAI,CAAC,UAAW;AAChB,gBAAM,MAAM,WAAW,KAAK;AAAA,QAC9B;AAAA,MACF,GAAG,EAAE,MAAM,CAAC,QAAQ,UAAU,QAAQ,uBAAuB,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC,EAAE,CAAC;AAEhH,gBAAU,QAAQ,mCAAmC,IAAI,SAAS,SAAS,GAAG;AAC9E,aAAO,MAAM;AACX,sBAAc,SAAS;AACvB,mBAAW,MAAM;AACjB,sBAAc;AACd,wBAAgB;AAChB,uBAAe,IAAI;AACnB,cAAM,YAAY;AAChB,cAAI;AACF,kBAAM,MAAO,MAAM,IAAI,QAAQ,IAAI,SAAS;AAC5C,gBAAI,KAAK,OAAO,GAAI,OAAM,IAAI,QAAQ,OAAO,SAAS;AAAA,UACxD,QAAQ;AAAA,UAER;AAAA,QACF,GAAG;AAAA,MACL;AAAA,IACF;AAAA,EACF,CAAC;AACH,EACC,MAAM,MAAM,MAAS;AAKxB,IAAO,iBAAQ;AAAA,EACb,GAAI,YAAY,CAAC;AAAA,EACjB,MAAM,OAAO,OAAgC;AAC3C,WAAO,oBAAoB,KAAK;AAAA,EAClC;AACF;","names":["loaded"]}
1
+ {"version":3,"sources":["../src/plugin.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\"\nimport { appendFileSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from \"node:fs\"\nimport { dirname, join } from \"node:path\"\nimport type { Plugin as V1Plugin } from \"@opencode-ai/plugin\"\nimport type { Plugin as V2PluginNS } from \"@opencode/plugin\"\nimport { findConfigFile, loadConfig, type LoadedConfig } from \"./config.js\"\nimport { runDeterministic, formatFindings } from \"./gates/deterministic/index.js\"\nimport { diffFingerprint } from \"./loop.js\"\nimport { setJevWarnSink } from \"./jev/client.js\"\nimport { Ledger } from \"./ledger.js\"\n\ninterface WatchState {\n lastFingerprint: string\n nudges: number\n greenAnnounced: boolean\n}\n\n// ---- V1 named export: unchanged ---------------------------------------------\n// V1 consumers re-export this by name (see README, \"OpenCode 1.x (legacy)\").\nexport const WranglerWatchPlugin: V1Plugin = async ({ client, directory }) => {\n const log = (level: \"debug\" | \"info\" | \"warn\" | \"error\", message: string) =>\n client.app.log({ body: { service: \"aiwrangler-watch\", level, message } }).catch(() => {})\n\n if (!directory || (!findConfigFile(directory) && !process.env.AIWRANGLER_CONFIG)) {\n return {}\n }\n\n let loaded\n try {\n loaded = loadConfig(directory)\n } catch (err) {\n void log(\"error\", `aiwrangler config failed to load; watch plugin inactive: ${err instanceof Error ? err.message : String(err)}`)\n return {}\n }\n const statePath = join(loaded.root, \".aiwrangler\", \"watch-state.json\")\n\n setJevWarnSink((message) => {\n void log(\"warn\", message)\n client.tui\n .showToast({ body: { title: \"aiwrangler\", message, variant: \"warning\", duration: 8000 } })\n .catch(() => {})\n try {\n new Ledger(loaded.config, loaded.root).append({ ts: new Date().toISOString(), kind: \"notice\", message })\n } catch {\n /* ledger unavailable; the app log above still captured it */\n }\n })\n let watch: WatchState = { lastFingerprint: \"\", nudges: 0, greenAnnounced: false }\n try {\n if (existsSync(statePath)) watch = { ...watch, ...(JSON.parse(readFileSync(statePath, \"utf8\")) as WatchState) }\n } catch {\n watch = { lastFingerprint: \"\", nudges: 0, greenAnnounced: false }\n }\n const save = () => {\n mkdirSync(dirname(statePath), { recursive: true })\n writeFileSync(statePath, JSON.stringify(watch), \"utf8\")\n }\n\n async function nudge(sessionID: string, text: string) {\n try {\n await client.session.promptAsync({\n path: { id: sessionID },\n body: { parts: [{ type: \"text\", text }] },\n })\n } catch (err) {\n void log(\"warn\", `nudge failed: ${err instanceof Error ? err.message : String(err)}`)\n }\n }\n\n return {\n event: async ({ event }) => {\n const e = event as { type?: string; properties?: Record<string, any> }\n if (e.type !== \"session.idle\") return\n const props = e.properties ?? {}\n const sessionID = String(props.sessionID ?? props.id ?? \"\")\n if (!sessionID) return\n\n try {\n const loaded = loadConfig(directory!)\n if (!loaded.config.gates.session.nudge) return\n const report = await runDeterministic(loaded.config, loaded.root, { cargo: false })\n const fingerprint = diffFingerprint(\n report.repoDiffs\n .flatMap((d) => d.files.map((f) => f.path + String(f.hunks.length)))\n .join(\"|\"),\n )\n if (fingerprint === watch.lastFingerprint) return\n watch.lastFingerprint = fingerprint\n\n const blocking = report.findings.filter((f) => f.severity === \"block\" || f.severity === \"human\")\n if (blocking.length > 0 && watch.nudges < loaded.config.gates.session.maxNudges) {\n watch.nudges++\n watch.greenAnnounced = false\n save()\n await nudge(\n sessionID,\n [\n \"aiwrangler gate: deterministic findings on the current dirty files. Fix these before finishing, then run the wrangler_check tool for the full battery:\",\n \"\",\n formatFindings(blocking),\n ].join(\"\\n\"),\n )\n return\n }\n\n if (report.repoDiffs.length > 0 && blocking.length === 0 && !watch.greenAnnounced && watch.nudges > 0) {\n watch.greenAnnounced = true\n save()\n await nudge(\n sessionID,\n \"aiwrangler gate: previous deterministic findings are resolved. Run the wrangler_check tool for the full semantic battery before declaring done.\",\n )\n }\n if (report.repoDiffs.length === 0) {\n watch.nudges = 0\n watch.greenAnnounced = false\n save()\n }\n } catch (err) {\n void log(\"warn\", `idle check error: ${err instanceof Error ? err.message : String(err)}`)\n }\n },\n }\n}\n\n// ---- V2 plugin ----------------------------------------------------------------\n//\n// opencode V2 loads plugins per location and every instance sees the same\n// server-wide event stream; the completion family is\n// `session.execution.succeeded` (V1's `session.idle` never fires and is kept\n// only as a deduped fallback). Event payloads live in `event.data`, and V2\n// client methods wrap their payloads (`{ location?, data: [...] }` for lists,\n// `{ data: {...} }` for singletons), so every read below unwraps defensively.\n// These shapes were verified empirically against opencode 2.1.1; where the\n// shipped types disagree, the runtime behavior wins.\n\ntype BusEvent = {\n type?: string\n data?: Record<string, unknown>\n properties?: Record<string, unknown>\n location?: { directory?: string }\n}\n\n// The Jev warn sink is process-global, but one leader instance now serves\n// multiple repos: currentRoot/currentLoaded point the sink and the per-repo\n// plugin log at the repo the current sweep is working on.\nlet currentRoot: string | null = null\nlet currentLoaded: LoadedConfig | null = null\n\n// console.error from plugin code does not reach opencode.log, and V1's\n// client.app.log / client.tui.showToast do not exist in V2; keep our own\n// trail beside the ledger, tail-truncated.\nconst pluginLog = (level: \"debug\" | \"info\" | \"warn\" | \"error\", message: string): void => {\n try {\n console.error(`[aiwrangler-nudge] ${level}: ${message}`)\n if (!currentRoot) return\n const logPath = join(currentRoot, \".aiwrangler\", \"plugin.log\")\n appendFileSync(logPath, `${new Date().toISOString()} ${level.toUpperCase()} ${message}\\n`)\n if (statSync(logPath).size > 128 * 1024) {\n writeFileSync(logPath, readFileSync(logPath, \"utf8\").split(\"\\n\").slice(-300).join(\"\\n\"))\n }\n } catch {\n /* never break on log trouble */\n }\n}\n\n// The V2 plugin definition. `Plugin.define` is the identity function in\n// @opencode/plugin 2.1.1, and opencode's package-plugin loader only requires\n// the default export to carry `id` and `setup` (it resolves the package's\n// `./server` export); the definition therefore stands on its own wherever the\n// SDK cannot be imported, e.g. opencode's package-plugin cache, which installs\n// the package without optional peers.\nconst v2Definition: V2PluginNS.Plugin = {\n id: \"aiwrangler.nudge\",\n async setup(ctx) {\n const states = new Map<string, WatchState>()\n const lastHandled = new Map<string, number>()\n\n // ---- single-actor lease -------------------------------------------------\n // V2 loads this plugin per location; every instance receives every\n // session's completion event. Unguarded, three loaded locations means\n // three sweeps, tripled nudges, and a burned maxNudges budget. One\n // lease holder sweeps; followers stay subscribed and take over within\n // <=15s of the leader disappearing (lease TTL 45s; the TTL is the\n // correctness mechanism, deletion in cleanup is an optimization).\n const me = randomUUID()\n const LEASE_KEY = \"leader.lease\" // plugin-scoped storage, shared across location instances\n let leader = false\n async function tryLead() {\n try {\n const cur = (await ctx.storage.get(LEASE_KEY)) as { id?: string; expires?: number } | undefined\n const now = Date.now()\n if (!cur || cur.id === me || (cur.expires ?? 0) < now) {\n await ctx.storage.set(LEASE_KEY, { id: me, expires: now + 45_000 })\n if (!leader) pluginLog(\"info\", `sweep lease acquired (instance ${me.slice(0, 8)})`)\n leader = true\n } else {\n leader = false\n }\n } catch {\n leader = true // storage unavailable: rather sweep than go silent\n }\n }\n void tryLead()\n const leadTimer = setInterval(() => void tryLead(), 15_000)\n\n function stateFor(statePath: string): WatchState {\n const cached = states.get(statePath)\n if (cached) return cached\n let loaded: WatchState = { lastFingerprint: \"\", nudges: 0, greenAnnounced: false }\n try {\n if (existsSync(statePath)) loaded = { ...loaded, ...(JSON.parse(readFileSync(statePath, \"utf8\")) as WatchState) }\n } catch {\n loaded = { lastFingerprint: \"\", nudges: 0, greenAnnounced: false }\n }\n states.set(statePath, loaded)\n return loaded\n }\n\n const save = (statePath: string, state: WatchState) => {\n mkdirSync(dirname(statePath), { recursive: true })\n writeFileSync(statePath, JSON.stringify(state), \"utf8\")\n }\n\n async function nudge(sessionID: string, text: string) {\n try {\n await ctx.session.prompt({ sessionID, text })\n } catch (err) {\n pluginLog(\"warn\", `nudge failed: ${err instanceof Error ? err.message : String(err)}`)\n }\n }\n\n // Resolve the repo directory from the event first (the session's\n // directory, not the plugin instance's), then from the session\n // record, unwrapped, because V2 wraps singleton payloads in `data`.\n async function directoryFor(sessionID: string, event: BusEvent): Promise<string | undefined> {\n const fromEvent = event.location?.directory\n if (fromEvent) return fromEvent\n try {\n const res = (await ctx.session.get({ sessionID })) as unknown as {\n data?: { location?: { directory?: string } }\n location?: { directory?: string }\n }\n const session = res?.data ?? res\n if (session?.location?.directory) return session.location.directory\n } catch {\n /* fall through */\n }\n return undefined\n }\n\n async function sweep(sessionID: string, event: BusEvent) {\n // dedupe execution.succeeded + session.idle fallback double-fire\n const now = Date.now()\n const at = lastHandled.get(sessionID) ?? 0\n if (now - at < 5_000) return\n lastHandled.set(sessionID, now)\n if (lastHandled.size > 1024) {\n for (const [key, ts] of lastHandled) if (ts < now - 60_000) lastHandled.delete(key)\n }\n\n const directory = await directoryFor(sessionID, event)\n if (!directory) return\n if (!findConfigFile(directory) && !process.env.AIWRANGLER_CONFIG) return\n\n let loaded: LoadedConfig\n try {\n loaded = loadConfig(directory)\n } catch {\n return // config problems are the MCP tools' business, not ours\n }\n if (!loaded.config.gates.session.nudge) return\n const statePath = join(loaded.root, \".aiwrangler\", \"watch-state.json\")\n const watch = stateFor(statePath)\n\n // Point the process-global Jev warn sink and the plugin log at\n // this repo for the duration of the sweep.\n currentRoot = loaded.root\n currentLoaded = loaded\n\n try {\n const report = await runDeterministic(loaded.config, loaded.root, { cargo: false })\n const fingerprint = diffFingerprint(\n report.repoDiffs.flatMap((d) => d.files.map((f) => f.path + String(f.hunks.length))).join(\"|\"),\n )\n const blocking = report.findings.filter((f) => f.severity === \"block\" || f.severity === \"human\")\n pluginLog(\n \"info\",\n `sweep session=${sessionID} root=${loaded.root} fingerprint=${fingerprint.slice(0, 12)} diffRepos=${report.repoDiffs.length} blocking=${blocking.length}`,\n )\n if (fingerprint === watch.lastFingerprint) return\n watch.lastFingerprint = fingerprint\n\n if (blocking.length > 0 && watch.nudges < loaded.config.gates.session.maxNudges) {\n watch.nudges++\n watch.greenAnnounced = false\n save(statePath, watch)\n await nudge(sessionID, [\n \"aiwrangler gate: deterministic findings on the current dirty files. Fix these before finishing, then run the wrangler_check tool for the full battery:\",\n \"\",\n formatFindings(blocking),\n ].join(\"\\n\"))\n return\n }\n if (report.repoDiffs.length > 0 && blocking.length === 0 && !watch.greenAnnounced && watch.nudges > 0) {\n watch.greenAnnounced = true\n save(statePath, watch)\n await nudge(sessionID, \"aiwrangler gate: previous deterministic findings are resolved. Run the wrangler_check tool for the full semantic battery before declaring done.\")\n }\n if (report.repoDiffs.length === 0) {\n watch.nudges = 0\n watch.greenAnnounced = false\n save(statePath, watch)\n }\n } catch (err) {\n pluginLog(\"warn\", `sweep error: ${err instanceof Error ? err.message : String(err)}`)\n }\n }\n\n // Process-global sink: log against the repo being swept and append a\n // notice to that repo's ledger. V1's client.tui.showToast is gone in\n // V2 and is not replaced.\n setJevWarnSink((message) => {\n pluginLog(\"warn\", message)\n if (!currentRoot || !currentLoaded) return\n try {\n new Ledger(currentLoaded.config, currentRoot).append({ ts: new Date().toISOString(), kind: \"notice\", message })\n } catch {\n /* ledger unavailable; the plugin log above still captured it */\n }\n })\n\n const controller = new AbortController()\n void (async () => {\n for await (const event of ctx.event.subscribe({ signal: controller.signal }) as unknown as AsyncIterable<BusEvent>) {\n if (event.type !== \"session.execution.succeeded\" && event.type !== \"session.idle\") continue\n if (!leader) continue // followers stay subscribed for seamless takeover\n const props = (event.data ?? event.properties ?? {}) as Record<string, unknown>\n const sessionID = String(props.sessionID ?? props.id ?? \"\")\n if (!sessionID) continue\n await sweep(sessionID, event)\n }\n })().catch((err) => pluginLog(\"warn\", `event stream ended: ${err instanceof Error ? err.message : String(err)}`))\n\n pluginLog(\"info\", `nudge tripwire loaded (location ${ctx.location.directory})`)\n return () => {\n clearInterval(leadTimer)\n controller.abort()\n currentRoot = null\n currentLoaded = null\n setJevWarnSink(null)\n void (async () => {\n try {\n const cur = (await ctx.storage.get(LEASE_KEY)) as { id?: string } | undefined\n if (cur?.id === me) await ctx.storage.remove(LEASE_KEY)\n } catch {\n /* best effort: the 45s lease TTL self-heals */\n }\n })()\n }\n },\n}\n\n// Prefer the real SDK's `Plugin.define` when the host provides it (it may\n// validate or wrap in newer versions); pass the definition through unchanged\n// when the SDK is absent (V1-only consumers, opencode's package cache).\nconst v2Plugin = await import(\"@opencode/plugin\")\n .then(({ Plugin }) => Plugin.define(v2Definition))\n .catch(() => v2Definition)\n\n// Dual entrypoint: V2 reads `id`/`setup` and ignores `server()`; V1 1.18.29+\n// reads `server()` on object entrypoints. Both halves are always present; the\n// V2 half needs no SDK at runtime.\nexport default {\n ...(v2Plugin ?? {}),\n async server(input: Parameters<V1Plugin>[0]) {\n return WranglerWatchPlugin(input)\n },\n}\n"],"mappings":"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","names":["loaded"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kodr/aiwrangler",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Ground-truth guardrails for coding agents: deterministic checks plus calibrated semantic judgments against your own spec, rules, and rulings",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -48,6 +48,10 @@
48
48
  "./plugin": {
49
49
  "import": "./dist/plugin.js",
50
50
  "types": "./dist/plugin.d.ts"
51
+ },
52
+ "./server": {
53
+ "import": "./dist/plugin.js",
54
+ "types": "./dist/plugin.d.ts"
51
55
  }
52
56
  },
53
57
  "scripts": {