@workit-dev/core 0.0.1 → 1.0.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/LICENSE +21 -0
- package/README.md +22 -43
- package/package.json +56 -6
- package/schemas/config.json +74 -0
- package/schemas/vcs.json +79 -0
- package/schemas/workspaces.json +1029 -0
- package/schemas/youtrack.json +84 -0
- package/scripts/analyze-release-scope.ts +215 -0
- package/scripts/bundled-deps.json +131 -0
- package/scripts/fixtures/sample-plan.md +15 -0
- package/scripts/fixtures/sample-sdd/progress.md +2 -0
- package/scripts/install-codex-plugin.sh +28 -0
- package/scripts/install-cursor-plugin.sh +146 -0
- package/scripts/install-opencode-plugin.sh +79 -0
- package/scripts/install-pi-package.sh +56 -0
- package/scripts/lib/config-dir.sh +28 -0
- package/scripts/publish-changed-packages.ts +395 -0
- package/scripts/rewrite-workspace-deps.ts +53 -0
- package/scripts/run-cursor-mcp.sh +6 -0
- package/scripts/sync-release-manifests.ts +82 -0
- package/scripts/sync-runtime.sh +142 -0
- package/scripts/validate-cursor-marketplace.ts +171 -0
- package/scripts/verify-bundle-sources.ts +134 -0
- package/scripts/write-schemas.ts +16 -0
- package/skills/workit-architecture/SKILL.md +72 -0
- package/skills/workit-architecture/agents/openai.yaml +3 -0
- package/skills/workit-architecture/references/instruction-files.md +52 -0
- package/skills/workit-architecture/references/vocabulary.md +53 -0
- package/skills/workit-bdd/SKILL.md +53 -0
- package/skills/workit-continue/SKILL.md +54 -0
- package/skills/workit-debug/SKILL.md +45 -0
- package/skills/workit-deslop/SKILL.md +58 -0
- package/skills/workit-fanout/SKILL.md +71 -0
- package/skills/workit-fanout/references/brief.md +141 -0
- package/skills/workit-fanout/references/orchestration.md +169 -0
- package/skills/workit-implement/SKILL.md +71 -0
- package/skills/workit-retro/SKILL.md +72 -0
- package/skills/workit-retro/agents/openai.yaml +3 -0
- package/skills/workit-retro/references/sources.md +35 -0
- package/skills/workit-retro/references/steering.md +39 -0
- package/skills/workit-review/SKILL.md +66 -0
- package/skills/workit-review/references/impact.md +24 -0
- package/skills/workit-shape/SKILL.md +71 -0
- package/skills/workit-shape/references/diagrams.md +17 -0
- package/skills/workit-shape/references/knowledge.md +61 -0
- package/skills/workit-shape/references/mockups.md +15 -0
- package/skills/workit-shape/references/slicing.md +44 -0
- package/skills/workit-ship/SKILL.md +71 -0
- package/skills/workit-ship/references/changelog.md +44 -0
- package/skills/workit-ship/references/pr-body.md +32 -0
- package/skills/workit-ship/references/release.md +39 -0
- package/skills/workit-test-audit/SKILL.md +54 -0
- package/skills/workit-verify-app/SKILL.md +68 -0
- package/skills/workit-verify-app/references/template.md +51 -0
- package/src/autonomy.ts +910 -0
- package/src/check-config.ts +613 -0
- package/src/checks.ts +534 -0
- package/src/ci-waiter.ts +159 -0
- package/src/core/boundary.ts +55 -0
- package/src/core/branch-policy.ts +112 -0
- package/src/core/branch.ts +1192 -0
- package/src/core/changelog-gate.ts +349 -0
- package/src/core/changelog-merge.ts +214 -0
- package/src/core/changelog.ts +392 -0
- package/src/core/cleanup.ts +638 -0
- package/src/core/cli-locate.ts +173 -0
- package/src/core/commit-flavors.ts +65 -0
- package/src/core/config-schemas.ts +653 -0
- package/src/core/config.ts +351 -0
- package/src/core/context-read.ts +144 -0
- package/src/core/docs-gate.ts +69 -0
- package/src/core/finding-evidence.ts +89 -0
- package/src/core/finding-queue.ts +36 -0
- package/src/core/git.ts +73 -0
- package/src/core/gitignore.ts +52 -0
- package/src/core/heavy-eta.ts +170 -0
- package/src/core/heavy-lease.ts +290 -0
- package/src/core/heavy-rules.ts +373 -0
- package/src/core/heavy-scope.ts +131 -0
- package/src/core/heavy-words.ts +83 -0
- package/src/core/heavy.ts +535 -0
- package/src/core/hygiene.ts +149 -0
- package/src/core/init.ts +218 -0
- package/src/core/live-session.ts +333 -0
- package/src/core/logger.ts +310 -0
- package/src/core/methods.ts +185 -0
- package/src/core/model-fallback.ts +208 -0
- package/src/core/node-resolve.mjs +249 -0
- package/src/core/operation-input.ts +632 -0
- package/src/core/package-root.ts +28 -0
- package/src/core/policy/attributes.ts +97 -0
- package/src/core/policy/bookkeeping.ts +119 -0
- package/src/core/policy/carry.ts +122 -0
- package/src/core/policy/check-evidence.ts +115 -0
- package/src/core/policy/current-judgment.ts +20 -0
- package/src/core/policy/derive.ts +387 -0
- package/src/core/policy/judgment.ts +246 -0
- package/src/core/policy/lane.ts +562 -0
- package/src/core/policy/pr-lane.ts +32 -0
- package/src/core/policy/substance.ts +365 -0
- package/src/core/policy-resolver.ts +94 -0
- package/src/core/pr-create.ts +448 -0
- package/src/core/pr-template.ts +162 -0
- package/src/core/release-notes.ts +197 -0
- package/src/core/release-tracks.ts +779 -0
- package/src/core/remotes.ts +436 -0
- package/src/core/repo-context.ts +542 -0
- package/src/core/repo-paths.ts +29 -0
- package/src/core/resources.ts +1299 -0
- package/src/core/role-models.ts +603 -0
- package/src/core/route-intent.ts +305 -0
- package/src/core/rules.ts +65 -0
- package/src/core/runtime-identity.ts +63 -0
- package/src/core/safe-write.ts +87 -0
- package/src/core/scope-glob.ts +56 -0
- package/src/core/scripts.ts +6 -0
- package/src/core/skill-manifests.ts +173 -0
- package/src/core/store-lock.ts +374 -0
- package/src/core/support-matrix.ts +24 -0
- package/src/core/task-context.ts +247 -0
- package/src/core/task-contract.ts +1279 -0
- package/src/core/task-delivery.ts +109 -0
- package/src/core/task-engine.ts +2248 -0
- package/src/core/task-evaluation.ts +1147 -0
- package/src/core/task-store.ts +2649 -0
- package/src/core/task-view.ts +180 -0
- package/src/core/templates.ts +22 -0
- package/src/core/tracker-issues.ts +174 -0
- package/src/core/vcs-config.ts +540 -0
- package/src/core/workers.ts +62 -0
- package/src/core/workit-paths.mjs +242 -0
- package/src/core/workspace-branch-policy.ts +123 -0
- package/src/core/workspaces.ts +1745 -0
- package/src/core/youtrack-backend.ts +288 -0
- package/src/core/youtrack-tools.ts +260 -0
- package/src/core/youtrack-writes.ts +73 -0
- package/src/core/youtrack-ytrack.ts +217 -0
- package/src/core/youtrack.ts +454 -0
- package/src/core/ytrack-skill.ts +297 -0
- package/src/core.ts +171 -0
- package/src/deslop/lex.ts +271 -0
- package/src/deslop/scan.ts +232 -0
- package/src/duration.ts +27 -0
- package/src/fanout-brief.ts +486 -0
- package/src/fanout-check.ts +455 -0
- package/src/fanout-landed.ts +370 -0
- package/src/fanout-stack.ts +108 -0
- package/src/fanout-status.ts +362 -0
- package/src/fanout-worktree.ts +893 -0
- package/src/fanout.ts +1473 -0
- package/src/forge/exec.ts +385 -0
- package/src/forge/github.ts +831 -0
- package/src/forge/gitlab.ts +906 -0
- package/src/forge/issue-forge.ts +209 -0
- package/src/forge/issue-link.ts +468 -0
- package/src/forge/issue-ops.ts +314 -0
- package/src/forge/merge-gate-base-red.ts +72 -0
- package/src/forge/pr-body.ts +100 -0
- package/src/forge/pr-edit.ts +292 -0
- package/src/forge/pr-ops.ts +1109 -0
- package/src/forge/pr-status.ts +10 -0
- package/src/forge/redact.ts +195 -0
- package/src/forge/report.ts +994 -0
- package/src/forge/reruns.ts +132 -0
- package/src/forge/resolve.ts +509 -0
- package/src/forge/types.ts +242 -0
- package/src/forge/verify.ts +398 -0
- package/src/git/default-base.ts +130 -0
- package/src/git/env-redirect.ts +28 -0
- package/src/git/facts.ts +122 -0
- package/src/git/ops.ts +1823 -0
- package/src/git/refs-read.ts +332 -0
- package/src/git/release.ts +1020 -0
- package/src/git/rev.ts +1175 -0
- package/src/git/session-hook.ts +597 -0
- package/src/git/stage.ts +63 -0
- package/src/hooks/agent-model.ts +149 -0
- package/src/hooks/budget-signal.ts +284 -0
- package/src/hooks/context.ts +461 -0
- package/src/hooks/cwd-normalize.ts +79 -0
- package/src/hooks/deadline.ts +228 -0
- package/src/hooks/delivery-verbs.ts +182 -0
- package/src/hooks/descriptor.ts +124 -0
- package/src/hooks/handle.ts +433 -0
- package/src/hooks/heavy-gate.ts +99 -0
- package/src/hooks/hook-runs.ts +130 -0
- package/src/hooks/hosts/claude-code.ts +380 -0
- package/src/hooks/hosts/codex.ts +372 -0
- package/src/hooks/hosts/cursor.ts +419 -0
- package/src/hooks/hosts/fields.ts +98 -0
- package/src/hooks/hosts/opencode.ts +79 -0
- package/src/hooks/hosts/pi.ts +78 -0
- package/src/hooks/index.ts +60 -0
- package/src/hooks/model-limit.ts +378 -0
- package/src/hooks/model-switch.ts +35 -0
- package/src/hooks/policy.ts +29 -0
- package/src/hooks/process.ts +48 -0
- package/src/hooks/protocol.ts +135 -0
- package/src/hooks/raw-git.ts +2289 -0
- package/src/hooks/repeat-failure.ts +115 -0
- package/src/hooks/run.ts +54 -0
- package/src/hooks/session-marker.ts +151 -0
- package/src/hooks/shell-words.ts +532 -0
- package/src/hooks/skill-nudge.ts +295 -0
- package/src/hooks/spawn-gate.ts +72 -0
- package/src/hooks/standing-signal.ts +99 -0
- package/src/hooks/standing.ts +225 -0
- package/src/hooks/steering.ts +74 -0
- package/src/hooks/stop.ts +408 -0
- package/src/hooks/worktree-confine.ts +415 -0
- package/src/hooks/write-gate.ts +1174 -0
- package/src/host-session.ts +459 -0
- package/src/knowledge.ts +368 -0
- package/src/ledger.ts +2913 -0
- package/src/stack.ts +2570 -0
- package/src/store/event-log.ts +217 -0
- package/src/store/notes.ts +29 -0
- package/src/store/patch.ts +135 -0
- package/src/store/paths.ts +368 -0
- package/src/store/reduce.ts +203 -0
- package/src/test-audit-mocks.ts +309 -0
- package/src/verdict-cover.ts +151 -0
- package/src/worktree-deps.ts +142 -0
- package/templates/headers.md +3 -0
- package/templates/hygiene/.editorconfig +8 -0
- package/templates/hygiene/.gitattributes +3 -0
- package/templates/hygiene/CHANGELOG.md +14 -0
- package/templates/hygiene/CONTRIBUTING.md +3 -0
- package/templates/hygiene/LICENSE +21 -0
- package/templates/hygiene/README.md +3 -0
- package/templates/issue-update.md +5 -0
- package/templates/plan-template.md +26 -0
- package/templates/spec-template.md +64 -0
- package/templates/workit-contract.md +13 -0
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
// The one resolver for every Workit file location (docs/agents/file-locations.md).
|
|
2
|
+
// Kinds: config, state, data, cache and runtime. Linux and macOS follow the
|
|
3
|
+
// XDG Base Directory spec; Windows uses %APPDATA% (config, roaming) and
|
|
4
|
+
// %LOCALAPPDATA% (state, data, cache). An absolute XDG_*_HOME wins on every
|
|
5
|
+
// platform; a relative one is ignored, as the spec says.
|
|
6
|
+
//
|
|
7
|
+
// Plain JavaScript with node: builtins only and no syntax newer than Node 16:
|
|
8
|
+
// TypeScript imports it, and the hook launchers carry a byte-equal copy
|
|
9
|
+
// (claude-code bin/, codex bin/, cursor hooks/; a test holds them equal). The
|
|
10
|
+
// POSIX shell launchers embed the *_SH functions below verbatim, and a test
|
|
11
|
+
// runs them against the JavaScript for the same environment.
|
|
12
|
+
// Side-effect free on import; only migrateLegacyDirs touches the disk.
|
|
13
|
+
import { existsSync, lstatSync, mkdirSync, renameSync, writeFileSync } from "node:fs";
|
|
14
|
+
import os from "node:os";
|
|
15
|
+
import path from "node:path";
|
|
16
|
+
|
|
17
|
+
/** Every location kind the resolver knows. */
|
|
18
|
+
export const WORKIT_DIR_KINDS = ["config", "state", "data", "cache", "runtime"];
|
|
19
|
+
|
|
20
|
+
/** Environment overrides per kind, first non-empty wins; each names the directory itself. */
|
|
21
|
+
export const WORKIT_DIR_OVERRIDES = {
|
|
22
|
+
config: ["WORKIT_CONFIG", "WORKIT_CONFIG_DIR"],
|
|
23
|
+
state: ["WORKIT_STATE_DIR"],
|
|
24
|
+
data: [],
|
|
25
|
+
cache: [],
|
|
26
|
+
runtime: [],
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/** The XDG base variable each kind follows (`<base>/workit`). */
|
|
30
|
+
export const WORKIT_DIR_XDG = {
|
|
31
|
+
config: "XDG_CONFIG_HOME",
|
|
32
|
+
state: "XDG_STATE_HOME",
|
|
33
|
+
data: "XDG_DATA_HOME",
|
|
34
|
+
cache: "XDG_CACHE_HOME",
|
|
35
|
+
runtime: "XDG_RUNTIME_DIR",
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/** The XDG defaults under $HOME when the base variable is unset. */
|
|
39
|
+
const XDG_DEFAULTS = {
|
|
40
|
+
config: [".config"],
|
|
41
|
+
state: [".local", "state"],
|
|
42
|
+
data: [".local", "share"],
|
|
43
|
+
cache: [".cache"],
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The user's home: $HOME, else %USERPROFILE%, else the OS record.
|
|
48
|
+
* @param {NodeJS.ProcessEnv} env
|
|
49
|
+
*/
|
|
50
|
+
export const userHome = (env) => env.HOME || env.USERPROFILE || os.homedir();
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The kind's XDG base when set to an absolute path (the spec ignores a relative one).
|
|
54
|
+
* @param {string} kind @param {NodeJS.ProcessEnv} env
|
|
55
|
+
*/
|
|
56
|
+
const xdgBase = (kind, env) => {
|
|
57
|
+
const base = env[WORKIT_DIR_XDG[kind]];
|
|
58
|
+
return base && path.isAbsolute(base) ? base : "";
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Where one kind of Workit file lives.
|
|
63
|
+
* @param {"config" | "state" | "data" | "cache" | "runtime"} kind
|
|
64
|
+
* @param {NodeJS.ProcessEnv} [env]
|
|
65
|
+
* @param {string} [platform]
|
|
66
|
+
* @returns {string}
|
|
67
|
+
*/
|
|
68
|
+
export const workitDir = (kind, env = process.env, platform = process.platform) => {
|
|
69
|
+
for (const name of WORKIT_DIR_OVERRIDES[kind]) if (env[name]) return String(env[name]);
|
|
70
|
+
const base = xdgBase(kind, env);
|
|
71
|
+
if (base) return path.join(base, "workit");
|
|
72
|
+
if (kind === "runtime") {
|
|
73
|
+
if (platform === "win32") return path.join(env.TEMP || env.TMP || os.tmpdir(), "workit");
|
|
74
|
+
const uid = typeof process.getuid === "function" ? process.getuid() : os.userInfo().username;
|
|
75
|
+
return path.join(env.TMPDIR || os.tmpdir(), `workit-${uid}`);
|
|
76
|
+
}
|
|
77
|
+
const home = userHome(env);
|
|
78
|
+
if (platform !== "win32") return path.join(home, ...XDG_DEFAULTS[kind], "workit");
|
|
79
|
+
if (kind === "config")
|
|
80
|
+
return path.join(env.APPDATA || path.join(home, "AppData", "Roaming"), "workit");
|
|
81
|
+
const local = path.join(env.LOCALAPPDATA || path.join(home, "AppData", "Local"), "workit");
|
|
82
|
+
return kind === "cache" ? path.join(local, "cache") : local;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
/** @param {NodeJS.ProcessEnv} [env] @param {string} [platform] */
|
|
86
|
+
export const configDir = (env, platform) => workitDir("config", env, platform);
|
|
87
|
+
/** @param {NodeJS.ProcessEnv} [env] @param {string} [platform] */
|
|
88
|
+
export const stateDir = (env, platform) => workitDir("state", env, platform);
|
|
89
|
+
/** @param {NodeJS.ProcessEnv} [env] @param {string} [platform] */
|
|
90
|
+
export const dataDir = (env, platform) => workitDir("data", env, platform);
|
|
91
|
+
/** @param {NodeJS.ProcessEnv} [env] @param {string} [platform] */
|
|
92
|
+
export const cacheDir = (env, platform) => workitDir("cache", env, platform);
|
|
93
|
+
/** @param {NodeJS.ProcessEnv} [env] @param {string} [platform] */
|
|
94
|
+
export const runtimeDir = (env, platform) => workitDir("runtime", env, platform);
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Where Workit keeps the worktrees it manages: `<state>/worktrees/<repo>/<slug>`.
|
|
98
|
+
* State, not cache: a worktree holds uncommitted work.
|
|
99
|
+
* @param {NodeJS.ProcessEnv} [env] @param {string} [platform]
|
|
100
|
+
*/
|
|
101
|
+
export const worktreesDir = (env, platform) => path.join(stateDir(env, platform), "worktrees");
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* The managed worktree root before it moved to worktreesDir:
|
|
105
|
+
* `$XDG_CACHE_HOME/workit-wt`, else `~/.cache/workit-wt` (on every platform).
|
|
106
|
+
* Never moved (a session may be using a worktree there); cleanup still sweeps it.
|
|
107
|
+
* @param {NodeJS.ProcessEnv} [env]
|
|
108
|
+
*/
|
|
109
|
+
export const legacyWorktreesDir = (env = process.env) =>
|
|
110
|
+
path.join(xdgBase("cache", env) || path.join(userHome(env), ".cache"), "workit-wt");
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* The POSIX shell form of stateDir, embedded verbatim by the shell launchers
|
|
114
|
+
* (claude-code bin/workit, the owned launcher `workit upgrade` writes).
|
|
115
|
+
*/
|
|
116
|
+
export const STATE_DIR_SH = `workit_state_dir() {
|
|
117
|
+
if [ -n "\${WORKIT_STATE_DIR:-}" ]; then printf '%s\\n' "$WORKIT_STATE_DIR"; return; fi
|
|
118
|
+
case "\${XDG_STATE_HOME:-}" in /*) printf '%s\\n' "$XDG_STATE_HOME/workit"; return ;; esac
|
|
119
|
+
case "$(uname -s 2>/dev/null)" in
|
|
120
|
+
MINGW*|MSYS*|CYGWIN*) printf '%s\\n' "\${LOCALAPPDATA:-$HOME/AppData/Local}/workit" ;;
|
|
121
|
+
*) printf '%s\\n' "$HOME/.local/state/workit" ;;
|
|
122
|
+
esac
|
|
123
|
+
}`;
|
|
124
|
+
|
|
125
|
+
/** The POSIX shell form of configDir, embedded verbatim by scripts/lib/config-dir.sh. */
|
|
126
|
+
export const CONFIG_DIR_SH = `workit_config_dir() {
|
|
127
|
+
if [ -n "\${WORKIT_CONFIG:-}" ]; then printf '%s\\n' "$WORKIT_CONFIG"; return; fi
|
|
128
|
+
if [ -n "\${WORKIT_CONFIG_DIR:-}" ]; then printf '%s\\n' "$WORKIT_CONFIG_DIR"; return; fi
|
|
129
|
+
case "\${XDG_CONFIG_HOME:-}" in /*) printf '%s\\n' "$XDG_CONFIG_HOME/workit"; return ;; esac
|
|
130
|
+
case "$(uname -s 2>/dev/null)" in
|
|
131
|
+
MINGW*|MSYS*|CYGWIN*) printf '%s\\n' "\${APPDATA:-$HOME/AppData/Roaming}/workit" ;;
|
|
132
|
+
*) printf '%s\\n' "$HOME/.config/workit" ;;
|
|
133
|
+
esac
|
|
134
|
+
}`;
|
|
135
|
+
|
|
136
|
+
/** The POSIX shell form of dataDir, embedded verbatim by scripts/lib/config-dir.sh. */
|
|
137
|
+
export const DATA_DIR_SH = `workit_data_dir() {
|
|
138
|
+
case "\${XDG_DATA_HOME:-}" in /*) printf '%s\\n' "$XDG_DATA_HOME/workit"; return ;; esac
|
|
139
|
+
case "$(uname -s 2>/dev/null)" in
|
|
140
|
+
MINGW*|MSYS*|CYGWIN*) printf '%s\\n' "\${LOCALAPPDATA:-$HOME/AppData/Local}/workit" ;;
|
|
141
|
+
*) printf '%s\\n' "$HOME/.local/share/workit" ;;
|
|
142
|
+
esac
|
|
143
|
+
}`;
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* The POSIX shell form of runtimeDir, embedded verbatim by scripts/lib/config-dir.sh.
|
|
147
|
+
* `os.tmpdir()` drops a trailing slash from TMPDIR; so does this.
|
|
148
|
+
*/
|
|
149
|
+
export const RUNTIME_DIR_SH = `workit_runtime_dir() {
|
|
150
|
+
case "\${XDG_RUNTIME_DIR:-}" in /*) printf '%s\\n' "$XDG_RUNTIME_DIR/workit"; return ;; esac
|
|
151
|
+
case "$(uname -s 2>/dev/null)" in
|
|
152
|
+
MINGW*|MSYS*|CYGWIN*) printf '%s\\n' "\${TEMP:-\${TMP:-/tmp}}/workit" ;;
|
|
153
|
+
*) set -- "\${TMPDIR:-/tmp}"; printf '%s\\n' "\${1%/}/workit-$(id -u)" ;;
|
|
154
|
+
esac
|
|
155
|
+
}`;
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Locations older Workit versions used, per kind, that migrateLegacyDirs moves
|
|
159
|
+
* to the resolver's default: macOS state lived in ~/Library/Application
|
|
160
|
+
* Support/workit, Windows config in ~\.config\workit. Only while the kind has
|
|
161
|
+
* no override or XDG variable (then the default is in use).
|
|
162
|
+
* @param {NodeJS.ProcessEnv} [env] @param {string} [platform]
|
|
163
|
+
* @returns {Array<{ kind: "config" | "state", legacy: string, target: string }>}
|
|
164
|
+
*/
|
|
165
|
+
export const legacyDirs = (env = process.env, platform = process.platform) => {
|
|
166
|
+
const home = userHome(env);
|
|
167
|
+
/** @type {Array<{ kind: "config" | "state", legacy: string }>} */
|
|
168
|
+
const old = [];
|
|
169
|
+
if (platform === "darwin")
|
|
170
|
+
old.push({
|
|
171
|
+
kind: "state",
|
|
172
|
+
legacy: path.join(home, "Library", "Application Support", "workit"),
|
|
173
|
+
});
|
|
174
|
+
if (platform === "win32")
|
|
175
|
+
old.push({ kind: "config", legacy: path.join(home, ".config", "workit") });
|
|
176
|
+
return old
|
|
177
|
+
.filter(
|
|
178
|
+
(entry) =>
|
|
179
|
+
!WORKIT_DIR_OVERRIDES[entry.kind].some((name) => env[name]) && !xdgBase(entry.kind, env),
|
|
180
|
+
)
|
|
181
|
+
.map((entry) => ({
|
|
182
|
+
kind: entry.kind,
|
|
183
|
+
legacy: entry.legacy,
|
|
184
|
+
target: workitDir(entry.kind, env, platform),
|
|
185
|
+
}));
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
/** The marker a move leaves beside the old location: `<legacy>.moved-<stamp>`. */
|
|
189
|
+
export const LEGACY_MARKER_INFIX = ".moved-";
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* A real directory, never a symlink to one: a migration must not move a link
|
|
193
|
+
* the user made (the data behind it would stay where the link pointed).
|
|
194
|
+
* @param {string} file
|
|
195
|
+
*/
|
|
196
|
+
export const isRealDir = (file) => {
|
|
197
|
+
try {
|
|
198
|
+
return lstatSync(file).isDirectory();
|
|
199
|
+
} catch {
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* One-time move of each legacy location whose new location does not exist yet.
|
|
206
|
+
* Leaves `<legacy>.moved-<UTC stamp>` naming the new location; a second run
|
|
207
|
+
* finds no legacy directory and does nothing. Never throws: a failed move
|
|
208
|
+
* leaves both places as they were, and `workit doctor` (legacy_dirs) names the
|
|
209
|
+
* command that finishes it.
|
|
210
|
+
* @param {{ env?: NodeJS.ProcessEnv, platform?: string, now?: Date }} [options]
|
|
211
|
+
* @returns {Array<{ kind: string, legacy: string, target: string, marker: string }>}
|
|
212
|
+
*/
|
|
213
|
+
export const migrateLegacyDirs = (options = {}) => {
|
|
214
|
+
const env = options.env || process.env;
|
|
215
|
+
const platform = options.platform || process.platform;
|
|
216
|
+
const stamp = (options.now || new Date())
|
|
217
|
+
.toISOString()
|
|
218
|
+
.replace(/[-:]/g, "")
|
|
219
|
+
.replace(/\..*$/, "Z");
|
|
220
|
+
/** @type {Array<{ kind: string, legacy: string, target: string, marker: string }>} */
|
|
221
|
+
const moved = [];
|
|
222
|
+
for (const entry of legacyDirs(env, platform)) {
|
|
223
|
+
if (!isRealDir(entry.legacy) || existsSync(entry.target)) continue;
|
|
224
|
+
const marker = `${entry.legacy}${LEGACY_MARKER_INFIX}${stamp}`;
|
|
225
|
+
try {
|
|
226
|
+
mkdirSync(path.dirname(entry.target), { recursive: true });
|
|
227
|
+
renameSync(entry.legacy, entry.target);
|
|
228
|
+
} catch {
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
try {
|
|
232
|
+
writeFileSync(
|
|
233
|
+
marker,
|
|
234
|
+
`Workit moved this ${entry.kind} directory to ${entry.target} at ${stamp}.\n`,
|
|
235
|
+
);
|
|
236
|
+
} catch {
|
|
237
|
+
// the move is done; a missing marker only loses the note
|
|
238
|
+
}
|
|
239
|
+
moved.push({ kind: entry.kind, legacy: entry.legacy, target: entry.target, marker });
|
|
240
|
+
}
|
|
241
|
+
return moved;
|
|
242
|
+
};
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { isDeepStrictEqual } from "node:util";
|
|
4
|
+
import { detectBranchPolicy } from "./branch-policy";
|
|
5
|
+
import { configDir } from "./config";
|
|
6
|
+
import {
|
|
7
|
+
readWorkspacesResult,
|
|
8
|
+
resolveWorkspaceFrom,
|
|
9
|
+
validateWorkspaceGlob,
|
|
10
|
+
validateWorkspacesDocument,
|
|
11
|
+
workspacesRevision,
|
|
12
|
+
type WorkspaceConfig,
|
|
13
|
+
} from "./workspaces";
|
|
14
|
+
|
|
15
|
+
const readFileSafe = (p: string): string | null => {
|
|
16
|
+
try {
|
|
17
|
+
return readFileSync(p, "utf8");
|
|
18
|
+
} catch {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// The one proposal→write path for a workspace branch policy, shared by the
|
|
24
|
+
// OpenCode `workit_init_apply` branch_policy action (init.ts) and the CLI
|
|
25
|
+
// wizard, so both produce byte-identical workspaces.json writes.
|
|
26
|
+
export function applyWorkspaceBranchPolicy(opts: {
|
|
27
|
+
workspace_root: string;
|
|
28
|
+
env?: NodeJS.ProcessEnv;
|
|
29
|
+
}): Record<string, any> {
|
|
30
|
+
const { workspace_root, env = process.env } = opts;
|
|
31
|
+
const dir = path.join(env.WORKIT_CONFIG ?? configDir());
|
|
32
|
+
const {
|
|
33
|
+
status,
|
|
34
|
+
path: wsPath,
|
|
35
|
+
entries,
|
|
36
|
+
document,
|
|
37
|
+
revision,
|
|
38
|
+
error: workspacesError,
|
|
39
|
+
} = readWorkspacesResult(dir);
|
|
40
|
+
if (status === "malformed" || status === "invalid")
|
|
41
|
+
return { ok: false, error: workspacesError ?? `invalid workspaces.json: ${wsPath}` };
|
|
42
|
+
const detection = detectBranchPolicy(workspace_root);
|
|
43
|
+
const name = String(env.WORKIT_BP_NAME ?? path.basename(workspace_root));
|
|
44
|
+
const integration = (env.WORKIT_BP_INTEGRATION ?? detection.integration) as "pr" | "merge";
|
|
45
|
+
const policy = {
|
|
46
|
+
preset: detection.preset,
|
|
47
|
+
developBranch: env.WORKIT_BP_DEVELOP ?? detection.developBranch ?? undefined,
|
|
48
|
+
prefixes: detection.prefixes,
|
|
49
|
+
allowed: detection.allowed,
|
|
50
|
+
protected: detection.protected,
|
|
51
|
+
integration,
|
|
52
|
+
};
|
|
53
|
+
const glob = `${workspace_root.replace(/[\\/]+$/, "")}/**`;
|
|
54
|
+
if (!validateWorkspaceGlob(glob).ok)
|
|
55
|
+
return { ok: false, error: `invalid workspace glob: ${glob}` };
|
|
56
|
+
let existing: WorkspaceConfig | null;
|
|
57
|
+
try {
|
|
58
|
+
existing = resolveWorkspaceFrom(workspace_root, dir);
|
|
59
|
+
} catch (error) {
|
|
60
|
+
const selectedName = env.WORKIT_BP_NAME?.trim();
|
|
61
|
+
if (!selectedName)
|
|
62
|
+
return { ok: false, error: error instanceof Error ? error.message : String(error) };
|
|
63
|
+
try {
|
|
64
|
+
existing = resolveWorkspaceFrom(workspace_root, dir, selectedName);
|
|
65
|
+
} catch (selectionError) {
|
|
66
|
+
return {
|
|
67
|
+
ok: false,
|
|
68
|
+
error: selectionError instanceof Error ? selectionError.message : String(selectionError),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (existing?.branchPolicy && isDeepStrictEqual(existing.branchPolicy, policy)) {
|
|
73
|
+
return {
|
|
74
|
+
ok: true,
|
|
75
|
+
status: "already-configured",
|
|
76
|
+
workspace: existing,
|
|
77
|
+
policy,
|
|
78
|
+
config_path: wsPath,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
const next = existing
|
|
82
|
+
? entries.map((w) => (w.name === existing?.name ? { ...w, branchPolicy: policy } : w))
|
|
83
|
+
: [...entries, { name, glob, branchPolicy: policy }];
|
|
84
|
+
if (!existing && entries.some((entry) => entry.name === name))
|
|
85
|
+
return { ok: false, error: `workspace name ${JSON.stringify(name)} is already configured` };
|
|
86
|
+
const proposed = validateWorkspacesDocument({ ...document, workspaces: next }, wsPath);
|
|
87
|
+
if (proposed.status !== "valid") return { ok: false, error: proposed.error };
|
|
88
|
+
if (workspacesRevision(readFileSafe(wsPath)) !== revision)
|
|
89
|
+
return {
|
|
90
|
+
ok: false,
|
|
91
|
+
error: `workspaces.json changed while applying the branch policy: ${wsPath}`,
|
|
92
|
+
};
|
|
93
|
+
mkdirSync(path.dirname(wsPath), { recursive: true });
|
|
94
|
+
const tmp = `${wsPath}.${process.pid}.tmp`;
|
|
95
|
+
try {
|
|
96
|
+
writeFileSync(tmp, JSON.stringify({ ...document, workspaces: next }, null, 2) + "\n", "utf8");
|
|
97
|
+
if (workspacesRevision(readFileSafe(wsPath)) !== revision) {
|
|
98
|
+
rmSync(tmp, { force: true });
|
|
99
|
+
return {
|
|
100
|
+
ok: false,
|
|
101
|
+
error: `workspaces.json changed while applying the branch policy: ${wsPath}`,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
renameSync(tmp, wsPath);
|
|
105
|
+
} catch (error) {
|
|
106
|
+
try {
|
|
107
|
+
rmSync(tmp, { force: true });
|
|
108
|
+
} catch {}
|
|
109
|
+
return {
|
|
110
|
+
ok: false,
|
|
111
|
+
error: `failed to write ${wsPath}: ${error instanceof Error ? error.message : String(error)}`,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
return {
|
|
115
|
+
ok: true,
|
|
116
|
+
status: existing ? "updated" : "configured",
|
|
117
|
+
workspace: existing
|
|
118
|
+
? { ...existing, branchPolicy: policy }
|
|
119
|
+
: { name, glob, branchPolicy: policy },
|
|
120
|
+
policy,
|
|
121
|
+
config_path: wsPath,
|
|
122
|
+
};
|
|
123
|
+
}
|