@kodr/aiwrangler 0.1.3 → 0.1.4
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 +11 -0
- package/dist/plugin.js +7 -3
- package/dist/plugin.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,17 @@ 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.4 - 2026-10-20
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- The per-repo plugin trail (`<repo>/.aiwrangler/plugin.log`) is written
|
|
13
|
+
again: the V2 plugin host's `console.error` can throw, and the logger
|
|
14
|
+
called it before the file append inside one try block, so a guarded
|
|
15
|
+
console suppressed the durable log entirely (sweeps, state, and nudges
|
|
16
|
+
were unaffected). The file append now runs first and the console attempt
|
|
17
|
+
is separately guarded.
|
|
18
|
+
|
|
8
19
|
## 0.1.3 - 2026-10-20
|
|
9
20
|
|
|
10
21
|
### Fixed
|
package/dist/plugin.js
CHANGED
|
@@ -110,10 +110,10 @@ var WranglerWatchPlugin = async ({ client, directory }) => {
|
|
|
110
110
|
var currentRoot = null;
|
|
111
111
|
var currentLoaded = null;
|
|
112
112
|
var pluginLog = (level, message) => {
|
|
113
|
+
const root = currentRoot;
|
|
113
114
|
try {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
const logPath = join(currentRoot, ".aiwrangler", "plugin.log");
|
|
115
|
+
if (!root) return;
|
|
116
|
+
const logPath = join(root, ".aiwrangler", "plugin.log");
|
|
117
117
|
appendFileSync(logPath, `${(/* @__PURE__ */ new Date()).toISOString()} ${level.toUpperCase()} ${message}
|
|
118
118
|
`);
|
|
119
119
|
if (statSync(logPath).size > 128 * 1024) {
|
|
@@ -121,6 +121,10 @@ var pluginLog = (level, message) => {
|
|
|
121
121
|
}
|
|
122
122
|
} catch {
|
|
123
123
|
}
|
|
124
|
+
try {
|
|
125
|
+
console.error(`[aiwrangler-nudge] ${level}: ${message}`);
|
|
126
|
+
} catch {
|
|
127
|
+
}
|
|
124
128
|
};
|
|
125
129
|
var v2Definition = {
|
|
126
130
|
id: "aiwrangler.nudge",
|
package/dist/plugin.js.map
CHANGED
|
@@ -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 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":";;;;;;;;;;;;;AAAA,SAAS,kBAAkB;AAC3B,SAAS,gBAAgB,YAAY,WAAW,cAAc,UAAU,qBAAqB;AAC7F,SAAS,SAAS,YAAY;AAiBvB,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;AAQA,IAAM,eAAkC;AAAA,EACtC,IAAI;AAAA,EACJ,MAAM,MAAM,KAAK;AACf,UAAM,SAAS,oBAAI,IAAwB;AAC3C,UAAM,cAAc,oBAAI,IAAoB;AASxC,UAAM,KAAK,WAAW;AACtB,UAAM,YAAY;AAClB,QAAI,SAAS;AACb,mBAAe,UAAU;AACvB,UAAI;AACF,cAAM,MAAO,MAAM,IAAI,QAAQ,IAAI,SAAS;AAC5C,cAAM,MAAM,KAAK,IAAI;AACrB,YAAI,CAAC,OAAO,IAAI,OAAO,OAAO,IAAI,WAAW,KAAK,KAAK;AACrD,gBAAM,IAAI,QAAQ,IAAI,WAAW,EAAE,IAAI,IAAI,SAAS,MAAM,KAAO,CAAC;AAClE,cAAI,CAAC,OAAQ,WAAU,QAAQ,kCAAkC,GAAG,MAAM,GAAG,CAAC,CAAC,GAAG;AAClF,mBAAS;AAAA,QACX,OAAO;AACL,mBAAS;AAAA,QACX;AAAA,MACF,QAAQ;AACN,iBAAS;AAAA,MACX;AAAA,IACF;AACA,SAAK,QAAQ;AACb,UAAM,YAAY,YAAY,MAAM,KAAK,QAAQ,GAAG,IAAM;AAE1D,aAAS,SAAS,WAA+B;AAC/C,YAAM,SAAS,OAAO,IAAI,SAAS;AACnC,UAAI,OAAQ,QAAO;AACnB,UAAI,SAAqB,EAAE,iBAAiB,IAAI,QAAQ,GAAG,gBAAgB,MAAM;AACjF,UAAI;AACF,YAAI,WAAW,SAAS,EAAG,UAAS,EAAE,GAAG,QAAQ,GAAI,KAAK,MAAM,aAAa,WAAW,MAAM,CAAC,EAAiB;AAAA,MAClH,QAAQ;AACN,iBAAS,EAAE,iBAAiB,IAAI,QAAQ,GAAG,gBAAgB,MAAM;AAAA,MACnE;AACA,aAAO,IAAI,WAAW,MAAM;AAC5B,aAAO;AAAA,IACT;AAEA,UAAM,OAAO,CAAC,WAAmB,UAAsB;AACrD,gBAAU,QAAQ,SAAS,GAAG,EAAE,WAAW,KAAK,CAAC;AACjD,oBAAc,WAAW,KAAK,UAAU,KAAK,GAAG,MAAM;AAAA,IACxD;AAEA,mBAAe,MAAM,WAAmB,MAAc;AACpD,UAAI;AACF,cAAM,IAAI,QAAQ,OAAO,EAAE,WAAW,KAAK,CAAC;AAAA,MAC9C,SAAS,KAAK;AACZ,kBAAU,QAAQ,iBAAiB,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC,EAAE;AAAA,MACvF;AAAA,IACF;AAKA,mBAAe,aAAa,WAAmB,OAA8C;AAC3F,YAAM,YAAY,MAAM,UAAU;AAClC,UAAI,UAAW,QAAO;AACtB,UAAI;AACF,cAAM,MAAO,MAAM,IAAI,QAAQ,IAAI,EAAE,UAAU,CAAC;AAIhD,cAAM,UAAU,KAAK,QAAQ;AAC7B,YAAI,SAAS,UAAU,UAAW,QAAO,QAAQ,SAAS;AAAA,MAC5D,QAAQ;AAAA,MAER;AACA,aAAO;AAAA,IACT;AAEA,mBAAe,MAAM,WAAmB,OAAiB;AAEvD,YAAM,MAAM,KAAK,IAAI;AACrB,YAAM,KAAK,YAAY,IAAI,SAAS,KAAK;AACzC,UAAI,MAAM,KAAK,IAAO;AACtB,kBAAY,IAAI,WAAW,GAAG;AAC9B,UAAI,YAAY,OAAO,MAAM;AAC3B,mBAAW,CAAC,KAAK,EAAE,KAAK,YAAa,KAAI,KAAK,MAAM,IAAQ,aAAY,OAAO,GAAG;AAAA,MACpF;AAEA,YAAM,YAAY,MAAM,aAAa,WAAW,KAAK;AACrD,UAAI,CAAC,UAAW;AAChB,UAAI,CAAC,eAAe,SAAS,KAAK,CAAC,QAAQ,IAAI,kBAAmB;AAElE,UAAI;AACJ,UAAI;AACF,iBAAS,WAAW,SAAS;AAAA,MAC/B,QAAQ;AACN;AAAA,MACF;AACA,UAAI,CAAC,OAAO,OAAO,MAAM,QAAQ,MAAO;AACxC,YAAM,YAAY,KAAK,OAAO,MAAM,eAAe,kBAAkB;AACrE,YAAM,QAAQ,SAAS,SAAS;AAIhC,oBAAc,OAAO;AACrB,sBAAgB;AAEhB,UAAI;AACF,cAAM,SAAS,MAAM,iBAAiB,OAAO,QAAQ,OAAO,MAAM,EAAE,OAAO,MAAM,CAAC;AAClF,cAAM,cAAc;AAAA,UAClB,OAAO,UAAU,QAAQ,CAAC,MAAM,EAAE,MAAM,IAAI,CAAC,MAAM,EAAE,OAAO,OAAO,EAAE,MAAM,MAAM,CAAC,CAAC,EAAE,KAAK,GAAG;AAAA,QAC/F;AACA,cAAM,WAAW,OAAO,SAAS,OAAO,CAAC,MAAM,EAAE,aAAa,WAAW,EAAE,aAAa,OAAO;AAC/F;AAAA,UACE;AAAA,UACA,iBAAiB,SAAS,SAAS,OAAO,IAAI,gBAAgB,YAAY,MAAM,GAAG,EAAE,CAAC,cAAc,OAAO,UAAU,MAAM,aAAa,SAAS,MAAM;AAAA,QACzJ;AACA,YAAI,gBAAgB,MAAM,gBAAiB;AAC3C,cAAM,kBAAkB;AAExB,YAAI,SAAS,SAAS,KAAK,MAAM,SAAS,OAAO,OAAO,MAAM,QAAQ,WAAW;AAC/E,gBAAM;AACN,gBAAM,iBAAiB;AACvB,eAAK,WAAW,KAAK;AACrB,gBAAM,MAAM,WAAW;AAAA,YACrB;AAAA,YACA;AAAA,YACA,eAAe,QAAQ;AAAA,UACzB,EAAE,KAAK,IAAI,CAAC;AACZ;AAAA,QACF;AACA,YAAI,OAAO,UAAU,SAAS,KAAK,SAAS,WAAW,KAAK,CAAC,MAAM,kBAAkB,MAAM,SAAS,GAAG;AACrG,gBAAM,iBAAiB;AACvB,eAAK,WAAW,KAAK;AACrB,gBAAM,MAAM,WAAW,iJAAiJ;AAAA,QAC1K;AACA,YAAI,OAAO,UAAU,WAAW,GAAG;AACjC,gBAAM,SAAS;AACf,gBAAM,iBAAiB;AACvB,eAAK,WAAW,KAAK;AAAA,QACvB;AAAA,MACF,SAAS,KAAK;AACZ,kBAAU,QAAQ,gBAAgB,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC,EAAE;AAAA,MACtF;AAAA,IACF;AAKA,mBAAe,CAAC,YAAY;AAC1B,gBAAU,QAAQ,OAAO;AACzB,UAAI,CAAC,eAAe,CAAC,cAAe;AACpC,UAAI;AACF,YAAI,OAAO,cAAc,QAAQ,WAAW,EAAE,OAAO,EAAE,KAAI,oBAAI,KAAK,GAAE,YAAY,GAAG,MAAM,UAAU,QAAQ,CAAC;AAAA,MAChH,QAAQ;AAAA,MAER;AAAA,IACF,CAAC;AAED,UAAM,aAAa,IAAI,gBAAgB;AACvC,UAAM,YAAY;AAChB,uBAAiB,SAAS,IAAI,MAAM,UAAU,EAAE,QAAQ,WAAW,OAAO,CAAC,GAAyC;AAClH,YAAI,MAAM,SAAS,iCAAiC,MAAM,SAAS,eAAgB;AACnF,YAAI,CAAC,OAAQ;AACb,cAAM,QAAS,MAAM,QAAQ,MAAM,cAAc,CAAC;AAClD,cAAM,YAAY,OAAO,MAAM,aAAa,MAAM,MAAM,EAAE;AAC1D,YAAI,CAAC,UAAW;AAChB,cAAM,MAAM,WAAW,KAAK;AAAA,MAC9B;AAAA,IACF,GAAG,EAAE,MAAM,CAAC,QAAQ,UAAU,QAAQ,uBAAuB,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC,EAAE,CAAC;AAEhH,cAAU,QAAQ,mCAAmC,IAAI,SAAS,SAAS,GAAG;AAC9E,WAAO,MAAM;AACX,oBAAc,SAAS;AACvB,iBAAW,MAAM;AACjB,oBAAc;AACd,sBAAgB;AAChB,qBAAe,IAAI;AACnB,YAAM,YAAY;AAChB,YAAI;AACF,gBAAM,MAAO,MAAM,IAAI,QAAQ,IAAI,SAAS;AAC5C,cAAI,KAAK,OAAO,GAAI,OAAM,IAAI,QAAQ,OAAO,SAAS;AAAA,QACxD,QAAQ;AAAA,QAER;AAAA,MACF,GAAG;AAAA,IACL;AAAA,EACF;AACN;AAKA,IAAM,WAAW,MAAM,OAAO,kBAAkB,EAC7C,KAAK,CAAC,EAAE,OAAO,MAAM,OAAO,OAAO,YAAY,CAAC,EAChD,MAAM,MAAM,YAAY;AAK3B,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 the V2\n// plugin host's console can throw), so write the durable trail first and\n// keep the console attempt separately guarded. Tail-truncated, beside the\n// ledger.\nconst pluginLog = (level: \"debug\" | \"info\" | \"warn\" | \"error\", message: string): void => {\n const root = currentRoot\n try {\n if (!root) return\n const logPath = join(root, \".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 try {\n console.error(`[aiwrangler-nudge] ${level}: ${message}`)\n } catch {\n /* the host console may throw from plugin code */\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.
|
|
3
|
+
"version": "0.1.4",
|
|
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",
|