@brainervirus/workit-opencode 8.4.1 → 8.5.2

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,274 @@ 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|regressed|(?:is|introduced) a regression|regression (?:in|after|since|from)|(?: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)(?=\s*$|\s+(?:for|to|on|with|and|against|into|from|after|once)\b|[.!,;])|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
+ [
19998
+ "workit-fanout",
19999
+ /\b(fan(?: |-)?out|parallelize|parallel agents|agent swarm|swarm of agents)\b/i
20000
+ ],
20001
+ ["workit-verify-app", /\b(verify the app|smoke test|prove it works)\b/i],
20002
+ ["workit-retro", /\b((?:a|the) retro(?![-\w]|\.\w)|retrospective)/i],
20003
+ ["workit-architecture", /\b(deepen (?:the )?modules|architecture (?:review|pass|audit))\b/i],
20004
+ [
20005
+ "workit-shape",
20006
+ /\b(brainstorm\w*|grill me|should we|let'?s plan|plan (?:out|how|the|a|for)|(?:write|draft) (?:a|the) spec)\b/i
20007
+ ],
20008
+ [
20009
+ "workit-implement",
20010
+ /\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
20011
+ ]
20012
+ ];
20013
+ var workitSkillOf = (name) => {
20014
+ const bare = name.trim().replace(/^\//, "").toLowerCase();
20015
+ const alias = WORKIT_SKILL_ALIASES[bare];
20016
+ if (alias)
20017
+ return alias;
20018
+ const skill = bare.replace(/^workit:/, "workit-");
20019
+ return WORKIT_METHOD_SKILLS.includes(skill) ? skill : null;
20020
+ };
20021
+ var promptTrigger = (prompt) => {
20022
+ const text = prompt.replace(/```[\s\S]*?```/g, " ").slice(0, 4000);
20023
+ for (const [skill, pattern] of PROMPT_INTENTS) {
20024
+ const match = pattern.exec(text);
20025
+ if (match)
20026
+ return { skill, trigger: match[1] ?? match[0] };
20027
+ }
20028
+ return null;
20029
+ };
20030
+ var markerFile2 = (input, session) => {
20031
+ const location = resolveStore(input.cwd);
20032
+ if (location instanceof Error || !location.shared)
20033
+ return null;
20034
+ const name = createHash12("sha256").update(`${input.host}\x00${session}`).digest("hex").slice(0, 32);
20035
+ return path23.join(location.dir, "hooks", `skills-${name}.json`);
20036
+ };
20037
+ var readMarker = (file) => {
20038
+ try {
20039
+ const value = JSON.parse(fs16.readFileSync(file, "utf8"));
20040
+ return { nudged: value.nudged ?? [], loaded: value.loaded ?? [] };
20041
+ } catch {
20042
+ return { nudged: [], loaded: [] };
20043
+ }
20044
+ };
20045
+ var writeMarker = (file, marker) => {
20046
+ try {
20047
+ fs16.mkdirSync(path23.dirname(file), { recursive: true });
20048
+ fs16.writeFileSync(file, JSON.stringify(marker));
20049
+ } catch {}
20050
+ };
20051
+ var loadedSkills = (cwd, session) => {
20052
+ const ledger = readLedger(cwd);
20053
+ const loaded = new Set;
20054
+ if (!ledger.ok)
20055
+ return loaded;
20056
+ for (const row of ledger.value.rows)
20057
+ if (row.type === "skill.loaded" && row.actor.session === session && typeof row.skill === "string")
20058
+ loaded.add(row.skill);
20059
+ return loaded;
20060
+ };
20061
+ var READERS = new Set([
20062
+ "cat",
20063
+ "sed",
20064
+ "head",
20065
+ "tail",
20066
+ "less",
20067
+ "more",
20068
+ "bat",
20069
+ "nl",
20070
+ "get-content",
20071
+ "gc",
20072
+ "type"
20073
+ ]);
20074
+ function recordSkillLoad(input, name, via) {
20075
+ try {
20076
+ const skill = workitSkillOf(name);
20077
+ const session = input.session.id;
20078
+ if (!skill || !session)
20079
+ return;
20080
+ if (!inWorkitWorkspace(input.cwd))
20081
+ return;
20082
+ const file = markerFile2(input, session);
20083
+ if (!file)
20084
+ return;
20085
+ const marker = readMarker(file);
20086
+ if (marker.loaded.includes(skill))
20087
+ return;
20088
+ writeMarker(file, { ...marker, loaded: [...marker.loaded, skill] });
20089
+ if (loadedSkills(input.cwd, session).has(skill))
20090
+ return;
20091
+ appendHookObserved(input.cwd, {
20092
+ type: "skill.loaded",
20093
+ actor: { host: input.host, session, agentId: input.session.agentId },
20094
+ skill,
20095
+ via
20096
+ });
20097
+ } catch {}
20098
+ }
20099
+ var nudgeOnce = (input, session, skill, line) => {
20100
+ const file = markerFile2(input, session);
20101
+ if (!file)
20102
+ return null;
20103
+ const marker = readMarker(file);
20104
+ if (marker.nudged.includes(skill) || marker.loaded.includes(skill))
20105
+ return null;
20106
+ if (loadedSkills(input.cwd, session).has(skill))
20107
+ return null;
20108
+ writeMarker(file, { ...marker, nudged: [...marker.nudged, skill] });
20109
+ return line;
20110
+ };
20111
+ 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.`;
20112
+ 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;
20113
+ function promptNudge(input, prompt) {
20114
+ try {
20115
+ const session = input.session.id;
20116
+ if (!prompt || !session || input.session.agentId)
20117
+ return null;
20118
+ const loads = promptLoads(prompt);
20119
+ if (loads) {
20120
+ recordSkillLoad(input, loads, "slash");
20121
+ return null;
20122
+ }
20123
+ const hit = promptTrigger(prompt);
20124
+ if (!hit || !inWorkitWorkspace(input.cwd))
20125
+ return null;
20126
+ return nudgeOnce(input, session, hit.skill, nudgeText(input, hit.skill, `this request ("${hit.trigger}")`));
20127
+ } catch {
20128
+ return null;
20129
+ }
20130
+ }
20131
+ var WORKIT_DELIVERY = /^(?:pr (?:create|merge)|ci wait|git push)\b/;
20132
+ var SHELLS2 = new Set(["sh", "bash", "zsh", "dash", "ksh"]);
20133
+ var LAUNCHERS = new Set([
20134
+ "npx",
20135
+ "bunx",
20136
+ "pnpx",
20137
+ "-y",
20138
+ "--yes",
20139
+ "pnpm",
20140
+ "dlx",
20141
+ "exec",
20142
+ "env",
20143
+ "sudo",
20144
+ "command"
20145
+ ]);
20146
+ var runsWorkitDelivery = (command, dialect, depth = 0) => segmentsOf(command, dialect).some(({ words }) => {
20147
+ const at = words.findIndex((word) => !/^\w+=/.test(word) && !LAUNCHERS.has(word));
20148
+ if (at >= 0 && /(?:^|[\\/])workit(?:\.exe)?$|^@brainervirus\/workit-cli$/.test(words[at])) {
20149
+ const args = words.slice(at + 1);
20150
+ if (args.some((arg) => arg === "--help" || arg === "-h" || arg === "--dry-run"))
20151
+ return false;
20152
+ return WORKIT_DELIVERY.test(args.join(" "));
20153
+ }
20154
+ const shell = SHELLS2.has(words[at]?.split(/[\\/]/).at(-1) ?? "");
20155
+ const script = shell && words[at + 1] === "-c" ? words[at + 2] : undefined;
20156
+ return depth < 3 && script !== undefined && runsWorkitDelivery(script, "posix", depth + 1);
20157
+ });
20158
+ var isDeliveryCommand = (cwd, command, dialect = "posix") => rawDelivery(cwd, command, dialect) || runsWorkitDelivery(command, dialect) && inWorkitWorkspace(cwd);
20159
+ function shipNudge(input, command) {
20160
+ try {
20161
+ const session = input.session.id;
20162
+ if (!session || input.session.agentId)
20163
+ return null;
20164
+ const dialect = input.event.kind === "shell.pre" ? input.event.dialect ?? "posix" : "posix";
20165
+ if (!isDeliveryCommand(input.cwd, command, dialect))
20166
+ return null;
20167
+ return nudgeOnce(input, session, "workit-ship", nudgeText(input, "workit-ship", "this delivery step"));
20168
+ } catch {
20169
+ return null;
20170
+ }
20171
+ }
20172
+ var withContextLine = (decision, line) => {
20173
+ if (!line)
20174
+ return decision;
20175
+ if (decision.kind === "none")
20176
+ return { kind: "context", text: line };
20177
+ if (decision.kind === "context")
20178
+ return { kind: "context", text: `${decision.text}
20179
+ ${line}` };
20180
+ return decision;
20181
+ };
20182
+ var shellNudge = (input, command) => withContextLine(rawGitPre(input, command), shipNudge(input, command));
19911
20183
  // packages/workit-core/src/hooks/hosts/fields.ts
19912
- var SHELLS2 = new Set(["sh", "bash", "zsh", "dash", "pwsh", "powershell", "cmd"]);
20184
+ var SHELLS3 = new Set(["sh", "bash", "zsh", "dash", "pwsh", "powershell", "cmd"]);
19913
20185
  var WRITE_TOOLS = new Set([
19914
20186
  "write",
19915
20187
  "edit",
@@ -19931,7 +20203,6 @@ var WRITE_TOOLS = new Set([
19931
20203
  ]);
19932
20204
  // packages/workit-core/src/hooks/handle.ts
19933
20205
  var offered = new Set;
19934
- var CLAUDE_JUDGES = new Set(["workit:verifier", "workit:reviewer"]);
19935
20206
  // packages/workit-core/src/hooks/hosts/claude-code.ts
19936
20207
  var SOURCES = new Set(["startup", "resume", "clear", "compact", "fork"]);
19937
20208
  var SHELL_TOOLS = new Set(["Bash", "PowerShell"]);
@@ -19945,7 +20216,7 @@ var CONTEXT_EVENTS = new Set([
19945
20216
 
19946
20217
  // packages/workit-core/src/hooks/hosts/codex.ts
19947
20218
  var SHELL_TOOLS2 = new Set(["bash", "unified-exec"]);
19948
- var SOURCES2 = new Set(["startup", "resume", "clear", "compact"]);
20219
+ var SOURCES2 = new Set(["startup", "resume", "clear", "compact", "fork"]);
19949
20220
 
19950
20221
  // packages/workit-core/src/hooks/hosts/cursor.ts
19951
20222
  var BLOCKING2 = new Set(["preToolUse", "beforeShellExecution", "subagentStart"]);
@@ -19970,21 +20241,23 @@ var OPENCODE_DESCRIPTOR = {
19970
20241
  "subagent.stop": { support: "native", native: 'tool.hook("execute.after") subagent' },
19971
20242
  "prompt.submit": { support: "undocumented", native: null },
19972
20243
  "compact.pre": { support: "native", native: 'session.hook("compaction")' },
19973
- stop: { support: "partial", native: "session.idle" }
20244
+ stop: { support: "none", native: null }
19974
20245
  },
19975
20246
  shellPolicy: { deny: "native", channel: "effect", failClosed: false },
19976
20247
  context: {
19977
20248
  sessionStart: "none",
19978
20249
  perTurn: "native",
19979
20250
  afterCompact: "native",
19980
- task: "session-bound"
20251
+ task: "session-bound",
20252
+ turnResend: "every-turn"
19981
20253
  },
19982
20254
  subagents: {
19983
20255
  identity: "native",
19984
20256
  parentBinding: "native",
19985
20257
  blockStart: "native",
19986
20258
  worktreeIsolation: "undocumented",
19987
- maxConcurrency: "undocumented"
20259
+ maxConcurrency: "undocumented",
20260
+ agentPrefix: "workit-"
19988
20261
  },
19989
20262
  provenance: { sessionId: "native", agentIdOnTool: "native", postToolObserve: "native" },
19990
20263
  interaction: { questions: "native", writeBoundary: "partial" },
@@ -20019,11 +20292,11 @@ var OPENCODE_DESCRIPTOR = {
20019
20292
  ]
20020
20293
  };
20021
20294
  // packages/workit-opencode/src/shared/init-apply.ts
20022
- import path27 from "node:path";
20295
+ import path28 from "node:path";
20023
20296
 
20024
20297
  // packages/workit-core/src/core/gitignore.ts
20025
20298
  import { existsSync as existsSync8, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "node:fs";
20026
- import path23 from "node:path";
20299
+ import path24 from "node:path";
20027
20300
  var GITIGNORE_ENTRIES = [
20028
20301
  "# workit: SDD working state (never commit)",
20029
20302
  "docs/*/sdd/",
@@ -20043,7 +20316,7 @@ var GITIGNORE_ENTRIES = [
20043
20316
  var ensureProjectGitignore = (workspaceRoot, confirmed) => {
20044
20317
  if (!confirmed)
20045
20318
  return { ok: false, error: "confirmed: true required" };
20046
- const file = path23.join(workspaceRoot, ".gitignore");
20319
+ const file = path24.join(workspaceRoot, ".gitignore");
20047
20320
  const existing = existsSync8(file) ? readFileSync6(file, "utf8") : "";
20048
20321
  const existingLines = new Set(existing.split(`
20049
20322
  `).map((l) => l.trim()).filter(Boolean));
@@ -20071,25 +20344,25 @@ var ensureProjectGitignore = (workspaceRoot, confirmed) => {
20071
20344
 
20072
20345
  // packages/workit-core/src/core/hygiene.ts
20073
20346
  import { existsSync as existsSync9, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "node:fs";
20074
- import path24 from "node:path";
20347
+ import path25 from "node:path";
20075
20348
  var repoRoot3 = assetRoot();
20076
- var templatesDir = () => path24.join(repoRoot3, "templates", "hygiene");
20349
+ var templatesDir = () => path25.join(repoRoot3, "templates", "hygiene");
20077
20350
  var packageJson = (root) => {
20078
- if (!existsSync9(path24.join(root, "package.json")))
20351
+ if (!existsSync9(path25.join(root, "package.json")))
20079
20352
  return null;
20080
20353
  try {
20081
- return JSON.parse(readFileSync7(path24.join(root, "package.json"), "utf8"));
20354
+ return JSON.parse(readFileSync7(path25.join(root, "package.json"), "utf8"));
20082
20355
  } catch {
20083
20356
  return null;
20084
20357
  }
20085
20358
  };
20086
20359
  var isOpenSource = (root) => {
20087
- if (existsSync9(path24.join(root, "LICENSE")))
20360
+ if (existsSync9(path25.join(root, "LICENSE")))
20088
20361
  return true;
20089
20362
  const pkg = packageJson(root);
20090
20363
  if (pkg && !pkg.private)
20091
20364
  return true;
20092
- return path24.basename(root) === "workflow-toolkit";
20365
+ return path25.basename(root) === "workflow-toolkit";
20093
20366
  };
20094
20367
  var licenseHolder = (root) => {
20095
20368
  const pkg = packageJson(root);
@@ -20112,13 +20385,13 @@ var planHygieneFiles = (root, opts = {}) => {
20112
20385
  files.push("LICENSE", "CONTRIBUTING.md");
20113
20386
  const planned = [];
20114
20387
  for (const file of files) {
20115
- if (existsSync9(path24.join(root, file)))
20388
+ if (existsSync9(path25.join(root, file)))
20116
20389
  continue;
20117
- const tpl = path24.join(templatesDir(), file);
20390
+ const tpl = path25.join(templatesDir(), file);
20118
20391
  if (!existsSync9(tpl))
20119
20392
  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 });
20393
+ const content = readFileSync7(tpl, "utf8").replace(/<PROJECT>/g, path25.basename(root)).replace(/<YEAR>/g, String(new Date().getFullYear())).replace(/<HOLDER>\s*/g, licenseHolder(root));
20394
+ planned.push({ path: path25.join(root, file), content });
20122
20395
  }
20123
20396
  return planned;
20124
20397
  };
@@ -20128,19 +20401,19 @@ var ensureHygieneFiles = (root, opts) => {
20128
20401
  const created = [];
20129
20402
  for (const planned of planHygieneFiles(root, opts)) {
20130
20403
  writeFileSync5(planned.path, planned.content, "utf8");
20131
- created.push(path24.basename(planned.path));
20404
+ created.push(path25.basename(planned.path));
20132
20405
  }
20133
20406
  return { ok: true, created };
20134
20407
  };
20135
20408
 
20136
20409
  // packages/workit-core/src/core/init.ts
20137
- import fs16 from "node:fs";
20138
- import path26 from "node:path";
20410
+ import fs17 from "node:fs";
20411
+ import path27 from "node:path";
20139
20412
  import { spawnSync as spawnSync16 } from "node:child_process";
20140
20413
 
20141
20414
  // packages/workit-core/src/core/workspace-branch-policy.ts
20142
20415
  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";
20416
+ import path26 from "node:path";
20144
20417
  import { isDeepStrictEqual } from "node:util";
20145
20418
  var readFileSafe = (p) => {
20146
20419
  try {
@@ -20151,7 +20424,7 @@ var readFileSafe = (p) => {
20151
20424
  };
20152
20425
  function applyWorkspaceBranchPolicy(opts) {
20153
20426
  const { workspace_root, env = process.env } = opts;
20154
- const dir = path25.join(env.WORKFLOW_TOOLKIT_CONFIG ?? configDir());
20427
+ const dir = path26.join(env.WORKFLOW_TOOLKIT_CONFIG ?? configDir());
20155
20428
  const {
20156
20429
  status,
20157
20430
  path: wsPath,
@@ -20163,7 +20436,7 @@ function applyWorkspaceBranchPolicy(opts) {
20163
20436
  if (status === "malformed" || status === "invalid")
20164
20437
  return { ok: false, error: workspacesError ?? `invalid workspaces.json: ${wsPath}` };
20165
20438
  const detection = detectBranchPolicy(workspace_root);
20166
- const name = String(env.WORKFLOW_BP_NAME ?? path25.basename(workspace_root));
20439
+ const name = String(env.WORKFLOW_BP_NAME ?? path26.basename(workspace_root));
20167
20440
  const integration = env.WORKFLOW_BP_INTEGRATION ?? detection.integration;
20168
20441
  const policy = {
20169
20442
  preset: detection.preset,
@@ -20212,7 +20485,7 @@ function applyWorkspaceBranchPolicy(opts) {
20212
20485
  ok: false,
20213
20486
  error: `workspaces.json changed while applying the branch policy: ${wsPath}`
20214
20487
  };
20215
- mkdirSync4(path25.dirname(wsPath), { recursive: true });
20488
+ mkdirSync4(path26.dirname(wsPath), { recursive: true });
20216
20489
  const tmp = `${wsPath}.${process.pid}.tmp`;
20217
20490
  try {
20218
20491
  writeFileSync6(tmp, JSON.stringify({ ...document, workspaces: next }, null, 2) + `
@@ -20247,7 +20520,7 @@ function applyWorkspaceBranchPolicy(opts) {
20247
20520
  var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE";
20248
20521
  var readJson = (p) => {
20249
20522
  try {
20250
- const parsed = JSON.parse(fs16.readFileSync(p, "utf8"));
20523
+ const parsed = JSON.parse(fs17.readFileSync(p, "utf8"));
20251
20524
  return isConfigObject(parsed) ? parsed : null;
20252
20525
  } catch {
20253
20526
  return null;
@@ -20277,7 +20550,7 @@ var youtrackJsonContent = (dir) => {
20277
20550
  const meetingIssue = process.env.WORKFLOW_YT_MEETING_ISSUE;
20278
20551
  return {
20279
20552
  baseUrl: process.env.WORKFLOW_YT_BASE_URL ?? "https://youtrack.example.com",
20280
- tokenFile: process.env.WORKFLOW_YT_TOKEN_FILE ?? path26.join(dir, "youtrack.token"),
20553
+ tokenFile: process.env.WORKFLOW_YT_TOKEN_FILE ?? path27.join(dir, "youtrack.token"),
20281
20554
  ...meetingIssue ? {
20282
20555
  meetingIssue,
20283
20556
  meetingIssues: { general: { issue: meetingIssue, label: "General meetings" } }
@@ -20307,19 +20580,19 @@ var vcsJsonContent = () => {
20307
20580
  };
20308
20581
  function initApplyData(action, env = process.env) {
20309
20582
  const dir = String(env.WORKFLOW_TOOLKIT_CONFIG ?? configDir());
20310
- fs16.mkdirSync(dir, { recursive: true });
20583
+ fs17.mkdirSync(dir, { recursive: true });
20311
20584
  switch (action) {
20312
20585
  case "youtrack_json": {
20313
- const out = path26.join(dir, "youtrack.json");
20314
- fs16.writeFileSync(out, JSON.stringify(youtrackJsonContent(dir), null, 2) + `
20586
+ const out = path27.join(dir, "youtrack.json");
20587
+ fs17.writeFileSync(out, JSON.stringify(youtrackJsonContent(dir), null, 2) + `
20315
20588
  `, "utf8");
20316
20589
  return { action, ok: true, path: out };
20317
20590
  }
20318
20591
  case "youtrack_token_placeholder": {
20319
- const p = path26.join(dir, "youtrack.token");
20592
+ const p = path27.join(dir, "youtrack.token");
20320
20593
  const preserved = writeFileExclusive(p, TOKEN_PLACEHOLDER2 + `
20321
20594
  `, 384) === "preserved";
20322
- const abs = path26.resolve(p);
20595
+ const abs = path27.resolve(p);
20323
20596
  return {
20324
20597
  action,
20325
20598
  ok: true,
@@ -20331,14 +20604,14 @@ function initApplyData(action, env = process.env) {
20331
20604
  };
20332
20605
  }
20333
20606
  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) + `
20607
+ const jsonOut = path27.join(dir, "youtrack.json");
20608
+ const tokenOut = path27.join(dir, "youtrack.token");
20609
+ fs17.writeFileSync(jsonOut, JSON.stringify(youtrackJsonContent(dir), null, 2) + `
20337
20610
  `, "utf8");
20338
20611
  const preserved = writeFileExclusive(tokenOut, TOKEN_PLACEHOLDER2 + `
20339
20612
  `, 384) === "preserved";
20340
- const configPath = path26.resolve(jsonOut);
20341
- const tokenPath = path26.resolve(tokenOut);
20613
+ const configPath = path27.resolve(jsonOut);
20614
+ const tokenPath = path27.resolve(tokenOut);
20342
20615
  const prev = process.env.WORKFLOW_YOUTRACK_CONFIG;
20343
20616
  process.env.WORKFLOW_YOUTRACK_CONFIG = configPath;
20344
20617
  let tokenCreate = {};
@@ -20390,10 +20663,10 @@ function initApplyData(action, env = process.env) {
20390
20663
  }
20391
20664
  }
20392
20665
  case "vcs_scaffold": {
20393
- const jsonOut = path26.join(dir, "vcs.json");
20394
- fs16.writeFileSync(jsonOut, JSON.stringify(vcsJsonContent(), null, 2) + `
20666
+ const jsonOut = path27.join(dir, "vcs.json");
20667
+ fs17.writeFileSync(jsonOut, JSON.stringify(vcsJsonContent(), null, 2) + `
20395
20668
  `, "utf8");
20396
- const configPath = path26.resolve(jsonOut);
20669
+ const configPath = path27.resolve(jsonOut);
20397
20670
  const prev = process.env.WORKFLOW_VCS_CONFIG;
20398
20671
  process.env.WORKFLOW_VCS_CONFIG = configPath;
20399
20672
  try {
@@ -20512,7 +20785,7 @@ var executeInitApply = (args, directory, runtime) => {
20512
20785
  protectedNames: args.branch_policy_protected
20513
20786
  }, current);
20514
20787
  writeConfig(next);
20515
- return output(ok({ action: "config", path: path27.join(configDir(), "config.json"), ...next }));
20788
+ return output(ok({ action: "config", path: path28.join(configDir(), "config.json"), ...next }));
20516
20789
  }
20517
20790
  const env = Object.fromEntries(Object.entries({
20518
20791
  WORKFLOW_YT_BASE_URL: args.base_url,
@@ -20529,14 +20802,14 @@ var executeInitApply = (args, directory, runtime) => {
20529
20802
 
20530
20803
  // packages/workit-opencode/src/shared/session.ts
20531
20804
  import { realpathSync as realpathSync7 } from "node:fs";
20532
- import path28 from "node:path";
20805
+ import path29 from "node:path";
20533
20806
  var sameWorkspace = (expected, observed) => {
20534
20807
  if (typeof observed !== "string" || !observed)
20535
20808
  return false;
20536
20809
  try {
20537
20810
  return realpathSync7(expected) === realpathSync7(observed);
20538
20811
  } catch {
20539
- return path28.resolve(expected) === path28.resolve(observed);
20812
+ return path29.resolve(expected) === path29.resolve(observed);
20540
20813
  }
20541
20814
  };
20542
20815
 
@@ -20608,7 +20881,7 @@ var workitFamilyOf = (toolName) => {
20608
20881
 
20609
20882
  // packages/workit-opencode/src/tools/context.ts
20610
20883
  import { realpathSync as realpathSync8 } from "node:fs";
20611
- import path29 from "node:path";
20884
+ import path30 from "node:path";
20612
20885
  var createContextTool = () => ({
20613
20886
  description: "Read Git, pull request, YouTrack, issue, changelog, release, or affected-file context.",
20614
20887
  execute: async (args, context) => {
@@ -20618,7 +20891,7 @@ var createContextTool = () => ({
20618
20891
  let root;
20619
20892
  try {
20620
20893
  const cwd = parsed.data.cwd;
20621
- root = realpathSync8(cwd ? path29.resolve(context.directory, cwd) : context.directory);
20894
+ root = realpathSync8(cwd ? path30.resolve(context.directory, cwd) : context.directory);
20622
20895
  } catch {
20623
20896
  return JSON.stringify(failure2("invalid_input", "context target directory cannot be resolved"), null, 2);
20624
20897
  }
@@ -21180,7 +21453,7 @@ var evaluateShellPermission = (root, event, workdirs = new Map) => {
21180
21453
  };
21181
21454
 
21182
21455
  // packages/workit-opencode/src/v2/shell.ts
21183
- import path30 from "node:path";
21456
+ import path31 from "node:path";
21184
21457
  var commandOf = (input) => {
21185
21458
  if (input === null || typeof input !== "object")
21186
21459
  return null;
@@ -21189,7 +21462,7 @@ var commandOf = (input) => {
21189
21462
  };
21190
21463
  var hookInput = (root, sessionID, id, shell) => ({
21191
21464
  host: "opencode",
21192
- cwd: shell.workdir ? path30.resolve(root, shell.workdir) : root,
21465
+ cwd: shell.workdir ? path31.resolve(root, shell.workdir) : root,
21193
21466
  session: { id: sessionID, agentId: null, agentType: null, parentId: null },
21194
21467
  permissionMode: null,
21195
21468
  transcriptPath: null,
@@ -21219,7 +21492,7 @@ var observeShellResult = (root, event) => {
21219
21492
  return;
21220
21493
  const input = hookInput(root, String(event.sessionID), String(event.id), shell);
21221
21494
  rawGitPost(input, shell.command, callKey(String(event.id), shell.command));
21222
- const nudge = rawGitPre(input, shell.command);
21495
+ const nudge = shellNudge(input, shell.command);
21223
21496
  const result = event.result;
21224
21497
  if (nudge.kind !== "context" || !result)
21225
21498
  return;
@@ -21235,84 +21508,78 @@ ${nudge.text}` };
21235
21508
  } catch {}
21236
21509
  };
21237
21510
 
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"
21511
+ // packages/workit-opencode/src/v2/skills.ts
21512
+ var hookInput2 = (root, sessionID, subagent) => ({
21513
+ host: "opencode",
21514
+ cwd: root,
21515
+ session: {
21516
+ id: sessionID,
21517
+ agentId: subagent ? sessionID : null,
21518
+ agentType: null,
21519
+ parentId: null
21520
+ },
21521
+ permissionMode: null,
21522
+ transcriptPath: null,
21523
+ event: { kind: "context.turn" }
21524
+ });
21525
+ var observeSkillLoad = (root, event) => {
21526
+ const input = event.input;
21527
+ if (typeof input?.name === "string")
21528
+ recordSkillLoad(hookInput2(root, String(event.sessionID), false), input.name, "tool");
21529
+ };
21530
+ var newUserPrompt = (messages) => {
21531
+ const last = messages.at(-1);
21532
+ if (last?.role !== "user")
21533
+ return null;
21534
+ if (typeof last.content === "string")
21535
+ return last.content;
21536
+ if (!Array.isArray(last.content))
21537
+ return null;
21538
+ const text = last.content.filter((part) => part.type === "text" && typeof part.text === "string").map((part) => part.text).join(`
21539
+ `);
21540
+ return text || null;
21272
21541
  };
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
- }
21542
+ var injectSkillNudge = (root, session, messages, system) => {
21543
+ try {
21544
+ const nudge = promptNudge(hookInput2(root, session.id, session.parentID !== undefined), newUserPrompt(messages));
21545
+ if (nudge)
21546
+ system.push({ type: "text", text: nudge });
21547
+ } catch {}
21282
21548
  };
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) {}
21549
+
21550
+ // packages/workit-opencode/src/v2/registry.ts
21551
+ import { existsSync as existsSync11, readFileSync as readFileSync9 } from "node:fs";
21552
+ import path33 from "node:path";
21286
21553
 
21287
21554
  // packages/workit-opencode/src/shared/assets.ts
21288
21555
  import { existsSync as existsSync10, realpathSync as realpathSync9 } from "node:fs";
21289
- import path31 from "node:path";
21556
+ import path32 from "node:path";
21290
21557
  import { fileURLToPath as fileURLToPath2 } from "node:url";
21291
21558
  var moduleDir = () => {
21292
21559
  try {
21293
- return realpathSync9(path31.dirname(fileURLToPath2(import.meta.url)));
21560
+ return realpathSync9(path32.dirname(fileURLToPath2(import.meta.url)));
21294
21561
  } catch {
21295
- return path31.dirname(fileURLToPath2(import.meta.url));
21562
+ return path32.dirname(fileURLToPath2(import.meta.url));
21296
21563
  }
21297
21564
  };
21298
21565
  var packageRoot2 = () => {
21299
21566
  const dir = moduleDir();
21300
21567
  for (const candidate of [
21301
- path31.resolve(dir, ".."),
21302
- path31.resolve(dir, "..", ".."),
21303
- path31.resolve(dir, "..", "..", "..")
21568
+ path32.resolve(dir, ".."),
21569
+ path32.resolve(dir, "..", ".."),
21570
+ path32.resolve(dir, "..", "..", "..")
21304
21571
  ]) {
21305
- if (existsSync10(path31.join(candidate, "package.json")))
21572
+ if (existsSync10(path32.join(candidate, "package.json")))
21306
21573
  return candidate;
21307
21574
  }
21308
- return path31.resolve(dir, "..");
21575
+ return path32.resolve(dir, "..");
21309
21576
  };
21310
21577
  var skillsRoot = () => {
21311
21578
  const root = packageRoot2();
21312
- const canonical = path31.resolve(root, "..", "workit-core", "skills");
21313
- if (existsSync10(path31.join(root, "src")) && existsSync10(canonical))
21579
+ const canonical = path32.resolve(root, "..", "workit-core", "skills");
21580
+ if (existsSync10(path32.join(root, "src")) && existsSync10(canonical))
21314
21581
  return canonical;
21315
- return path31.join(root, "assets", "skills");
21582
+ return path32.join(root, "assets", "skills");
21316
21583
  };
21317
21584
 
21318
21585
  // packages/workit-opencode/src/v2/registry.ts
@@ -21339,11 +21606,11 @@ var registerSkills = async (ctx) => {
21339
21606
  if (!existsSync11(skillsDir))
21340
21607
  return new Set;
21341
21608
  const listed = await ctx.skill.list().catch(() => ({ data: [] }));
21342
- const own = (skill) => typeof skill.path === "string" && path32.resolve(skill.path).startsWith(skillsDir + path32.sep);
21609
+ const own = (skill) => typeof skill.path === "string" && path33.resolve(skill.path).startsWith(skillsDir + path33.sep);
21343
21610
  const userSkills = new Set((listed?.data ?? []).filter((skill) => !own(skill)).map((skill) => skill.id));
21344
21611
  const packaged = new Map;
21345
21612
  for (const id of WORKIT_METHOD_SKILLS) {
21346
- const file = path32.join(skillsDir, id, "SKILL.md");
21613
+ const file = path33.join(skillsDir, id, "SKILL.md");
21347
21614
  if (!userSkills.has(id) && existsSync11(file))
21348
21615
  packaged.set(id, file);
21349
21616
  }
@@ -21391,22 +21658,22 @@ var registerCommands = async (ctx, registeredSkills) => {
21391
21658
 
21392
21659
  // packages/workit-opencode/src/stale-sources.ts
21393
21660
  import { existsSync as existsSync12, readdirSync as readdirSync5 } from "node:fs";
21394
- import path33 from "node:path";
21661
+ import path34 from "node:path";
21395
21662
  import { fileURLToPath as fileURLToPath3 } from "node:url";
21396
21663
  var typescriptFiles = (directory) => {
21397
21664
  if (!existsSync12(directory))
21398
21665
  return [];
21399
21666
  return readdirSync5(directory, { withFileTypes: true }).flatMap((entry) => {
21400
- const file = path33.join(directory, entry.name);
21667
+ const file = path34.join(directory, entry.name);
21401
21668
  return entry.isDirectory() ? typescriptFiles(file) : entry.isFile() && file.endsWith(".ts") ? [file] : [];
21402
21669
  });
21403
21670
  };
21404
21671
  var pluginSourceFiles = [
21405
21672
  ...new Set([
21406
21673
  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"))
21674
+ ...typescriptFiles(path34.join(packageRoot2(), "src")),
21675
+ path34.join(packageRoot2(), "..", "workit-core", "src", "core.ts"),
21676
+ ...typescriptFiles(path34.join(packageRoot2(), "..", "workit-core", "src", "core"))
21410
21677
  ])
21411
21678
  ].filter((file) => existsSync12(file)).toSorted();
21412
21679
 
@@ -21511,6 +21778,8 @@ var setup = async (ctx) => {
21511
21778
  await ctx.tool.hook("execute.before", async (event) => {
21512
21779
  if (event.tool === "shell")
21513
21780
  return observeShellBefore(root, event, shellWorkdirs);
21781
+ if (event.tool === "skill")
21782
+ return observeSkillLoad(root, event);
21514
21783
  if (event.tool !== "subagent")
21515
21784
  return;
21516
21785
  await lifecycle.executeBefore({
@@ -21540,6 +21809,8 @@ var setup = async (ctx) => {
21540
21809
  const session = await sessionFacts(ctx, String(event.sessionID));
21541
21810
  injectAgentContext(root, session, lifecycle.directChildren, event.system);
21542
21811
  injectHistoryOffer(root, session, historyOfferSessions, event.system);
21812
+ if (session)
21813
+ injectSkillNudge(root, session, event.messages, event.system);
21543
21814
  if (!staleSourcesWarned) {
21544
21815
  const changed = changedSourcesSinceLoad(sourceMarker);
21545
21816
  if (changed.length > 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brainervirus/workit-opencode",
3
- "version": "8.4.1",
3
+ "version": "8.5.2",
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.1"
42
+ "@brainervirus/workit-core": "^8.5.2"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@opencode/plugin": "2.0.18"