@brainervirus/workit-core 0.11.0 → 1.0.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.
Files changed (150) hide show
  1. package/README.md +5 -3
  2. package/package.json +8 -3
  3. package/scripts/analyze-release-scope.ts +4 -1
  4. package/scripts/doctor-check.ts +3 -1
  5. package/scripts/install-codex-plugin.sh +28 -0
  6. package/scripts/install-cursor-plugin.sh +1 -0
  7. package/scripts/install-opencode-plugin.sh +1 -0
  8. package/scripts/install-pi-package.sh +56 -0
  9. package/scripts/rewrite-workspace-deps.ts +22 -5
  10. package/scripts/sync-release-manifests.ts +8 -5
  11. package/scripts/sync-runtime.sh +7 -3
  12. package/scripts/validate-cursor-marketplace.ts +16 -62
  13. package/skills/workit-babysit/SKILL.md +33 -0
  14. package/skills/workit-behavioral-tdd/SKILL.md +53 -0
  15. package/skills/workit-blast-radius/SKILL.md +31 -0
  16. package/skills/workit-challenge/SKILL.md +62 -0
  17. package/skills/workit-debug/SKILL.md +61 -0
  18. package/skills/workit-deslop/SKILL.md +36 -0
  19. package/skills/workit-diagram/SKILL.md +32 -0
  20. package/skills/workit-green-run/SKILL.md +29 -0
  21. package/skills/workit-handoff/SKILL.md +43 -0
  22. package/skills/workit-implement/SKILL.md +46 -0
  23. package/skills/workit-mockup/SKILL.md +28 -0
  24. package/skills/workit-plan/SKILL.md +66 -0
  25. package/skills/workit-review/SKILL.md +60 -0
  26. package/skills/workit-steer/SKILL.md +32 -0
  27. package/src/core/authority.ts +918 -0
  28. package/src/core/boundary.ts +29 -0
  29. package/src/core/branch.ts +26 -255
  30. package/src/core/changelog.ts +95 -18
  31. package/src/core/commit-flavors.ts +65 -0
  32. package/src/core/config-conversion.ts +183 -0
  33. package/src/core/config.ts +42 -0
  34. package/src/core/cutover.ts +721 -0
  35. package/src/core/detect-hosts.ts +107 -0
  36. package/src/core/doctor.ts +428 -18
  37. package/src/core/external-action-effects.ts +1171 -0
  38. package/src/core/external-action.ts +541 -0
  39. package/src/core/init.ts +83 -32
  40. package/src/core/methods.ts +125 -0
  41. package/src/core/policy-resolver.ts +523 -0
  42. package/src/core/pr-create.ts +7 -1
  43. package/src/core/registration.ts +69 -4
  44. package/src/core/repo-context.ts +59 -1
  45. package/src/core/setup.ts +28 -0
  46. package/src/core/skill-manifests.ts +40 -42
  47. package/src/core/support-matrix.ts +8 -3
  48. package/src/core/sync-runtime.ts +15 -3
  49. package/src/core/task-context.ts +179 -0
  50. package/src/core/task-contract.ts +1136 -0
  51. package/src/core/task-engine.ts +2562 -0
  52. package/src/core/task-evaluation.ts +679 -0
  53. package/src/core/task-store.ts +1071 -0
  54. package/src/core/tracker-issues.ts +237 -0
  55. package/src/core/uninstall.ts +67 -3
  56. package/src/core/vcs-config.ts +53 -22
  57. package/src/core/workers.ts +166 -0
  58. package/src/core/workspaces.ts +3 -1
  59. package/src/core/youtrack-tools.ts +2 -0
  60. package/src/core/youtrack.ts +145 -10
  61. package/src/core.ts +126 -0
  62. package/templates/execution-contract.md +17 -50
  63. package/templates/plan-template.md +3 -3
  64. package/templates/spec-template.md +15 -3
  65. package/templates/workit-contract.md +12 -0
  66. package/commands/wk-changelog.md +0 -2
  67. package/commands/wk-commit.md +0 -2
  68. package/commands/wk-docs-refresh.md +0 -2
  69. package/commands/wk-handoff.md +0 -2
  70. package/commands/wk-implement.md +0 -2
  71. package/commands/wk-init.md +0 -2
  72. package/commands/wk-issue-update.md +0 -2
  73. package/commands/wk-meetings.md +0 -2
  74. package/commands/wk-pr.md +0 -2
  75. package/commands/wk-release-notes.md +0 -2
  76. package/commands/wk-status.md +0 -2
  77. package/commands/wk-verify.md +0 -2
  78. package/scripts/update-superpowers.sh +0 -82
  79. package/scripts/vendor-assets.ts +0 -37
  80. package/skills/wk-changelog/SKILL.md +0 -15
  81. package/skills/wk-commit/SKILL.md +0 -16
  82. package/skills/wk-docs-refresh/SKILL.md +0 -15
  83. package/skills/wk-handoff/SKILL.md +0 -20
  84. package/skills/wk-implement/SKILL.md +0 -47
  85. package/skills/wk-init/SKILL.md +0 -31
  86. package/skills/wk-issue-update/SKILL.md +0 -27
  87. package/skills/wk-issue-update/references/youtrack-update-style.md +0 -81
  88. package/skills/wk-meetings/SKILL.md +0 -17
  89. package/skills/wk-pr/SKILL.md +0 -27
  90. package/skills/wk-release-notes/SKILL.md +0 -15
  91. package/skills/wk-status/SKILL.md +0 -16
  92. package/skills/wk-verify/SKILL.md +0 -16
  93. package/src/core/detector.ts +0 -239
  94. package/src/core/flow-state.ts +0 -3308
  95. package/src/core/handoff-context.ts +0 -136
  96. package/src/core/handoff-tools.ts +0 -133
  97. package/src/core/menu.ts +0 -70
  98. package/src/core/plan-tasks.ts +0 -43
  99. package/src/core/reminder.ts +0 -124
  100. package/src/core/sdd.ts +0 -353
  101. package/src/state.ts +0 -22
  102. package/templates/superpowers-doc-contract.md +0 -75
  103. package/vendor/superpowers/skills/brainstorming/SKILL.md +0 -159
  104. package/vendor/superpowers/skills/brainstorming/scripts/frame-template.html +0 -213
  105. package/vendor/superpowers/skills/brainstorming/scripts/helper.js +0 -167
  106. package/vendor/superpowers/skills/brainstorming/scripts/server.cjs +0 -723
  107. package/vendor/superpowers/skills/brainstorming/scripts/start-server.sh +0 -209
  108. package/vendor/superpowers/skills/brainstorming/scripts/stop-server.sh +0 -120
  109. package/vendor/superpowers/skills/brainstorming/spec-document-reviewer-prompt.md +0 -49
  110. package/vendor/superpowers/skills/brainstorming/visual-companion.md +0 -291
  111. package/vendor/superpowers/skills/dispatching-parallel-agents/SKILL.md +0 -185
  112. package/vendor/superpowers/skills/executing-plans/SKILL.md +0 -70
  113. package/vendor/superpowers/skills/finishing-a-development-branch/SKILL.md +0 -241
  114. package/vendor/superpowers/skills/receiving-code-review/SKILL.md +0 -213
  115. package/vendor/superpowers/skills/requesting-code-review/SKILL.md +0 -103
  116. package/vendor/superpowers/skills/requesting-code-review/code-reviewer.md +0 -172
  117. package/vendor/superpowers/skills/subagent-driven-development/SKILL.md +0 -428
  118. package/vendor/superpowers/skills/subagent-driven-development/implementer-prompt.md +0 -139
  119. package/vendor/superpowers/skills/subagent-driven-development/scripts/review-package +0 -44
  120. package/vendor/superpowers/skills/subagent-driven-development/scripts/sdd-workspace +0 -22
  121. package/vendor/superpowers/skills/subagent-driven-development/scripts/task-brief +0 -40
  122. package/vendor/superpowers/skills/subagent-driven-development/task-reviewer-prompt.md +0 -188
  123. package/vendor/superpowers/skills/systematic-debugging/CREATION-LOG.md +0 -119
  124. package/vendor/superpowers/skills/systematic-debugging/SKILL.md +0 -296
  125. package/vendor/superpowers/skills/systematic-debugging/condition-based-waiting-example.ts +0 -158
  126. package/vendor/superpowers/skills/systematic-debugging/condition-based-waiting.md +0 -115
  127. package/vendor/superpowers/skills/systematic-debugging/defense-in-depth.md +0 -122
  128. package/vendor/superpowers/skills/systematic-debugging/find-polluter.sh +0 -63
  129. package/vendor/superpowers/skills/systematic-debugging/root-cause-tracing.md +0 -169
  130. package/vendor/superpowers/skills/systematic-debugging/test-academic.md +0 -14
  131. package/vendor/superpowers/skills/systematic-debugging/test-pressure-1.md +0 -58
  132. package/vendor/superpowers/skills/systematic-debugging/test-pressure-2.md +0 -68
  133. package/vendor/superpowers/skills/systematic-debugging/test-pressure-3.md +0 -69
  134. package/vendor/superpowers/skills/test-driven-development/SKILL.md +0 -371
  135. package/vendor/superpowers/skills/test-driven-development/testing-anti-patterns.md +0 -299
  136. package/vendor/superpowers/skills/using-git-worktrees/SKILL.md +0 -202
  137. package/vendor/superpowers/skills/using-superpowers/SKILL.md +0 -62
  138. package/vendor/superpowers/skills/using-superpowers/references/antigravity-tools.md +0 -23
  139. package/vendor/superpowers/skills/using-superpowers/references/codex-tools.md +0 -39
  140. package/vendor/superpowers/skills/using-superpowers/references/pi-tools.md +0 -16
  141. package/vendor/superpowers/skills/verification-before-completion/SKILL.md +0 -139
  142. package/vendor/superpowers/skills/writing-plans/SKILL.md +0 -174
  143. package/vendor/superpowers/skills/writing-plans/plan-document-reviewer-prompt.md +0 -49
  144. package/vendor/superpowers/skills/writing-skills/SKILL.md +0 -689
  145. package/vendor/superpowers/skills/writing-skills/anthropic-best-practices.md +0 -1150
  146. package/vendor/superpowers/skills/writing-skills/examples/CLAUDE_MD_TESTING.md +0 -189
  147. package/vendor/superpowers/skills/writing-skills/graphviz-conventions.dot +0 -172
  148. package/vendor/superpowers/skills/writing-skills/persuasion-principles.md +0 -187
  149. package/vendor/superpowers/skills/writing-skills/render-graphs.js +0 -168
  150. package/vendor/superpowers/skills/writing-skills/testing-skills-with-subagents.md +0 -384
