@brainervirus/workit-core 0.6.0 → 0.7.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 (95) hide show
  1. package/package.json +10 -14
  2. package/scripts/doctor-check.ts +20 -0
  3. package/scripts/install-cursor-plugin.sh +51 -28
  4. package/scripts/install-opencode-plugin.sh +25 -22
  5. package/scripts/rewrite-workspace-deps.ts +15 -9
  6. package/scripts/sync-runtime.sh +71 -19
  7. package/scripts/vendor-assets.ts +37 -0
  8. package/skills/wk-implement/SKILL.md +2 -2
  9. package/skills/wk-pr/SKILL.md +1 -1
  10. package/src/core/boundary.ts +27 -0
  11. package/src/core/branch-policy.ts +63 -0
  12. package/src/core/branch.ts +166 -57
  13. package/src/core/changelog.ts +17 -14
  14. package/src/core/config-guard.ts +9 -2
  15. package/src/core/config.ts +227 -32
  16. package/src/core/detector.ts +22 -11
  17. package/src/core/docs-layout.ts +251 -0
  18. package/src/core/docs-migration.ts +639 -0
  19. package/src/core/docs-repo.ts +58 -21
  20. package/src/core/docs-validate.ts +181 -43
  21. package/src/core/doctor.ts +801 -0
  22. package/src/core/flow-state.ts +1579 -137
  23. package/src/core/git.ts +22 -5
  24. package/src/core/gitignore.ts +11 -2
  25. package/src/core/handoff-context.ts +18 -5
  26. package/src/{tools/handoff.ts → core/handoff-tools.ts} +16 -63
  27. package/src/core/hygiene.ts +53 -17
  28. package/src/core/init.ts +128 -31
  29. package/src/core/logger.ts +321 -0
  30. package/src/core/package-root.ts +28 -0
  31. package/src/core/parse-sections.ts +2 -2
  32. package/src/core/plan-tasks.ts +13 -3
  33. package/src/core/ports/init-toolkit-status.ts +1 -1
  34. package/src/core/ports/vcs-verify-token.ts +1 -1
  35. package/src/core/ports/youtrack-api.ts +4 -2
  36. package/src/core/ports/youtrack-config.ts +1 -3
  37. package/src/core/ports/youtrack-verify-token.ts +1 -1
  38. package/src/core/pr-create.ts +156 -29
  39. package/src/core/present.ts +11 -2
  40. package/src/core/registration.ts +215 -0
  41. package/src/core/reminder.ts +1 -2
  42. package/src/core/repo-context.ts +447 -0
  43. package/src/core/repo-tool.ts +4 -1
  44. package/src/core/repo-tools.ts +23 -0
  45. package/src/core/rules.ts +10 -7
  46. package/src/core/safe-write.ts +22 -0
  47. package/src/core/scripts.ts +3 -39
  48. package/src/core/sdd.ts +56 -31
  49. package/src/core/setup-state.ts +54 -0
  50. package/src/core/setup.ts +1216 -0
  51. package/src/core/skill-manifests.ts +95 -0
  52. package/src/core/support-matrix.ts +12 -0
  53. package/src/core/sync-runtime.ts +348 -0
  54. package/src/core/templates.ts +16 -6
  55. package/src/core/vcs-config.ts +191 -62
  56. package/src/core/verify-parse.ts +4 -2
  57. package/src/core/verify-project.ts +181 -0
  58. package/src/core/workspaces.ts +136 -17
  59. package/src/core/youtrack-tools.ts +228 -0
  60. package/src/core/youtrack.ts +320 -87
  61. package/src/core.ts +18 -3
  62. package/templates/execution-contract.md +9 -7
  63. package/templates/superpowers-doc-contract.md +5 -4
  64. package/scripts/_shared/common.sh +0 -158
  65. package/scripts/changelog-context.sh +0 -42
  66. package/scripts/docs-refresh-context.sh +0 -40
  67. package/scripts/init/apply.sh +0 -5
  68. package/scripts/init/status.sh +0 -5
  69. package/scripts/init/toolkit-status.sh +0 -5
  70. package/scripts/pr-create.sh +0 -5
  71. package/scripts/pr-ready-context.sh +0 -88
  72. package/scripts/present/ascii-wireframe.sh +0 -5
  73. package/scripts/present/flow-diagram.sh +0 -5
  74. package/scripts/release-notes-context.sh +0 -40
  75. package/scripts/vcs/config.sh +0 -5
  76. package/scripts/vcs/merged-style.sh +0 -5
  77. package/scripts/vcs/token-create-urls.sh +0 -5
  78. package/scripts/vcs/verify-token.sh +0 -5
  79. package/scripts/verify-project.sh +0 -140
  80. package/scripts/youtrack/api.sh +0 -5
  81. package/scripts/youtrack/config.sh +0 -5
  82. package/scripts/youtrack/greeting.sh +0 -5
  83. package/scripts/youtrack/parse-duration.sh +0 -5
  84. package/scripts/youtrack/token-create-url.sh +0 -5
  85. package/scripts/youtrack/verify-token.sh +0 -5
  86. package/scripts/youtrack/work-date-ms.sh +0 -5
  87. package/src/tools/docs-repo.ts +0 -42
  88. package/src/tools/flow.ts +0 -88
  89. package/src/tools/index.ts +0 -22
  90. package/src/tools/present.ts +0 -45
  91. package/src/tools/repo.ts +0 -357
  92. package/src/tools/rules.ts +0 -30
  93. package/src/tools/sdd.ts +0 -189
  94. package/src/tools/templates.ts +0 -27
  95. package/src/tools/youtrack.ts +0 -360
