@brainervirus/workit-core 0.8.5 → 0.8.6
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 +1 -1
- package/scripts/install-cursor-plugin.sh +33 -7
- package/scripts/install-opencode-plugin.sh +1 -1
- package/scripts/run-cursor-mcp.sh +1 -1
- package/scripts/sync-runtime.sh +2 -2
- package/scripts/update-superpowers.sh +26 -0
- package/skills/wk-handoff/SKILL.md +1 -0
- package/skills/wk-implement/SKILL.md +4 -0
- package/skills/wk-init/SKILL.md +5 -5
- package/skills/wk-status/SKILL.md +2 -2
- package/src/core/doctor.ts +19 -1
- package/src/core/flow-state.ts +1 -1
- package/src/core/init.ts +2 -2
- package/src/core/registration.ts +32 -1
- package/src/core/sdd.ts +27 -3
- package/src/core/setup.ts +7 -7
- package/src/core/sync-runtime.ts +2 -5
- package/templates/execution-contract.md +2 -0
- package/templates/plan-template.md +2 -0
- package/vendor/superpowers/skills/subagent-driven-development/SKILL.md +9 -1
package/package.json
CHANGED
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
set -euo pipefail
|
|
4
4
|
|
|
5
5
|
REPO_SLUG="${WORKFLOW_TOOLKIT_REPO:-BrainerVirus/workit}"
|
|
6
|
-
SHARE="${HOME}/.local/share/
|
|
6
|
+
SHARE="${HOME}/.local/share/workit"
|
|
7
7
|
SKILLS_DIR="${HOME}/.cursor/skills"
|
|
8
8
|
|
|
9
9
|
FROM_GITHUB=0
|
|
10
|
+
LOCAL_DIST=0
|
|
10
11
|
LOCAL_ROOT=""
|
|
11
12
|
if [ -n "${BASH_SOURCE[0]:-}" ] && [ -f "${BASH_SOURCE[0]}" ]; then
|
|
12
13
|
SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
|
@@ -19,6 +20,7 @@ fi
|
|
|
19
20
|
for arg in "$@"; do
|
|
20
21
|
case "$arg" in
|
|
21
22
|
--github|--from-github) FROM_GITHUB=1 ;;
|
|
23
|
+
--local-dist) LOCAL_DIST=1 ;;
|
|
22
24
|
esac
|
|
23
25
|
done
|
|
24
26
|
|
|
@@ -53,10 +55,18 @@ fi
|
|
|
53
55
|
|
|
54
56
|
PLUGIN_DIR="$HOME/.cursor/plugins/local/workit"
|
|
55
57
|
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 '
|
|
58
|
+
CURSOR_SETTINGS="$HOME/.cursor/settings.json" CURSOR_MCP="$HOME/.cursor/mcp.json" PLUGIN_DIR="$PLUGIN_DIR" REGISTRATION_TS="$REGISTRATION_TS" LOCAL_DIST="$LOCAL_DIST" bun -e '
|
|
57
59
|
import fs from "node:fs";
|
|
58
|
-
|
|
59
|
-
|
|
60
|
+
import path from "node:path";
|
|
61
|
+
const {
|
|
62
|
+
mergeCursorSettings,
|
|
63
|
+
mergeCursorMcp,
|
|
64
|
+
mergeCursorHooks,
|
|
65
|
+
cursorMcpServerEntry,
|
|
66
|
+
cursorHooksEntry,
|
|
67
|
+
cursorMcpLocalDistEntry,
|
|
68
|
+
cursorHookLocalDistEntry,
|
|
69
|
+
} = await import(process.env.REGISTRATION_TS!);
|
|
60
70
|
|
|
61
71
|
// RR-06: collapse current + legacy identities; never replace unrelated settings.
|
|
62
72
|
const settingsPath = process.env.CURSOR_SETTINGS!;
|
|
@@ -66,15 +76,31 @@ const settings = fs.existsSync(settingsPath)
|
|
|
66
76
|
const merged = mergeCursorSettings(settings, process.env.PLUGIN_DIR!);
|
|
67
77
|
fs.writeFileSync(settingsPath, JSON.stringify(merged.config, null, 2) + "\n");
|
|
68
78
|
|
|
69
|
-
// RR-06/CA-17: one portable workit MCP registration
|
|
70
|
-
//
|
|
71
|
-
|
|
79
|
+
// RR-06/CA-17: one portable workit MCP registration. Default is the published
|
|
80
|
+
// npx pin (Marketplace-safe); --local-dist runs the installed plugin'"'"'s own
|
|
81
|
+
// built dist through node so a checkout install carries the current code.
|
|
82
|
+
const pluginDir = process.env.PLUGIN_DIR!;
|
|
83
|
+
const localDist = process.env.LOCAL_DIST === "1";
|
|
84
|
+
const server = localDist
|
|
85
|
+
? cursorMcpLocalDistEntry(pluginDir)
|
|
86
|
+
: cursorMcpServerEntry(pluginDir);
|
|
72
87
|
const mcpPath = process.env.CURSOR_MCP!;
|
|
73
88
|
const mcp = fs.existsSync(mcpPath)
|
|
74
89
|
? JSON.parse(fs.readFileSync(mcpPath, "utf8"))
|
|
75
90
|
: { mcpServers: {} };
|
|
76
91
|
const mergedMcp = mergeCursorMcp(mcp, "workit", server);
|
|
77
92
|
fs.writeFileSync(mcpPath, JSON.stringify(mergedMcp.config, null, 2) + "\n");
|
|
93
|
+
|
|
94
|
+
// The plugin'"'"'s own hooks-cursor.json ships the npx pin; the local-dist
|
|
95
|
+
// install swaps its sessionStart entry to node against the installed dist.
|
|
96
|
+
if (localDist) {
|
|
97
|
+
const hooksPath = path.join(pluginDir, "hooks", "hooks-cursor.json");
|
|
98
|
+
const hooks = fs.existsSync(hooksPath)
|
|
99
|
+
? JSON.parse(fs.readFileSync(hooksPath, "utf8"))
|
|
100
|
+
: { version: 1, hooks: {} };
|
|
101
|
+
const mergedHooks = mergeCursorHooks(hooks, cursorHookLocalDistEntry(pluginDir));
|
|
102
|
+
fs.writeFileSync(hooksPath, JSON.stringify(mergedHooks.config, null, 2) + "\n");
|
|
103
|
+
}
|
|
78
104
|
'
|
|
79
105
|
|
|
80
106
|
# DG-09: verify the just-written Cursor registration with the shared offline doctor.
|
|
@@ -5,7 +5,7 @@ set -euo pipefail
|
|
|
5
5
|
SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
|
|
6
6
|
ROOT="$(CDPATH= cd -- "$SCRIPT_DIR/../../.." && pwd)"
|
|
7
7
|
CORE_SCRIPTS="$ROOT/packages/workit-core/scripts"
|
|
8
|
-
SHARE="${HOME}/.local/share/
|
|
8
|
+
SHARE="${HOME}/.local/share/workit"
|
|
9
9
|
DEV="${WORKFLOW_TOOLKIT_DEV:-$ROOT}"
|
|
10
10
|
CONFIG="${HOME}/.config/opencode/opencode.json"
|
|
11
11
|
|
|
@@ -3,4 +3,4 @@
|
|
|
3
3
|
# Launch the published package's MCP bin through npx, never a repo-relative
|
|
4
4
|
# dist or share clone; a startup/network failure surfaces via npx's nonzero exit.
|
|
5
5
|
set -euo pipefail
|
|
6
|
-
exec npx -y --package=@brainervirus/workit-cursor@0.8.
|
|
6
|
+
exec npx -y --package=@brainervirus/workit-cursor@0.8.5 workit-cursor-mcp "$@"
|
package/scripts/sync-runtime.sh
CHANGED
|
@@ -6,7 +6,7 @@ set -euo pipefail
|
|
|
6
6
|
SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
|
7
7
|
. "$SCRIPT_DIR/lib/config-dir.sh"
|
|
8
8
|
|
|
9
|
-
SHARE="${HOME}/.local/share/
|
|
9
|
+
SHARE="${HOME}/.local/share/workit"
|
|
10
10
|
PLUGIN_DIR="${HOME}/.cursor/plugins/local/workit"
|
|
11
11
|
OPENCODE_PLUGINS="${HOME}/.config/opencode/plugins"
|
|
12
12
|
DEV_DEFAULT="${HOME}/Documents/projects/personal/workflow-toolkit"
|
|
@@ -126,7 +126,7 @@ if [ -d "$CONFIG_RULES_DIR" ]; then
|
|
|
126
126
|
});
|
|
127
127
|
" >/dev/null 2>&1 || true
|
|
128
128
|
fi
|
|
129
|
-
printf '%s\n' "$SHARE/packages/workit-core" >"$PLUGIN_DIR/.
|
|
129
|
+
printf '%s\n' "$SHARE/packages/workit-core" >"$PLUGIN_DIR/.workit-root"
|
|
130
130
|
|
|
131
131
|
# CA-08/CA-09: migrate the legacy local plugin identity to `workit` only after
|
|
132
132
|
# the canonical sync succeeded; carry the legacy user rules forward first.
|
|
@@ -51,6 +51,32 @@ find "$VENDOR/skills" -type f \( -name '*.md' -o -name '*.ts' \) -print0 | while
|
|
|
51
51
|
"$f"
|
|
52
52
|
done
|
|
53
53
|
|
|
54
|
+
# Re-apply the per-task commit-range rule (CA-02) to the vendored
|
|
55
|
+
# subagent-driven-development skill after every re-sync: upstream does not
|
|
56
|
+
# carry it, and the hand-edit is current truth. Idempotent — when the rule is
|
|
57
|
+
# already present the block is a no-op, so re-running leaves one copy. The awk
|
|
58
|
+
# temp file lives in $STAGE (already trap-cleaned), so a failed awk leaves no
|
|
59
|
+
# .tmp behind in the vendor dir, and if the marker headings were renamed or
|
|
60
|
+
# reordered upstream the rule is appended at end-of-file instead of silently
|
|
61
|
+
# no-oping.
|
|
62
|
+
SDD_SKILL="$VENDOR/skills/subagent-driven-development/SKILL.md"
|
|
63
|
+
RULE="- Each task produces exactly one contiguous non-empty commit range: fix rounds append commits to it, never amend or rewrite an active review range, and the ledger progress line records the task's real base..head shas."
|
|
64
|
+
if [ -f "$SDD_SKILL" ] && ! grep -qF "one contiguous non-empty commit range" "$SDD_SKILL"; then
|
|
65
|
+
TMP="$STAGE/sdd-skill.tmp"
|
|
66
|
+
awk -v rule="$RULE" '
|
|
67
|
+
/^## Durable Progress$/ { in_section=1 }
|
|
68
|
+
in_section && !inserted && /^## Prompt Templates$/ {
|
|
69
|
+
print rule;
|
|
70
|
+
inserted=1;
|
|
71
|
+
}
|
|
72
|
+
{ print }
|
|
73
|
+
' "$SDD_SKILL" > "$TMP"
|
|
74
|
+
if ! grep -qF "one contiguous non-empty commit range" "$TMP"; then
|
|
75
|
+
printf '%s\n' "$RULE" >> "$TMP"
|
|
76
|
+
fi
|
|
77
|
+
mv "$TMP" "$SDD_SKILL"
|
|
78
|
+
fi
|
|
79
|
+
|
|
54
80
|
printf '%s\n' "$VERSION" > "$VENDOR/VERSION"
|
|
55
81
|
echo "Vendored superpowers $VERSION -> $VENDOR (patched to docs/<slug>/ layout)"
|
|
56
82
|
echo "Review with: git status && git diff --stat"
|
|
@@ -13,5 +13,6 @@ disable-model-invocation: true
|
|
|
13
13
|
5. Pass only `message`; the tool itself recognizes an exact `--stay` flag and otherwise selects the new session.
|
|
14
14
|
6. Report the structured success, failure stage, or partial result; never infer success.
|
|
15
15
|
7. After any `workflow_handoff_session` result—success, partial, or failure—end the originating turn immediately after one status message. Never create todos, execute the plan inline, modify files, retry handoff, or call another tool.
|
|
16
|
+
8. A destination run that executes the plan must still end with `workflow_plan_complete` (or the CLI `workit flow complete`) once the SDD ledger is complete and repository verification passes, and never finish the run while the plan is still `active`.
|
|
16
17
|
|
|
17
18
|
Never emit a continuation prompt, use the clipboard, or ask the user to copy text. If selected, report the session ID only if the current session remains visible. If staying, report the seeded session ID. On failure, report `stage` and `error`; preserve any returned session for the session picker and never recreate it automatically. `todowrite` and `task` are unnecessary here.
|
|
@@ -34,8 +34,12 @@ For every plan task whose ID is absent from `completed_task_ids`:
|
|
|
34
34
|
6. Dispatch separate `general` agents for spec-compliance review and code-quality review. **Blocking findings** (Critical, Important, or spec-compliance) may trigger at most **two** fix+re-review rounds per task. **Advisory** findings (Minor, style, YAGNI, taste) never pause the loop — append them to `<SDD_DIR>/advisories.md` with the task id.
|
|
35
35
|
7. Append the validated ledger line with `workflow_sdd_append_progress` using `confirmed: true`, then mark the task completed with `todowrite`.
|
|
36
36
|
|
|
37
|
+
Each task lands exactly one contiguous non-empty commit range (`base..head`): fix rounds append commits to that range and never rewrite/amend an active review range; each progress line records the task's real base..head shas.
|
|
38
|
+
|
|
37
39
|
Never redispatch completed task IDs. Pass task briefs and review diffs to agents; do not make them reparse the plan. Keep commits on the in-place feature/bugfix branch.
|
|
38
40
|
|
|
39
41
|
## Final gate
|
|
40
42
|
|
|
41
43
|
After all remaining tasks, dispatch a final full-branch code review, run `workflow_verify`, and report exact per-check results. Present the full `<SDD_DIR>/advisories.md` roll-up once, then use native `question` so the user can choose which advisory items to fix, discuss, or discard. Only then may advisory fixes run. Use `workflow_git_context` for the final commit preview and the `wk-commit` skill for any approved commit. If a tracked stash reference exists, preview reapplication with `question`, then call `workflow_branch_setup` with `confirmed: true` only after approval.
|
|
44
|
+
|
|
45
|
+
**Mandatory:** end the run by calling `workflow_plan_complete` (OpenCode/Cursor) or the CLI `workit flow complete` (CLI host) after the final task once the SDD ledger is complete (all task IDs appended) and `workflow_verify` passes — a complete ledger and green verification are the tool's gates. Never finish the run while the plan is still `active`.
|
package/skills/wk-init/SKILL.md
CHANGED
|
@@ -7,10 +7,10 @@ disable-model-invocation: true
|
|
|
7
7
|
# Init
|
|
8
8
|
|
|
9
9
|
1. Load this skill explicitly through OpenCode's `skill` tool.
|
|
10
|
-
2. Call the read-only `
|
|
10
|
+
2. Call the read-only `workit_init_status` context tool; its result is ground truth.
|
|
11
11
|
3. Draft an exact preview of each missing scaffold and the active VCS provider.
|
|
12
12
|
4. For each guarded mutation, use native `question` with concise choices and allow a custom answer.
|
|
13
|
-
5. Call `
|
|
13
|
+
5. Call `workit_init_apply` only after approval with `confirmed: true` and the chosen action.
|
|
14
14
|
6. Report the structured success, failure stage, or partial result; never infer success.
|
|
15
15
|
|
|
16
16
|
Never ask for or accept tokens in chat. For missing YouTrack configuration, preview `youtrack_scaffold`; for missing VCS configuration, ask GitLab or GitHub, preview `vcs_scaffold`, and pass `vcs_provider`. Both provider token placeholders may be created, but identify which one is active. Show the returned token-create URL and local edit path after apply. Verification remains a separate `wk-status` run. `todowrite` and `task` are unnecessary here.
|
|
@@ -24,8 +24,8 @@ When the user wants to personalize the toolkit (locale, timezone, branch policy)
|
|
|
24
24
|
3. **Branch policy preset** — gitflow / github-flow / trunk-based / custom.
|
|
25
25
|
4. **Custom branch lists** (only when preset = custom): allowed patterns (`feature/*`, `codex/*`, …) and protected names (`main`, …).
|
|
26
26
|
|
|
27
|
-
Then call `
|
|
27
|
+
Then call `workit_init_apply` with `action: "config"`, `confirmed: true`, and the answered values. Report the written `config.json` path and its contents.
|
|
28
28
|
|
|
29
|
-
After config, call `
|
|
29
|
+
After config, call `workit_init_apply` with `action: "gitignore"` and `confirmed: true` to ensure the project `.gitignore` covers `docs/*/sdd/` and common OS/editor entries (creates it if missing, preserves existing entries).
|
|
30
30
|
|
|
31
|
-
After gitignore, call `
|
|
31
|
+
After gitignore, call `workit_init_apply` with `action: "hygiene"`, `confirmed: true`, and `include_open_source: true` for open-source repos — creates CHANGELOG.md (Keep a Changelog), README.md, .editorconfig, .gitattributes, and optionally LICENSE + CONTRIBUTING.md. Never overwrites existing files.
|
|
@@ -7,10 +7,10 @@ disable-model-invocation: true
|
|
|
7
7
|
# Status
|
|
8
8
|
|
|
9
9
|
1. Load this skill explicitly through OpenCode's `skill` tool.
|
|
10
|
-
2. Call the read-only `
|
|
10
|
+
2. Call the read-only `workit_status` context tool once; its result is ground truth.
|
|
11
11
|
3. Draft the health report from structured facts.
|
|
12
12
|
4. This workflow has no guarded mutation and does not need `question`.
|
|
13
13
|
5. Do not call any mutation tool.
|
|
14
14
|
6. Report the structured success, failure stage, or partial result; never infer success.
|
|
15
15
|
|
|
16
|
-
Show every `items[]` result, the resolved YouTrack settings and verification, the VCS provider and verification, and the exact `ready` verdict. For placeholders, point to the returned token creation and edit paths; never request tokens in chat. Use only `
|
|
16
|
+
Show every `items[]` result, the resolved YouTrack settings and verification, the VCS provider and verification, and the exact `ready` verdict. For placeholders, point to the returned token creation and edit paths; never request tokens in chat. Use only `workit_status` and report its structured state. Do not run shell, Git, direct HTTP, isolated token verification, or any mutation. `todowrite` and `task` are unnecessary for this read-only workflow.
|
package/src/core/doctor.ts
CHANGED
|
@@ -454,6 +454,24 @@ const registeredCursorHook = (res: Resolved): string | null | "invalid" => {
|
|
|
454
454
|
return typeof entry.command === "string" ? entry.command : "invalid";
|
|
455
455
|
};
|
|
456
456
|
|
|
457
|
+
// The local-dist install points the session-start hook at the plugin's own
|
|
458
|
+
// built dist (`node <pluginDir>/dist/cursor-session-start.js`) so Cursor runs
|
|
459
|
+
// the checkout's code. Accept that form — validated against the real dist entry
|
|
460
|
+
// — alongside the canonical npx pin, mirroring the MCP launcher's node form.
|
|
461
|
+
const validLocalDistHook = (command: string, res: Resolved): boolean => {
|
|
462
|
+
const m = /^node\s+(.+)$/.exec(command.trim());
|
|
463
|
+
if (!m) return false;
|
|
464
|
+
const entry = m[1].trim();
|
|
465
|
+
if (!path.isAbsolute(entry)) return false;
|
|
466
|
+
if (
|
|
467
|
+
path.resolve(entry) !==
|
|
468
|
+
path.resolve(path.join(res.cursorPluginDir, "dist", "cursor-session-start.js"))
|
|
469
|
+
) {
|
|
470
|
+
return false;
|
|
471
|
+
}
|
|
472
|
+
return validNodeEntry(entry, "node", res.env);
|
|
473
|
+
};
|
|
474
|
+
|
|
457
475
|
const checkLauncher = (res: Resolved): DoctorCheck => {
|
|
458
476
|
const dev = res.dev;
|
|
459
477
|
const hosts = hostsFor(res.host);
|
|
@@ -483,7 +501,7 @@ const checkLauncher = (res: Resolved): DoctorCheck => {
|
|
|
483
501
|
missing.push(
|
|
484
502
|
`cursor: canonical session-start hook in ${path.join(res.cursorPluginDir, "hooks", "hooks-cursor.json")}`,
|
|
485
503
|
);
|
|
486
|
-
} else if (hook !== null && hook !== canonicalCursorHook) {
|
|
504
|
+
} else if (hook !== null && hook !== canonicalCursorHook && !validLocalDistHook(hook, res)) {
|
|
487
505
|
missing.push(
|
|
488
506
|
`cursor: canonical session-start hook in ${path.join(res.cursorPluginDir, "hooks", "hooks-cursor.json")} (registered ${hook})`,
|
|
489
507
|
);
|
package/src/core/flow-state.ts
CHANGED
|
@@ -2032,7 +2032,7 @@ export const COORDINATOR_WRITE_TOOLS: readonly string[] = [
|
|
|
2032
2032
|
"workflow_template_edit",
|
|
2033
2033
|
"workflow_changelog_apply",
|
|
2034
2034
|
"workflow_branch_setup",
|
|
2035
|
-
"
|
|
2035
|
+
"workit_init_apply",
|
|
2036
2036
|
"workflow_docs_promote",
|
|
2037
2037
|
"workflow_docs_layout",
|
|
2038
2038
|
"workflow_docs_repo_link",
|
package/src/core/init.ts
CHANGED
|
@@ -122,7 +122,7 @@ export function initStatusData(configDirPath = configDir()): Record<string, any>
|
|
|
122
122
|
ok: fs.existsSync(ytJson) && youtrackConfig !== null && !("error" in youtrackConfig),
|
|
123
123
|
path: fs.existsSync(ytJson) ? resolvePath(ytJson) : path.resolve(ytJson),
|
|
124
124
|
config_edit_path: resolvePath(ytJson),
|
|
125
|
-
fix: "
|
|
125
|
+
fix: "workit_init_apply action=youtrack_scaffold",
|
|
126
126
|
});
|
|
127
127
|
|
|
128
128
|
const ytTokenPath = youtrackConfig?.tokenFile ?? path.join(configDirPath, "youtrack.token");
|
|
@@ -192,7 +192,7 @@ export function initStatusData(configDirPath = configDir()): Record<string, any>
|
|
|
192
192
|
ok: fs.existsSync(vcsJson) && vcsCfg !== null && !("error" in vcsCfg),
|
|
193
193
|
path: fs.existsSync(vcsJson) ? resolvePath(vcsJson) : path.resolve(vcsJson),
|
|
194
194
|
config_edit_path: resolvePath(vcsJson),
|
|
195
|
-
fix: "
|
|
195
|
+
fix: "workit_init_apply action=vcs_scaffold",
|
|
196
196
|
});
|
|
197
197
|
|
|
198
198
|
const provActive = vcsCfg && !("error" in vcsCfg) ? vcsCfg.provider : null;
|
package/src/core/registration.ts
CHANGED
|
@@ -184,7 +184,7 @@ export function mergeCursorHooks(
|
|
|
184
184
|
* The single source for every source-derived Cursor runtime selector; the
|
|
185
185
|
* committed manifests keep the literal (static data cannot import TS).
|
|
186
186
|
*/
|
|
187
|
-
export const CURSOR_RUNTIME_PACKAGE = "@brainervirus/workit-cursor@0.8.
|
|
187
|
+
export const CURSOR_RUNTIME_PACKAGE = "@brainervirus/workit-cursor@0.8.5";
|
|
188
188
|
|
|
189
189
|
/**
|
|
190
190
|
* Portable Cursor MCP server entry (CA-16/CA-17): launch the published package
|
|
@@ -214,3 +214,34 @@ export function cursorHooksEntry(_packageDir: string): {
|
|
|
214
214
|
args: [],
|
|
215
215
|
};
|
|
216
216
|
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Local-dev Cursor MCP server entry: run the installed plugin's own built dist
|
|
220
|
+
* through node instead of the published npx pin, so a checkout install runs the
|
|
221
|
+
* branch's code (rename included) without waiting for a release. The committed
|
|
222
|
+
* Marketplace manifests keep the reviewed pin; this form is written by the
|
|
223
|
+
* local install path only.
|
|
224
|
+
*/
|
|
225
|
+
export function cursorMcpLocalDistEntry(packageDir: string): {
|
|
226
|
+
command: string;
|
|
227
|
+
args: string[];
|
|
228
|
+
} {
|
|
229
|
+
return {
|
|
230
|
+
command: "node",
|
|
231
|
+
args: [path.join(packageDir, "dist", "mcp-server.js"), "${workspaceFolder}"],
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Local-dev Cursor sessionStart hook entry: node against the installed dist, in
|
|
237
|
+
* Cursor's documented single-command-string hook format (no args array).
|
|
238
|
+
*/
|
|
239
|
+
export function cursorHookLocalDistEntry(packageDir: string): {
|
|
240
|
+
command: string;
|
|
241
|
+
args: string[];
|
|
242
|
+
} {
|
|
243
|
+
return {
|
|
244
|
+
command: `node ${path.join(packageDir, "dist", "cursor-session-start.js")}`,
|
|
245
|
+
args: [],
|
|
246
|
+
};
|
|
247
|
+
}
|
package/src/core/sdd.ts
CHANGED
|
@@ -221,8 +221,17 @@ export function sddReviewPackage({
|
|
|
221
221
|
}) {
|
|
222
222
|
const contained = resolveDocsPath({ workspace_root, path: sdd_dir });
|
|
223
223
|
if (!contained.ok) return { error: contained.error };
|
|
224
|
+
// Empty-range guard (Phase 2): a review package over `base..head` where the
|
|
225
|
+
// endpoints collapse to nothing is a contract error, not an artifact. Reject
|
|
226
|
+
// BEFORE any diff file is created or written (Task 1's RED evidence watches
|
|
227
|
+
// the guard, and the adapters only surface this core error — never a copy).
|
|
228
|
+
if (base_sha === head_sha) {
|
|
229
|
+
return {
|
|
230
|
+
error: `empty commit range (base_sha ${base_sha} === head_sha ${head_sha}): nothing to review`,
|
|
231
|
+
code: "empty_commit_range",
|
|
232
|
+
};
|
|
233
|
+
}
|
|
224
234
|
const dir = contained.path;
|
|
225
|
-
mkdirSync(dir, { recursive: true });
|
|
226
235
|
const base7 = base_sha.slice(0, 7);
|
|
227
236
|
const head7 = head_sha.slice(0, 7);
|
|
228
237
|
const diffPath = path.join(dir, `review-${base7}..${head7}.diff`);
|
|
@@ -234,6 +243,17 @@ export function sddReviewPackage({
|
|
|
234
243
|
// captured error, never in the suite output.
|
|
235
244
|
stdio: ["pipe", "pipe", "pipe"],
|
|
236
245
|
});
|
|
246
|
+
// Distinct shas that still diff to nothing are an empty range too (the
|
|
247
|
+
// "or a diff that is empty" clause): no artifact is written for it, and
|
|
248
|
+
// the sdd/ dir is created only once a real diff survives (an empty-range
|
|
249
|
+
// rejection must leave no directory behind).
|
|
250
|
+
if (diff.trim() === "") {
|
|
251
|
+
return {
|
|
252
|
+
error: `empty commit range (${base_sha}..${head_sha}): diff is empty, nothing to review`,
|
|
253
|
+
code: "empty_commit_range",
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
mkdirSync(dir, { recursive: true });
|
|
237
257
|
writeFileSync(diffPath, diff, "utf8");
|
|
238
258
|
const rel = posix(path.relative(contained.base, diffPath));
|
|
239
259
|
return { diff_path: rel, base_sha, head_sha, base7, head7 };
|
|
@@ -242,7 +262,7 @@ export function sddReviewPackage({
|
|
|
242
262
|
}
|
|
243
263
|
}
|
|
244
264
|
|
|
245
|
-
const PROGRESS_RE = /^Task\s+\d+:\s+complete\s+\(commits\s+[0-9a-f]{7,40}\.\.[0-9a-f]{7,40},/i;
|
|
265
|
+
const PROGRESS_RE = /^Task\s+\d+:\s+complete\s+\(commits\s+([0-9a-f]{7,40})\.\.([0-9a-f]{7,40}),/i;
|
|
246
266
|
|
|
247
267
|
export function sddAppendProgress({
|
|
248
268
|
progress_path,
|
|
@@ -257,7 +277,11 @@ export function sddAppendProgress({
|
|
|
257
277
|
if (!contained.ok) return { error: contained.error };
|
|
258
278
|
const path_ = contained.path;
|
|
259
279
|
const trimmed = line.trim();
|
|
260
|
-
|
|
280
|
+
const match = PROGRESS_RE.exec(trimmed);
|
|
281
|
+
// A regex alone cannot compare two groups: reject identical base/head shas
|
|
282
|
+
// after the format matches so `5db04ae..bd0a810` still passes while
|
|
283
|
+
// `abcdef0123456789..abcdef0123456789` is rejected (Phase 2).
|
|
284
|
+
if (!match || match[1].toLowerCase() === match[2].toLowerCase()) {
|
|
261
285
|
return { error: "invalid progress line format" };
|
|
262
286
|
}
|
|
263
287
|
if (existsSync(path_) && statSync(path_).isDirectory()) {
|
package/src/core/setup.ts
CHANGED
|
@@ -710,7 +710,7 @@ function applyMutation(m: CoreMutation): SetupResultEntry {
|
|
|
710
710
|
// Package-native adapter resolution: the packaged CLI/core resolve the adapter
|
|
711
711
|
// packages from the same install (node_modules sibling), a dev checkout
|
|
712
712
|
// (WORKFLOW_TOOLKIT_DEV / packages/workit-<host> walking up from cwd), or the
|
|
713
|
-
// share clone (~/.local/share/
|
|
713
|
+
// share clone (~/.local/share/workit) — mirroring the install scripts.
|
|
714
714
|
const isAdapter = (root: string, platform: Platform): boolean => {
|
|
715
715
|
try {
|
|
716
716
|
const pkg = JSON.parse(readFileSync(path.join(root, "package.json"), "utf8")) as {
|
|
@@ -742,7 +742,7 @@ function adapterRoot(platform: Platform, res: ResolvedApply): string | null {
|
|
|
742
742
|
}
|
|
743
743
|
}
|
|
744
744
|
candidates.push(
|
|
745
|
-
path.join(res.home, ".local", "share", "
|
|
745
|
+
path.join(res.home, ".local", "share", "workit", "packages", `workit-${platform}`),
|
|
746
746
|
);
|
|
747
747
|
for (const candidate of candidates) {
|
|
748
748
|
if (isAdapter(candidate, platform)) return candidate;
|
|
@@ -805,7 +805,7 @@ function applyOpenCode(root: string, res: ResolvedApply): SetupResultEntry {
|
|
|
805
805
|
}
|
|
806
806
|
|
|
807
807
|
// Mirror the sync-runtime plugin mirror: copy the package (minus node_modules)
|
|
808
|
-
// and write the same `.
|
|
808
|
+
// and write the same `.workit-root` marker so a re-sync from the same
|
|
809
809
|
// source is a truthful Skipped. The shipped mcp.json is the Marketplace-safe
|
|
810
810
|
// npx command (CA-17), so it is copied verbatim — no absolute dist derivation.
|
|
811
811
|
const samePluginContent = (src: string, dest: string, relative = ""): boolean => {
|
|
@@ -827,7 +827,7 @@ const samePluginContent = (src: string, dest: string, relative = ""): boolean =>
|
|
|
827
827
|
for (const entry of readdirSync(dest, { withFileTypes: true })) {
|
|
828
828
|
if (existsSync(path.join(src, entry.name))) continue;
|
|
829
829
|
const rel = path.join(relative, entry.name);
|
|
830
|
-
if (rel === ".
|
|
830
|
+
if (rel === ".workit-root") continue;
|
|
831
831
|
if (relative === "rules" && entry.isFile() && entry.name.endsWith(".mdc")) continue;
|
|
832
832
|
return false;
|
|
833
833
|
}
|
|
@@ -854,7 +854,7 @@ const preservedCursorRules = (src: string, dest: string): Map<string, Buffer> =>
|
|
|
854
854
|
};
|
|
855
855
|
|
|
856
856
|
function copyPluginDir(src: string, dest: string): SetupResultStatus {
|
|
857
|
-
const marker = path.join(dest, ".
|
|
857
|
+
const marker = path.join(dest, ".workit-root");
|
|
858
858
|
const synced = readFileSafe(marker)?.trim() === src && samePluginContent(src, dest);
|
|
859
859
|
if (synced) return "Skipped";
|
|
860
860
|
const hadDir = existsSync(dest);
|
|
@@ -873,7 +873,7 @@ function copyPluginDir(src: string, dest: string): SetupResultStatus {
|
|
|
873
873
|
mkdirSync(path.join(stage, "rules"), { recursive: true });
|
|
874
874
|
writeFileSync(path.join(stage, "rules", name), content);
|
|
875
875
|
}
|
|
876
|
-
writeFileSync(path.join(stage, ".
|
|
876
|
+
writeFileSync(path.join(stage, ".workit-root"), src + "\n", "utf8");
|
|
877
877
|
if (!samePluginContent(src, stage)) throw new Error("staged adapter content is incomplete");
|
|
878
878
|
if (hadDir) renameSync(dest, backup);
|
|
879
879
|
try {
|
|
@@ -892,7 +892,7 @@ function copyPluginDir(src: string, dest: string): SetupResultStatus {
|
|
|
892
892
|
// CA-08/CA-09: remove the exact legacy local plugin identity only AFTER the
|
|
893
893
|
// canonical `workit` copy and registration succeeded. The legacy dir's own
|
|
894
894
|
// user-compiled rules are carried forward first so no user data is lost; the
|
|
895
|
-
// `.
|
|
895
|
+
// `.workit-root` marker, share path, and unrelated sibling dirs are
|
|
896
896
|
// left untouched.
|
|
897
897
|
const removeLegacyCursorDir = (res: ResolvedApply): void => {
|
|
898
898
|
const legacy = path.join(res.home, ".cursor", "plugins", "local", "workflow-toolkit");
|
package/src/core/sync-runtime.ts
CHANGED
|
@@ -107,7 +107,7 @@ export async function syncRuntime(options: SyncRuntimeOptions = {}): Promise<Syn
|
|
|
107
107
|
env.WORKFLOW_TOOLKIT_DEV ??
|
|
108
108
|
path.join(home, "Documents/projects/personal/workflow-toolkit");
|
|
109
109
|
const repoSlug = options.repoSlug ?? env.WORKFLOW_TOOLKIT_REPO ?? "BrainerVirus/workit";
|
|
110
|
-
const share = path.join(home, ".local/share/
|
|
110
|
+
const share = path.join(home, ".local/share/workit");
|
|
111
111
|
const pluginDir = path.join(home, ".cursor/plugins/local/workit");
|
|
112
112
|
const opencodePlugins = path.join(home, ".config/opencode/plugins");
|
|
113
113
|
const lock = path.join(
|
|
@@ -271,10 +271,7 @@ export async function syncRuntime(options: SyncRuntimeOptions = {}): Promise<Syn
|
|
|
271
271
|
);
|
|
272
272
|
}
|
|
273
273
|
}
|
|
274
|
-
writeFileSync(
|
|
275
|
-
path.join(pluginDir, ".workflow-toolkit-root"),
|
|
276
|
-
`${share}/packages/workit-core\n`,
|
|
277
|
-
);
|
|
274
|
+
writeFileSync(path.join(pluginDir, ".workit-root"), `${share}/packages/workit-core\n`);
|
|
278
275
|
|
|
279
276
|
// CA-08/CA-09: migrate the legacy local plugin identity to `workit` only
|
|
280
277
|
// after the canonical sync succeeded; carry the legacy user rules forward
|
|
@@ -59,6 +59,8 @@ For each top-level task absent from `completed_task_ids`:
|
|
|
59
59
|
|
|
60
60
|
Run a separate full-branch code review, then `workflow_verify`. Present the full `<SDD_DIR>/advisories.md` roll-up once, then use native `question` so the user can choose which advisory items to fix, discuss, or discard. Report exact check results and never infer success. Use `workflow_git_context` for a commit preview and load `wk-commit` through `skill` for an approved commit. If working state contains a stash reference, preview reapplication through `question`, then call `workflow_branch_setup` with `confirmed: true` after approval.
|
|
61
61
|
|
|
62
|
+
**Mandatory:** end the run by calling `workflow_plan_complete` (OpenCode/Cursor) or the CLI `workit flow complete` (CLI host) after the final task once the SDD ledger is complete (all task IDs appended) and `workflow_verify` passes — a complete ledger and green verification are the tool's gates. Never finish the run while the plan is still `active`.
|
|
63
|
+
|
|
62
64
|
## Task order
|
|
63
65
|
|
|
64
66
|
<TASK_LIST>
|
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
|
|
10
10
|
## Global Constraints
|
|
11
11
|
|
|
12
|
+
- Each task lands exactly one contiguous non-empty commit range (`base..head`): fix rounds append commits to that range and never rewrite/amend an active review range; each progress line records the task's real base..head shas.
|
|
13
|
+
- The final task ends execution with `workflow_plan_complete` (or the CLI `workit flow complete`) once the SDD ledger is complete and repository verification passes — a run never finishes while the plan is still `active`.
|
|
12
14
|
- <project-wide requirements, one line each>
|
|
13
15
|
|
|
14
16
|
---
|
|
@@ -63,6 +63,7 @@ digraph process {
|
|
|
63
63
|
"Read plan, note context and global constraints, create todos" [shape=box];
|
|
64
64
|
"More tasks remain?" [shape=diamond];
|
|
65
65
|
"Dispatch final code reviewer subagent (../requesting-code-review/code-reviewer.md)" [shape=box];
|
|
66
|
+
"Run verification, then workflow_plan_complete once the ledger is complete and verification passes" [shape=box];
|
|
66
67
|
"Use superpowers:finishing-a-development-branch" [shape=box style=filled fillcolor=lightgreen];
|
|
67
68
|
|
|
68
69
|
"Read plan, note context and global constraints, create todos" -> "Dispatch implementer subagent (./implementer-prompt.md)";
|
|
@@ -78,10 +79,13 @@ digraph process {
|
|
|
78
79
|
"Mark task complete in todo list and progress ledger" -> "More tasks remain?";
|
|
79
80
|
"More tasks remain?" -> "Dispatch implementer subagent (./implementer-prompt.md)" [label="yes"];
|
|
80
81
|
"More tasks remain?" -> "Dispatch final code reviewer subagent (../requesting-code-review/code-reviewer.md)" [label="no"];
|
|
81
|
-
"Dispatch final code reviewer subagent (../requesting-code-review/code-reviewer.md)" -> "
|
|
82
|
+
"Dispatch final code reviewer subagent (../requesting-code-review/code-reviewer.md)" -> "Run verification, then workflow_plan_complete once the ledger is complete and verification passes";
|
|
83
|
+
"Run verification, then workflow_plan_complete once the ledger is complete and verification passes" -> "Use superpowers:finishing-a-development-branch";
|
|
82
84
|
}
|
|
83
85
|
```
|
|
84
86
|
|
|
87
|
+
**Mandatory completion:** after the final whole-branch review, run repository verification. Once the SDD ledger is complete and verification passes, end the run by calling `workflow_plan_complete` (or the CLI `workit flow complete`) — a complete ledger and green verification are the tool's gates. Never finish while the plan is still `active`.
|
|
88
|
+
|
|
85
89
|
## Pre-Flight Plan Review
|
|
86
90
|
|
|
87
91
|
Before dispatching Task 1, scan the plan once for conflicts:
|
|
@@ -257,6 +261,10 @@ a ledger file, not only in todos.
|
|
|
257
261
|
- When a task's review comes back clean, append one line to the ledger in
|
|
258
262
|
the same message as your other bookkeeping:
|
|
259
263
|
`Task N: complete (commits <base7>..<head7>, review clean)`.
|
|
264
|
+
- Each task produces exactly one contiguous non-empty commit range: fix
|
|
265
|
+
rounds append commits to it, never amend or rewrite an active review
|
|
266
|
+
range, and the ledger progress line records the task's real base..head
|
|
267
|
+
shas.
|
|
260
268
|
- The ledger is your recovery map: the commits it names exist in git even
|
|
261
269
|
when your context no longer remembers creating them. After compaction,
|
|
262
270
|
trust the ledger and `git log` over your own recollection.
|