@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brainervirus/workit-core",
3
- "version": "0.8.5",
3
+ "version": "0.8.6",
4
4
  "private": false,
5
5
  "description": "Workit — workflow rails for agentic coding: specs, plans, YouTrack, CI-gated commits (shared core)",
6
6
  "keywords": [
@@ -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/workflow-toolkit"
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
- const { mergeCursorSettings, mergeCursorMcp, cursorMcpServerEntry } =
59
- await import(process.env.REGISTRATION_TS!);
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 via the published npx bin;
70
- // stale "workflow-toolkit" entries removed.
71
- const server = cursorMcpServerEntry(process.env.PLUGIN_DIR!);
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/workflow-toolkit"
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.0 workit-cursor-mcp "$@"
6
+ exec npx -y --package=@brainervirus/workit-cursor@0.8.5 workit-cursor-mcp "$@"
@@ -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/workflow-toolkit"
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/.workflow-toolkit-root"
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`.
@@ -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 `workflow_toolkit_init_status` context tool; its result is ground truth.
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 `workflow_toolkit_init_apply` only after approval with `confirmed: true` and the chosen action.
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 `workflow_toolkit_init_apply` with `action: "config"`, `confirmed: true`, and the answered values. Report the written `config.json` path and its contents.
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 `workflow_toolkit_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).
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 `workflow_toolkit_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.
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 `workflow_toolkit_status` context tool once; its result is ground truth.
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 `workflow_toolkit_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.
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.
@@ -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
  );
@@ -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
- "workflow_toolkit_init_apply",
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: "workflow_toolkit_init_apply action=youtrack_scaffold",
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: "workflow_toolkit_init_apply action=vcs_scaffold",
195
+ fix: "workit_init_apply action=vcs_scaffold",
196
196
  });
197
197
 
198
198
  const provActive = vcsCfg && !("error" in vcsCfg) ? vcsCfg.provider : null;
@@ -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.0";
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
- if (!PROGRESS_RE.test(trimmed)) {
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/workflow-toolkit) — mirroring the install scripts.
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", "workflow-toolkit", "packages", `workit-${platform}`),
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 `.workflow-toolkit-root` marker so a re-sync from 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 === ".workflow-toolkit-root") continue;
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, ".workflow-toolkit-root");
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, ".workflow-toolkit-root"), src + "\n", "utf8");
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
- // `.workflow-toolkit-root` marker, share path, and unrelated sibling dirs are
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");
@@ -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/workflow-toolkit");
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)" -> "Use superpowers:finishing-a-development-branch";
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.