@brainervirus/workit-opencode 8.4.0 → 8.5.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.
@@ -15,8 +15,8 @@ proposes and stops. Nothing changes until the user approves.
15
15
  `retro:` row in `workit ledger list --type decision`), else the last ~10
16
16
  sessions. State the window in one line.
17
17
  2. **Read through workit, cheapest first.** Every finding cites these:
18
- - `workit ledger list --last 200`: rulings (ambiguities the agent had to
19
- settle), failed or self verdicts, handoffs, check runs, CI reruns.
18
+ - `workit ledger list --last 200`: rulings, failed/self verdicts, handoffs,
19
+ check runs, CI reruns, `skill.loaded` rows (skipped skills: routing).
20
20
  - `workit pr status --pr <n>` for each recent PR: threads, failing checks.
21
21
  - `git log --oneline -50`, plus reverts and fixups after review.
22
22
  - `workit knowledge lint`: today's AGENTS.md and need-based files.
package/dist/plugin.js CHANGED
@@ -19157,6 +19157,7 @@ function entryCompactContext(store, entry, session, context) {
19157
19157
  cache.delete(cache.keys().next().value);
19158
19158
  return compact.data;
19159
19159
  }
19160
+ var TURN_TTL_MS = 7 * 24 * 60 * 60 * 1000;
19160
19161
  // packages/workit-core/src/hooks/policy.ts