package/package.json CHANGED
@@ -1,20 +1,20 @@
1
1
  {
2
2
  "name": "@brainervirus/workit-core",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "private": false,
5
5
  "description": "Workit — workflow rails for agentic coding: specs, plans, YouTrack, CI-gated commits (shared core)",
6
- "license": "MIT",
7
6
  "keywords": [
8
- "opencode",
7
+ "agent",
9
8
  "cursor",
10
- "workflow",
11
9
  "mcp",
12
- "youTrack",
13
- "agent"
10
+ "opencode",
11
+ "workflow",
12
+ "youTrack"
14
13
  ],
15
14
  "bugs": {
16
15
  "url": "https://github.com/BrainerVirus/workit/issues"
17
16
  },
17
+ "license": "MIT",
18
18
  "repository": {
19
19
  "type": "git",
20
20
  "url": "https://github.com/BrainerVirus/workit.git"
@@ -27,24 +27,20 @@
27
27
  "vendor/superpowers/skills/",
28
28
  "templates/",
29
29
  "README.md",
30
- "LICENSE"
30
+ "LICENSE",
31
+ "!scripts/verify-project.sh"
31
32
  ],
32
33
  "type": "module",
33
34
  "main": "./src/core.ts",
34
35
  "exports": {
35
- "./src/tools": "./src/tools/index.ts",
36
36
  "./src/*.ts": "./src/*.ts",
37
37
  "./src/*": "./src/*.ts",
38
38
  "./package.json": "./package.json"
39
39
  },
40
- "scripts": {
41
- "typecheck": "tsc --noEmit"
42
- },
43
40
  "publishConfig": {
44
41
  "access": "public"
45
42
  },
46
- "dependencies": {
47
- "@modelcontextprotocol/sdk": "^1.12.0",
48
- "zod": "^3.24.0"
43
+ "scripts": {
44
+ "typecheck": "tsc --noEmit"
49
45
  }
50
46
  }
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env bun
2
+ // DG-09: installers run the shared offline doctor after writing configuration and
3
+ // return nonzero when a required registration/config check fails. `installer:
4
+ // true` limits the enforced checks to the ones the installer just guaranteed, so
5
+ // a dev/stub checkout without built assets does not fail a post-install run.
6
+ import { runDoctor } from "../src/core/doctor";
7
+
8
+ const host = process.argv[2] === "cursor" ? "cursor" : "opencode";
9
+ const report = runDoctor({ host, installer: true });
10
+
11
+ if (report.exitCode !== 0) {
12
+ for (const check of report.checks) {
13
+ if (check.status === "fail") {
14
+ process.stderr.write(`FATAL: workit doctor ${check.id}: ${check.detail}\n`);
15
+ if (check.fix) process.stderr.write(` fix: ${check.fix}\n`);
16
+ }
17
+ }
18
+ process.exit(1);
19
+ }
20
+ process.exit(0);
@@ -10,7 +10,8 @@ FROM_GITHUB=0
10
10
  LOCAL_ROOT=""
11
11
  if [ -n "${BASH_SOURCE[0]:-}" ] && [ -f "${BASH_SOURCE[0]}" ]; then
12
12
  SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
13
- LOCAL_ROOT="$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd)"
13
+ # RR-05: LOCAL_ROOT is the checkout root (scripts live under <root>/packages/workit-core/scripts)
14
+ LOCAL_ROOT="$(CDPATH= cd -- "$SCRIPT_DIR/../../.." && pwd)"
14
15
  else
15
16
  FROM_GITHUB=1
16
17
  fi
@@ -33,7 +34,7 @@ if [ "$FROM_GITHUB" -eq 1 ]; then
33
34
  git -C "$SHARE" pull --ff-only origin main
34
35
  else
35
36
  rm -rf "$SHARE"
36
- git clone --depth 1 "git@github.com:${REPO_SLUG}.git" "$SHARE"
37
+ git clone --depth 1 "https://github.com/${REPO_SLUG}.git" "$SHARE"
37
38
  fi
38
39
  ROOT="$SHARE"
39
40
  else
@@ -42,42 +43,64 @@ fi
42
43
 
43
44
  chmod +x "$ROOT/packages/workit-core/scripts/sync-runtime.sh" "$ROOT/packages/workit-core/scripts/"*.sh
44
45
  # Prefer syncing from this ROOT (dev or freshly cloned share)
45
- WORKFLOW_TOOLKIT_DEV="$ROOT" "$ROOT/packages/workit-core/scripts/sync-runtime.sh"
46
+ export WORKFLOW_TOOLKIT_DEV="$ROOT"
47
+ "$ROOT/packages/workit-core/scripts/sync-runtime.sh"
46
48
 
47
49
  # Drop stale CLI skill symlinks (duplicate /wf-* and /wk-* entries)
48
50
  if [ -d "$SKILLS_DIR" ]; then
49
51
  rm -f "$SKILLS_DIR"/wf-* "$SKILLS_DIR"/wk-*
50
52
  fi
51
53
 
52
- CURSOR_SETTINGS="$HOME/.cursor/settings.json" bun -e '
54
+ PLUGIN_DIR="$HOME/.cursor/plugins/local/workflow-toolkit"
55
+ REGISTRATION_TS="$ROOT/packages/workit-core/src/core/registration.ts"
56
+ CURSOR_SETTINGS="$HOME/.cursor/settings.json" CURSOR_MCP="$HOME/.cursor/mcp.json" PLUGIN_DIR="$PLUGIN_DIR" REGISTRATION_TS="$REGISTRATION_TS" bun -e '
53
57
  import fs from "node:fs";
54
- import os from "node:os";
55
- const path = process.env.CURSOR_SETTINGS!;
56
- let data = {};
57
- if (fs.existsSync(path)) data = JSON.parse(fs.readFileSync(path, "utf8"));
58
- const prev = data.enabled_plugins && typeof data.enabled_plugins === "object" ? data.enabled_plugins : {};
59
- const plugin = `${os.homedir()}/.cursor/plugins/local/workflow-toolkit`;
60
- data.enabled_plugins = { ...prev, "workflow-toolkit": true, "local/workflow-toolkit": true };
61
- data.plugin_dirs = [plugin];
62
- fs.writeFileSync(path, JSON.stringify(data, null, 2) + "\n");
63
- '
58
+ import path from "node:path";
59
+ const { mergeCursorSettings, mergeCursorMcp, mergeCursorHooks, cursorMcpServerEntry } =
60
+ await import(process.env.REGISTRATION_TS!);
64
61
 
65
- CURSOR_MCP="$HOME/.cursor/mcp.json" bun -e '
66
- import fs from "node:fs";
67
- const path = process.env.CURSOR_MCP!;
68
- const data = fs.existsSync(path) ? JSON.parse(fs.readFileSync(path, "utf8")) : { mcpServers: {} };
69
- data.mcpServers = data.mcpServers ?? {};
70
- data.mcpServers["workflow-toolkit"] = {
71
- command: "bash",
72
- args: [
73
- "-lc",
74
- "exec \"$HOME/.local/share/workflow-toolkit/packages/workit-core/scripts/run-cursor-mcp.sh\" \"$0\"",
75
- "${workspaceFolder}",
76
- ],
77
- };
78
- fs.writeFileSync(path, JSON.stringify(data, null, 2) + "\n");
62
+ // RR-06: collapse current + legacy identities; never replace unrelated settings.
63
+ const settingsPath = process.env.CURSOR_SETTINGS!;
64
+ const settings = fs.existsSync(settingsPath)
65
+ ? JSON.parse(fs.readFileSync(settingsPath, "utf8"))
66
+ : {};
67
+ const merged = mergeCursorSettings(settings, process.env.PLUGIN_DIR!);
68
+ fs.writeFileSync(settingsPath, JSON.stringify(merged.config, null, 2) + "\n");
69
+
70
+ // RR-06: one portable workit MCP registration (node dist when present, bash
71
+ // shim in a dist-less dev checkout); stale "workflow-toolkit" entries removed.
72
+ const server = cursorMcpServerEntry(process.env.PLUGIN_DIR!);
73
+ const mcpPath = process.env.CURSOR_MCP!;
74
+ const mcp = fs.existsSync(mcpPath)
75
+ ? JSON.parse(fs.readFileSync(mcpPath, "utf8"))
76
+ : { mcpServers: {} };
77
+ const mergedMcp = mergeCursorMcp(mcp, "workit", server);
78
+ fs.writeFileSync(mcpPath, JSON.stringify(mergedMcp.config, null, 2) + "\n");
79
+
80
+ // Keep the synced plugin manifests consistent with the current checkout state:
81
+ // only in a dist-less dev checkout, point them at the shims so Cursor still
82
+ // launches MCP/hooks (the shipped manifests already reference dist).
83
+ const distMcp = path.join(process.env.PLUGIN_DIR!, "dist", "mcp-server.js");
84
+ const pluginMcp = path.join(process.env.PLUGIN_DIR!, "mcp.json");
85
+ if (!fs.existsSync(distMcp) && fs.existsSync(pluginMcp)) {
86
+ const inPkg = JSON.parse(fs.readFileSync(pluginMcp, "utf8"));
87
+ const patched = mergeCursorMcp(inPkg, "workit", server);
88
+ fs.writeFileSync(pluginMcp, JSON.stringify(patched.config, null, 2) + "\n");
89
+ }
90
+ const pluginHooks = path.join(process.env.PLUGIN_DIR!, "hooks", "hooks-cursor.json");
91
+ if (!fs.existsSync(path.join(process.env.PLUGIN_DIR!, "dist", "cursor-session-start.js")) && fs.existsSync(pluginHooks)) {
92
+ const inPkg = JSON.parse(fs.readFileSync(pluginHooks, "utf8"));
93
+ const patched = mergeCursorHooks(inPkg, { command: "./hooks/session-start" });
94
+ fs.writeFileSync(pluginHooks, JSON.stringify(patched.config, null, 2) + "\n");
95
+ }
79
96
  '
80
97
 
98
+ # DG-09: verify the just-written Cursor registration with the shared offline doctor.
99
+ if ! bun "$ROOT/packages/workit-core/scripts/doctor-check.ts" cursor; then
100
+ echo "FATAL: post-install doctor found an unhealthy Cursor registration" >&2
101
+ exit 1
102
+ fi
103
+
81
104
  echo "Cursor plugin installed + auto-sync enabled (sessionStart)."
82
105
  echo "Share: $SHARE"
83
106
  ls "$HOME/.cursor/plugins/local/workflow-toolkit/skills" | grep '^wk-' || true
@@ -3,14 +3,14 @@
3
3
  set -euo pipefail
4
4
 
5
5
  SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
6
- ROOT="$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd)"
6
+ ROOT="$(CDPATH= cd -- "$SCRIPT_DIR/../../.." && pwd)"
7
+ CORE_SCRIPTS="$ROOT/packages/workit-core/scripts"
7
8
  SHARE="${HOME}/.local/share/workflow-toolkit"
8
- DEV_DEFAULT="${HOME}/Documents/projects/personal/workflow-toolkit"
9
- DEV="${WORKFLOW_TOOLKIT_DEV:-$DEV_DEFAULT}"
9
+ DEV="${WORKFLOW_TOOLKIT_DEV:-$ROOT}"
10
10
  CONFIG="${HOME}/.config/opencode/opencode.json"
11
11
 
12
- chmod +x "$ROOT/scripts/sync-runtime.sh"
13
- WORKFLOW_TOOLKIT_DEV="$ROOT" "$ROOT/scripts/sync-runtime.sh"
12
+ chmod +x "$CORE_SCRIPTS/sync-runtime.sh"
13
+ WORKFLOW_TOOLKIT_DEV="$ROOT" "$CORE_SCRIPTS/sync-runtime.sh"
14
14
 
15
15
  # Prefer monorepo with .git. file:// pins skip opencode's bundled npm installer —
16
16
  # git+file:// installs an EMPTY cache dir and fails silently, so the plugin never
@@ -23,7 +23,10 @@ else
23
23
  # Last resort: ensure share is a clone, then pin it
24
24
  if [ ! -d "${SHARE}/.git" ]; then
25
25
  TMP=$(mktemp -d)
26
- git clone --depth 1 "git@github.com:${WORKFLOW_TOOLKIT_REPO:-BrainerVirus/workit}.git" "$TMP"
26
+ if ! git clone --depth 1 "https://github.com/${WORKFLOW_TOOLKIT_REPO:-BrainerVirus/workit}.git" "$TMP"; then
27
+ echo "FATAL: could not clone https://github.com/${WORKFLOW_TOOLKIT_REPO:-BrainerVirus/workit}.git" >&2
28
+ exit 1
29
+ fi
27
30
  rsync -a --delete --exclude node_modules --exclude cursor/mcp/node_modules "$TMP/" "$SHARE/"
28
31
  rm -rf "$TMP"
29
32
  fi
@@ -33,24 +36,18 @@ fi
33
36
  mkdir -p "$(dirname "$CONFIG")"
34
37
  [ -f "$CONFIG" ] || printf '%s\n' "{}" >"$CONFIG"
35
38
 
36
- CONFIG_PATH="$CONFIG" PIN_PATH="$PIN" bun -e '
39
+ # Merge/dedupe registrations via the shared core helper (RR-06): one dev pin,
40
+ # no legacy/current duplicates, unrelated user settings preserved.
41
+ CONFIG_PATH="$CONFIG" PIN_PATH="$PIN" REGISTRATION_TS="$ROOT/packages/workit-core/src/core/registration.ts" bun -e '
37
42
  import fs from "node:fs";
43
+ const { mergeOpenCodeConfig } = await import(process.env.REGISTRATION_TS!);
38
44
  const path = process.env.CONFIG_PATH!;
39
- const pin = process.env.PIN_PATH!;
40
45
  const data = JSON.parse(fs.readFileSync(path, "utf8"));
41
- let plugins = data.plugin || [];
42
- if (typeof plugins === "string") plugins = [plugins];
43
- // Drop stale workflow-toolkit pins — the new file:// pin is written fresh.
44
- data.plugin = plugins.filter((p) => !String(p).includes("workflow-toolkit"));
45
- data.plugin.push(pin);
46
- // Drop share skills.paths — native ~/.config/opencode/skills links avoid triple-load dups
47
- const skills = data.skills;
48
- if (skills && typeof skills === "object") {
49
- skills.paths = (skills.paths || []).filter((p) => !String(p).includes("workflow-toolkit"));
50
- data.skills = skills;
51
- }
52
- fs.writeFileSync(path, JSON.stringify(data, null, 2) + "\n");
53
- console.log("Pinned:", pin);
46
+ const { config, changed } = mergeOpenCodeConfig(data, process.env.PIN_PATH!);
47
+ fs.writeFileSync(path, JSON.stringify(config, null, 2) + "\n");
48
+ console.log("Pinned:", process.env.PIN_PATH!);
49
+ if (changed.includes("plugin")) console.log("Deduplicated Workit plugin entries");
50
+ if (changed.includes("skills.paths")) console.log("Dropped share skill paths");
54
51
  console.log("Native skills/commands via ~/.config/opencode/{skills,commands}");
55
52
  '
56
53
 
@@ -73,4 +70,10 @@ if [ ! -s "$PLUGIN_ENTRY" ]; then
73
70
  exit 1
74
71
  fi
75
72
 
76
- echo "OpenCode install done. Fully quit all opencode processes, then restart."
73
+ # DG-09: verify the just-written registration with the shared offline doctor.
74
+ if ! bun "$ROOT/packages/workit-core/scripts/doctor-check.ts" opencode; then
75
+ echo "FATAL: post-install doctor found an unhealthy OpenCode registration" >&2
76
+ exit 1
77
+ fi
78
+
79
+ echo "OpenCode install done. Fully quit all opencode processes, then restart."
@@ -1,21 +1,27 @@
1
1
  #!/usr/bin/env bun
2
- // Release-sync before publish: rewrite workspace:* core deps to ^<released
3
- // version> in the platform packages, and mirror the core version + canonical
4
- // URLs into the cursor plugin/marketplace manifests. Neither npm nor bun
5
- // rewrites workspace:*, so a packed tarball with it silently drops the core
6
- // dependency. Runs as semantic-release prepareCmd (after the version bumps,
7
- // before the npm publish phase); the writes stay in CI and never reach the
8
- // repo (semantic-release does not commit back).
2
+ // Release-sync before publish: rewrite every internal workspace:* dependency to
3
+ // ^<released version> in the platform packages, and mirror the core version +
4
+ // canonical URLs into the cursor plugin/marketplace manifests. Neither npm nor
5
+ // bun rewrites workspace:*, so a packed tarball with it silently drops the
6
+ // dependency (RR-01/AR-03). Runs as semantic-release prepareCmd (after the
7
+ // version bumps, before the npm publish phase); the writes stay in CI and never
8
+ // reach the repo (semantic-release does not commit back).
9
9
  import { readFileSync, writeFileSync } from "node:fs";
10
10
  import { resolve } from "node:path";
11
11
 
12
12
  const root = process.argv[2] ? resolve(process.argv[2]) : resolve(import.meta.dir, "..", "..", "..");
13
13
  const core = JSON.parse(readFileSync(resolve(root, "packages/workit-core/package.json"), "utf8"));
14
+ if (!core.version || typeof core.version !== "string") {
15
+ throw new Error(`workit-core version missing in ${resolve(root, "packages/workit-core/package.json")}`);
16
+ }
14
17
  for (const pkg of ["workit-opencode", "workit-cursor", "workit-cli"]) {
15
18
  const file = resolve(root, `packages/${pkg}/package.json`);
16
19
  const data = JSON.parse(readFileSync(file, "utf8"));
17
- if (data.dependencies?.["@brainervirus/workit-core"] !== "workspace:*") continue;
18
- data.dependencies["@brainervirus/workit-core"] = `^${core.version}`;
20
+ const deps = data.dependencies;
21
+ if (!deps) continue;
22
+ for (const name of Object.keys(deps)) {
23
+ if (name.startsWith("@brainervirus/")) deps[name] = `^${core.version}`;
24
+ }
19
25
  writeFileSync(file, `${JSON.stringify(data, null, 2)}\n`);
20
26
  }
21
27
  for (const file of [
@@ -14,26 +14,87 @@ DEV="${WORKFLOW_TOOLKIT_DEV:-$DEV_DEFAULT}"
14
14
  REPO_SLUG="${WORKFLOW_TOOLKIT_REPO:-BrainerVirus/workit}"
15
15
  LOCK="${XDG_RUNTIME_DIR:-/tmp}/workflow-toolkit-sync.lock"
16
16
 
17
+ # RR-05: missing required tools, an unacquirable lock, or a failed clone must
18
+ # never look like a successful sync.
19
+ if ! command -v flock >/dev/null 2>&1; then
20
+ echo "FATAL: sync-runtime requires flock (util-linux) — not found in PATH" >&2
21
+ exit 1
22
+ fi
17
23
  exec 9>"$LOCK"
18
24
  if ! flock -n 9; then
19
- exit 0
25
+ echo "sync-runtime: another process holds $LOCK — state unverified, failing" >&2
26
+ exit 1
20
27
  fi
21
28
 
22
29
  SRC=""
23
30
  if [ -f "${DEV}/packages/workit-opencode/src/plugin.ts" ] && [ -d "${DEV}/packages/workit-cursor/.cursor-plugin" ]; then
24
31
  SRC="$DEV"
25
32
  elif [ -d "${SHARE}/.git" ]; then
26
- git -C "$SHARE" fetch --quiet origin 2>/dev/null || true
27
- git -C "$SHARE" pull --ff-only --quiet origin main 2>/dev/null || true
33
+ # RR-05: a failed update of an existing share clone must not look like a
34
+ # successful sync — propagate fetch/pull failures to a FATAL nonzero exit.
35
+ if ! git -C "$SHARE" fetch --quiet origin; then
36
+ echo "FATAL: could not fetch updates for $SHARE" >&2
37
+ exit 1
38
+ fi
39
+ if ! git -C "$SHARE" pull --ff-only --quiet origin main; then
40
+ echo "FATAL: could not update $SHARE from origin/main" >&2
41
+ exit 1
42
+ fi
28
43
  SRC="$SHARE"
29
44
  elif [ ! -d "${SHARE}/packages/workit-core/src" ]; then
30
45
  mkdir -p "$(dirname "$SHARE")"
31
- git clone --depth 1 "git@github.com:${REPO_SLUG}.git" "$SHARE" 2>/dev/null || exit 0
46
+ if ! git clone --depth 1 "https://github.com/${REPO_SLUG}.git" "$SHARE"; then
47
+ echo "FATAL: could not clone https://github.com/${REPO_SLUG}.git into $SHARE" >&2
48
+ exit 1
49
+ fi
32
50
  SRC="$SHARE"
33
51
  else
34
52
  SRC="$SHARE"
35
53
  fi
36
54
 
55
+ CURSOR_SRC="$SRC/packages/workit-cursor"
56
+ BUN_BIN=""
57
+ if [ -n "${BUN:-}" ]; then
58
+ BUN_BIN="$BUN"
59
+ if [ ! -x "$BUN_BIN" ]; then
60
+ echo "FATAL: BUN is set but unusable: $BUN_BIN" >&2
61
+ exit 1
62
+ fi
63
+ elif [ -x "$HOME/.bun/bin/bun" ]; then
64
+ BUN_BIN="$HOME/.bun/bin/bun"
65
+ elif command -v bun >/dev/null 2>&1; then
66
+ BUN_BIN="$(command -v bun)"
67
+ else
68
+ echo "FATAL: Bun is required to build the Cursor adapter" >&2
69
+ exit 1
70
+ fi
71
+
72
+ for DEP in @brainervirus/workit-core @modelcontextprotocol/sdk zod; do
73
+ if [ ! -e "$SRC/node_modules/$DEP" ]; then
74
+ if [ ! -f "$SRC/bun.lock" ]; then
75
+ echo "FATAL: dependency install requires $SRC/bun.lock" >&2
76
+ exit 1
77
+ fi
78
+ if ! (cd "$SRC" && "$BUN_BIN" install --frozen-lockfile); then
79
+ echo "FATAL: root dependency install failed in $SRC" >&2
80
+ exit 1
81
+ fi
82
+ break
83
+ fi
84
+ done
85
+
86
+ if ! (cd "$SRC" && PATH="$(dirname "$BUN_BIN"):$PATH" "$BUN_BIN" "$CURSOR_SRC/scripts/build.ts"); then
87
+ echo "FATAL: Cursor adapter build failed in $CURSOR_SRC" >&2
88
+ exit 1
89
+ fi
90
+ for ENTRY in mcp-server.js cursor-session-start.js; do
91
+ DIST_ENTRY="$CURSOR_SRC/dist/$ENTRY"
92
+ if [ ! -f "$DIST_ENTRY" ] || [ ! -s "$DIST_ENTRY" ] || [ "$(IFS= read -r LINE <"$DIST_ENTRY"; printf '%s' "$LINE")" != '#!/usr/bin/env node' ]; then
93
+ echo "FATAL: Cursor adapter invalid dist entry: $DIST_ENTRY" >&2
94
+ exit 1
95
+ fi
96
+ done
97
+
37
98
  if [ "$SRC" != "$SHARE" ]; then
38
99
  mkdir -p "$SHARE"
39
100
  # Keep .git if share is a clone; never wipe it when syncing from the monorepo
@@ -51,15 +112,15 @@ mkdir -p "$PLUGIN_DIR"
51
112
  rsync -a --delete \
52
113
  --exclude 'mcp/node_modules' \
53
114
  "$SHARE/packages/workit-cursor/" "$PLUGIN_DIR/"
54
- # Vendored skills for Cursor (same folder layout as OpenCode registration)
55
- mkdir -p "$PLUGIN_DIR/vendor/superpowers"
56
- if [ -d "$SHARE/packages/workit-core/vendor/superpowers/skills" ]; then
57
- rsync -a --delete "$SHARE/packages/workit-core/vendor/superpowers/skills" "$PLUGIN_DIR/vendor/superpowers/"
115
+
116
+ if ! "$BUN_BIN" "$SHARE/packages/workit-core/src/core/skill-manifests.ts" "$PLUGIN_DIR"; then
117
+ echo "FATAL: Cursor skill validation failed: $PLUGIN_DIR" >&2
118
+ exit 1
58
119
  fi
59
120
  # Canonical user rules -> Cursor .mdc (compiled by the shared core)
60
121
  CONFIG_RULES_DIR="$(resolve_config_dir)/rules"
61
122
  if [ -d "$CONFIG_RULES_DIR" ]; then
62
- "$HOME/.bun/bin/bun" -e "
123
+ "$BUN_BIN" -e "
63
124
  import('${SHARE}/packages/workit-core/src/core/rules.ts').then(async ({ writeCompiledCursorRules }) => {
64
125
  writeCompiledCursorRules('${PLUGIN_DIR}/rules');
65
126
  });
@@ -68,22 +129,13 @@ fi
68
129
  printf '%s\n' "$SHARE/packages/workit-core" >"$PLUGIN_DIR/.workflow-toolkit-root"
69
130
  chmod +x "$PLUGIN_DIR/hooks/session-start" "$PLUGIN_DIR/mcp/run-server.sh" 2>/dev/null || true
70
131
 
71
- if [ ! -d "$PLUGIN_DIR/mcp/node_modules" ]; then
72
- (cd "$PLUGIN_DIR/mcp" && npm install --silent) || true
73
- fi
74
-
75
- # Share MCP also needs deps when launched via run-cursor-mcp fallback
76
- if [ ! -d "$SHARE/packages/workit-cursor/mcp/node_modules" ]; then
77
- (cd "$SHARE/packages/workit-cursor/mcp" && npm install --silent) || true
78
- fi
79
-
80
132
  # Remove broken TLA live-loader if present (OpenCode ignored it; /wk-* vanished)
81
133
  rm -f "${OPENCODE_PLUGINS}/workflow-toolkit.ts"
82
134
 
83
135
  # Ensure OpenCode has plugin peer dep
84
136
  PKG="${HOME}/.config/opencode/package.json"
85
137
  if [ -f "$PKG" ]; then
86
- PKG_PATH="$PKG" bun -e '
138
+ PKG_PATH="$PKG" "$BUN_BIN" -e '
87
139
  import fs from "node:fs";
88
140
  const path = process.env.PKG_PATH!;
89
141
  const data = JSON.parse(fs.readFileSync(path, "utf8"));
@@ -0,0 +1,37 @@
1
+ import { cpSync, existsSync, readFileSync, statSync, writeFileSync } from "node:fs";
2
+ import path from "node:path";
3
+
4
+ const vendorFilter = (source: string): boolean => {
5
+ if (source.endsWith(".sh")) return false;
6
+ const stat = statSync(source);
7
+ return (
8
+ stat.isDirectory() ||
9
+ ((stat.mode & 0o111) === 0 && readFileSync(source).subarray(0, 2).toString("latin1") !== "#!")
10
+ );
11
+ };
12
+
13
+ export const copySanitizedVendor = (source: string, dest: string): void => {
14
+ cpSync(source, dest, { recursive: true, filter: vendorFilter });
15
+
16
+ const companion = path.join(dest, "brainstorming/visual-companion.md");
17
+ if (!existsSync(companion)) return;
18
+ let md = readFileSync(companion, "utf8");
19
+ const fromStart = md.indexOf("## Starting a Session");
20
+ const toLoop = md.indexOf("## The Loop", fromStart);
21
+ const fromCleanup = md.indexOf("## Cleaning Up");
22
+ const toReference = md.indexOf("## Reference", fromCleanup);
23
+ if (fromStart !== -1 && toLoop !== -1 && fromCleanup !== -1 && toReference !== -1) {
24
+ md =
25
+ md.slice(0, fromStart) +
26
+ "The packaged browser-companion runtime is not shipped with this package; its\n" +
27
+ "launcher scripts are excluded as vendored shell. Consult the upstream skill\n" +
28
+ "source for launch instructions.\n\n" +
29
+ md.slice(toLoop, fromCleanup) +
30
+ md.slice(toReference);
31
+ }
32
+ md = md.replace(
33
+ /If it has shut down, restart it with `start-server\.sh` using the \*\*same `--project-dir`\*\*[\s\S]*?you don't need to send a new URL\. /,
34
+ "",
35
+ );
36
+ writeFileSync(companion, md);
37
+ };
@@ -16,11 +16,11 @@ The parent agent is coordinator-only. It must not edit product code or perform d
16
16
  4. Initialize native `todowrite` from returned tasks and mark ledger-completed task IDs completed.
17
17
  5. Call `workflow_resolve_branch`, then show the current branch, target branch, and stash behavior before any branch checkout/setup mutation.
18
18
  6. Always use native `question` before that mutation. For a clean tree, ask whether to proceed or cancel. For a dirty tree, add the stash choice and state what will be stashed; allow a custom answer.
19
- 7. Call `workflow_branch_setup` with `confirmed: true` only after approval; never use worktrees.
19
+ 7. Call `workflow_branch_setup` with `confirmed: true` only after approval; never use worktrees. Flow-tool confirmations are never agent-typed booleans and never caller-supplied evidence: the plugin records your native-`question` answer as a host-observed one-use receipt (`attested: true`, `callID`, `selectedLabel`, `recordedAt`) consumed by the approval/menu tools — no evidence argument exists. Delegated worker status comes from host session parentage (`parentID`), never a caller `role` field.
20
20
  8. Report any setup failure stage or partial result; never infer success.
21
21
  9. Fill specs/plans from the quality templates: `templates/spec-template.md` for specs, `templates/plan-template.md` for plans. After `workflow_docs_validate`, surface the returned `quality` findings: hard findings (missing required section, missing CA-XX) block task start unless the user explicitly waives them; warnings are advisory.
22
22
 
23
- State lives only in tracked `docs/<slug>/sdd/<slug>/`. Never use an untracked or legacy SDD directory. Load the package-neutral execution contract by name, not an installation-specific path.
23
+ Working state lives only in gitignored `docs/<slug>/sdd/` never `.superpowers/sdd`, never an extra nested slug level. Load the package-neutral execution contract by name, not an installation-specific path.
24
24
 
25
25
  ## Per-task loop
26
26
 
@@ -16,7 +16,7 @@ disable-model-invocation: true
16
16
 
17
17
  ## GitHub issue linking
18
18
 
19
- When the resolved workspace is github with `link_on_pr` — `bash scripts/vcs/config.sh resolve` returns `issues_provider: "github"` and `link_on_pr: true` — and no issue is derivable (no `WORKFLOW_GH_ISSUE` env, no numeric branch id like `feature/42-title`, which auto-links without asking), ask with native `question` before creation, exactly three options:
19
+ When the resolved workspace is github with `link_on_pr` — the `workflow_pr_context` tool's `vcs_config` returns `issues_provider: "github"` and `link_on_pr: true` — and no issue is derivable (no `WORKFLOW_GH_ISSUE` env, no numeric branch id like `feature/42-title`, which auto-links without asking), ask with native `question` before creation, exactly three options:
20
20
 
21
21
  1. **Use an existing issue** — the user provides the number (extract it from a URL if pasted); verify it exists with `gh issue view <n>` before proceeding.
22
22
  2. **Create a new issue** — via `gh issue create --title "<title>" --body "<body>"`; on success pass the returned number. Reuse the missing-CLI guard: if `gh` is not installed, surface the structured error with the install link and ask the user to confirm once installed.
@@ -0,0 +1,27 @@
1
+ import type { JsonValue } from "./logger";
2
+
3
+ // Named diagnostic boundary events (DG-04) shared by all adapters so event
4
+ // names stay consolidated across OpenCode, Cursor, and the CLI.
5
+ export const EVENT = {
6
+ initialization: "initialization",
7
+ provenance: "provenance",
8
+ assets: "assets",
9
+ configurationSource: "configuration_source",
10
+ hooks: "hooks",
11
+ mcpConnection: "mcp_connection",
12
+ migration: "migration",
13
+ installSteps: "install_steps",
14
+ uncaughtFailure: "uncaught_failure",
15
+ toolsFailed: "tools_failed",
16
+ doctor: "doctor",
17
+ } as const;
18
+
19
+ // Safe error metadata for log events: name + message only. The message passes
20
+ // through the logger's value redaction (tokens, key=value secrets, URL queries,
21
+ // home prefixes); the raw stack never enters a context.
22
+ export const errorDetail = (err: unknown): Record<string, JsonValue> => {
23
+ if (err instanceof Error) {
24
+ return { error_name: err.name, error: err.message };
25
+ }
26
+ return { error_name: "unknown", error: String(err) };
27
+ };
@@ -0,0 +1,63 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { PRESETS, type BranchPreset } from "./config";
3
+ import type { IntegrationMode } from "./workspaces";
4
+
5
+ const PREFIXES = {
6
+ feature: "feature/*",
7
+ bugfix: "bugfix/*",
8
+ release: "release/*",
9
+ hotfix: "hotfix/*",
10
+ };
11
+
12
+ export const detectBranchPolicy = (workspaceRoot: string) => {
13
+ const branchExists = (name: string): boolean => {
14
+ const r = spawnSync("git", ["branch", "--list", name], {
15
+ cwd: workspaceRoot,
16
+ encoding: "utf8",
17
+ });
18
+ return r.status === 0 && (r.stdout ?? "").trim() !== "";
19
+ };
20
+ const develop = branchExists("develop");
21
+ const main = branchExists("main");
22
+ const master = branchExists("master");
23
+ const root = main ? "main" : master ? "master" : null;
24
+
25
+ if (develop) {
26
+ return {
27
+ preset: "gitflow" as BranchPreset,
28
+ developBranch: "develop",
29
+ integration: "merge" as IntegrationMode,
30
+ protected: root ? [root, "develop"] : ["develop"],
31
+ allowed: [...PRESETS.gitflow.allowed],
32
+ prefixes: PREFIXES,
33
+ };
34
+ }
35
+ if (root === "main") {
36
+ return {
37
+ preset: "github-flow" as BranchPreset,
38
+ developBranch: null,
39
+ integration: "pr" as IntegrationMode,
40
+ protected: [root],
41
+ allowed: ["*"],
42
+ prefixes: PREFIXES,
43
+ };
44
+ }
45
+ if (root === "master") {
46
+ return {
47
+ preset: "trunk-based" as BranchPreset,
48
+ developBranch: null,
49
+ integration: "pr" as IntegrationMode,
50
+ protected: [root],
51
+ allowed: ["*"],
52
+ prefixes: PREFIXES,
53
+ };
54
+ }
55
+ return {
56
+ preset: "gitflow" as BranchPreset,
57
+ developBranch: null,
58
+ integration: "merge" as IntegrationMode,
59
+ protected: [],
60
+ allowed: [],
61
+ prefixes: PREFIXES,
62
+ };
63
+ };