@brainervirus/workit-opencode 8.13.1 → 8.14.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.
@@ -25,9 +25,9 @@ ceremony; do it yourself. Workit never spawns agents; you do.
25
25
  it; change the plan or the standing orders and render again.
26
26
  4. **Spawn in a rolling window**, in the background, each in its own worktree:
27
27
  keep 4-6 in flight and refill from `spawnable` in `workit fanout status` as
28
- each one finishes. Pick the model from TIER (`references/orchestration.md`).
29
- Claude Code: the `implementer` agent; elsewhere first `workit fanout
30
- worktree create <slice>`.
28
+ each one finishes. Model: the brief's `model:` line, else TIER
29
+ (`references/orchestration.md`). Claude Code: the `implementer` agent;
30
+ elsewhere first `workit fanout worktree create <slice>`.
31
31
  5. **Judge liveness by side effects only:** `workit fanout status` (head age,
32
32
  PR, CI, verdict, landed; STUCK past the TIMEBOX). Stop a stuck worker and
33
33
  observe that it exited (a timeout is not proof). Removing a worktree drops
@@ -53,7 +53,7 @@ ceremony; do it yourself. Workit never spawns agents; you do.
53
53
  status` order, release the worktrees you made, and `workit ledger standing
54
54
  clear`. Only you touch topology: workers never rebase, retarget or merge,
55
55
  except the integration-tip merge in integration mode. Then ship
56
- (call the skill tool with `workit-ship`).
56
+ (call the skill tool with `workit-ship`) up to `defaultEndpoint` from `workit grant show`.
57
57
 
58
58
  ## Example
59
59
 
@@ -5,8 +5,10 @@ and fans in. Workit records and checks; the host's own subagents run the work.
5
5
 
6
6
  ## TIER to model
7
7
 
8
- TIER is a field of each slice and of its brief. Map it where the host lets you
9
- pick a model per spawn; nothing in the workspace config routes it.
8
+ TIER is a field of each slice and of its brief. When the workspace sets
9
+ `fanout.tierModels`, the brief has a `model:` line under TIER and `workit
10
+ fanout status` names the model next to each spawnable slice: use that model.
11
+ Without one, map TIER where the host lets you pick a model per spawn:
10
12
 
11
13
  | TIER | Claude Code (`model` on the Agent call) | Hosts without a per-spawn model |
12
14
  | --- | --- | --- |
package/dist/plugin.js CHANGED
@@ -13161,6 +13161,8 @@ var workspaceConfigSchema = object({
13161
13161
  defaultEndpoint: unknown().optional(),
13162
13162
  verification: _enum(["self", "independent"]).optional(),
13163
13163
  trailers: boolean2().optional(),
13164
+ liveSessionGuard: boolean2().optional(),
13165
+ fanout: object({ tierModels: unknown().optional() }).passthrough().optional(),
13164
13166
  autonomy: object(Object.fromEntries(["push", "pr", "merge", "release", "rerun"].map((kind) => [
13165
13167
  kind,
13166
13168
  union([boolean2(), literal("verified")]).optional()
@@ -13478,6 +13480,31 @@ var resolveRuntimeReleaseTracks = (cwd) => {
13478
13480
  const matched = matchRuntimeWorkspace(cwd);
13479
13481
  return matched ? { workspace: matched.selected.name, releaseTracks: matched.selected.releaseTracks } : null;
13480
13482
  };
13483
+ var inheritedSetting = (cwd, dir, has) => {
13484
+ let matched;
13485
+ try {
13486
+ matched = matchRuntimeWorkspace(cwd, dir);
13487
+ } catch {
13488
+ return null;
13489
+ }
13490
+ if (!matched)
13491
+ return null;
13492
+ const { selected, matches } = matched;
13493
+ const enclosing = matches.filter((entry) => entry !== selected).toSorted((a, b) => globSpecificity(b.glob) - globSpecificity(a.glob));
13494
+ return { workspace: selected.name, source: [selected, ...enclosing].find(has) ?? null };
13495
+ };
13496
+ var workspaceLiveSessionGuard = (cwd, dir) => inheritedSetting(cwd, dir, (entry) => typeof entry.liveSessionGuard === "boolean")?.source?.liveSessionGuard !== false;
13497
+ var TIER_KEYS = new Set(["mundane", "standard", "hard"]);
13498
+ var TIER_HOSTS = new Set([
13499
+ "claude_code",
13500
+ "opencode",
13501
+ "codex",
13502
+ "codex_cli",
13503
+ "codex_desktop",
13504
+ "cursor",
13505
+ "pi",
13506
+ "default"
13507
+ ]);
13481
13508
  var resolveRuntimeWorkspaceCandidate = (cwd, kind) => {
13482
13509
  const matched = matchRuntimeWorkspace(cwd);
13483
13510
  if (!matched)
@@ -21684,27 +21711,231 @@ var shellPolicy = (cwd, command, dialect = "posix") => {
21684
21711
  };
21685
21712
  // packages/workit-core/src/hooks/write-gate.ts
21686
21713
  import { realpathSync as realpathSync6 } from "node:fs";
21714
+ import os7 from "node:os";
21715
+ import path27 from "node:path";
21716
+
21717
+ // packages/workit-core/src/core/live-session.ts
21718
+ import { spawnSync as spawnSync17 } from "node:child_process";
21719
+ import { createHash as createHash14 } from "node:crypto";
21720
+ import fs18 from "node:fs";
21721
+ import os6 from "node:os";
21687
21722
  import path26 from "node:path";
21723
+ var LIVE_SESSION_TTL_MS = 15 * 60000;
21724
+ var HEARTBEAT_EVERY_MS = 30000;
21725
+ var liveProbe = {
21726
+ platform: process.platform,
21727
+ run: (args) => spawnSync17("ps", args, { encoding: "utf8", timeout: 2000 }).stdout ?? "",
21728
+ start: (pid) => processStartOf(pid)
21729
+ };
21730
+ var leadOf = (session) => session.split(":")[0].replace(/-w-.*$/su, "") || session;
21731
+ var familyOf = (host) => host.replace(/_(?:cli|desktop)$/u, "");
21732
+ var processAlive = (pid) => {
21733
+ try {
21734
+ process.kill(pid, 0);
21735
+ return true;
21736
+ } catch (error) {
21737
+ return error.code === "EPERM";
21738
+ }
21739
+ };
21740
+ var processInfo = (pid) => {
21741
+ try {
21742
+ if (liveProbe.platform === "linux") {
21743
+ const stat = fs18.readFileSync(`/proc/${pid}/stat`, "utf8");
21744
+ const ppid = Number(stat.slice(stat.lastIndexOf(")") + 2).split(" ")[1]);
21745
+ const command = fs18.readFileSync(`/proc/${pid}/cmdline`, "utf8").replaceAll("\x00", " ");
21746
+ return Number.isInteger(ppid) ? { ppid, command: command.trim() } : null;
21747
+ }
21748
+ if (liveProbe.platform === "win32")
21749
+ return null;
21750
+ const match = /^\s*(\d+)\s+(.*)$/su.exec(liveProbe.run(["-o", "ppid=,command=", "-p", String(pid)]));
21751
+ return match ? { ppid: Number(match[1]), command: match[2].trim() } : null;
21752
+ } catch {
21753
+ return null;
21754
+ }
21755
+ };
21756
+ var WRAPPER = /^(?:\S*\/)?(?:sh|bash|dash|zsh|ash|ksh|fish|env|timeout|npx|npm|bunx|cmd\.exe|pwsh|powershell)(?:\s|$)|launch\.mjs|npx-cli\.js/u;
21757
+ var PIDS = new Map;
21758
+ var hostPid = (transport) => {
21759
+ if (transport !== "hook-process")
21760
+ return process.pid;
21761
+ const key = `${liveProbe.platform}\x00${process.ppid}`;
21762
+ if (PIDS.has(key))
21763
+ return PIDS.get(key) ?? null;
21764
+ if (liveProbe.platform === "win32") {
21765
+ PIDS.set(key, null);
21766
+ return null;
21767
+ }
21768
+ let pid = process.ppid;
21769
+ for (let hop = 0;hop < 4; hop++) {
21770
+ const info = processInfo(pid);
21771
+ if (!info || !WRAPPER.test(info.command) || info.ppid <= 1)
21772
+ break;
21773
+ pid = info.ppid;
21774
+ }
21775
+ PIDS.set(key, pid);
21776
+ return pid;
21777
+ };
21778
+ var sessionSafe = (value) => value.replace(/[^A-Za-z0-9_.:@/+-]/gu, "-");
21779
+ var liveOwner = (input, transport, env = process.env) => {
21780
+ const session = input.session.id?.trim();
21781
+ if (!session)
21782
+ return null;
21783
+ const leads = new Set([leadOf(sessionSafe(session))]);
21784
+ if (input.session.parentId)
21785
+ leads.add(leadOf(sessionSafe(input.session.parentId)));
21786
+ if (env.WORKIT_PI_WORKER_SESSION && env.WORKIT_SESSION_ID)
21787
+ leads.add(leadOf(sessionSafe(env.WORKIT_SESSION_ID)));
21788
+ return {
21789
+ host: input.host,
21790
+ session: sessionSafe(session),
21791
+ leads: [...leads],
21792
+ pid: () => hostPid(transport)
21793
+ };
21794
+ };
21795
+ var checkoutOf = (target) => {
21796
+ let dir = path26.resolve(target);
21797
+ while (!fs18.existsSync(dir) && path26.dirname(dir) !== dir)
21798
+ dir = path26.dirname(dir);
21799
+ if (fs18.existsSync(dir) && !fs18.statSync(dir).isDirectory())
21800
+ dir = path26.dirname(dir);
21801
+ const parts = dir.split(path26.sep);
21802
+ const dotGit = parts.lastIndexOf(".git");
21803
+ if (dotGit > 0)
21804
+ dir = parts.slice(0, dotGit).join(path26.sep) || path26.sep;
21805
+ const location = resolveStore(dir);
21806
+ if (location instanceof Error || !location.shared || !location.top)
21807
+ return null;
21808
+ let top = location.top;
21809
+ try {
21810
+ top = fs18.realpathSync.native(top);
21811
+ } catch {}
21812
+ return top;
21813
+ };
21814
+ var markerFile2 = (top) => path26.join(resolveStateDir(), "live-sessions", `${createHash14("sha256").update(checkoutKey(top)).digest("hex").slice(0, 32)}.json`);
21815
+ var checkoutKey = (top, platform = process.platform) => {
21816
+ let key = path26.resolve(top);
21817
+ try {
21818
+ key = fs18.realpathSync.native(key);
21819
+ } catch {}
21820
+ return platform === "win32" ? key.replaceAll("/", "\\").toLowerCase() : key;
21821
+ };
21822
+ var readMarker = (file) => {
21823
+ try {
21824
+ const value = JSON.parse(fs18.readFileSync(file, "utf8"));
21825
+ if (value?.version !== 1 || typeof value.session !== "string" || typeof value.host !== "string" || typeof value.heartbeatAt !== "string" || Number.isNaN(Date.parse(value.heartbeatAt)))
21826
+ return null;
21827
+ return {
21828
+ version: 1,
21829
+ session: value.session,
21830
+ leads: Array.isArray(value.leads) ? value.leads.filter((item) => typeof item === "string") : [],
21831
+ host: value.host,
21832
+ pid: typeof value.pid === "number" ? value.pid : null,
21833
+ pidStart: typeof value.pidStart === "string" ? value.pidStart : null,
21834
+ machine: typeof value.machine === "string" ? value.machine : "",
21835
+ checkout: typeof value.checkout === "string" ? value.checkout : "",
21836
+ heartbeatAt: value.heartbeatAt
21837
+ };
21838
+ } catch {
21839
+ return null;
21840
+ }
21841
+ };
21842
+ var ageOf2 = (marker, now) => now.getTime() - Date.parse(marker.heartbeatAt);
21843
+ var isLive = (marker, deps) => {
21844
+ const age = ageOf2(marker, deps.now ?? new Date);
21845
+ if (age > (deps.ttlMs ?? LIVE_SESSION_TTL_MS))
21846
+ return false;
21847
+ if (marker.machine !== (deps.machine ?? os6.hostname()) || marker.pid === null)
21848
+ return true;
21849
+ if (!(deps.alive ?? processAlive)(marker.pid))
21850
+ return false;
21851
+ if (marker.pidStart === null)
21852
+ return true;
21853
+ const start = liveProbe.start(marker.pid);
21854
+ return start === null || start === marker.pidStart;
21855
+ };
21856
+ var sameLead = (marker, owner) => marker.session === owner.session || owner.leads.some((lead) => lead === marker.session || marker.leads.includes(lead));
21857
+ var sameOwner = (marker, owner, machine) => {
21858
+ if (sameLead(marker, owner))
21859
+ return true;
21860
+ if (marker.machine !== machine)
21861
+ return false;
21862
+ const pid = owner.pid();
21863
+ if (pid === null || marker.pid === null || marker.pidStart === null)
21864
+ return familyOf(marker.host) === familyOf(owner.host);
21865
+ return marker.pid === pid;
21866
+ };
21867
+ var liveConflict = (target, owner, deps = {}) => {
21868
+ try {
21869
+ const checkout = checkoutOf(target);
21870
+ if (!checkout)
21871
+ return null;
21872
+ const file = markerFile2(checkout);
21873
+ const marker = readMarker(file);
21874
+ if (!marker || sameLead(marker, owner) || !isLive(marker, deps))
21875
+ return null;
21876
+ if (sameOwner(marker, owner, deps.machine ?? os6.hostname()))
21877
+ return null;
21878
+ return { marker, file, ageMs: ageOf2(marker, deps.now ?? new Date), checkout };
21879
+ } catch {
21880
+ return null;
21881
+ }
21882
+ };
21883
+ var claimLiveSession = (target, owner, deps = {}) => {
21884
+ try {
21885
+ const checkout = checkoutOf(target);
21886
+ if (!checkout)
21887
+ return;
21888
+ const file = markerFile2(checkout);
21889
+ const now = deps.now ?? new Date;
21890
+ const machine = deps.machine ?? os6.hostname();
21891
+ const current = readMarker(file);
21892
+ if (current) {
21893
+ const age = ageOf2(current, now);
21894
+ if (sameLead(current, owner) && age >= 0 && age < HEARTBEAT_EVERY_MS)
21895
+ return;
21896
+ if (!sameLead(current, owner) && isLive(current, deps) && !sameOwner(current, owner, machine))
21897
+ return;
21898
+ }
21899
+ const pid = owner.pid();
21900
+ const marker = {
21901
+ version: 1,
21902
+ session: owner.session,
21903
+ leads: owner.leads,
21904
+ host: owner.host,
21905
+ pid,
21906
+ pidStart: pid === null ? null : liveProbe.start(pid),
21907
+ machine,
21908
+ checkout,
21909
+ heartbeatAt: now.toISOString()
21910
+ };
21911
+ fs18.mkdirSync(path26.dirname(file), { recursive: true });
21912
+ const temp = `${file}.${process.pid}.tmp`;
21913
+ fs18.writeFileSync(temp, JSON.stringify(marker));
21914
+ fs18.renameSync(temp, file);
21915
+ } catch {}
21916
+ };
21917
+
21918
+ // packages/workit-core/src/hooks/write-gate.ts
21688
21919
  var NONE3 = { kind: "none" };
21689
21920
  var CASE_INSENSITIVE = process.platform === "darwin" || process.platform === "win32";
21690
21921
  var canonical2 = (file) => {
21691
21922
  let rest = "";
21692
- for (let dir = path26.resolve(file);; dir = path26.dirname(dir)) {
21923
+ for (let dir = path27.resolve(file);; dir = path27.dirname(dir)) {
21693
21924
  try {
21694
- return path26.join(realpathSync6(dir), rest);
21925
+ return path27.join(realpathSync6(dir), rest);
21695
21926
  } catch {
21696
- if (path26.dirname(dir) === dir)
21697
- return path26.resolve(file);
21698
- rest = rest ? path26.join(path26.basename(dir), rest) : path26.basename(dir);
21927
+ if (path27.dirname(dir) === dir)
21928
+ return path27.resolve(file);
21929
+ rest = rest ? path27.join(path27.basename(dir), rest) : path27.basename(dir);
21699
21930
  }
21700
21931
  }
21701
21932
  };
21702
21933
  var exempt = (cwd, root, file, plans) => {
21703
21934
  const fold = (value) => CASE_INSENSITIVE ? value.toLowerCase() : value;
21704
- const relative = path26.relative(fold(canonical2(root)), fold(canonical2(path26.resolve(cwd, file))));
21705
- if (relative.startsWith("..") || path26.isAbsolute(relative))
21935
+ const relative = path27.relative(fold(canonical2(root)), fold(canonical2(path27.resolve(cwd, file))));
21936
+ if (relative.startsWith("..") || path27.isAbsolute(relative))
21706
21937
  return true;
21707
- const posix = relative.split(path26.sep).join("/");
21938
+ const posix = relative.split(path27.sep).join("/");
21708
21939
  const segments = posix.split("/");
21709
21940
  return /\.mdx?$/i.test(posix) || segments[0] === "docs" || segments.includes("plans") || [...plans].some((plan) => fold(plan) === posix);
21710
21941
  };
@@ -21802,7 +22033,7 @@ var shellWrites = (command) => {
21802
22033
  return { writes: true, targets: unknown ? null : targets };
21803
22034
  };
21804
22035
  var cache2 = new Map;
21805
- var planKeyOf = (root, plans) => [...plans].map((file) => fileSignature(path26.resolve(root, file)) ?? "-").join("\x00");
22036
+ var planKeyOf = (root, plans) => [...plans].map((file) => fileSignature(path27.resolve(root, file)) ?? "-").join("\x00");
21806
22037
  var gateFor = (cwd) => {
21807
22038
  const store = new TaskStore(cwd);
21808
22039
  const listed = store.listTaskIndex();
@@ -21840,7 +22071,218 @@ var gateFor = (cwd) => {
21840
22071
  cache2.delete(cache2.keys().next().value);
21841
22072
  return gate;
21842
22073
  };
21843
- function writeGate(cwd, paths) {
22074
+ var MUTATING_GIT = new Set([
22075
+ "add",
22076
+ "am",
22077
+ "apply",
22078
+ "checkout",
22079
+ "cherry-pick",
22080
+ "clean",
22081
+ "commit",
22082
+ "merge",
22083
+ "mv",
22084
+ "pull",
22085
+ "rebase",
22086
+ "reset",
22087
+ "restore",
22088
+ "revert",
22089
+ "rm",
22090
+ "stash",
22091
+ "switch"
22092
+ ]);
22093
+ var DRY_N = new Set(["add", "clean", "rm", "mv"]);
22094
+ var MUTATING_WORKIT_GIT = new Set(["commit", "branch"]);
22095
+ var DIR_TARGET = ".workit-live-target";
22096
+ var OVERRIDE = "WORKIT_LIVE_SESSION=override";
22097
+ var inert = (args, sub) => args.some((arg) => arg === "--help" || arg === "-h" || arg === "--dry-run" || DRY_N.has(sub) && /^-[a-zA-Z]*n[a-zA-Z]*$/.test(arg) && !arg.startsWith("--"));
22098
+ var gitMutationDirs = (cwd, command, dialect) => {
22099
+ let invocations;
22100
+ try {
22101
+ invocations = rawInvocations(command, dialect);
22102
+ } catch {
22103
+ return [];
22104
+ }
22105
+ return invocations.flatMap((invocation) => {
22106
+ if (invocation.tool !== "git" || invocation.dir === undefined)
22107
+ return [];
22108
+ const at = invocation.args.findIndex((arg) => !arg.startsWith("-"));
22109
+ const sub = invocation.args[at] ?? "";
22110
+ const rest = invocation.args.slice(at + 1);
22111
+ if (!MUTATING_GIT.has(sub) || inert(rest, sub))
22112
+ return [];
22113
+ if (sub === "stash" && ["list", "show"].includes(rest[0] ?? ""))
22114
+ return [];
22115
+ return [path27.resolve(cwd, invocation.dir ?? ".")];
22116
+ });
22117
+ };
22118
+ var CD2 = new Set(["cd", "chdir", "set-location", "sl"]);
22119
+ var PUSHD2 = new Set(["pushd", "push-location"]);
22120
+ var POPD2 = new Set(["popd", "pop-location"]);
22121
+ var cdInto = (dir, words) => {
22122
+ const target = words.slice(1).find((word) => !word.startsWith("-") || word === "-");
22123
+ if (dir === undefined || target === "-" || target && /^~|[$`*?]/.test(target))
22124
+ return;
22125
+ return target === undefined ? os7.homedir() : path27.resolve(dir, target);
22126
+ };
22127
+ var quoteWord = (word) => /^[\w@%+=:,./-]+$/.test(word) ? word : `'${word.replaceAll("'", "'\\''")}'`;
22128
+ var segmentTargets = (cwd, command, dialect) => {
22129
+ const targets = [];
22130
+ let dir = cwd;
22131
+ let stack = [];
22132
+ const groups = [];
22133
+ const at = (file) => path27.isAbsolute(file) ? file : dir === undefined ? null : path27.resolve(dir, file);
22134
+ for (const segment of segmentsOf(command, dialect)) {
22135
+ if (segment.group === "open") {
22136
+ groups.push({ dir, stack: [...stack] });
22137
+ continue;
22138
+ }
22139
+ if (segment.group === "close") {
22140
+ const saved = groups.pop();
22141
+ if (saved)
22142
+ ({ dir, stack } = saved);
22143
+ continue;
22144
+ }
22145
+ const lead = segment.words.findIndex((word) => !/^\w+=/.test(word));
22146
+ const words = lead < 0 ? [] : segment.words.slice(lead);
22147
+ const name = (words[0]?.split(/[\\/]/).at(-1) ?? "").toLowerCase();
22148
+ if (CD2.has(name)) {
22149
+ dir = cdInto(dir, words);
22150
+ continue;
22151
+ }
22152
+ if (PUSHD2.has(name)) {
22153
+ stack.push(dir);
22154
+ dir = cdInto(dir, words);
22155
+ continue;
22156
+ }
22157
+ if (POPD2.has(name)) {
22158
+ dir = stack.length ? stack.pop() : undefined;
22159
+ continue;
22160
+ }
22161
+ for (const file of segment.redirects)
22162
+ if (!/^\/dev\/(?:null|stdout|stderr|tty)$/.test(file)) {
22163
+ const resolved = at(file);
22164
+ if (resolved)
22165
+ targets.push(resolved);
22166
+ }
22167
+ const edits = editsOf(segment.words);
22168
+ if (edits === null) {
22169
+ if (dir !== undefined)
22170
+ targets.push(path27.join(dir, DIR_TARGET));
22171
+ } else
22172
+ for (const file of edits) {
22173
+ const resolved = at(file);
22174
+ if (resolved)
22175
+ targets.push(resolved);
22176
+ }
22177
+ if (dir !== undefined && workitInvocations(segment.words.map(quoteWord).join(" "), "posix").some((args) => args[0] === "git" && MUTATING_WORKIT_GIT.has(args[1] ?? "") && !inert(args.slice(2), "")))
22178
+ targets.push(path27.join(dir, DIR_TARGET));
22179
+ }
22180
+ return targets;
22181
+ };
22182
+ var overridden = (command, dialect) => {
22183
+ const segments = segmentsOf(command, dialect);
22184
+ if (segments.length !== 1 || segments[0].group || segments[0].subs?.length)
22185
+ return false;
22186
+ const words = segments[0].words;
22187
+ const lead = words.findIndex((word) => !/^\w+=/.test(word));
22188
+ return lead > 0 && words.slice(0, lead).includes(OVERRIDE);
22189
+ };
22190
+ var HOST_LABELS = {
22191
+ claude_code: "Claude Code",
22192
+ opencode: "OpenCode",
22193
+ codex_cli: "Codex CLI",
22194
+ codex_desktop: "Codex Desktop",
22195
+ cursor: "Cursor",
22196
+ pi: "Pi"
22197
+ };
22198
+ var ageText = (ms) => {
22199
+ const seconds = Math.max(0, Math.round(ms / 1000));
22200
+ return seconds < 120 ? `${seconds}s` : `${Math.round(seconds / 60)} min`;
22201
+ };
22202
+ var liveDeny = ({ marker, checkout, ageMs, file }) => {
22203
+ const unblock = `Work in your own worktree (\`git worktree add <path> -b <branch>\`, or \`workit fanout worktree create <slice>\`). Only if the user confirms that session is finished: release it with \`rm '${file}'\`, or prefix one command with ${OVERRIDE}.`;
22204
+ return {
22205
+ kind: "deny",
22206
+ reason: `workit live-session guard: ${checkout} belongs to another live agent session, ${HOST_LABELS[marker.host] ?? marker.host} session ${marker.session}${marker.pid === null ? "" : ` (pid ${marker.pid})`}, last active ${ageText(ageMs)} ago. Do not edit a checkout another session is working in; read-only commands stay allowed. ${unblock}`,
22207
+ unblock
22208
+ };
22209
+ };
22210
+ var guarded = (targets) => {
22211
+ const seen = new Set;
22212
+ return targets.filter((target) => {
22213
+ const dir = path27.dirname(target);
22214
+ if (seen.has(dir))
22215
+ return false;
22216
+ seen.add(dir);
22217
+ return true;
22218
+ });
22219
+ };
22220
+ var liveGate = (owner, targets, deps) => {
22221
+ try {
22222
+ for (const target of guarded(targets)) {
22223
+ const conflict = liveConflict(target, owner, deps);
22224
+ if (conflict && workspaceLiveSessionGuard(conflict.checkout))
22225
+ return liveDeny(conflict);
22226
+ }
22227
+ } catch {}
22228
+ return NONE3;
22229
+ };
22230
+ var claimAll = (owner, targets, deps) => {
22231
+ for (const target of guarded(targets))
22232
+ claimLiveSession(target, owner, deps);
22233
+ };
22234
+ var liveTargets = (cwd, paths) => paths?.length ? paths.map((file) => path27.resolve(cwd, file)) : [path27.join(cwd, DIR_TARGET)];
22235
+ function writeGate(cwd, paths, owner, deps) {
22236
+ if (!owner)
22237
+ return beforeWriteGate(cwd, paths);
22238
+ const targets = liveTargets(cwd, paths);
22239
+ const live = liveGate(owner, targets, deps);
22240
+ if (live.kind !== "none")
22241
+ return live;
22242
+ const decision = beforeWriteGate(cwd, paths);
22243
+ if (decision.kind === "none")
22244
+ claimAll(owner, targets, deps);
22245
+ return decision;
22246
+ }
22247
+ function shellGate(cwd, command, owner, dialect = "posix", deps) {
22248
+ const writes = shellWrites(command);
22249
+ let targets = [];
22250
+ if (owner) {
22251
+ try {
22252
+ targets = [
22253
+ ...segmentTargets(cwd, command, dialect),
22254
+ ...gitMutationDirs(cwd, command, dialect).map((dir) => path27.join(dir, DIR_TARGET))
22255
+ ];
22256
+ } catch {
22257
+ targets = [];
22258
+ }
22259
+ }
22260
+ if (owner && targets.length) {
22261
+ const live = liveGate(owner, targets, deps);
22262
+ if (live.kind !== "none") {
22263
+ if (!overridden(command, dialect))
22264
+ return live;
22265
+ noteOverride(cwd, owner, command);
22266
+ }
22267
+ }
22268
+ const decision = writes.writes ? beforeWriteGate(cwd, writes.targets) : NONE3;
22269
+ if (owner && targets.length && decision.kind === "none")
22270
+ claimAll(owner, targets, deps);
22271
+ return decision;
22272
+ }
22273
+ var noteOverride = (cwd, owner, command) => {
22274
+ try {
22275
+ if (!inWorkitWorkspace(cwd))
22276
+ return;
22277
+ appendHookObserved(cwd, {
22278
+ type: "live_session.override",
22279
+ actor: { host: owner.host, session: owner.session, agentId: null },
22280
+ session: owner.session,
22281
+ command: command.slice(0, 200)
22282
+ });
22283
+ } catch {}
22284
+ };
22285
+ function beforeWriteGate(cwd, paths) {
21844
22286
  try {
21845
22287
  const gate = gateFor(cwd);
21846
22288
  if (!gate || gate.blockers.length === 0)
@@ -21858,7 +22300,7 @@ function writeGate(cwd, paths) {
21858
22300
  }
21859
22301
  }
21860
22302
  // packages/workit-core/src/hooks/stop.ts
21861
- import { spawnSync as spawnSync17 } from "node:child_process";
22303
+ import { spawnSync as spawnSync18 } from "node:child_process";
21862
22304
  var NONE4 = { kind: "none" };
21863
22305
  var QUESTION = /[??؟](?=[\s*_`"')\]>]*(?:\s|$))|¿/;
21864
22306
  var OFFER = /\b(?:let me know|want me to|should I|shall I|would you like|do you want|if you(?:'d| would) like|may I|can I go ahead)\b/i;
@@ -21879,7 +22321,7 @@ var asksUser = (message) => {
21879
22321
  };
21880
22322
  var short = (sha) => (sha ?? "?").slice(0, 12);
21881
22323
  var gitList = (cwd, args) => {
21882
- const run = spawnSync17("git", args, {
22324
+ const run = spawnSync18("git", args, {
21883
22325
  cwd,
21884
22326
  encoding: "utf8",
21885
22327
  timeout: 3000,
@@ -22341,11 +22783,11 @@ var OPENCODE_DESCRIPTOR = {
22341
22783
  ]
22342
22784
  };
22343
22785
  // packages/workit-opencode/src/shared/init-apply.ts
22344
- import path31 from "node:path";
22786
+ import path32 from "node:path";
22345
22787
 
22346
22788
  // packages/workit-core/src/core/gitignore.ts
22347
22789
  import { existsSync as existsSync9, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "node:fs";
22348
- import path27 from "node:path";
22790
+ import path28 from "node:path";
22349
22791
  var GITIGNORE_ENTRIES = [
22350
22792
  "# workit: SDD working state (never commit)",
22351
22793
  "docs/*/sdd/",
@@ -22365,7 +22807,7 @@ var GITIGNORE_ENTRIES = [
22365
22807
  var ensureProjectGitignore = (workspaceRoot, confirmed) => {
22366
22808
  if (!confirmed)
22367
22809
  return { ok: false, error: "confirmed: true required" };
22368
- const file = path27.join(workspaceRoot, ".gitignore");
22810
+ const file = path28.join(workspaceRoot, ".gitignore");
22369
22811
  const existing = existsSync9(file) ? readFileSync6(file, "utf8") : "";
22370
22812
  const existingLines = new Set(existing.split(`
22371
22813
  `).map((l) => l.trim()).filter(Boolean));
@@ -22393,25 +22835,25 @@ var ensureProjectGitignore = (workspaceRoot, confirmed) => {
22393
22835
 
22394
22836
  // packages/workit-core/src/core/hygiene.ts
22395
22837
  import { existsSync as existsSync10, readFileSync as readFileSync7, readdirSync as readdirSync5, writeFileSync as writeFileSync5 } from "node:fs";
22396
- import path28 from "node:path";
22838
+ import path29 from "node:path";
22397
22839
  var repoRoot3 = assetRoot();
22398
- var templatesDir = () => path28.join(repoRoot3, "templates", "hygiene");
22840
+ var templatesDir = () => path29.join(repoRoot3, "templates", "hygiene");
22399
22841
  var packageJson = (root) => {
22400
- if (!existsSync10(path28.join(root, "package.json")))
22842
+ if (!existsSync10(path29.join(root, "package.json")))
22401
22843
  return null;
22402
22844
  try {
22403
- return JSON.parse(readFileSync7(path28.join(root, "package.json"), "utf8"));
22845
+ return JSON.parse(readFileSync7(path29.join(root, "package.json"), "utf8"));
22404
22846
  } catch {
22405
22847
  return null;
22406
22848
  }
22407
22849
  };
22408
22850
  var isOpenSource = (root) => {
22409
- if (existsSync10(path28.join(root, "LICENSE")))
22851
+ if (existsSync10(path29.join(root, "LICENSE")))
22410
22852
  return true;
22411
22853
  const pkg = packageJson(root);
22412
22854
  if (pkg && !pkg.private)
22413
22855
  return true;
22414
- return path28.basename(root) === "workflow-toolkit";
22856
+ return path29.basename(root) === "workflow-toolkit";
22415
22857
  };
22416
22858
  var licenseHolder = (root) => {
22417
22859
  const pkg = packageJson(root);
@@ -22434,13 +22876,13 @@ var planHygieneFiles = (root, opts = {}) => {
22434
22876
  files.push("LICENSE", "CONTRIBUTING.md");
22435
22877
  const planned = [];
22436
22878
  for (const file of files) {
22437
- if (existsSync10(path28.join(root, file)))
22879
+ if (existsSync10(path29.join(root, file)))
22438
22880
  continue;
22439
- const tpl = path28.join(templatesDir(), file);
22881
+ const tpl = path29.join(templatesDir(), file);
22440
22882
  if (!existsSync10(tpl))
22441
22883
  continue;
22442
- const content = readFileSync7(tpl, "utf8").replace(/<PROJECT>/g, path28.basename(root)).replace(/<YEAR>/g, String(new Date().getFullYear())).replace(/<HOLDER>\s*/g, licenseHolder(root));
22443
- planned.push({ path: path28.join(root, file), content });
22884
+ const content = readFileSync7(tpl, "utf8").replace(/<PROJECT>/g, path29.basename(root)).replace(/<YEAR>/g, String(new Date().getFullYear())).replace(/<HOLDER>\s*/g, licenseHolder(root));
22885
+ planned.push({ path: path29.join(root, file), content });
22444
22886
  }
22445
22887
  return planned;
22446
22888
  };
@@ -22450,19 +22892,19 @@ var ensureHygieneFiles = (root, opts) => {
22450
22892
  const created = [];
22451
22893
  for (const planned of planHygieneFiles(root, opts)) {
22452
22894
  writeFileSync5(planned.path, planned.content, "utf8");
22453
- created.push(path28.basename(planned.path));
22895
+ created.push(path29.basename(planned.path));
22454
22896
  }
22455
22897
  return { ok: true, created };
22456
22898
  };
22457
22899
 
22458
22900
  // packages/workit-core/src/core/init.ts
22459
- import fs18 from "node:fs";
22460
- import path30 from "node:path";
22461
- import { spawnSync as spawnSync18 } from "node:child_process";
22901
+ import fs19 from "node:fs";
22902
+ import path31 from "node:path";
22903
+ import { spawnSync as spawnSync19 } from "node:child_process";
22462
22904
 
22463
22905
  // packages/workit-core/src/core/workspace-branch-policy.ts
22464
22906
  import { mkdirSync as mkdirSync4, readFileSync as readFileSync8, renameSync as renameSync3, rmSync as rmSync2, writeFileSync as writeFileSync6 } from "node:fs";
22465
- import path29 from "node:path";
22907
+ import path30 from "node:path";
22466
22908
  import { isDeepStrictEqual } from "node:util";
22467
22909
  var readFileSafe = (p) => {
22468
22910
  try {
@@ -22473,7 +22915,7 @@ var readFileSafe = (p) => {
22473
22915
  };
22474
22916
  function applyWorkspaceBranchPolicy(opts) {
22475
22917
  const { workspace_root, env = process.env } = opts;
22476
- const dir = path29.join(env.WORKFLOW_TOOLKIT_CONFIG ?? configDir());
22918
+ const dir = path30.join(env.WORKFLOW_TOOLKIT_CONFIG ?? configDir());
22477
22919
  const {
22478
22920
  status,
22479
22921
  path: wsPath,
@@ -22485,7 +22927,7 @@ function applyWorkspaceBranchPolicy(opts) {
22485
22927
  if (status === "malformed" || status === "invalid")
22486
22928
  return { ok: false, error: workspacesError ?? `invalid workspaces.json: ${wsPath}` };
22487
22929
  const detection = detectBranchPolicy(workspace_root);
22488
- const name = String(env.WORKFLOW_BP_NAME ?? path29.basename(workspace_root));
22930
+ const name = String(env.WORKFLOW_BP_NAME ?? path30.basename(workspace_root));
22489
22931
  const integration = env.WORKFLOW_BP_INTEGRATION ?? detection.integration;
22490
22932
  const policy = {
22491
22933
  preset: detection.preset,
@@ -22534,7 +22976,7 @@ function applyWorkspaceBranchPolicy(opts) {
22534
22976
  ok: false,
22535
22977
  error: `workspaces.json changed while applying the branch policy: ${wsPath}`
22536
22978
  };
22537
- mkdirSync4(path29.dirname(wsPath), { recursive: true });
22979
+ mkdirSync4(path30.dirname(wsPath), { recursive: true });
22538
22980
  const tmp = `${wsPath}.${process.pid}.tmp`;
22539
22981
  try {
22540
22982
  writeFileSync6(tmp, JSON.stringify({ ...document, workspaces: next }, null, 2) + `
@@ -22569,7 +23011,7 @@ function applyWorkspaceBranchPolicy(opts) {
22569
23011
  var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE";
22570
23012
  var readJson = (p) => {
22571
23013
  try {
22572
- const parsed = JSON.parse(fs18.readFileSync(p, "utf8"));
23014
+ const parsed = JSON.parse(fs19.readFileSync(p, "utf8"));
22573
23015
  return isConfigObject(parsed) ? parsed : null;
22574
23016
  } catch {
22575
23017
  return null;
@@ -22581,7 +23023,7 @@ var resolveInitProvider = (cwd) => {
22581
23023
  return env.toLowerCase();
22582
23024
  const root = cwd ?? process.env.WORKFLOW_WORKSPACE_ROOT ?? process.cwd();
22583
23025
  try {
22584
- const out = spawnSync18("git", ["remote", "get-url", "origin"], {
23026
+ const out = spawnSync19("git", ["remote", "get-url", "origin"], {
22585
23027
  cwd: root,
22586
23028
  encoding: "utf8"
22587
23029
  });
@@ -22599,7 +23041,7 @@ var youtrackJsonContent = (dir) => {
22599
23041
  const meetingIssue = process.env.WORKFLOW_YT_MEETING_ISSUE;
22600
23042
  return {
22601
23043
  baseUrl: process.env.WORKFLOW_YT_BASE_URL ?? "https://youtrack.example.com",
22602
- tokenFile: process.env.WORKFLOW_YT_TOKEN_FILE ?? path30.join(dir, "youtrack.token"),
23044
+ tokenFile: process.env.WORKFLOW_YT_TOKEN_FILE ?? path31.join(dir, "youtrack.token"),
22603
23045
  ...meetingIssue ? {
22604
23046
  meetingIssue,
22605
23047
  meetingIssues: { general: { issue: meetingIssue, label: "General meetings" } }
@@ -22629,19 +23071,19 @@ var vcsJsonContent = () => {
22629
23071
  };
22630
23072
  function initApplyData(action, env = process.env) {
22631
23073
  const dir = String(env.WORKFLOW_TOOLKIT_CONFIG ?? configDir());
22632
- fs18.mkdirSync(dir, { recursive: true });
23074
+ fs19.mkdirSync(dir, { recursive: true });
22633
23075
  switch (action) {
22634
23076
  case "youtrack_json": {
22635
- const out = path30.join(dir, "youtrack.json");
22636
- fs18.writeFileSync(out, JSON.stringify(youtrackJsonContent(dir), null, 2) + `
23077
+ const out = path31.join(dir, "youtrack.json");
23078
+ fs19.writeFileSync(out, JSON.stringify(youtrackJsonContent(dir), null, 2) + `
22637
23079
  `, "utf8");
22638
23080
  return { action, ok: true, path: out };
22639
23081
  }
22640
23082
  case "youtrack_token_placeholder": {
22641
- const p = path30.join(dir, "youtrack.token");
23083
+ const p = path31.join(dir, "youtrack.token");
22642
23084
  const preserved = writeFileExclusive(p, TOKEN_PLACEHOLDER2 + `
22643
23085
  `, 384) === "preserved";
22644
- const abs = path30.resolve(p);
23086
+ const abs = path31.resolve(p);
22645
23087
  return {
22646
23088
  action,
22647
23089
  ok: true,
@@ -22653,14 +23095,14 @@ function initApplyData(action, env = process.env) {
22653
23095
  };
22654
23096
  }
22655
23097
  case "youtrack_scaffold": {
22656
- const jsonOut = path30.join(dir, "youtrack.json");
22657
- const tokenOut = path30.join(dir, "youtrack.token");
22658
- fs18.writeFileSync(jsonOut, JSON.stringify(youtrackJsonContent(dir), null, 2) + `
23098
+ const jsonOut = path31.join(dir, "youtrack.json");
23099
+ const tokenOut = path31.join(dir, "youtrack.token");
23100
+ fs19.writeFileSync(jsonOut, JSON.stringify(youtrackJsonContent(dir), null, 2) + `
22659
23101
  `, "utf8");
22660
23102
  const preserved = writeFileExclusive(tokenOut, TOKEN_PLACEHOLDER2 + `
22661
23103
  `, 384) === "preserved";
22662
- const configPath = path30.resolve(jsonOut);
22663
- const tokenPath = path30.resolve(tokenOut);
23104
+ const configPath = path31.resolve(jsonOut);
23105
+ const tokenPath = path31.resolve(tokenOut);
22664
23106
  const prev = process.env.WORKFLOW_YOUTRACK_CONFIG;
22665
23107
  process.env.WORKFLOW_YOUTRACK_CONFIG = configPath;
22666
23108
  let tokenCreate = {};
@@ -22712,10 +23154,10 @@ function initApplyData(action, env = process.env) {
22712
23154
  }
22713
23155
  }
22714
23156
  case "vcs_scaffold": {
22715
- const jsonOut = path30.join(dir, "vcs.json");
22716
- fs18.writeFileSync(jsonOut, JSON.stringify(vcsJsonContent(), null, 2) + `
23157
+ const jsonOut = path31.join(dir, "vcs.json");
23158
+ fs19.writeFileSync(jsonOut, JSON.stringify(vcsJsonContent(), null, 2) + `
22717
23159
  `, "utf8");
22718
- const configPath = path30.resolve(jsonOut);
23160
+ const configPath = path31.resolve(jsonOut);
22719
23161
  const prev = process.env.WORKFLOW_VCS_CONFIG;
22720
23162
  process.env.WORKFLOW_VCS_CONFIG = configPath;
22721
23163
  try {
@@ -22834,7 +23276,7 @@ var executeInitApply = (args, directory, runtime) => {
22834
23276
  protectedNames: args.branch_policy_protected
22835
23277
  }, current);
22836
23278
  writeConfig(next);
22837
- return output(ok({ action: "config", path: path31.join(configDir(), "config.json"), ...next }));
23279
+ return output(ok({ action: "config", path: path32.join(configDir(), "config.json"), ...next }));
22838
23280
  }
22839
23281
  const env = Object.fromEntries(Object.entries({
22840
23282
  WORKFLOW_YT_BASE_URL: args.base_url,
@@ -22851,14 +23293,14 @@ var executeInitApply = (args, directory, runtime) => {
22851
23293
 
22852
23294
  // packages/workit-opencode/src/shared/session.ts
22853
23295
  import { realpathSync as realpathSync7 } from "node:fs";
22854
- import path32 from "node:path";
23296
+ import path33 from "node:path";
22855
23297
  var sameWorkspace = (expected, observed) => {
22856
23298
  if (typeof observed !== "string" || !observed)
22857
23299
  return false;
22858
23300
  try {
22859
23301
  return realpathSync7(expected) === realpathSync7(observed);
22860
23302
  } catch {
22861
- return path32.resolve(expected) === path32.resolve(observed);
23303
+ return path33.resolve(expected) === path33.resolve(observed);
22862
23304
  }
22863
23305
  };
22864
23306
 
@@ -22930,7 +23372,7 @@ var workitFamilyOf = (toolName) => {
22930
23372
 
22931
23373
  // packages/workit-opencode/src/tools/context.ts
22932
23374
  import { realpathSync as realpathSync8 } from "node:fs";
22933
- import path33 from "node:path";
23375
+ import path34 from "node:path";
22934
23376
  var createContextTool = () => ({
22935
23377
  description: "Read Git, pull request, YouTrack, issue, changelog, release, or affected-file context.",
22936
23378
  execute: async (args, context) => {
@@ -22940,7 +23382,7 @@ var createContextTool = () => ({
22940
23382
  let root;
22941
23383
  try {
22942
23384
  const cwd = parsed.data.cwd;
22943
- root = realpathSync8(cwd ? path33.resolve(context.directory, cwd) : context.directory);
23385
+ root = realpathSync8(cwd ? path34.resolve(context.directory, cwd) : context.directory);
22944
23386
  } catch {
22945
23387
  return JSON.stringify(failure2("invalid_input", "context target directory cannot be resolved"), null, 2);
22946
23388
  }
@@ -23484,8 +23926,9 @@ var evaluateShellPermission = (root, event, workdirs = new Map) => {
23484
23926
  event.message = reason;
23485
23927
  };
23486
23928
  const resources = event.resources.filter((resource) => typeof resource === "string");
23929
+ const owner = liveOwner({ host: "opencode", session: { id: event.sessionID ?? "", parentId: null } }, "in-process-plugin");
23487
23930
  if (WRITE_ACTIONS.has(event.action)) {
23488
- const decision = writeGate(root, resources);
23931
+ const decision = writeGate(root, resources, owner);
23489
23932
  if (decision.kind === "deny")
23490
23933
  deny(decision.reason);
23491
23934
  return;
@@ -23507,9 +23950,8 @@ var evaluateShellPermission = (root, event, workdirs = new Map) => {
23507
23950
  }, resource, { nudge: false });
23508
23951
  if (raw.kind === "deny")
23509
23952
  return deny(raw.reason);
23510
- const writes = shellWrites(resource);
23511
- const gated = writes.writes ? writeGate(root, writes.targets) : null;
23512
- if (gated?.kind === "deny")
23953
+ const gated = shellGate(cwd, resource, owner);
23954
+ if (gated.kind === "deny")
23513
23955
  return deny(gated.reason);
23514
23956
  }
23515
23957
  };
@@ -23533,7 +23975,7 @@ var childTaskMissing = (store, input) => {
23533
23975
  var childDenial = (identity) => `OpenCode child session has no validated Workit worker: as ${identity} it may only record evidence or findings (workit_evidence/workit_finding action "record") on an existing task; for anything else the lead assigns it a worker with workit_worker before launching it`;
23534
23976
 
23535
23977
  // packages/workit-opencode/src/v2/shell.ts
23536
- import path34 from "node:path";
23978
+ import path35 from "node:path";
23537
23979
  var commandOf = (input) => {
23538
23980
  if (input === null || typeof input !== "object")
23539
23981
  return null;
@@ -23542,7 +23984,7 @@ var commandOf = (input) => {
23542
23984
  };
23543
23985
  var hookInput = (root, sessionID, id, shell) => ({
23544
23986
  host: "opencode",
23545
- cwd: shell.workdir ? path34.resolve(root, shell.workdir) : root,
23987
+ cwd: shell.workdir ? path35.resolve(root, shell.workdir) : root,
23546
23988
  session: { id: sessionID, agentId: null, agentType: null, parentId: null },
23547
23989
  permissionMode: null,
23548
23990
  transcriptPath: null,
@@ -23727,37 +24169,37 @@ var createStopControl = (root, port) => {
23727
24169
 
23728
24170
  // packages/workit-opencode/src/v2/registry.ts
23729
24171
  import { existsSync as existsSync12, readFileSync as readFileSync9 } from "node:fs";
23730
- import path36 from "node:path";
24172
+ import path37 from "node:path";
23731
24173
 
23732
24174
  // packages/workit-opencode/src/shared/assets.ts
23733
24175
  import { existsSync as existsSync11, realpathSync as realpathSync9 } from "node:fs";
23734
- import path35 from "node:path";
24176
+ import path36 from "node:path";
23735
24177
  import { fileURLToPath as fileURLToPath2 } from "node:url";
23736
24178
  var moduleDir = () => {
23737
24179
  try {
23738
- return realpathSync9(path35.dirname(fileURLToPath2(import.meta.url)));
24180
+ return realpathSync9(path36.dirname(fileURLToPath2(import.meta.url)));
23739
24181
  } catch {
23740
- return path35.dirname(fileURLToPath2(import.meta.url));
24182
+ return path36.dirname(fileURLToPath2(import.meta.url));
23741
24183
  }
23742
24184
  };
23743
24185
  var packageRoot2 = () => {
23744
24186
  const dir = moduleDir();
23745
24187
  for (const candidate of [
23746
- path35.resolve(dir, ".."),
23747
- path35.resolve(dir, "..", ".."),
23748
- path35.resolve(dir, "..", "..", "..")
24188
+ path36.resolve(dir, ".."),
24189
+ path36.resolve(dir, "..", ".."),
24190
+ path36.resolve(dir, "..", "..", "..")
23749
24191
  ]) {
23750
- if (existsSync11(path35.join(candidate, "package.json")))
24192
+ if (existsSync11(path36.join(candidate, "package.json")))
23751
24193
  return candidate;
23752
24194
  }
23753
- return path35.resolve(dir, "..");
24195
+ return path36.resolve(dir, "..");
23754
24196
  };
23755
24197
  var skillsRoot = () => {
23756
24198
  const root = packageRoot2();
23757
- const canonical = path35.resolve(root, "..", "workit-core", "skills");
23758
- if (existsSync11(path35.join(root, "src")) && existsSync11(canonical))
24199
+ const canonical = path36.resolve(root, "..", "workit-core", "skills");
24200
+ if (existsSync11(path36.join(root, "src")) && existsSync11(canonical))
23759
24201
  return canonical;
23760
- return path35.join(root, "assets", "skills");
24202
+ return path36.join(root, "assets", "skills");
23761
24203
  };
23762
24204
 
23763
24205
  // packages/workit-opencode/src/v2/registry.ts
@@ -23784,11 +24226,11 @@ var registerSkills = async (ctx) => {
23784
24226
  if (!existsSync12(skillsDir))
23785
24227
  return new Set;
23786
24228
  const listed = await ctx.skill.list().catch(() => ({ data: [] }));
23787
- const own = (skill) => typeof skill.path === "string" && path36.resolve(skill.path).startsWith(skillsDir + path36.sep);
24229
+ const own = (skill) => typeof skill.path === "string" && path37.resolve(skill.path).startsWith(skillsDir + path37.sep);
23788
24230
  const userSkills = new Set((listed?.data ?? []).filter((skill) => !own(skill)).map((skill) => skill.id));
23789
24231
  const packaged = new Map;
23790
24232
  for (const id of WORKIT_METHOD_SKILLS) {
23791
- const file = path36.join(skillsDir, id, "SKILL.md");
24233
+ const file = path37.join(skillsDir, id, "SKILL.md");
23792
24234
  if (!userSkills.has(id) && existsSync12(file))
23793
24235
  packaged.set(id, file);
23794
24236
  }
@@ -23836,22 +24278,22 @@ var registerCommands = async (ctx, registeredSkills) => {
23836
24278
 
23837
24279
  // packages/workit-opencode/src/stale-sources.ts
23838
24280
  import { existsSync as existsSync13, readdirSync as readdirSync6 } from "node:fs";
23839
- import path37 from "node:path";
24281
+ import path38 from "node:path";
23840
24282
  import { fileURLToPath as fileURLToPath3 } from "node:url";
23841
24283
  var typescriptFiles = (directory) => {
23842
24284
  if (!existsSync13(directory))
23843
24285
  return [];
23844
24286
  return readdirSync6(directory, { withFileTypes: true }).flatMap((entry) => {
23845
- const file = path37.join(directory, entry.name);
24287
+ const file = path38.join(directory, entry.name);
23846
24288
  return entry.isDirectory() ? typescriptFiles(file) : entry.isFile() && file.endsWith(".ts") ? [file] : [];
23847
24289
  });
23848
24290
  };
23849
24291
  var pluginSourceFiles = [
23850
24292
  ...new Set([
23851
24293
  fileURLToPath3(import.meta.url),
23852
- ...typescriptFiles(path37.join(packageRoot2(), "src")),
23853
- path37.join(packageRoot2(), "..", "workit-core", "src", "core.ts"),
23854
- ...typescriptFiles(path37.join(packageRoot2(), "..", "workit-core", "src", "core"))
24294
+ ...typescriptFiles(path38.join(packageRoot2(), "src")),
24295
+ path38.join(packageRoot2(), "..", "workit-core", "src", "core.ts"),
24296
+ ...typescriptFiles(path38.join(packageRoot2(), "..", "workit-core", "src", "core"))
23855
24297
  ])
23856
24298
  ].filter((file) => existsSync13(file)).toSorted();
23857
24299
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brainervirus/workit-opencode",
3
- "version": "8.13.1",
3
+ "version": "8.14.0",
4
4
  "private": false,
5
5
  "description": "Workit OpenCode plugin — native tools, adaptive policy, and sixteen method skills",
6
6
  "keywords": [
@@ -39,7 +39,7 @@
39
39
  "typecheck": "tsc --noEmit"
40
40
  },
41
41
  "devDependencies": {
42
- "@brainervirus/workit-core": "^8.13.1",
42
+ "@brainervirus/workit-core": "^8.14.0",
43
43
  "@opencode/plugin": "2.0.18"
44
44
  },
45
45
  "engines": {