19161
19162
  var shellPolicy = (cwd, command) => {
19162
19163
  const policy = shellBranchPolicyViolation(cwd, command);
@@ -19450,7 +19451,7 @@ function writeGate(cwd, paths) {
19450
19451
  const gate = gateFor(cwd);
19451
19452
  if (!gate || gate.blockers.length === 0)
19452
19453
  return NONE2;
19453
- if (paths && paths.every((file) => exempt(cwd, gate.root, file, gate.plans)))
19454
+ if (paths?.length && paths.every((file) => exempt(cwd, gate.root, file, gate.plans)))
19454
19455
  return NONE2;
19455
19456
  const reasons = gate.blockers.map((item) => `${item.ruleId}: ${item.reason}`).join("; ");
19456
19457
  return {
@@ -19800,6 +19801,11 @@ var actionsIn = (cwd, command, dialect) => {
19800
19801
  }
19801
19802
  return out;
19802
19803
  };
19804
+ var inWorkitWorkspace = (dir) => {
19805
+ const repo = repoAt(dir);
19806
+ return repo !== null && isWorkitWorkspace(repo);
19807
+ };
19808
+ var rawDelivery = (cwd, command, dialect) => mentionsRawTool(command) && actionsIn(cwd, command, dialect).some(({ action }) => action.kind !== "commit" && action.kind !== "pr-read");
19803
19809
  var mentionsRawTool = (command) => /\b(?:git|gh|glab)\b/i.test(command);
19804
19810
  var dialectOf = (input) => (input.event.kind === "shell.pre" || input.event.kind === "shell.post" ? input.event.dialect : undefined) ?? "posix";
19805
19811
  function rawGitPre(input, command) {
@@ -19908,8 +19914,271 @@ function rawGitPost(input, command, key) {
19908
19914
  settle2(input, repo, markerFile(repo, input.session.id, key));
19909
19915
  } catch {}
19910
19916
  }
19917
+ // packages/workit-core/src/hooks/skill-nudge.ts
19918
+ import { createHash as createHash12 } from "node:crypto";
19919
+ import fs16 from "node:fs";
19920
+ import path23 from "node:path";
19921
+
19922
+ // packages/workit-core/src/core/skill-manifests.ts
19923
+ var WORKIT_METHOD_SKILLS = [
19924
+ "workit-shape",
19925
+ "workit-implement",
19926
+ "workit-review",
19927
+ "workit-debug",
19928
+ "workit-ship",
19929
+ "workit-continue",
19930
+ "workit-bdd",
19931
+ "workit-test-audit",
19932
+ "workit-deslop",
19933
+ "workit-fanout",
19934
+ "workit-verify-app",
19935
+ "workit-retro",
19936
+ "workit-architecture"
19937
+ ];
19938
+ var WORKIT_SKILL_ALIASES = {
19939
+ "wk-shape": "workit-shape",
19940
+ "wk-implement": "workit-implement",
19941
+ "wk-review": "workit-review",
19942
+ "wk-debug": "workit-debug",
19943
+ "wk-ship": "workit-ship",
19944
+ "wk-continue": "workit-continue",
19945
+ "wk-bdd": "workit-bdd",
19946
+ "wk-test-audit": "workit-test-audit",
19947
+ "wk-deslop": "workit-deslop",
19948
+ "wk-fanout": "workit-fanout",
19949
+ "wk-verify-app": "workit-verify-app",
19950
+ "wk-retro": "workit-retro",
19951
+ "wk-architecture": "workit-architecture"
19952
+ };
19953
+ var skillLoadWording = (host, skill) => {
19954
+ switch (host) {
19955
+ case "claude-code":
19956
+ return `call the Skill tool with \`workit:${skill.replace(/^workit-/, "")}\``;
19957
+ case "opencode":
19958
+ return `call the skill tool with \`${skill}\``;
19959
+ default:
19960
+ return `read the \`${skill}\` skill's SKILL.md and follow it`;
19961
+ }
19962
+ };
19963
+ var SKILL_REFERENCE = /\((workit-[a-z-]+)\)/g;
19964
+ var renderSkillText = (text, host) => text.replace(SKILL_REFERENCE, (match, name) => WORKIT_METHOD_SKILLS.includes(name) ? `(${skillLoadWording(host, name)})` : match);
19965
+ if (false) {}
19966
+
19967
+ // packages/workit-core/src/hooks/skill-nudge.ts
19968
+ var USER_INVOKED = new Set(["workit-retro", "workit-architecture"]);
19969
+ var SKILL_HOST = {
19970
+ claude_code: "claude-code",
19971
+ opencode: "opencode",
19972
+ codex_cli: "codex",
19973
+ codex_desktop: "codex",
19974
+ cursor: "cursor",
19975
+ pi: "pi"
19976
+ };
19977
+ var PROMPT_INTENTS = [
19978
+ [
19979
+ "workit-debug",
19980
+ /\b(flaky|regression|(?:is|are|got|looks) broken|debug (?:this|it|the|why)|fix (?:the|this|a) bug)\b/i
19981
+ ],
19982
+ [
19983
+ "workit-continue",
19984
+ /\b(pick up where|where (?:were|did) we|resume (?:the|this|my|our|work|where)|hand ?off)\b/i
19985
+ ],
19986
+ [
19987
+ "workit-review",
19988
+ /\b(blast radius|code review|review (?:the|this|my|PR|pr|#\d+|branch|diff|changes))\b/i
19989
+ ],
19990
+ [
19991
+ "workit-ship",
19992
+ /\b(ship (?:it|this|the)|babysit|open (?:a|the) (?:PR|pr|MR|mr)|merge (?:the|this|my) (?:PR|pr|MR|mr|branch)|(?:get|until|make) CI (?:is )?green)\b/i
19993
+ ],
19994
+ ["workit-bdd", /\b(BDD|TDD|acceptance criteria|Given\/When\/Then)(?![\w/])/],
19995
+ ["workit-test-audit", /\b(test audit|audit the tests|tautolog\w*|weak tests)\b/i],
19996
+ ["workit-deslop", /\b(deslop|slop|dead code)\b/i],
19997
+ ["workit-fanout", /\b(fan(?: |-)?out|parallelize|parallel agents|swarm)\b/i],
19998
+ ["workit-verify-app", /\b(verify the app|smoke test|prove it works)\b/i],
19999
+ ["workit-retro", /\b(retro|retrospective)\b(?!\.\w)/i],
20000
+ ["workit-architecture", /\b(deepen (?:the )?modules|architecture (?:review|pass|audit))\b/i],
20001
+ [
20002
+ "workit-shape",
20003
+ /\b(brainstorm\w*|grill me|should we|let'?s plan|plan (?:out|how|the|a|for)|(?:write|draft) (?:a|the) spec)\b/i
20004
+ ],
20005
+ [
20006
+ "workit-implement",
20007
+ /\b(implement (?:the|this|a|it)|add (?:a|the) feature|build (?:a|an|the) (?:new )?(?:feature|command|verb|page|endpoint|component|cli|tool))\b/i
20008
+ ]
20009
+ ];
20010
+ var workitSkillOf = (name) => {
20011
+ const bare = name.trim().replace(/^\//, "").toLowerCase();
20012
+ const alias = WORKIT_SKILL_ALIASES[bare];
20013
+ if (alias)
20014
+ return alias;
20015
+ const skill = bare.replace(/^workit:/, "workit-");
20016
+ return WORKIT_METHOD_SKILLS.includes(skill) ? skill : null;
20017
+ };
20018
+ var promptTrigger = (prompt) => {
20019
+ const text = prompt.replace(/```[\s\S]*?```/g, " ").slice(0, 4000);
20020
+ for (const [skill, pattern] of PROMPT_INTENTS) {
20021
+ const match = pattern.exec(text);
20022
+ if (match)
20023
+ return { skill, trigger: match[1] ?? match[0] };
20024
+ }
20025
+ return null;
20026
+ };
20027
+ var markerFile2 = (input, session) => {
20028
+ const location = resolveStore(input.cwd);
20029
+ if (location instanceof Error || !location.shared)
20030
+ return null;
20031
+ const name = createHash12("sha256").update(`${input.host}\x00${session}`).digest("hex").slice(0, 32);
20032
+ return path23.join(location.dir, "hooks", `skills-${name}.json`);
20033
+ };
20034
+ var readMarker = (file) => {
20035
+ try {
20036
+ const value = JSON.parse(fs16.readFileSync(file, "utf8"));
20037
+ return { nudged: value.nudged ?? [], loaded: value.loaded ?? [] };
20038
+ } catch {
20039
+ return { nudged: [], loaded: [] };
20040
+ }
20041
+ };
20042
+ var writeMarker = (file, marker) => {
20043
+ try {
20044
+ fs16.mkdirSync(path23.dirname(file), { recursive: true });
20045
+ fs16.writeFileSync(file, JSON.stringify(marker));
20046
+ } catch {}
20047
+ };
20048
+ var loadedSkills = (cwd, session) => {
20049
+ const ledger = readLedger(cwd);
20050
+ const loaded = new Set;
20051
+ if (!ledger.ok)
20052
+ return loaded;
20053
+ for (const row of ledger.value.rows)
20054
+ if (row.type === "skill.loaded" && row.actor.session === session && typeof row.skill === "string")
20055
+ loaded.add(row.skill);
20056
+ return loaded;
20057
+ };
20058
+ var READERS = new Set([
20059
+ "cat",
20060
+ "sed",
20061
+ "head",
20062
+ "tail",
20063
+ "less",
20064
+ "more",
20065
+ "bat",
20066
+ "nl",
20067
+ "get-content",
20068
+ "gc",
20069
+ "type"
20070
+ ]);
20071
+ function recordSkillLoad(input, name, via) {
20072
+ try {
20073
+ const skill = workitSkillOf(name);
20074
+ const session = input.session.id;
20075
+ if (!skill || !session)
20076
+ return;
20077
+ if (!inWorkitWorkspace(input.cwd))
20078
+ return;
20079
+ const file = markerFile2(input, session);
20080
+ if (!file)
20081
+ return;
20082
+ const marker = readMarker(file);
20083
+ if (marker.loaded.includes(skill))
20084
+ return;
20085
+ writeMarker(file, { ...marker, loaded: [...marker.loaded, skill] });
20086
+ if (loadedSkills(input.cwd, session).has(skill))
20087
+ return;
20088
+ appendHookObserved(input.cwd, {
20089
+ type: "skill.loaded",
20090
+ actor: { host: input.host, session, agentId: input.session.agentId },
20091
+ skill,
20092
+ via
20093
+ });
20094
+ } catch {}
20095
+ }
20096
+ var nudgeOnce = (input, session, skill, line) => {
20097
+ const file = markerFile2(input, session);
20098
+ if (!file)
20099
+ return null;
20100
+ const marker = readMarker(file);
20101
+ if (marker.nudged.includes(skill) || marker.loaded.includes(skill))
20102
+ return null;
20103
+ if (loadedSkills(input.cwd, session).has(skill))
20104
+ return null;
20105
+ writeMarker(file, { ...marker, nudged: [...marker.nudged, skill] });
20106
+ return line;
20107
+ };
20108
+ var nudgeText = (input, skill, why) => USER_INVOKED.has(skill) ? `Workit: ${why} looks like ${skill} work, which the user starts; offer it (/${skill.replace(/^workit-/, "wk-")}) rather than loading it.` : `Workit: ${why} looks like ${skill} work; if it is, ${skillLoadWording(SKILL_HOST[input.host], skill)} first.`;
20109
+ var promptLoads = (prompt) => /^\s*\/(?:skill:)?([\w:-]+)/.exec(prompt)?.[1] ?? /^\s*<skill name="([\w:-]+)"/.exec(prompt)?.[1] ?? /^\s*Load the (workit-[a-z-]+) skill with the skill tool\b/.exec(prompt)?.[1] ?? null;
20110
+ function promptNudge(input, prompt) {
20111
+ try {
20112
+ const session = input.session.id;
20113
+ if (!prompt || !session || input.session.agentId)
20114
+ return null;
20115
+ const loads = promptLoads(prompt);
20116
+ if (loads) {
20117
+ recordSkillLoad(input, loads, "slash");
20118
+ return null;
20119
+ }
20120
+ const hit = promptTrigger(prompt);
20121
+ if (!hit || !inWorkitWorkspace(input.cwd))
20122
+ return null;
20123
+ return nudgeOnce(input, session, hit.skill, nudgeText(input, hit.skill, `this request ("${hit.trigger}")`));
20124
+ } catch {
20125
+ return null;
20126
+ }
20127
+ }
20128
+ var WORKIT_DELIVERY = /^(?:pr (?:create|merge)|ci wait|git push)\b/;
20129
+ var SHELLS2 = new Set(["sh", "bash", "zsh", "dash", "ksh"]);
20130
+ var LAUNCHERS = new Set([
20131
+ "npx",
20132
+ "bunx",
20133
+ "pnpx",
20134
+ "-y",
20135
+ "--yes",
20136
+ "pnpm",
20137
+ "dlx",
20138
+ "exec",
20139
+ "env",
20140
+ "sudo",
20141
+ "command"
20142
+ ]);
20143
+ var runsWorkitDelivery = (command, dialect, depth = 0) => segmentsOf(command, dialect).some(({ words }) => {
20144
+ const at = words.findIndex((word) => !/^\w+=/.test(word) && !LAUNCHERS.has(word));
20145
+ if (at >= 0 && /(?:^|[\\/])workit(?:\.exe)?$|^@brainervirus\/workit-cli$/.test(words[at])) {
20146
+ const args = words.slice(at + 1);
20147
+ if (args.some((arg) => arg === "--help" || arg === "-h" || arg === "--dry-run"))
20148
+ return false;
20149
+ return WORKIT_DELIVERY.test(args.join(" "));
20150
+ }
20151
+ const shell = SHELLS2.has(words[at]?.split(/[\\/]/).at(-1) ?? "");
20152
+ const script = shell && words[at + 1] === "-c" ? words[at + 2] : undefined;
20153
+ return depth < 3 && script !== undefined && runsWorkitDelivery(script, "posix", depth + 1);
20154
+ });
20155
+ var isDeliveryCommand = (cwd, command, dialect = "posix") => rawDelivery(cwd, command, dialect) || runsWorkitDelivery(command, dialect) && inWorkitWorkspace(cwd);
20156
+ function shipNudge(input, command) {
20157
+ try {
20158
+ const session = input.session.id;
20159
+ if (!session || input.session.agentId)
20160
+ return null;
20161
+ const dialect = input.event.kind === "shell.pre" ? input.event.dialect ?? "posix" : "posix";
20162
+ if (!isDeliveryCommand(input.cwd, command, dialect))
20163
+ return null;
20164
+ return nudgeOnce(input, session, "workit-ship", nudgeText(input, "workit-ship", "this delivery step"));
20165
+ } catch {
20166
+ return null;
20167
+ }
20168
+ }
20169
+ var withContextLine = (decision, line) => {
20170
+ if (!line)
20171
+ return decision;
20172
+ if (decision.kind === "none")
20173
+ return { kind: "context", text: line };
20174
+ if (decision.kind === "context")
20175
+ return { kind: "context", text: `${decision.text}
20176
+ ${line}` };
20177
+ return decision;
20178
+ };
20179
+ var shellNudge = (input, command) => withContextLine(rawGitPre(input, command), shipNudge(input, command));
19911
20180
  // packages/workit-core/src/hooks/hosts/fields.ts
19912
- var SHELLS2 = new Set(["sh", "bash", "zsh", "dash", "pwsh", "powershell", "cmd"]);
20181
+ var SHELLS3 = new Set(["sh", "bash", "zsh", "dash", "pwsh", "powershell", "cmd"]);
19913
20182
  var WRITE_TOOLS = new Set([
19914
20183
  "write",
19915
20184
  "edit",
@@ -19931,7 +20200,6 @@ var WRITE_TOOLS = new Set([
19931
20200
  ]);
19932
20201
  // packages/workit-core/src/hooks/handle.ts
19933
20202
  var offered = new Set;
19934
- var CLAUDE_JUDGES = new Set(["workit:verifier", "workit:reviewer"]);
19935
20203
  // packages/workit-core/src/hooks/hosts/claude-code.ts
19936
20204
  var SOURCES = new Set(["startup", "resume", "clear", "compact", "fork"]);
19937
20205
  var SHELL_TOOLS = new Set(["Bash", "PowerShell"]);
@@ -19945,7 +20213,7 @@ var CONTEXT_EVENTS = new Set([
19945
20213
 
19946
20214
  // packages/workit-core/src/hooks/hosts/codex.ts
19947
20215
  var SHELL_TOOLS2 = new Set(["bash", "unified-exec"]);
19948
- var SOURCES2 = new Set(["startup", "resume", "clear", "compact"]);
20216
+ var SOURCES2 = new Set(["startup", "resume", "clear", "compact", "fork"]);
19949
20217
 
19950
20218
  // packages/workit-core/src/hooks/hosts/cursor.ts
19951
20219
  var BLOCKING2 = new Set(["preToolUse", "beforeShellExecution", "subagentStart"]);
@@ -19970,21 +20238,23 @@ var OPENCODE_DESCRIPTOR = {
19970
20238
  "subagent.stop": { support: "native", native: 'tool.hook("execute.after") subagent' },
19971
20239
  "prompt.submit": { support: "undocumented", native: null },
19972
20240
  "compact.pre": { support: "native", native: 'session.hook("compaction")' },
19973
- stop: { support: "partial", native: "session.idle" }
20241
+ stop: { support: "none", native: null }
19974
20242
  },
19975
20243
  shellPolicy: { deny: "native", channel: "effect", failClosed: false },
19976
20244
  context: {
19977
20245
  sessionStart: "none",
19978
20246
  perTurn: "native",
19979
20247
  afterCompact: "native",
19980
- task: "session-bound"
20248
+ task: "session-bound",
20249
+ turnResend: "every-turn"
19981
20250
  },
19982
20251
  subagents: {
19983
20252
  identity: "native",
19984
20253
  parentBinding: "native",
19985
20254
  blockStart: "native",
19986
20255
  worktreeIsolation: "undocumented",
19987
- maxConcurrency: "undocumented"
20256
+ maxConcurrency: "undocumented",
20257
+ agentPrefix: "workit-"
19988
20258
  },
19989
20259
  provenance: { sessionId: "native", agentIdOnTool: "native", postToolObserve: "native" },
19990
20260
  interaction: { questions: "native", writeBoundary: "partial" },
@@ -20019,11 +20289,11 @@ var OPENCODE_DESCRIPTOR = {
20019
20289
  ]
20020
20290
  };
20021
20291
  // packages/workit-opencode/src/shared/init-apply.ts
20022
- import path27 from "node:path";
20292
+ import path28 from "node:path";
20023
20293
 
20024
20294
  // packages/workit-core/src/core/gitignore.ts
20025
20295
  import { existsSync as existsSync8, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "node:fs";
20026
- import path23 from "node:path";
20296
+ import path24 from "node:path";
20027
20297
  var GITIGNORE_ENTRIES = [
20028
20298
  "# workit: SDD working state (never commit)",
20029
20299
  "docs/*/sdd/",
@@ -20043,7 +20313,7 @@ var GITIGNORE_ENTRIES = [
20043
20313
  var ensureProjectGitignore = (workspaceRoot, confirmed) => {
20044
20314
  if (!confirmed)
20045
20315
  return { ok: false, error: "confirmed: true required" };
20046
- const file = path23.join(workspaceRoot, ".gitignore");
20316
+ const file = path24.join(workspaceRoot, ".gitignore");
20047
20317
  const existing = existsSync8(file) ? readFileSync6(file, "utf8") : "";
20048
20318
  const existingLines = new Set(existing.split(`
20049
20319
  `).map((l) => l.trim()).filter(Boolean));
@@ -20071,25 +20341,25 @@ var ensureProjectGitignore = (workspaceRoot, confirmed) => {
20071
20341
 
20072
20342
  // packages/workit-core/src/core/hygiene.ts
20073
20343
  import { existsSync as existsSync9, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "node:fs";
20074
- import path24 from "node:path";
20344
+ import path25 from "node:path";
20075
20345
  var repoRoot3 = assetRoot();
20076
- var templatesDir = () => path24.join(repoRoot3, "templates", "hygiene");
20346
+ var templatesDir = () => path25.join(repoRoot3, "templates", "hygiene");
20077
20347
  var packageJson = (root) => {
20078
- if (!existsSync9(path24.join(root, "package.json")))
20348
+ if (!existsSync9(path25.join(root, "package.json")))
20079
20349
  return null;
20080
20350
  try {
20081
- return JSON.parse(readFileSync7(path24.join(root, "package.json"), "utf8"));
20351
+ return JSON.parse(readFileSync7(path25.join(root, "package.json"), "utf8"));
20082
20352
  } catch {
20083
20353
  return null;
20084
20354
  }
20085
20355
  };
20086
20356
  var isOpenSource = (root) => {
20087
- if (existsSync9(path24.join(root, "LICENSE")))
20357
+ if (existsSync9(path25.join(root, "LICENSE")))
20088
20358
  return true;
20089
20359
  const pkg = packageJson(root);
20090
20360
  if (pkg && !pkg.private)
20091
20361
  return true;
20092
- return path24.basename(root) === "workflow-toolkit";
20362
+ return path25.basename(root) === "workflow-toolkit";
20093
20363
  };
20094
20364
  var licenseHolder = (root) => {
20095
20365
  const pkg = packageJson(root);
@@ -20112,13 +20382,13 @@ var planHygieneFiles = (root, opts = {}) => {
20112
20382
  files.push("LICENSE", "CONTRIBUTING.md");
20113
20383
  const planned = [];
20114
20384
  for (const file of files) {
20115
- if (existsSync9(path24.join(root, file)))
20385
+ if (existsSync9(path25.join(root, file)))
20116
20386
  continue;
20117
- const tpl = path24.join(templatesDir(), file);
20387
+ const tpl = path25.join(templatesDir(), file);
20118
20388
  if (!existsSync9(tpl))
20119
20389
  continue;
20120
- const content = readFileSync7(tpl, "utf8").replace(/<PROJECT>/g, path24.basename(root)).replace(/<YEAR>/g, String(new Date().getFullYear())).replace(/<HOLDER>\s*/g, licenseHolder(root));
20121
- planned.push({ path: path24.join(root, file), content });
20390
+ const content = readFileSync7(tpl, "utf8").replace(/<PROJECT>/g, path25.basename(root)).replace(/<YEAR>/g, String(new Date().getFullYear())).replace(/<HOLDER>\s*/g, licenseHolder(root));
20391
+ planned.push({ path: path25.join(root, file), content });
20122
20392
  }
20123
20393
  return planned;
20124
20394
  };
@@ -20128,19 +20398,19 @@ var ensureHygieneFiles = (root, opts) => {
20128
20398
  const created = [];
20129
20399
  for (const planned of planHygieneFiles(root, opts)) {
20130
20400
  writeFileSync5(planned.path, planned.content, "utf8");
20131
- created.push(path24.basename(planned.path));
20401
+ created.push(path25.basename(planned.path));
20132
20402
  }
20133
20403
  return { ok: true, created };
20134
20404
  };
20135
20405
 
20136
20406
  // packages/workit-core/src/core/init.ts
20137
- import fs16 from "node:fs";
20138
- import path26 from "node:path";
20407
+ import fs17 from "node:fs";
20408
+ import path27 from "node:path";
20139
20409
  import { spawnSync as spawnSync16 } from "node:child_process";
20140
20410
 
20141
20411
  // packages/workit-core/src/core/workspace-branch-policy.ts
20142
20412
  import { mkdirSync as mkdirSync4, readFileSync as readFileSync8, renameSync as renameSync3, rmSync as rmSync2, writeFileSync as writeFileSync6 } from "node:fs";
20143
- import path25 from "node:path";
20413
+ import path26 from "node:path";
20144
20414
  import { isDeepStrictEqual } from "node:util";
20145
20415
  var readFileSafe = (p) => {
20146
20416
  try {
@@ -20151,7 +20421,7 @@ var readFileSafe = (p) => {
20151
20421
  };
20152
20422
  function applyWorkspaceBranchPolicy(opts) {
20153
20423
  const { workspace_root, env = process.env } = opts;
20154
- const dir = path25.join(env.WORKFLOW_TOOLKIT_CONFIG ?? configDir());
20424
+ const dir = path26.join(env.WORKFLOW_TOOLKIT_CONFIG ?? configDir());
20155
20425
  const {
20156
20426
  status,
20157
20427
  path: wsPath,
@@ -20163,7 +20433,7 @@ function applyWorkspaceBranchPolicy(opts) {
20163
20433
  if (status === "malformed" || status === "invalid")
20164
20434
  return { ok: false, error: workspacesError ?? `invalid workspaces.json: ${wsPath}` };
20165
20435
  const detection = detectBranchPolicy(workspace_root);
20166
- const name = String(env.WORKFLOW_BP_NAME ?? path25.basename(workspace_root));
20436
+ const name = String(env.WORKFLOW_BP_NAME ?? path26.basename(workspace_root));
20167
20437
  const integration = env.WORKFLOW_BP_INTEGRATION ?? detection.integration;
20168
20438
  const policy = {
20169
20439
  preset: detection.preset,
@@ -20212,7 +20482,7 @@ function applyWorkspaceBranchPolicy(opts) {
20212
20482
  ok: false,
20213
20483
  error: `workspaces.json changed while applying the branch policy: ${wsPath}`
20214
20484
  };
20215
- mkdirSync4(path25.dirname(wsPath), { recursive: true });
20485
+ mkdirSync4(path26.dirname(wsPath), { recursive: true });
20216
20486
  const tmp = `${wsPath}.${process.pid}.tmp`;
20217
20487
  try {
20218
20488
  writeFileSync6(tmp, JSON.stringify({ ...document, workspaces: next }, null, 2) + `
@@ -20247,7 +20517,7 @@ function applyWorkspaceBranchPolicy(opts) {
20247
20517
  var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE";
20248
20518
  var readJson = (p) => {
20249
20519
  try {
20250
- const parsed = JSON.parse(fs16.readFileSync(p, "utf8"));
20520
+ const parsed = JSON.parse(fs17.readFileSync(p, "utf8"));
20251
20521
  return isConfigObject(parsed) ? parsed : null;
20252
20522
  } catch {
20253
20523
  return null;
@@ -20277,7 +20547,7 @@ var youtrackJsonContent = (dir) => {
20277
20547
  const meetingIssue = process.env.WORKFLOW_YT_MEETING_ISSUE;
20278
20548
  return {
20279
20549
  baseUrl: process.env.WORKFLOW_YT_BASE_URL ?? "https://youtrack.example.com",
20280
- tokenFile: process.env.WORKFLOW_YT_TOKEN_FILE ?? path26.join(dir, "youtrack.token"),
20550
+ tokenFile: process.env.WORKFLOW_YT_TOKEN_FILE ?? path27.join(dir, "youtrack.token"),
20281
20551
  ...meetingIssue ? {
20282
20552
  meetingIssue,
20283
20553
  meetingIssues: { general: { issue: meetingIssue, label: "General meetings" } }
@@ -20307,19 +20577,19 @@ var vcsJsonContent = () => {
20307
20577
  };
20308
20578
  function initApplyData(action, env = process.env) {
20309
20579
  const dir = String(env.WORKFLOW_TOOLKIT_CONFIG ?? configDir());
20310
- fs16.mkdirSync(dir, { recursive: true });
20580
+ fs17.mkdirSync(dir, { recursive: true });
20311
20581
  switch (action) {
20312
20582
  case "youtrack_json": {
20313
- const out = path26.join(dir, "youtrack.json");
20314
- fs16.writeFileSync(out, JSON.stringify(youtrackJsonContent(dir), null, 2) + `
20583
+ const out = path27.join(dir, "youtrack.json");
20584
+ fs17.writeFileSync(out, JSON.stringify(youtrackJsonContent(dir), null, 2) + `
20315
20585
  `, "utf8");
20316
20586
  return { action, ok: true, path: out };
20317
20587
  }
20318
20588
  case "youtrack_token_placeholder": {
20319
- const p = path26.join(dir, "youtrack.token");
20589
+ const p = path27.join(dir, "youtrack.token");
20320
20590
  const preserved = writeFileExclusive(p, TOKEN_PLACEHOLDER2 + `
20321
20591
  `, 384) === "preserved";
20322
- const abs = path26.resolve(p);
20592
+ const abs = path27.resolve(p);
20323
20593
  return {
20324
20594
  action,
20325
20595
  ok: true,
@@ -20331,14 +20601,14 @@ function initApplyData(action, env = process.env) {
20331
20601
  };
20332
20602
  }
20333
20603
  case "youtrack_scaffold": {
20334
- const jsonOut = path26.join(dir, "youtrack.json");
20335
- const tokenOut = path26.join(dir, "youtrack.token");
20336
- fs16.writeFileSync(jsonOut, JSON.stringify(youtrackJsonContent(dir), null, 2) + `
20604
+ const jsonOut = path27.join(dir, "youtrack.json");
20605
+ const tokenOut = path27.join(dir, "youtrack.token");
20606
+ fs17.writeFileSync(jsonOut, JSON.stringify(youtrackJsonContent(dir), null, 2) + `
20337
20607
  `, "utf8");
20338
20608
  const preserved = writeFileExclusive(tokenOut, TOKEN_PLACEHOLDER2 + `
20339
20609
  `, 384) === "preserved";
20340
- const configPath = path26.resolve(jsonOut);
20341
- const tokenPath = path26.resolve(tokenOut);
20610
+ const configPath = path27.resolve(jsonOut);
20611
+ const tokenPath = path27.resolve(tokenOut);
20342
20612
  const prev = process.env.WORKFLOW_YOUTRACK_CONFIG;
20343
20613
  process.env.WORKFLOW_YOUTRACK_CONFIG = configPath;
20344
20614
  let tokenCreate = {};
@@ -20390,10 +20660,10 @@ function initApplyData(action, env = process.env) {
20390
20660
  }
20391
20661
  }
20392
20662
  case "vcs_scaffold": {
20393
- const jsonOut = path26.join(dir, "vcs.json");
20394
- fs16.writeFileSync(jsonOut, JSON.stringify(vcsJsonContent(), null, 2) + `
20663
+ const jsonOut = path27.join(dir, "vcs.json");
20664
+ fs17.writeFileSync(jsonOut, JSON.stringify(vcsJsonContent(), null, 2) + `
20395
20665
  `, "utf8");
20396
- const configPath = path26.resolve(jsonOut);
20666
+ const configPath = path27.resolve(jsonOut);
20397
20667
  const prev = process.env.WORKFLOW_VCS_CONFIG;
20398
20668
  process.env.WORKFLOW_VCS_CONFIG = configPath;
20399
20669
  try {
@@ -20512,7 +20782,7 @@ var executeInitApply = (args, directory, runtime) => {
20512
20782
  protectedNames: args.branch_policy_protected
20513
20783
  }, current);
20514
20784
  writeConfig(next);
20515
- return output(ok({ action: "config", path: path27.join(configDir(), "config.json"), ...next }));
20785
+ return output(ok({ action: "config", path: path28.join(configDir(), "config.json"), ...next }));
20516
20786
  }
20517
20787
  const env = Object.fromEntries(Object.entries({
20518
20788
  WORKFLOW_YT_BASE_URL: args.base_url,
@@ -20529,14 +20799,14 @@ var executeInitApply = (args, directory, runtime) => {
20529
20799
 
20530
20800
  // packages/workit-opencode/src/shared/session.ts
20531
20801
  import { realpathSync as realpathSync7 } from "node:fs";
20532
- import path28 from "node:path";
20802
+ import path29 from "node:path";
20533
20803
  var sameWorkspace = (expected, observed) => {
20534
20804
  if (typeof observed !== "string" || !observed)
20535
20805
  return false;
20536
20806
  try {
20537
20807
  return realpathSync7(expected) === realpathSync7(observed);
20538
20808
  } catch {
20539
- return path28.resolve(expected) === path28.resolve(observed);
20809
+ return path29.resolve(expected) === path29.resolve(observed);
20540
20810
  }
20541
20811
  };
20542
20812
 
@@ -20608,7 +20878,7 @@ var workitFamilyOf = (toolName) => {
20608
20878
 
20609
20879
  // packages/workit-opencode/src/tools/context.ts
20610
20880
  import { realpathSync as realpathSync8 } from "node:fs";
20611
- import path29 from "node:path";
20881
+ import path30 from "node:path";
20612
20882
  var createContextTool = () => ({
20613
20883
  description: "Read Git, pull request, YouTrack, issue, changelog, release, or affected-file context.",
20614
20884
  execute: async (args, context) => {
@@ -20618,7 +20888,7 @@ var createContextTool = () => ({
20618
20888
  let root;
20619
20889
  try {
20620
20890
  const cwd = parsed.data.cwd;
20621
- root = realpathSync8(cwd ? path29.resolve(context.directory, cwd) : context.directory);
20891
+ root = realpathSync8(cwd ? path30.resolve(context.directory, cwd) : context.directory);
20622
20892
  } catch {
20623
20893
  return JSON.stringify(failure2("invalid_input", "context target directory cannot be resolved"), null, 2);
20624
20894
  }
@@ -21180,7 +21450,7 @@ var evaluateShellPermission = (root, event, workdirs = new Map) => {
21180
21450
  };
21181
21451
 
21182
21452
  // packages/workit-opencode/src/v2/shell.ts
21183
- import path30 from "node:path";
21453
+ import path31 from "node:path";
21184
21454
  var commandOf = (input) => {
21185
21455
  if (input === null || typeof input !== "object")
21186
21456
  return null;
@@ -21189,7 +21459,7 @@ var commandOf = (input) => {
21189
21459
  };
21190
21460
  var hookInput = (root, sessionID, id, shell) => ({
21191
21461
  host: "opencode",
21192
- cwd: shell.workdir ? path30.resolve(root, shell.workdir) : root,
21462
+ cwd: shell.workdir ? path31.resolve(root, shell.workdir) : root,
21193
21463
  session: { id: sessionID, agentId: null, agentType: null, parentId: null },
21194
21464
  permissionMode: null,
21195
21465
  transcriptPath: null,
@@ -21219,7 +21489,7 @@ var observeShellResult = (root, event) => {
21219
21489
  return;
21220
21490
  const input = hookInput(root, String(event.sessionID), String(event.id), shell);
21221
21491
  rawGitPost(input, shell.command, callKey(String(event.id), shell.command));
21222
- const nudge = rawGitPre(input, shell.command);
21492
+ const nudge = shellNudge(input, shell.command);
21223
21493
  const result = event.result;
21224
21494
  if (nudge.kind !== "context" || !result)
21225
21495
  return;
@@ -21235,84 +21505,78 @@ ${nudge.text}` };
21235
21505
  } catch {}
21236
21506
  };
21237
21507
 
21238
- // packages/workit-opencode/src/v2/registry.ts
21239
- import { existsSync as existsSync11, readFileSync as readFileSync9 } from "node:fs";
21240
- import path32 from "node:path";
21241
-
21242
- // packages/workit-core/src/core/skill-manifests.ts
21243
- var WORKIT_METHOD_SKILLS = [
21244
- "workit-shape",
21245
- "workit-implement",
21246
- "workit-review",
21247
- "workit-debug",
21248
- "workit-ship",
21249
- "workit-continue",
21250
- "workit-bdd",
21251
- "workit-test-audit",
21252
- "workit-deslop",
21253
- "workit-fanout",
21254
- "workit-verify-app",
21255
- "workit-retro",
21256
- "workit-architecture"
21257
- ];
21258
- var WORKIT_SKILL_ALIASES = {
21259
- "wk-shape": "workit-shape",
21260
- "wk-implement": "workit-implement",
21261
- "wk-review": "workit-review",
21262
- "wk-debug": "workit-debug",
21263
- "wk-ship": "workit-ship",
21264
- "wk-continue": "workit-continue",
21265
- "wk-bdd": "workit-bdd",
21266
- "wk-test-audit": "workit-test-audit",
21267
- "wk-deslop": "workit-deslop",
21268
- "wk-fanout": "workit-fanout",
21269
- "wk-verify-app": "workit-verify-app",
21270
- "wk-retro": "workit-retro",
21271
- "wk-architecture": "workit-architecture"
21508
+ // packages/workit-opencode/src/v2/skills.ts
21509
+ var hookInput2 = (root, sessionID, subagent) => ({
21510
+ host: "opencode",
21511
+ cwd: root,
21512
+ session: {
21513
+ id: sessionID,
21514
+ agentId: subagent ? sessionID : null,
21515
+ agentType: null,
21516
+ parentId: null
21517
+ },
21518
+ permissionMode: null,
21519
+ transcriptPath: null,
21520
+ event: { kind: "context.turn" }
21521
+ });
21522
+ var observeSkillLoad = (root, event) => {
21523
+ const input = event.input;
21524
+ if (typeof input?.name === "string")
21525
+ recordSkillLoad(hookInput2(root, String(event.sessionID), false), input.name, "tool");
21526
+ };
21527
+ var newUserPrompt = (messages) => {
21528
+ const last = messages.at(-1);
21529
+ if (last?.role !== "user")
21530
+ return null;
21531
+ if (typeof last.content === "string")
21532
+ return last.content;
21533
+ if (!Array.isArray(last.content))
21534
+ return null;
21535
+ const text = last.content.filter((part) => part.type === "text" && typeof part.text === "string").map((part) => part.text).join(`
21536
+ `);
21537
+ return text || null;
21272
21538
  };
21273
- var skillLoadWording = (host, skill) => {
21274
- switch (host) {
21275
- case "claude-code":
21276
- return `call the Skill tool with \`workit:${skill.replace(/^workit-/, "")}\``;
21277
- case "opencode":
21278
- return `call the skill tool with \`${skill}\``;
21279
- default:
21280
- return `read the \`${skill}\` skill's SKILL.md and follow it`;
21281
- }
21539
+ var injectSkillNudge = (root, session, messages, system) => {
21540
+ try {
21541
+ const nudge = promptNudge(hookInput2(root, session.id, session.parentID !== undefined), newUserPrompt(messages));
21542
+ if (nudge)
21543
+ system.push({ type: "text", text: nudge });
21544
+ } catch {}
21282
21545
  };
21283
- var SKILL_REFERENCE = /\((workit-[a-z-]+)\)/g;
21284
- var renderSkillText = (text, host) => text.replace(SKILL_REFERENCE, (match, name) => WORKIT_METHOD_SKILLS.includes(name) ? `(${skillLoadWording(host, name)})` : match);
21285
- if (false) {}
21546
+
21547
+ // packages/workit-opencode/src/v2/registry.ts
21548
+ import { existsSync as existsSync11, readFileSync as readFileSync9 } from "node:fs";
21549
+ import path33 from "node:path";
21286
21550
 
21287
21551
  // packages/workit-opencode/src/shared/assets.ts
21288
21552
  import { existsSync as existsSync10, realpathSync as realpathSync9 } from "node:fs";
21289
- import path31 from "node:path";
21553
+ import path32 from "node:path";
21290
21554
  import { fileURLToPath as fileURLToPath2 } from "node:url";
21291
21555
  var moduleDir = () => {
21292
21556
  try {
21293
- return realpathSync9(path31.dirname(fileURLToPath2(import.meta.url)));
21557
+ return realpathSync9(path32.dirname(fileURLToPath2(import.meta.url)));
21294
21558
  } catch {
21295
- return path31.dirname(fileURLToPath2(import.meta.url));
21559
+ return path32.dirname(fileURLToPath2(import.meta.url));
21296
21560
  }
21297
21561
  };
21298
21562
  var packageRoot2 = () => {
21299
21563
  const dir = moduleDir();
21300
21564
  for (const candidate of [
21301
- path31.resolve(dir, ".."),
21302
- path31.resolve(dir, "..", ".."),
21303
- path31.resolve(dir, "..", "..", "..")
21565
+ path32.resolve(dir, ".."),
21566
+ path32.resolve(dir, "..", ".."),
21567
+ path32.resolve(dir, "..", "..", "..")
21304
21568
  ]) {
21305
- if (existsSync10(path31.join(candidate, "package.json")))
21569
+ if (existsSync10(path32.join(candidate, "package.json")))
21306
21570
  return candidate;
21307
21571
  }
21308
- return path31.resolve(dir, "..");
21572
+ return path32.resolve(dir, "..");
21309
21573
  };
21310
21574
  var skillsRoot = () => {
21311
21575
  const root = packageRoot2();
21312
- const canonical = path31.resolve(root, "..", "workit-core", "skills");
21313
- if (existsSync10(path31.join(root, "src")) && existsSync10(canonical))
21576
+ const canonical = path32.resolve(root, "..", "workit-core", "skills");
21577
+ if (existsSync10(path32.join(root, "src")) && existsSync10(canonical))
21314
21578
  return canonical;
21315
- return path31.join(root, "assets", "skills");
21579
+ return path32.join(root, "assets", "skills");
21316
21580
  };
21317
21581
 
21318
21582
  // packages/workit-opencode/src/v2/registry.ts
@@ -21339,11 +21603,11 @@ var registerSkills = async (ctx) => {
21339
21603
  if (!existsSync11(skillsDir))
21340
21604
  return new Set;
21341
21605
  const listed = await ctx.skill.list().catch(() => ({ data: [] }));
21342
- const own = (skill) => typeof skill.path === "string" && path32.resolve(skill.path).startsWith(skillsDir + path32.sep);
21606
+ const own = (skill) => typeof skill.path === "string" && path33.resolve(skill.path).startsWith(skillsDir + path33.sep);
21343
21607
  const userSkills = new Set((listed?.data ?? []).filter((skill) => !own(skill)).map((skill) => skill.id));
21344
21608
  const packaged = new Map;
21345
21609
  for (const id of WORKIT_METHOD_SKILLS) {
21346
- const file = path32.join(skillsDir, id, "SKILL.md");
21610
+ const file = path33.join(skillsDir, id, "SKILL.md");
21347
21611
  if (!userSkills.has(id) && existsSync11(file))
21348
21612
  packaged.set(id, file);
21349
21613
  }
@@ -21391,22 +21655,22 @@ var registerCommands = async (ctx, registeredSkills) => {
21391
21655
 
21392
21656
  // packages/workit-opencode/src/stale-sources.ts
21393
21657
  import { existsSync as existsSync12, readdirSync as readdirSync5 } from "node:fs";
21394
- import path33 from "node:path";
21658
+ import path34 from "node:path";
21395
21659
  import { fileURLToPath as fileURLToPath3 } from "node:url";
21396
21660
  var typescriptFiles = (directory) => {
21397
21661
  if (!existsSync12(directory))
21398
21662
  return [];
21399
21663
  return readdirSync5(directory, { withFileTypes: true }).flatMap((entry) => {
21400
- const file = path33.join(directory, entry.name);
21664
+ const file = path34.join(directory, entry.name);
21401
21665
  return entry.isDirectory() ? typescriptFiles(file) : entry.isFile() && file.endsWith(".ts") ? [file] : [];
21402
21666
  });
21403
21667
  };
21404
21668
  var pluginSourceFiles = [
21405
21669
  ...new Set([
21406
21670
  fileURLToPath3(import.meta.url),
21407
- ...typescriptFiles(path33.join(packageRoot2(), "src")),
21408
- path33.join(packageRoot2(), "..", "workit-core", "src", "core.ts"),
21409
- ...typescriptFiles(path33.join(packageRoot2(), "..", "workit-core", "src", "core"))
21671
+ ...typescriptFiles(path34.join(packageRoot2(), "src")),
21672
+ path34.join(packageRoot2(), "..", "workit-core", "src", "core.ts"),
21673
+ ...typescriptFiles(path34.join(packageRoot2(), "..", "workit-core", "src", "core"))
21410
21674
  ])
21411
21675
  ].filter((file) => existsSync12(file)).toSorted();
21412
21676
 
@@ -21511,6 +21775,8 @@ var setup = async (ctx) => {
21511
21775
  await ctx.tool.hook("execute.before", async (event) => {
21512
21776
  if (event.tool === "shell")
21513
21777
  return observeShellBefore(root, event, shellWorkdirs);
21778
+ if (event.tool === "skill")
21779
+ return observeSkillLoad(root, event);
21514
21780
  if (event.tool !== "subagent")
21515
21781
  return;
21516
21782
  await lifecycle.executeBefore({
@@ -21540,6 +21806,8 @@ var setup = async (ctx) => {
21540
21806
  const session = await sessionFacts(ctx, String(event.sessionID));
21541
21807
  injectAgentContext(root, session, lifecycle.directChildren, event.system);
21542
21808
  injectHistoryOffer(root, session, historyOfferSessions, event.system);
21809
+ if (session)
21810
+ injectSkillNudge(root, session, event.messages, event.system);
21543
21811
  if (!staleSourcesWarned) {
21544
21812
  const changed = changedSourcesSinceLoad(sourceMarker);
21545
21813
  if (changed.length > 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brainervirus/workit-opencode",
3
- "version": "8.4.0",
3
+ "version": "8.5.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
  "dependencies": {
42
- "@brainervirus/workit-core": "^8.4.0"
42
+ "@brainervirus/workit-core": "^8.5.0"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@opencode/plugin": "2.0.18"