@brainervirus/workit-core 0.6.1 → 0.7.1
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.
- package/package.json +3 -7
- package/scripts/doctor-check.ts +20 -0
- package/scripts/install-cursor-plugin.sh +51 -28
- package/scripts/install-opencode-plugin.sh +19 -21
- package/scripts/rewrite-workspace-deps.ts +15 -9
- package/scripts/sync-runtime.sh +71 -19
- package/scripts/vendor-assets.ts +37 -0
- package/skills/wk-implement/SKILL.md +2 -2
- package/skills/wk-pr/SKILL.md +1 -1
- package/src/core/boundary.ts +27 -0
- package/src/core/branch-policy.ts +63 -0
- package/src/core/branch.ts +30 -16
- package/src/core/config.ts +193 -31
- package/src/core/docs-layout.ts +251 -0
- package/src/core/docs-migration.ts +639 -0
- package/src/core/docs-repo.ts +11 -9
- package/src/core/docs-validate.ts +18 -6
- package/src/core/doctor.ts +801 -0
- package/src/core/flow-state.ts +1579 -141
- package/src/core/git.ts +22 -5
- package/src/{tools/handoff.ts → core/handoff-tools.ts} +5 -57
- package/src/core/hygiene.ts +26 -12
- package/src/core/init.ts +43 -11
- package/src/core/logger.ts +321 -0
- package/src/core/package-root.ts +28 -0
- package/src/core/ports/init-toolkit-status.ts +1 -1
- package/src/core/ports/vcs-verify-token.ts +1 -1
- package/src/core/ports/youtrack-api.ts +1 -1
- package/src/core/ports/youtrack-verify-token.ts +1 -1
- package/src/core/pr-create.ts +116 -21
- package/src/core/registration.ts +215 -0
- package/src/core/repo-context.ts +447 -0
- package/src/core/repo-tools.ts +23 -0
- package/src/core/safe-write.ts +22 -0
- package/src/core/scripts.ts +3 -44
- package/src/core/sdd.ts +45 -28
- package/src/core/setup-state.ts +54 -0
- package/src/core/setup.ts +1216 -0
- package/src/core/skill-manifests.ts +95 -0
- package/src/core/support-matrix.ts +12 -0
- package/src/core/sync-runtime.ts +348 -0
- package/src/core/templates.ts +2 -2
- package/src/core/vcs-config.ts +107 -37
- package/src/core/verify-project.ts +181 -0
- package/src/core/workspaces.ts +136 -17
- package/src/core/youtrack-tools.ts +228 -0
- package/src/core/youtrack.ts +125 -67
- package/templates/execution-contract.md +9 -7
- package/templates/superpowers-doc-contract.md +4 -3
- package/scripts/_shared/common.sh +0 -173
- package/scripts/changelog-context.sh +0 -42
- package/scripts/docs-refresh-context.sh +0 -40
- package/scripts/init/apply.sh +0 -5
- package/scripts/init/status.sh +0 -5
- package/scripts/init/toolkit-status.sh +0 -5
- package/scripts/pr-create.sh +0 -5
- package/scripts/pr-ready-context.sh +0 -88
- package/scripts/present/ascii-wireframe.sh +0 -5
- package/scripts/present/flow-diagram.sh +0 -5
- package/scripts/release-notes-context.sh +0 -40
- package/scripts/vcs/config.sh +0 -5
- package/scripts/vcs/merged-style.sh +0 -5
- package/scripts/vcs/token-create-urls.sh +0 -5
- package/scripts/vcs/verify-token.sh +0 -5
- package/scripts/verify-project.sh +0 -140
- package/scripts/youtrack/api.sh +0 -5
- package/scripts/youtrack/config.sh +0 -5
- package/scripts/youtrack/greeting.sh +0 -5
- package/scripts/youtrack/parse-duration.sh +0 -5
- package/scripts/youtrack/token-create-url.sh +0 -5
- package/scripts/youtrack/verify-token.sh +0 -5
- package/scripts/youtrack/work-date-ms.sh +0 -5
- package/src/tools/docs-repo.ts +0 -51
- package/src/tools/flow.ts +0 -99
- package/src/tools/index.ts +0 -22
- package/src/tools/present.ts +0 -49
- package/src/tools/repo.ts +0 -490
- package/src/tools/rules.ts +0 -30
- package/src/tools/sdd.ts +0 -216
- package/src/tools/templates.ts +0 -27
- package/src/tools/youtrack.ts +0 -423
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@brainervirus/workit-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Workit — workflow rails for agentic coding: specs, plans, YouTrack, CI-gated commits (shared core)",
|
|
6
6
|
"keywords": [
|
|
@@ -27,12 +27,12 @@
|
|
|
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"
|
|
@@ -42,9 +42,5 @@
|
|
|
42
42
|
},
|
|
43
43
|
"scripts": {
|
|
44
44
|
"typecheck": "tsc --noEmit"
|
|
45
|
-
},
|
|
46
|
-
"dependencies": {
|
|
47
|
-
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
48
|
-
"zod": "^3.24.0"
|
|
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
|
|
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 "
|
|
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"
|
|
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
|
-
|
|
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
|
|
55
|
-
const
|
|
56
|
-
|
|
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
|
-
|
|
66
|
-
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
|
@@ -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 "
|
|
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,29 +36,18 @@ fi
|
|
|
33
36
|
mkdir -p "$(dirname "$CONFIG")"
|
|
34
37
|
[ -f "$CONFIG" ] || printf '%s\n' "{}" >"$CONFIG"
|
|
35
38
|
|
|
36
|
-
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
return value.includes("workflow-toolkit") ||
|
|
47
|
-
value.includes("@brainervirus/workit-opencode") ||
|
|
48
|
-
value.includes("/packages/workit-opencode/");
|
|
49
|
-
};
|
|
50
|
-
data.plugin = [pin, ...plugins.filter((p) => !isWorkit(p))];
|
|
51
|
-
// Drop share skills.paths — native ~/.config/opencode/skills links avoid triple-load dups
|
|
52
|
-
const skills = data.skills;
|
|
53
|
-
if (skills && typeof skills === "object") {
|
|
54
|
-
skills.paths = (skills.paths || []).filter((p) => !String(p).includes("workflow-toolkit"));
|
|
55
|
-
data.skills = skills;
|
|
56
|
-
}
|
|
57
|
-
fs.writeFileSync(path, JSON.stringify(data, null, 2) + "\n");
|
|
58
|
-
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");
|
|
59
51
|
console.log("Native skills/commands via ~/.config/opencode/{skills,commands}");
|
|
60
52
|
'
|
|
61
53
|
|
|
@@ -78,4 +70,10 @@ if [ ! -s "$PLUGIN_ENTRY" ]; then
|
|
|
78
70
|
exit 1
|
|
79
71
|
fi
|
|
80
72
|
|
|
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
|
+
|
|
81
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:*
|
|
3
|
-
// version> in the platform packages, and mirror the core version +
|
|
4
|
-
// URLs into the cursor plugin/marketplace manifests. Neither npm nor
|
|
5
|
-
// rewrites workspace:*, so a packed tarball with it silently drops the
|
|
6
|
-
// dependency. Runs as semantic-release prepareCmd (after the
|
|
7
|
-
// before the npm publish phase); the writes stay in CI and never
|
|
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
|
-
|
|
18
|
-
|
|
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 [
|
package/scripts/sync-runtime.sh
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
27
|
-
|
|
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 "
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
"$
|
|
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"
|
|
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
|
-
|
|
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
|
|
package/skills/wk-pr/SKILL.md
CHANGED
|
@@ -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` — `
|
|
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
|
+
};
|