@brainervirus/workit-core 0.5.5 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,12 +4,12 @@ Multi-platform Superpowers workflow plugin for **Cursor** and **OpenCode**: veri
4
4
 
5
5
  | Platform | Path | Version |
6
6
  | --- | --- | --- |
7
- | **OpenCode** | `packages/workit-opencode/src/plugin.ts` | 0.4.0 |
8
- | **Cursor** | `packages/workit-cursor/` (MCP + hooks + rules + skills) | 0.4.0 |
9
- | **Shared core** | `packages/workit-core/` (src, skills, commands, scripts, templates) | 0.4.0 |
10
- | **CLI** | `packages/workit-cli/` (Ink wizard, bin `workit`) | 0.4.0 |
7
+ | **OpenCode** | `packages/workit-opencode/src/plugin.ts` | ![npm](https://img.shields.io/npm/v/@brainervirus/workit-opencode) |
8
+ | **Cursor** | `packages/workit-cursor/` (MCP + hooks + rules + skills) | ![npm](https://img.shields.io/npm/v/@brainervirus/workit-cursor) |
9
+ | **Shared core** | `packages/workit-core/` (src, skills, commands, scripts, templates) | ![npm](https://img.shields.io/npm/v/@brainervirus/workit-core) |
10
+ | **CLI** | `packages/workit-cli/` (Ink wizard, bin `workit`) | ![npm](https://img.shields.io/npm/v/@brainervirus/workit-cli) |
11
11
 
12
- Config directory (both platforms): `~/.config/workflow-toolkit/` (kept as-is for install stability).
12
+ Config directory (both platforms): `~/.config/workit/` — legacy `~/.config/workflow-toolkit/` is auto-migrated on first run and kept as a fallback.
13
13
 
14
14
  [![npm version](https://img.shields.io/npm/v/@brainervirus/workit-opencode)](https://www.npmjs.com/package/@brainervirus/workit-opencode)
15
15
  [![CI](https://img.shields.io/github/actions/workflow/status/BrainerVirus/workit/ci.yml?branch=main&label=CI)](https://github.com/BrainerVirus/workit/actions)
@@ -119,7 +119,6 @@ GitHub Actions:
119
119
 
120
120
  - **CI** — on push/PR to `main`: matrix of 3 OS (ubuntu, macos, windows) running `actions/checkout@v7` + `oven-sh/setup-bun@v2`, then `bun install --frozen-lockfile` + `bun run check`
121
121
  - **Release** — on push to `main`: [semantic-release](https://github.com/semantic-release/semantic-release) computes the next version from Conventional Commits, publishes the four workspaces to npm in dependency order — `workit-core`, `workit-opencode`, `workit-cursor`, `workit-cli` (with provenance), and creates the git tag + GitHub Release
122
- - **Code review** — on every PR: the [OpenCode GitHub integration](https://opencode.ai/docs/github/) reviews the diff (see [Code review](#code-review))
123
122
 
124
123
  No manual tags — semantic-release owns the version/tag flow:
125
124
 
@@ -127,22 +126,10 @@ No manual tags — semantic-release owns the version/tag flow:
127
126
  git push origin main
128
127
  ```
129
128
 
130
- ## Code review
129
+ ## Quality gates
131
130
 
132
- Every pull request is automatically reviewed by [OpenCode](https://opencode.ai/docs/github/) via `.github/workflows/opencode-review.yml` it checks code quality, potential bugs, and this repo's own standards (the spec/plan contract, Conventional Commits, the CONTRIBUTING.md review flow). Model: `opencode-go/deepseek-v4-flash`.
133
-
134
- Checks fall into two categories:
135
-
136
- - **BLOCKING — the `pull_request` review** (`review` job): runs on every PR (opened/synchronized/reopened/ready_for_review) and is registered as a required check in branch protection — the PR cannot merge while it's red.
137
- - **ADVISORY — on-demand and triage flows** (never block merge): mention `/oc` or `/opencode` in a comment — issue, PR thread, or a specific line in the Files tab — to have OpenCode fix, explain, or update the PR (`oc` job); new issues are triaged with guidance and doc links (`triage` job).
138
-
139
- To activate the review on a fork or fresh clone:
140
-
141
- 1. Install the [OpenCode GitHub app](https://github.com/apps/opencode-agent) on the repository.
142
- 2. Add the `OPENCODE_API_KEY` secret under Settings → Secrets and variables → Actions.
143
- 3. PRs trigger the review on opened/synchronized/reopened events automatically.
144
-
145
- [Cursor Bugbot](https://cursor.com/dashboard/bugbot) is an optional alternative to the OpenCode review.
131
+ - **CI checks** the per-package check jobs (workit-core/opencode/cursor/cli/shared) are required in branch protection; the PR cannot merge while any is red.
132
+ - **Subagent review** — `wk-implement` runs a two-stage review (spec compliance + code quality) per task with fresh `general` agents.
146
133
 
147
134
  ## Architecture
148
135
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brainervirus/workit-core",
3
- "version": "0.5.5",
3
+ "version": "0.6.0",
4
4
  "private": false,
5
5
  "description": "Workit — workflow rails for agentic coding: specs, plans, YouTrack, CI-gated commits (shared core)",
6
6
  "license": "MIT",
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env bash
2
+ # Resolve the config dir (workit default) with one-time legacy migration.
3
+ # Mirrors src/core/config.ts resolveConfigDir + ensureConfigDir.
4
+ # Env overrides bypass migration (explicit user intent wins); migration is
5
+ # idempotent: only runs when workit/ is absent; never overwrites, never
6
+ # deletes the legacy workflow-toolkit/ dir.
7
+ resolve_config_dir() {
8
+ if [ -n "${WORKFLOW_TOOLKIT_CONFIG:-}" ]; then
9
+ printf '%s\n' "$WORKFLOW_TOOLKIT_CONFIG"
10
+ return
11
+ fi
12
+ if [ -n "${WORKFLOW_TOOLKIT_CONFIG_DIR:-}" ]; then
13
+ printf '%s\n' "$WORKFLOW_TOOLKIT_CONFIG_DIR"
14
+ return
15
+ fi
16
+ local base="${XDG_CONFIG_HOME:-$HOME/.config}"
17
+ local NEW="$base/workit"
18
+ local LEGACY="$base/workflow-toolkit"
19
+ if [ ! -d "$NEW" ] && [ -d "$LEGACY" ]; then
20
+ mkdir -p "$NEW"
21
+ if ! cp -r "$LEGACY/." "$NEW/"; then
22
+ printf 'workit: warning: partial config migration from %s (kept in place; retry on next run)\n' "$LEGACY" >&2
23
+ fi
24
+ fi
25
+ printf '%s\n' "$NEW"
26
+ }
@@ -1,8 +1,11 @@
1
1
  #!/usr/bin/env bun
2
- // Rewrite workspace:* core deps to ^<released version> in the platform packages
3
- // before publish: neither npm nor bun rewrites the protocol, so a packed tarball
4
- // with workspace:* silently drops the core dependency. Runs as semantic-release
5
- // prepareCmd (after the version bumps, before the npm publish phase).
2
+ // Release-sync before publish: rewrite workspace:* core deps to ^<released
3
+ // version> in the platform packages, and mirror the core version + canonical
4
+ // URLs into the cursor plugin/marketplace manifests. Neither npm nor bun
5
+ // rewrites workspace:*, so a packed tarball with it silently drops the core
6
+ // dependency. Runs as semantic-release prepareCmd (after the version bumps,
7
+ // before the npm publish phase); the writes stay in CI and never reach the
8
+ // repo (semantic-release does not commit back).
6
9
  import { readFileSync, writeFileSync } from "node:fs";
7
10
  import { resolve } from "node:path";
8
11
 
@@ -15,3 +18,13 @@ for (const pkg of ["workit-opencode", "workit-cursor", "workit-cli"]) {
15
18
  data.dependencies["@brainervirus/workit-core"] = `^${core.version}`;
16
19
  writeFileSync(file, `${JSON.stringify(data, null, 2)}\n`);
17
20
  }
21
+ for (const file of [
22
+ resolve(root, "packages/workit-cursor/.cursor-plugin/plugin.json"),
23
+ resolve(root, "packages/workit-cursor/marketplace.json"),
24
+ ]) {
25
+ const data = JSON.parse(readFileSync(file, "utf8"));
26
+ data.version = core.version;
27
+ if (data.homepage) data.homepage = data.homepage.replace("BrainerVirus/workflow-toolkit", "BrainerVirus/workit");
28
+ if (data.repository) data.repository = data.repository.replace("BrainerVirus/workflow-toolkit", "BrainerVirus/workit");
29
+ writeFileSync(file, `${JSON.stringify(data, null, 2)}\n`);
30
+ }
@@ -3,6 +3,9 @@
3
3
  # Prefer local monorepo (dev) when present; otherwise git-pull the share clone.
4
4
  set -euo pipefail
5
5
 
6
+ SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
7
+ . "$SCRIPT_DIR/lib/config-dir.sh"
8
+
6
9
  SHARE="${HOME}/.local/share/workflow-toolkit"
7
10
  PLUGIN_DIR="${HOME}/.cursor/plugins/local/workflow-toolkit"
8
11
  OPENCODE_PLUGINS="${HOME}/.config/opencode/plugins"
@@ -54,7 +57,7 @@ if [ -d "$SHARE/packages/workit-core/vendor/superpowers/skills" ]; then
54
57
  rsync -a --delete "$SHARE/packages/workit-core/vendor/superpowers/skills" "$PLUGIN_DIR/vendor/superpowers/"
55
58
  fi
56
59
  # Canonical user rules -> Cursor .mdc (compiled by the shared core)
57
- CONFIG_RULES_DIR="${HOME}/.config/workflow-toolkit/rules"
60
+ CONFIG_RULES_DIR="$(resolve_config_dir)/rules"
58
61
  if [ -d "$CONFIG_RULES_DIR" ]; then
59
62
  "$HOME/.bun/bin/bun" -e "
60
63
  import('${SHARE}/packages/workit-core/src/core/rules.ts').then(async ({ writeCompiledCursorRules }) => {
@@ -1,4 +1,4 @@
1
- import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
1
+ import { copyFileSync, cpSync, existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
2
2
  import os from "node:os";
3
3
  import path from "node:path";
4
4
 
@@ -18,10 +18,57 @@ export const PRESETS: Record<BranchPreset, { allowed: string[]; protected: strin
18
18
  custom: { allowed: [], protected: [] },
19
19
  };
20
20
 
21
- export const configDir = (): string =>
21
+ export const resolveConfigDir = (): string =>
22
22
  process.env.WORKFLOW_TOOLKIT_CONFIG
23
23
  ?? process.env.WORKFLOW_TOOLKIT_CONFIG_DIR
24
- ?? path.join(process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config"), "workflow-toolkit");
24
+ ?? path.join(process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config"), "workit");
25
+
26
+ // One-time lazy migration from the legacy ~/.config/workflow-toolkit dir.
27
+ // migratedDir remembers the resolved dir already checked: configDir() is on
28
+ // hot paths, so subsequent calls are one string compare. Re-checking per
29
+ // unique dir also keeps tests with swapped env working.
30
+ // ponytail: cache keyed by dir value — an env override explicitly set to the
31
+ // default path caches before migration could trigger; only matters if that
32
+ // env is cleared mid-process (next unique dir value re-checks).
33
+ let migratedDir: string | null = null;
34
+ // A mid-loop copy failure leaves the new dir half-populated; keep retrying
35
+ // until a full pass succeeds instead of silently skipping the failed entry.
36
+ let migrationFailed = false;
37
+
38
+ export const ensureConfigDir = (dir: string = resolveConfigDir()): string => {
39
+ if (migratedDir === dir) return dir;
40
+ if (process.env.WORKFLOW_TOOLKIT_CONFIG || process.env.WORKFLOW_TOOLKIT_CONFIG_DIR) {
41
+ migratedDir = dir;
42
+ return dir;
43
+ }
44
+ const legacy = path.join(process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config"), "workflow-toolkit");
45
+ if (!existsSync(legacy)) {
46
+ migratedDir = dir;
47
+ return dir;
48
+ }
49
+ if (!migrationFailed && existsSync(dir)) {
50
+ migratedDir = dir;
51
+ return dir;
52
+ }
53
+ migrationFailed = false;
54
+ mkdirSync(dir, { recursive: true });
55
+ for (const entry of readdirSync(legacy, { withFileTypes: true })) {
56
+ const src = path.join(legacy, entry.name);
57
+ const dest = path.join(dir, entry.name);
58
+ if (existsSync(dest)) continue;
59
+ try {
60
+ if (entry.isDirectory()) cpSync(src, dest, { recursive: true });
61
+ else if (entry.isFile()) copyFileSync(src, dest);
62
+ } catch (err) {
63
+ migrationFailed = true;
64
+ console.warn(`[workit] config migration: failed to copy ${src}: ${(err as Error).message}`);
65
+ }
66
+ }
67
+ if (!migrationFailed) migratedDir = dir;
68
+ return dir;
69
+ };
70
+
71
+ export const configDir = (): string => ensureConfigDir();
25
72
 
26
73
  export const LOCALE_RE = /^[a-z]{2,3}(-[A-Z]{2})?$/;
27
74
 
@@ -1,11 +1,11 @@
1
1
  import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync } from "node:fs";
2
2
  import { execFileSync } from "node:child_process";
3
- import os from "node:os";
4
3
  import path from "node:path";
4
+ import { configDir } from "./config";
5
5
 
6
6
  const configPath = () =>
7
7
  process.env.WORKFLOW_DOCS_REPO_CONFIG
8
- ?? path.join(os.homedir(), ".config", "workflow-toolkit", "docs-repo.json");
8
+ ?? path.join(configDir(), "docs-repo.json");
9
9
 
10
10
  export const readDocsRepoConfig = (): { path: string } | null => {
11
11
  try {
@@ -1,5 +1,4 @@
1
1
  import fs from "node:fs";
2
- import os from "node:os";
3
2
  import path from "node:path";
4
3
  import { spawnSync } from "node:child_process";
5
4
  import { configDir } from "./config";
@@ -69,7 +68,7 @@ export function vcsConfig(mode: "load" | "summary" | "resolve", cwd?: string): R
69
68
  if (!cfgOk) return { ok: false, error: "missing or invalid vcs.json" };
70
69
 
71
70
  const prov = (cfg[provider] ?? {}) as Record<string, any>;
72
- const tokenFile = String(prov.tokenFile ?? path.join(os.homedir(), ".config", "workflow-toolkit", `${provider}.token`));
71
+ const tokenFile = String(prov.tokenFile ?? path.join(configDir(), `${provider}.token`));
73
72
  const tokenPath = path.resolve(tokenFile);
74
73
  let tokenOk = false;
75
74
  if (fs.existsSync(tokenPath)) {
@@ -170,13 +169,13 @@ export function vcsTokenCreateUrls(): Record<string, any> {
170
169
  const provider = String(cfg.provider ?? "gitlab").toLowerCase();
171
170
  const active = {
172
171
  gitlab: {
173
- tokenFile: gitlab.tokenFile ?? path.join(os.homedir(), ".config", "workflow-toolkit", "gitlab.token"),
172
+ tokenFile: gitlab.tokenFile ?? path.join(configDir(), "gitlab.token"),
174
173
  createUrl: gitlabUrl,
175
174
  scopes: gitlabScopes,
176
175
  name,
177
176
  },
178
177
  github: {
179
- tokenFile: (cfg.github as Record<string, any>)?.tokenFile ?? path.join(os.homedir(), ".config", "workflow-toolkit", "github.token"),
178
+ tokenFile: (cfg.github as Record<string, any>)?.tokenFile ?? path.join(configDir(), "github.token"),
180
179
  createUrl: githubFineUrl,
181
180
  createUrlClassic: githubClassicUrl,
182
181
  permissions: githubPerms,
@@ -1,18 +1,18 @@
1
1
  import fs from "node:fs";
2
- import os from "node:os";
3
2
  import path from "node:path";
4
3
  import { spawnSync } from "node:child_process";
5
4
  import { readTemplate } from "./templates";
6
5
  import { resolveWorkspaceRoot } from "./scripts";
6
+ import { configDir } from "./config";
7
7
 
8
8
  const ISSUE_RE = /^[A-Z]+-\d+$/;
9
9
  const TOKEN_PLACEHOLDER = "YOUR_TOKEN_HERE";
10
10
 
11
11
  // Port of scripts/youtrack/config.sh chain: WORKFLOW_YOUTRACK_CONFIG ->
12
- // XDG_CONFIG_HOME / HOME .config + workflow-toolkit/youtrack.json.
12
+ // configDir() (XDG_CONFIG_HOME / HOME .config + workit).
13
13
  export const youTrackConfigPath = (): string =>
14
14
  process.env.WORKFLOW_YOUTRACK_CONFIG ??
15
- path.join(process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config"), "workflow-toolkit", "youtrack.json");
15
+ path.join(configDir(), "youtrack.json");
16
16
 
17
17
  const youTrackTokenModeOk = (p: string): boolean => {
18
18
  if (process.platform === "win32") return true;
@@ -4,6 +4,7 @@ import path from "node:path";
4
4
  import { tool, type ToolContext } from "@opencode-ai/plugin";
5
5
  import { fail, ok, resolveInside, type Result } from "../core";
6
6
  import { configGuardError, describeConfigGaps } from "../core/config-guard";
7
+ import { configDir } from "../core/config";
7
8
  import {
8
9
  buildDraft as legacyBuildDraft,
9
10
  context as legacyContext,
@@ -20,11 +21,14 @@ const message = (error: unknown) => error instanceof Error ? error.message : Str
20
21
 
21
22
  // Both override names point at the config dir itself, same precedence as
22
23
  // src/core/config.ts and scripts/init/status.sh: WORKFLOW_TOOLKIT_CONFIG → WORKFLOW_TOOLKIT_CONFIG_DIR → XDG.
24
+ // Default env (no args) routes through configDir() so the legacy migration runs.
23
25
  export const configPath = (env: NodeJS.ProcessEnv = process.env, home = os.homedir()) =>
24
26
  path.join(
25
- env.WORKFLOW_TOOLKIT_CONFIG
26
- ?? env.WORKFLOW_TOOLKIT_CONFIG_DIR
27
- ?? path.join(env.XDG_CONFIG_HOME || path.join(home, ".config"), "workflow-toolkit"),
27
+ env === process.env
28
+ ? configDir()
29
+ : env.WORKFLOW_TOOLKIT_CONFIG
30
+ ?? env.WORKFLOW_TOOLKIT_CONFIG_DIR
31
+ ?? path.join(env.XDG_CONFIG_HOME || path.join(home, ".config"), "workit"),
28
32
  "youtrack.json",
29
33
  );
30
34