@@ -2,6 +2,7 @@ import { spawnSync } from "node:child_process";
2
2
  import { existsSync, readFileSync, readdirSync } from "node:fs";
3
3
  import path from "node:path";
4
4
  import { vcsConfig, mergedPrStyle } from "./vcs-config";
5
+ import { gitRevisionParts, resolveGitRevision } from "../core";
5
6
 
6
7
  // Ports of scripts/_shared/common.sh + the four maintained context generators
7
8
  // (pr-ready-context.sh, changelog-context.sh, docs-refresh-context.sh,
@@ -10,6 +11,37 @@ import { vcsConfig, mergedPrStyle } from "./vcs-config";
10
11
 
11
12
  export type ContextResult = { stdout: string; stderr: string; exitCode: number; cwd: string };
12
13
 
14
+ // Range/revision values are passed as positional git arguments. Reject option
15
+ // syntax and control characters before any context generator invokes git; the
16
+ // trailing `--` used for file paths cannot protect an earlier revision arg.
17
+ export const isSafeContextRange = (value: string): boolean =>
18
+ value.length <= 4096 &&
19
+ (() => {
20
+ try {
21
+ gitRevisionParts(value);
22
+ return true;
23
+ } catch {
24
+ return false;
25
+ }
26
+ })();
27
+
28
+ const isResolvableContextRange = (cwd: string, value: string): boolean => {
29
+ if (!isSafeContextRange(value)) return false;
30
+ try {
31
+ resolveGitRevision(cwd, value);
32
+ return true;
33
+ } catch {
34
+ return false;
35
+ }
36
+ };
37
+
38
+ const invalidRange = (cwd: string): ContextResult => ({
39
+ stdout: "",
40
+ stderr: "ERROR: invalid revision range\n",
41
+ exitCode: 2,
42
+ cwd,
43
+ });
44
+
13
45
  const runGit = (
14
46
  cwd: string,
15
47
  args: string[],
@@ -238,6 +270,7 @@ const readTrimmed = (file: string, maxLines: number): string => {
238
270
  /** Port of pr-ready-context.sh — PR-ready repository context. */
239
271
  export function prReadyContext(root: string, range?: string): ContextResult {
240
272
  const cwd = path.resolve(repoRoot(root));
273
+ if (range !== undefined && !isResolvableContextRange(cwd, range)) return invalidRange(cwd);
241
274
  let stdout = "";
242
275
  const autoRange = range === undefined;
243
276
 
@@ -306,7 +339,7 @@ export function prReadyContext(root: string, range?: string): ContextResult {
306
339
  const vc = vcsConfig("resolve", cwd);
307
340
  if (vc.ok) {
308
341
  stdout += `workspace: ${String(vc.workspace_name ?? "none")}\n`;
309
- stdout += `provider: ${String(vc.provider ?? "gitlab")}\n`;
342
+ stdout += `provider: ${String(vc.provider ?? "unconfigured")}\n`;
310
343
  } else {
311
344
  // RL-01: never report silent defaults for malformed vcs.json — surface the
312
345
  // exact-path diagnostic instead.
@@ -336,7 +369,9 @@ const CHANGELOG_RULES = `- Use an [Unreleased] section.
336
369
  /** Port of changelog-context.sh — changelog update context. */
337
370
  export function changelogContext(root: string, range?: string): ContextResult {
338
371
  const cwd = path.resolve(repoRoot(root));
372
+ if (range !== undefined && !isResolvableContextRange(cwd, range)) return invalidRange(cwd);
339
373
  const resolvedRange = rangeArgOrDefault(range, cwd);
374
+ if (!isResolvableContextRange(cwd, resolvedRange)) return invalidRange(cwd);
340
375
  let stdout = "";
341
376
 
342
377
  stdout += printSection("Repository");
@@ -370,7 +405,9 @@ export function changelogContext(root: string, range?: string): ContextResult {
370
405
  /** Port of docs-refresh-context.sh — documentation refresh context. */
371
406
  export function docsRefreshContext(root: string, range?: string): ContextResult {
372
407
  const cwd = path.resolve(repoRoot(root));
408
+ if (range !== undefined && !isResolvableContextRange(cwd, range)) return invalidRange(cwd);
373
409
  const resolvedRange = rangeArgOrDefault(range, cwd);
410
+ if (!isResolvableContextRange(cwd, resolvedRange)) return invalidRange(cwd);
374
411
  let stdout = "";
375
412
 
376
413
  stdout += printSection("Repository");
@@ -416,7 +453,9 @@ export function releaseNotesContext(root: string, rangeOrTag: string): ContextRe
416
453
  if (!rangeOrTag) {
417
454
  return { stdout: "", stderr: "ERROR: release tag or range required\n", exitCode: 1, cwd };
418
455
  }
456
+ if (!isResolvableContextRange(cwd, rangeOrTag)) return invalidRange(cwd);
419
457
  const resolvedRange = rangeArgOrDefault(rangeOrTag, cwd);
458
+ if (!isResolvableContextRange(cwd, resolvedRange)) return invalidRange(cwd);
420
459
  let stdout = "";
421
460
 
422
461
  stdout += printSection("Repository");
@@ -443,5 +482,24 @@ export function releaseNotesContext(root: string, rangeOrTag: string): ContextRe
443
482
  if (existsSync(path.join(cwd, rel))) stdout += rel + "\n";
444
483
  }
445
484
 
485
+ // A deterministic draft is useful to every read-only host surface. It is
486
+ // derived only from the already-resolved range; publication and file edits
487
+ // remain separate, writer-guarded actions.
488
+ stdout += printSection("Release Notes Draft");
489
+ stdout += `## Release notes (${resolvedRange})\n\n`;
490
+ const commits = commitLogForRange(cwd, resolvedRange)
491
+ .split("\n")
492
+ .map((line) => line.trim())
493
+ .filter(Boolean);
494
+ stdout += commits.length
495
+ ? `${commits.map((line) => `- ${line}`).join("\n")}\n`
496
+ : "- No commits in range.\n";
497
+ stdout += "\n### Changed files\n\n";
498
+ const files = changedFilesForRange(cwd, resolvedRange)
499
+ .split("\n")
500
+ .map((line) => line.trim())
501
+ .filter(Boolean);
502
+ stdout += files.length ? `${files.map((line) => `- ${line}`).join("\n")}\n` : "- None.\n";
503
+
446
504
  return { stdout, stderr: "", exitCode: 0, cwd };
447
505
  }
package/src/core/setup.ts CHANGED
@@ -1231,3 +1231,31 @@ export function applyWorkspaceBranchPolicy(opts: {
1231
1231
  config_path: wsPath,
1232
1232
  };
1233
1233
  }
1234
+
1235
+ export {
1236
+ applyCutover,
1237
+ applyRollback,
1238
+ classifyHostGeneration,
1239
+ readCutoverReceipt,
1240
+ detectCursorLatest,
1241
+ detectSourceLinkedOpenCode,
1242
+ previewCutover,
1243
+ previewRollback,
1244
+ readGenerationState,
1245
+ type CutoverDecision,
1246
+ type CutoverHost,
1247
+ type CutoverPaths,
1248
+ type CutoverPlan,
1249
+ type CutoverReceipt,
1250
+ type GenerationState,
1251
+ type RollbackPreview,
1252
+ } from "./cutover";
1253
+ export {
1254
+ applyConversionConfig,
1255
+ previewConversion,
1256
+ redactConversionPreview,
1257
+ type ConversionApplyResult,
1258
+ type ConversionInput,
1259
+ type ConversionMapping,
1260
+ type ConversionPreview,
1261
+ } from "./config-conversion";
@@ -1,37 +1,40 @@
1
1
  import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
2
2
  import path from "node:path";
3
3
 
4
- export const CANONICAL_SKILLS = {
5
- superpowers: [
6
- "brainstorming",
7
- "dispatching-parallel-agents",
8
- "executing-plans",
9
- "finishing-a-development-branch",
10
- "receiving-code-review",
11
- "requesting-code-review",
12
- "subagent-driven-development",
13
- "systematic-debugging",
14
- "test-driven-development",
15
- "using-git-worktrees",
16
- "using-superpowers",
17
- "verification-before-completion",
18
- "writing-plans",
19
- "writing-skills",
20
- ],
21
- workit: [
22
- "wk-changelog",
23
- "wk-commit",
24
- "wk-docs-refresh",
25
- "wk-handoff",
26
- "wk-implement",
27
- "wk-init",
28
- "wk-issue-update",
29
- "wk-meetings",
30
- "wk-pr",
31
- "wk-release-notes",
32
- "wk-status",
33
- "wk-verify",
34
- ],
4
+ export const WORKIT_METHOD_SKILLS = [
5
+ "workit-challenge",
6
+ "workit-behavioral-tdd",
7
+ "workit-review",
8
+ "workit-plan",
9
+ "workit-implement",
10
+ "workit-debug",
11
+ "workit-handoff",
12
+ "workit-babysit",
13
+ "workit-blast-radius",
14
+ "workit-deslop",
15
+ "workit-diagram",
16
+ "workit-mockup",
17
+ "workit-green-run",
18
+ "workit-steer",
19
+ ] as const;
20
+
21
+ /** wk- slash aliases (one per skill): alias → method skill. An alias routes
22
+ * through policy to model skills; an alias never calls another alias. */
23
+ export const WORKIT_SKILL_ALIASES = {
24
+ "wk-challenge": "workit-challenge",
25
+ "wk-babysit": "workit-babysit",
26
+ "wk-implement": "workit-implement",
27
+ "wk-plan": "workit-plan",
28
+ "wk-debug": "workit-debug",
29
+ "wk-review": "workit-review",
30
+ "wk-handoff": "workit-handoff",
31
+ "wk-tdd": "workit-behavioral-tdd",
32
+ "wk-blast-radius": "workit-blast-radius",
33
+ "wk-deslop": "workit-deslop",
34
+ "wk-diagram": "workit-diagram",
35
+ "wk-mockup": "workit-mockup",
36
+ "wk-green-run": "workit-green-run",
37
+ "wk-steer": "workit-steer",
35
38
  } as const;
36
39
 
37
40
  export const skillManifestNames = (root: string): string[] =>
@@ -54,22 +57,17 @@ export const validateSkillManifests = (
54
57
  : `${label} mismatch at ${root} (missing: ${missing.join(", ") || "none"}; extra: ${extra.join(", ") || "none"})`;
55
58
  };
56
59
 
57
- export const validateCursorSkills = (pluginDir: string): string | null => {
60
+ export const validateCursorSkills = (
61
+ pluginDir: string,
62
+ expected: readonly string[] = WORKIT_METHOD_SKILLS,
63
+ ): string | null => {
58
64
  const workit = validateSkillManifests(
59
65
  path.join(pluginDir, "skills"),
60
- CANONICAL_SKILLS.workit,
66
+ expected,
61
67
  "Cursor Workit skills",
62
68
  );
63
69
  if (workit) return workit;
64
- const vendor = path.join(pluginDir, "vendor/superpowers/skills");
65
- const superpowers = validateSkillManifests(
66
- vendor,
67
- CANONICAL_SKILLS.superpowers,
68
- "Cursor Superpowers skills",
69
- );
70
- if (superpowers) return superpowers;
71
-
72
- const pending = [vendor];
70
+ const pending = [path.join(pluginDir, "skills")];
73
71
  while (pending.length > 0) {
74
72
  const dir = pending.pop()!;
75
73
  for (const entry of readdirSync(dir, { withFileTypes: true })) {
@@ -5,8 +5,13 @@
5
5
  // Deno is intentionally not part of the matrix (PT-12): nothing advertises a
6
6
  // host that has no executable artifact/test evidence.
7
7
  export const SUPPORT_MATRIX = {
8
- bun: "1.3.14",
9
- node: { minimum: "22", current: "22" },
10
- opencode: { minimum: "1.15.0", current: "1.17.7" },
8
+ bun: "1.4.1",
9
+ node: { minimum: "24", current: "24.20.0" },
10
+ opencode: { minimum: "1.15.0", current: "1.18.30" },
11
+ // Codex is a qualification host, not a runtime dependency: the CLI version
12
+ // below is the one live qualification evidence covers. The doctor warns when
13
+ // an installed CLI drifts ahead so a fresh install never silently outruns
14
+ // the qualification pin.
15
+ codex: { cli: "0.153.4", desktopPackage: "26.901.20858", bundledCodexCli: "0.153.0-alpha.5" },
11
16
  os: ["ubuntu-latest", "macos-latest", "windows-latest"],
12
17
  } as const;
@@ -165,10 +165,15 @@ export async function syncRuntime(options: SyncRuntimeOptions = {}): Promise<Syn
165
165
  }
166
166
 
167
167
  const cursorSrc = path.join(src, "packages/workit-cursor");
168
- const cursorEntries = ["mcp-server.js", "cursor-session-start.js"];
168
+ const cursorEntries = ["mcp-server.js", "cursor-session-start.js", "workit-hook.js"];
169
169
  const bun = resolveBun(env, home);
170
170
  if (!bun.ok) return bun;
171
- const dependencies = ["@brainervirus/workit-core", "@modelcontextprotocol/sdk", "zod"];
171
+ const dependencies = [
172
+ "@brainervirus/workit-core",
173
+ "@brainervirus/workit-mcp",
174
+ "@modelcontextprotocol/sdk",
175
+ "zod",
176
+ ];
172
177
  if (
173
178
  dependencies.some((dependency) => !existsSync(path.join(src, "node_modules", dependency)))
174
179
  ) {
@@ -183,6 +188,13 @@ export async function syncRuntime(options: SyncRuntimeOptions = {}): Promise<Syn
183
188
  };
184
189
  }
185
190
  }
191
+ const mcpBuild = run(bun.path, [path.join(src, "packages/workit-mcp/scripts/build.ts")], {
192
+ cwd: src,
193
+ env: { ...env, PATH: `${path.dirname(bun.path)}${path.delimiter}${env.PATH ?? ""}` },
194
+ });
195
+ if (mcpBuild.exitCode !== 0) {
196
+ return { ok: false, error: `FATAL: shared MCP build failed in ${src}: ${mcpBuild.stderr}` };
197
+ }
186
198
  const build = run(bun.path, [path.join(cursorSrc, "scripts/build.ts")], {
187
199
  cwd: src,
188
200
  env: { ...env, PATH: `${path.dirname(bun.path)}${path.delimiter}${env.PATH ?? ""}` },
@@ -301,7 +313,7 @@ export async function syncRuntime(options: SyncRuntimeOptions = {}): Promise<Syn
301
313
  dependencies?: Record<string, string>;
302
314
  };
303
315
  data.dependencies = data.dependencies ?? {};
304
- data.dependencies["@opencode-ai/plugin"] ??= "1.17.7";
316
+ data.dependencies["@opencode-ai/plugin"] ??= "1.18.30";
305
317
  writeFileSync(pkg, JSON.stringify(data, null, 2) + "\n");
306
318
  } catch {
307
319
  /* best-effort */
@@ -0,0 +1,179 @@
1
+ import {
2
+ POLICY_VERSION,
3
+ failure,
4
+ sha256,
5
+ success,
6
+ type Candidate,
7
+ type Decision,
8
+ type Provenance,
9
+ type Progress,
10
+ type Ref,
11
+ type Result,
12
+ type TaskView,
13
+ type Utc,
14
+ } from "./task-contract";
15
+ import { captureCandidate, evaluateEvidence } from "./task-evaluation";
16
+ import type { NativeWorkerObservation } from "./workers";
17
+
18
+ export type ResumeObservation = {
19
+ observation: NativeWorkerObservation;
20
+ authority: Provenance | null;
21
+ };
22
+ export type ResumeObservationInput = ResumeObservation | NativeWorkerObservation;
23
+
24
+ export type ResumeReconciliation = {
25
+ candidate: Candidate;
26
+ staleEvidenceIds: string[];
27
+ workerUpdates: NativeWorkerObservation[];
28
+ blockers: Progress["blockers"];
29
+ reassessmentRequired: boolean;
30
+ };
31
+
32
+ export function reconcileResume(
33
+ view: TaskView,
34
+ observations: ResumeObservationInput[] = [],
35
+ ): Result<ResumeReconciliation> {
36
+ if (observations.length > 0)
37
+ return failure("permission_denied", "worker observations require native host verification");
38
+ const candidate = captureCandidate(view.workspace.root, view.task.intent.data.scope, []);
39
+ if (!candidate.ok) return candidate as Result<never>;
40
+ const staleEvidenceIds = evaluateEvidence(view.task, candidate.data)
41
+ .filter((entry) => entry.status === "stale")
42
+ .map((entry) => entry.evidenceId);
43
+ const blockers = [...view.task.progress.blockers];
44
+ if (
45
+ view.task.workers.some((entry) =>
46
+ ["running", "cancelling", "unknown"].includes(entry.data.state),
47
+ )
48
+ )
49
+ blockers.push({
50
+ reason: "worker state requires reconciliation",
51
+ dependentAction: "resume",
52
+ refs: [],
53
+ });
54
+ return success(null, null, {
55
+ candidate: candidate.data,
56
+ staleEvidenceIds,
57
+ workerUpdates: [],
58
+ blockers,
59
+ reassessmentRequired: view.task.policy?.policyVersion !== POLICY_VERSION,
60
+ });
61
+ }
62
+
63
+ type CompactReference = { kind: Ref["kind"] } & Partial<
64
+ Pick<Extract<Ref, { kind: "file" }>, "path">
65
+ > &
66
+ Partial<Pick<Extract<Ref, { kind: "record" }>, "collection" | "id">>;
67
+ type CompactDecision = {
68
+ id: string;
69
+ purpose: Decision["purpose"];
70
+ status: Decision["response"];
71
+ digest: string;
72
+ references: CompactReference[];
73
+ };
74
+ export type CompactTaskContext = {
75
+ objective: string;
76
+ status: TaskView["task"]["status"];
77
+ decisions: CompactDecision[];
78
+ gaps: string[];
79
+ nextAction: string | null;
80
+ };
81
+
82
+ const COMPACT_MAX_BYTES = 4096;
83
+ const COMPACT_MAX_ITEMS = 8;
84
+ const COMPACT_TEXT_BYTES = 128;
85
+ const sensitiveUrl = /\bhttps?:\/\/[^/\s:@]+:[^@\s]+@/gi;
86
+ const sensitiveValue =
87
+ /(?:token|secret|password|passwd|api[-_ ]?key|private[-_ ]?key|authorization)\s*[:=]\s*(?:bearer\s+)?\S+|\bbearer\s+\S+/gi;
88
+ const privateKey = /-----BEGIN [^-]*PRIVATE KEY-----[\s\S]*?-----END [^-]*PRIVATE KEY-----/gi;
89
+ const compactText = (value: string | null, limit: number): string | null =>
90
+ value === null
91
+ ? null
92
+ : value
93
+ .replace(privateKey, "<redacted-private-key>")
94
+ .replace(sensitiveUrl, "<redacted-url>")
95
+ .replace(sensitiveValue, (match) => {
96
+ const separator = match.search(/[:=]/u);
97
+ if (separator >= 0) return `${match.slice(0, separator)}=<redacted>`;
98
+ const space = match.search(/\s/u);
99
+ return `${match.slice(0, space)} <redacted>`;
100
+ })
101
+ .slice(0, limit);
102
+ const compactBytes = (value: string): number => new TextEncoder().encode(value).byteLength;
103
+ const compactReference = (ref: Ref): CompactReference => {
104
+ if (ref.kind === "file")
105
+ return {
106
+ kind: "file",
107
+ path: compactText(ref.path, COMPACT_TEXT_BYTES) ?? "",
108
+ };
109
+ if (ref.kind === "record")
110
+ return {
111
+ kind: "record",
112
+ collection: ref.collection,
113
+ id: ref.id,
114
+ };
115
+ return { kind: ref.kind };
116
+ };
117
+
118
+ export function compactTaskContext(view: TaskView): string {
119
+ const decisions: CompactDecision[] = view.task.decisions
120
+ .slice()
121
+ .sort((left, right) => (left.id < right.id ? -1 : left.id > right.id ? 1 : 0))
122
+ .slice(0, COMPACT_MAX_ITEMS)
123
+ .map((entry) => ({
124
+ id: entry.id,
125
+ purpose: entry.data.purpose,
126
+ status: entry.data.response,
127
+ digest: entry.data.digest,
128
+ references: entry.data.binding.contentRefs.slice(0, 1).map(compactReference),
129
+ }));
130
+ const gaps = Array.from(
131
+ new Set([
132
+ ...view.evidence
133
+ .filter((entry) => entry.status === "stale")
134
+ .map((entry) => `stale evidence: ${entry.evidenceId}`),
135
+ ...view.requirements
136
+ .filter((entry) => entry.status !== "satisfied")
137
+ .map((entry) => entry.reason),
138
+ ...view.task.progress.blockers.map((entry) => entry.reason),
139
+ ]),
140
+ )
141
+ .sort()
142
+ .slice(0, COMPACT_MAX_ITEMS)
143
+ .map((gap) => compactText(gap, COMPACT_TEXT_BYTES)!);
144
+ const context: CompactTaskContext = {
145
+ objective: compactText(view.task.intent.data.objective, COMPACT_TEXT_BYTES) ?? "",
146
+ status: view.task.status,
147
+ decisions,
148
+ gaps,
149
+ nextAction: compactText(view.task.progress.nextAction, COMPACT_TEXT_BYTES),
150
+ };
151
+ let encoded = JSON.stringify(context);
152
+ while (
153
+ compactBytes(encoded) > COMPACT_MAX_BYTES &&
154
+ (context.decisions.length || context.gaps.length)
155
+ ) {
156
+ if (context.gaps.length) context.gaps.pop();
157
+ else context.decisions.pop();
158
+ encoded = JSON.stringify(context);
159
+ }
160
+ if (compactBytes(encoded) > COMPACT_MAX_BYTES) {
161
+ context.objective = compactText(context.objective, 64) ?? "";
162
+ context.nextAction = compactText(context.nextAction, 64);
163
+ encoded = JSON.stringify(context);
164
+ }
165
+ return encoded;
166
+ }
167
+
168
+ export const exportDigest = (bundle: {
169
+ schemaVersion: 1;
170
+ exportedAt: Utc;
171
+ sourceWorkspaceId: string;
172
+ task: unknown;
173
+ }): string =>
174
+ sha256({
175
+ schemaVersion: bundle.schemaVersion,
176
+ exportedAt: bundle.exportedAt,
177
+ sourceWorkspaceId: bundle.sourceWorkspaceId,
178
+ task: bundle.task,
179
+ });