@brainervirus/workit-cli 2.0.1 → 2.1.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.
Files changed (3) hide show
  1. package/README.md +36 -7
  2. package/dist/index.js +2405 -343
  3. package/package.json +4 -4
package/dist/index.js CHANGED
@@ -49793,13 +49793,6 @@ var workspaceConfigSchema = object({
49793
49793
  profiles: record(nonBlank, workspaceProfileSchema).optional(),
49794
49794
  releaseTracks: record(nonBlank, releaseTrackSchema).optional()
49795
49795
  }).passthrough().superRefine((workspace, context) => {
49796
- if (workspace.youtrack && workspace.vcs?.provider !== "gitlab") {
49797
- context.addIssue({
49798
- code: "custom",
49799
- path: ["youtrack"],
49800
- message: `YouTrack issue linking requires the gitlab provider, got ${workspace.vcs?.provider ?? "unset"}`
49801
- });
49802
- }
49803
49796
  if (workspace.issues && workspace.vcs?.provider !== "github") {
49804
49797
  context.addIssue({
49805
49798
  code: "custom",
@@ -49992,13 +49985,10 @@ var selectWorkspaceMatch = (matches, cwd, workspaceName) => {
49992
49985
  }
49993
49986
  return mostSpecific[0];
49994
49987
  };
49995
- var resolveWorkspaceFrom = (cwd, dir, workspaceName) => {
49996
- const result = readWorkspacesResult(dir);
49997
- if (result.status === "malformed" || result.status === "invalid")
49998
- throw new Error(result.error);
49988
+ var resolveWorkspaceFromEntries = (cwd, entries, workspaceName) => {
49999
49989
  const targets = [cwd, realpathOf(cwd)].map((p) => p.replaceAll("\\", "/"));
50000
49990
  const matches = [];
50001
- for (const ws of result.entries) {
49991
+ for (const ws of entries) {
50002
49992
  const glob = ws.glob.replaceAll("\\", "/");
50003
49993
  const canonical = canonicalGlob(glob);
50004
49994
  let matched = false;
@@ -50013,6 +50003,12 @@ var resolveWorkspaceFrom = (cwd, dir, workspaceName) => {
50013
50003
  }
50014
50004
  return selectWorkspaceMatch(matches, cwd, workspaceName) ?? null;
50015
50005
  };
50006
+ var resolveWorkspaceFrom = (cwd, dir, workspaceName) => {
50007
+ const result = readWorkspacesResult(dir);
50008
+ if (result.status === "malformed" || result.status === "invalid")
50009
+ throw new Error(result.error);
50010
+ return resolveWorkspaceFromEntries(cwd, result.entries, workspaceName);
50011
+ };
50016
50012
  var runtimeWorkspacePolicySchema = (kind) => {
50017
50013
  const key = kind === "branch" ? "branchPolicy" : kind === "commit" ? "commitPolicy" : "vcs";
50018
50014
  const policySchema = kind === "branch" ? workspaceBranchPolicySchema : kind === "commit" ? workspaceCommitPolicySchema : object({
@@ -50096,6 +50092,48 @@ var resolveRuntimeWorkspacePolicy = (cwd, kind, requestedProfile) => {
50096
50092
  source: profilePolicy ? `profile:${profileName}` : workspacePolicy ? `workspace:${candidate.name}` : "user-default"
50097
50093
  };
50098
50094
  };
50095
+ var resolveWorkspaceProfile = (workspace, requestedProfile) => {
50096
+ const profileName = requestedProfile ?? workspace.defaultProfile ?? null;
50097
+ const profile = profileName ? workspace.profiles?.[profileName] : undefined;
50098
+ if (profileName && !profile) {
50099
+ return {
50100
+ status: "invalid",
50101
+ error: `workspace ${JSON.stringify(workspace.name)} has no profile ${JSON.stringify(profileName)}`
50102
+ };
50103
+ }
50104
+ const effectiveWorkspace = profile ? {
50105
+ ...workspace,
50106
+ ...profile.branchPolicy ? { branchPolicy: profile.branchPolicy } : {},
50107
+ ...profile.commitPolicy ? { commitPolicy: profile.commitPolicy } : {}
50108
+ } : workspace;
50109
+ return {
50110
+ status: "resolved",
50111
+ profileName,
50112
+ workspace: effectiveWorkspace,
50113
+ provenance: {
50114
+ branchPolicy: profile?.branchPolicy ? `profile:${profileName}` : workspace.branchPolicy ? `workspace:${workspace.name}` : "user-default",
50115
+ commitPolicy: profile?.commitPolicy ? `profile:${profileName}` : workspace.commitPolicy ? `workspace:${workspace.name}` : "user-default"
50116
+ }
50117
+ };
50118
+ };
50119
+ var resolveWorkspacePolicy = (config, workspace, profileName) => {
50120
+ const selected = workspace ? resolveWorkspaceProfile(workspace, profileName) : {
50121
+ status: "resolved",
50122
+ profileName: null,
50123
+ workspace: null,
50124
+ provenance: { branchPolicy: "user-default", commitPolicy: "user-default" }
50125
+ };
50126
+ if (selected.status === "invalid")
50127
+ return selected;
50128
+ return {
50129
+ status: "resolved",
50130
+ workspace: selected.workspace,
50131
+ profileName: selected.profileName,
50132
+ branchPolicy: resolveBranchPolicy(config, selected.workspace),
50133
+ commitPolicy: resolveCommitPolicy(config, selected.workspace),
50134
+ provenance: selected.provenance
50135
+ };
50136
+ };
50099
50137
 
50100
50138
  // packages/workit-core/src/core/gitignore.ts
50101
50139
  var GITIGNORE_ENTRIES = [
@@ -50607,20 +50645,20 @@ function writeFileAtomic(file, content, mode) {
50607
50645
  import {
50608
50646
  copyFileSync as copyFileSync2,
50609
50647
  cpSync as cpSync3,
50610
- existsSync as existsSync10,
50648
+ existsSync as existsSync11,
50611
50649
  lstatSync as lstatSync4,
50612
50650
  mkdirSync as mkdirSync5,
50613
50651
  mkdtempSync,
50614
- readFileSync as readFileSync11,
50615
- readdirSync as readdirSync4,
50652
+ readFileSync as readFileSync12,
50653
+ readdirSync as readdirSync5,
50616
50654
  realpathSync as realpathSync2,
50617
50655
  renameSync as renameSync2,
50618
50656
  rmSync,
50619
- statSync as statSync3,
50657
+ statSync as statSync4,
50620
50658
  writeFileSync as writeFileSync5
50621
50659
  } from "node:fs";
50622
- import os6 from "node:os";
50623
- import path13 from "node:path";
50660
+ import os7 from "node:os";
50661
+ import path14 from "node:path";
50624
50662
  import { isDeepStrictEqual as isDeepStrictEqual2 } from "node:util";
50625
50663
 
50626
50664
  // packages/workit-core/src/core/branch-policy.ts
@@ -50729,7 +50767,7 @@ var named = (s, name) => s === name || s.startsWith(`${name}@`);
50729
50767
  function isWorkitPlugin(value) {
50730
50768
  const s = String(value).replaceAll("\\", "/");
50731
50769
  const url = s.startsWith("file://") || s.startsWith("git+file://");
50732
- const pkgPath = s.includes("/packages/workit-opencode/") || s.includes("/packages/workit-cursor/") || s.includes("/node_modules/@brainervirus/workit-opencode/") || s.includes("/node_modules/@brainervirus/workit-cursor/");
50770
+ const pkgPath = /\/(?:packages\/workit-(?:opencode|cursor)|node_modules\/@brainervirus\/workit-(?:opencode|cursor))(?:\/|$)/.test(s);
50733
50771
  return named(s, "workflow-toolkit") || named(s, "workflow-toolkit-opencode") || named(s, "local/workflow-toolkit") || named(s, "workit") || named(s, "local/workit") || named(s, "@brainervirus/workit-opencode") || named(s, "@brainervirus/workit-cursor") || url && pkgPath || s.startsWith("git+file://") && s.includes("workflow-toolkit");
50734
50772
  }
50735
50773
  function mergeOpenCodePlugins(plugin, pin) {
@@ -53344,9 +53382,10 @@ var parsesAsConfigObject = (p) => {
53344
53382
  }
53345
53383
  };
53346
53384
  var pluginEntries = (cfg) => {
53347
- const plugin = cfg?.plugin;
53348
- const list = Array.isArray(plugin) ? plugin : typeof plugin === "string" ? [plugin] : [];
53349
- return list.map(String).filter(isWorkitPlugin);
53385
+ const entries = [cfg?.plugins, cfg?.plugin].flatMap((value) => Array.isArray(value) ? value : typeof value === "string" ? [value] : []);
53386
+ return [
53387
+ ...new Set(entries.filter((entry) => typeof entry === "string" && isWorkitPlugin(entry)))
53388
+ ];
53350
53389
  };
53351
53390
  var commandOnPath = (name, env) => {
53352
53391
  const dirs = (env.PATH ?? process.env.PATH ?? "").split(path12.delimiter);
@@ -54603,6 +54642,351 @@ var runDoctor = (options = {}) => {
54603
54642
  return report;
54604
54643
  };
54605
54644
 
54645
+ // packages/workit-core/src/core/host-install.ts
54646
+ import { spawnSync as spawnSync3 } from "node:child_process";
54647
+ import { accessSync, constants as constants2, existsSync as existsSync10, readFileSync as readFileSync11, readdirSync as readdirSync4, statSync as statSync3 } from "node:fs";
54648
+ import os6 from "node:os";
54649
+ import path13 from "node:path";
54650
+ function hostCommand(name, args, options = {}) {
54651
+ const executable = findHostExecutable(name, options);
54652
+ if (!executable)
54653
+ throw new Error(`${name} executable was not found on PATH or supported user bin directories`);
54654
+ if (process.platform === "win32" && /\.(?:cmd|bat)$/i.test(executable)) {
54655
+ const node = findHostExecutable("node", options);
54656
+ if (!node)
54657
+ throw new Error(`Node.js is required to run the ${name} Windows shim`);
54658
+ const script = resolveWindowsShimScript(executable);
54659
+ if (!script)
54660
+ throw new Error(`could not resolve the ${name} Windows shim to a JavaScript entrypoint`);
54661
+ return { command: node, args: [script, ...args], cwd: options.cwd, purpose: `${name} CLI` };
54662
+ }
54663
+ return { command: executable, args, cwd: options.cwd, purpose: `${name} CLI` };
54664
+ }
54665
+ function resolveWindowsShimEntry(contents, shim, exists = existsSync10) {
54666
+ const root = path13.win32.dirname(shim);
54667
+ const matches = contents.matchAll(/(["']?)([^\r\n"']*node_modules[\\/][^\r\n"']+?\.js)\1/gi);
54668
+ for (const match of matches) {
54669
+ const raw = match[2].replace(/%~?dp0%?/gi, root);
54670
+ const candidate = path13.win32.isAbsolute(raw) ? raw : path13.win32.resolve(root, raw);
54671
+ if (exists(candidate))
54672
+ return candidate;
54673
+ }
54674
+ return null;
54675
+ }
54676
+ var resolveWindowsShimScript = (shim) => {
54677
+ try {
54678
+ const contents = readFileSync11(shim, "utf8");
54679
+ return resolveWindowsShimEntry(contents, shim);
54680
+ } catch {
54681
+ return null;
54682
+ }
54683
+ };
54684
+ var MARKETPLACE = "https://github.com/BrainerVirus/workit.git";
54685
+ var CODEX_MARKETPLACE_NAME = "workflow-toolkit";
54686
+ var validPackageVersion = (value) => value === "latest" || /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(value);
54687
+ var nodePrerequisite = (options) => {
54688
+ const node = findHostExecutable("node", options);
54689
+ if (!node)
54690
+ throw new Error("Workit host packages require Node.js 24+; install Node.js and retry");
54691
+ return {
54692
+ command: node,
54693
+ args: [
54694
+ "-e",
54695
+ 'const v=Number(process.versions.node.split(".")[0]);if(v<24){console.error(`Workit host packages require Node.js 24+ (found ${process.versions.node})`);process.exit(1)}'
54696
+ ],
54697
+ cwd: options.cwd,
54698
+ purpose: "Check the Node.js 24+ package runtime requirement"
54699
+ };
54700
+ };
54701
+ function findHostExecutable(name, options = {}) {
54702
+ const env = options.env ?? process.env;
54703
+ const home = options.home ?? env.HOME ?? os6.homedir();
54704
+ const suffixes = process.platform === "win32" ? ["", ".exe", ".cmd", ".bat"] : [""];
54705
+ const dirs = (env.PATH ?? "").split(path13.delimiter).filter(Boolean);
54706
+ dirs.push(path13.join(home, ".local", "bin"), path13.join(home, ".asdf", "shims"));
54707
+ for (const [base, tail] of [
54708
+ [path13.join(home, ".local", "share", "fnm", "node-versions"), path13.join("installation", "bin")],
54709
+ [path13.join(home, ".nvm", "versions", "node"), "bin"]
54710
+ ]) {
54711
+ try {
54712
+ for (const entry of readdirSync4(base, { withFileTypes: true })) {
54713
+ if (entry.isDirectory())
54714
+ dirs.push(path13.join(base, entry.name, tail));
54715
+ }
54716
+ } catch {}
54717
+ }
54718
+ for (const dir of dirs) {
54719
+ for (const suffix of suffixes) {
54720
+ const candidate = path13.join(dir, `${name}${suffix}`);
54721
+ try {
54722
+ if (statSync3(candidate).isFile()) {
54723
+ if (process.platform === "win32")
54724
+ return candidate;
54725
+ accessSync(candidate, constants2.X_OK);
54726
+ return candidate;
54727
+ }
54728
+ } catch {}
54729
+ }
54730
+ }
54731
+ return null;
54732
+ }
54733
+ var isPiWorkitInstalled = (home, env = process.env) => {
54734
+ const settings = path13.join(env.PI_CODING_AGENT_DIR ?? path13.join(home, ".pi", "agent"), "settings.json");
54735
+ try {
54736
+ const document2 = JSON.parse(readFileSync11(settings, "utf8"));
54737
+ return (document2.packages ?? []).some((entry) => {
54738
+ const source = entry && typeof entry === "object" && !Array.isArray(entry) ? entry.source : entry;
54739
+ const value = String(source).replaceAll("\\", "/").replace(/\/+$/, "");
54740
+ return /^npm:@brainervirus\/workit-pi(?:@[^/]+)?$/.test(value) || value === "@brainervirus/workit-pi" || /(?:^|\/)packages\/workit-pi$/.test(value);
54741
+ });
54742
+ } catch {
54743
+ return false;
54744
+ }
54745
+ };
54746
+ var isCanonicalCodexPackage = (root) => {
54747
+ for (const file of [
54748
+ path13.join(root, ".codex-plugin", "plugin.json"),
54749
+ path13.join(root, "package.json")
54750
+ ]) {
54751
+ try {
54752
+ const manifest = JSON.parse(readFileSync11(file, "utf8"));
54753
+ const repository = typeof manifest.repository === "string" ? manifest.repository : manifest.repository?.url;
54754
+ if ((manifest.name === "workit" || manifest.name === "@brainervirus/workit-codex") && repository?.replace(/\.git$/, "").toLowerCase() === "https://github.com/brainervirus/workit")
54755
+ return true;
54756
+ } catch {}
54757
+ }
54758
+ return false;
54759
+ };
54760
+ var isCodexWorkitInstalled = (home, env = process.env) => {
54761
+ const codexHome = env.CODEX_HOME ?? path13.join(home, ".codex");
54762
+ const isPackageOrVersionDirectory = (root) => {
54763
+ if (isCanonicalCodexPackage(root))
54764
+ return true;
54765
+ try {
54766
+ return readdirSync4(root, { withFileTypes: true }).some((entry) => entry.isDirectory() && isCanonicalCodexPackage(path13.join(root, entry.name)));
54767
+ } catch {
54768
+ return false;
54769
+ }
54770
+ };
54771
+ if (isPackageOrVersionDirectory(path13.join(codexHome, "plugins", "workit")))
54772
+ return true;
54773
+ const root = path13.join(codexHome, "plugins", "cache");
54774
+ try {
54775
+ for (const marketplace of readdirSync4(root, { withFileTypes: true })) {
54776
+ if (!marketplace.isDirectory())
54777
+ continue;
54778
+ if (isPackageOrVersionDirectory(path13.join(root, marketplace.name, "workit")))
54779
+ return true;
54780
+ }
54781
+ } catch {
54782
+ return false;
54783
+ }
54784
+ return false;
54785
+ };
54786
+ function installedHostApp(host, home, env = process.env, platform = process.platform, exists = existsSync10) {
54787
+ const candidates = [];
54788
+ const pathApi = platform === "win32" ? path13.win32 : path13;
54789
+ if (platform === "darwin") {
54790
+ const app = host === "cursor" ? "Cursor.app" : host === "codex" ? "Codex.app" : host === "opencode" ? "OpenCode.app" : "";
54791
+ if (app)
54792
+ candidates.push(`/Applications/${app}`, pathApi.join(home, "Applications", app));
54793
+ } else if (platform === "win32") {
54794
+ const local = env.LOCALAPPDATA ?? pathApi.join(home, "AppData", "Local");
54795
+ if (host === "cursor")
54796
+ candidates.push(pathApi.join(local, "Programs", "Cursor", "Cursor.exe"));
54797
+ if (host === "codex")
54798
+ candidates.push(pathApi.join(local, "Programs", "Codex", "Codex.exe"));
54799
+ if (host === "opencode")
54800
+ candidates.push(pathApi.join(local, "Programs", "OpenCode", "OpenCode.exe"));
54801
+ }
54802
+ return candidates.some(exists);
54803
+ }
54804
+ function planHostInstall(host, options = {}) {
54805
+ const env = options.env ?? process.env;
54806
+ const home = options.home ?? env.HOME ?? os6.homedir();
54807
+ const cwd = options.cwd ?? process.cwd();
54808
+ const version = options.packageVersion ?? "latest";
54809
+ if (!validPackageVersion(version))
54810
+ throw new Error(`unsupported package version selector: ${version}`);
54811
+ if (options.alreadyInstalled && options.mode !== "upgrade")
54812
+ return [];
54813
+ const upgrading = options.mode === "upgrade";
54814
+ if (host === "codex") {
54815
+ if (!upgrading && isCodexWorkitInstalled(home, env))
54816
+ return [];
54817
+ const base = hostCommand("codex", [], { home, cwd, env });
54818
+ return [
54819
+ nodePrerequisite({ home, cwd, env }),
54820
+ {
54821
+ ...base,
54822
+ args: [...base.args, "plugin", "marketplace", "list"],
54823
+ purpose: "Check whether the Workit marketplace is already registered"
54824
+ },
54825
+ {
54826
+ ...base,
54827
+ args: [
54828
+ ...base.args,
54829
+ "plugin",
54830
+ "marketplace",
54831
+ upgrading ? "upgrade" : "add",
54832
+ ...upgrading ? [CODEX_MARKETPLACE_NAME] : [MARKETPLACE]
54833
+ ],
54834
+ purpose: upgrading ? "Refresh the Workit Codex marketplace" : "Register the Workit Codex marketplace",
54835
+ ...upgrading ? {} : { skipWhenOutputIncludes: CODEX_MARKETPLACE_NAME }
54836
+ },
54837
+ {
54838
+ ...base,
54839
+ args: [...base.args, "plugin", "add", `workit@${CODEX_MARKETPLACE_NAME}`],
54840
+ purpose: upgrading ? "Refresh the Workit Codex plugin" : "Install the Workit Codex plugin"
54841
+ }
54842
+ ];
54843
+ }
54844
+ if (host === "pi") {
54845
+ if (!upgrading && isPiWorkitInstalled(home, env))
54846
+ return [];
54847
+ const base = hostCommand("pi", [], { home, cwd, env });
54848
+ return [
54849
+ nodePrerequisite({ home, cwd, env }),
54850
+ {
54851
+ ...base,
54852
+ args: upgrading ? [...base.args, "update", "--extension", "npm:@brainervirus/workit-pi"] : [...base.args, "install", `npm:@brainervirus/workit-pi@${version}`],
54853
+ purpose: upgrading ? "Update the Workit Pi package" : "Install the Workit Pi package in personal Pi settings"
54854
+ }
54855
+ ];
54856
+ }
54857
+ if (host === "cursor") {
54858
+ const node = findHostExecutable("node", { home, env });
54859
+ if (!node)
54860
+ throw new Error("Workit host packages require Node.js 24+; install Node.js and retry");
54861
+ const npm = findHostExecutable("npm", { home, env });
54862
+ const npmCli = npm ? findNpmCli(npm) : null;
54863
+ if (!npmCli)
54864
+ throw new Error("npm CLI could not be resolved; install Node.js 24+ and npm, then retry");
54865
+ const stage = path13.join(home, ".cursor", "plugins", "local", ".workit-package-bootstrap");
54866
+ const target = options.targetDir ?? path13.join(home, ".cursor", "plugins", "local", "workit");
54867
+ return [
54868
+ nodePrerequisite({ home, cwd, env }),
54869
+ {
54870
+ command: node,
54871
+ args: [
54872
+ npmCli,
54873
+ "install",
54874
+ "--prefix",
54875
+ stage,
54876
+ "--no-save",
54877
+ "--package-lock=false",
54878
+ `@brainervirus/workit-cursor@${version}`
54879
+ ],
54880
+ cwd,
54881
+ purpose: upgrading ? "Download the current Workit Cursor adapter for the managed local plugin directory" : "Download the Workit Cursor adapter package for the managed local plugin directory",
54882
+ packageRoot: path13.join(stage, "node_modules", "@brainervirus", "workit-cursor"),
54883
+ packageTarget: target
54884
+ }
54885
+ ];
54886
+ }
54887
+ if (host === "opencode") {
54888
+ if (upgrading)
54889
+ throw new Error("OpenCode CLI cannot target the Workit server plugin for a scoped update; upgrade is unsupported and the current registration was preserved");
54890
+ return [];
54891
+ }
54892
+ return [];
54893
+ }
54894
+ function runHostInstall(commands, runner, context = {}) {
54895
+ const results = [];
54896
+ let packageRoot;
54897
+ let outputHistory = "";
54898
+ for (const command of commands) {
54899
+ if (command.skipWhenOutputIncludes && outputHistory.includes(command.skipWhenOutputIncludes)) {
54900
+ results.push({
54901
+ command,
54902
+ result: { exitCode: 0, stdout: "", stderr: "already registered; skipped" }
54903
+ });
54904
+ continue;
54905
+ }
54906
+ const result = runner ? runner(command) : runHostCommand(command, context);
54907
+ results.push({ command, result });
54908
+ if (result.exitCode !== 0)
54909
+ return { ok: false, results };
54910
+ outputHistory += `
54911
+ ${result.stdout}
54912
+ ${result.stderr}`;
54913
+ if (command.packageRoot && command.packageTarget) {
54914
+ try {
54915
+ if (!existsSync10(command.packageRoot)) {
54916
+ return {
54917
+ ok: false,
54918
+ results: [
54919
+ {
54920
+ command,
54921
+ result: {
54922
+ ...result,
54923
+ exitCode: 1,
54924
+ stderr: "npm completed without producing the expected Cursor package"
54925
+ }
54926
+ }
54927
+ ]
54928
+ };
54929
+ } else {
54930
+ packageRoot = command.packageRoot;
54931
+ }
54932
+ } catch (error) {
54933
+ return {
54934
+ ok: false,
54935
+ results: [
54936
+ {
54937
+ command,
54938
+ result: {
54939
+ ...result,
54940
+ exitCode: 1,
54941
+ stderr: error instanceof Error ? error.message : String(error)
54942
+ }
54943
+ }
54944
+ ]
54945
+ };
54946
+ }
54947
+ }
54948
+ }
54949
+ return { ok: true, results, packageRoot };
54950
+ }
54951
+ function runHostCommand(step, context = {}) {
54952
+ if (process.platform === "win32" && /\.(?:cmd|bat)$/i.test(step.command)) {
54953
+ return {
54954
+ exitCode: 1,
54955
+ stdout: "",
54956
+ stderr: `Cannot safely execute batch shim ${step.command}; resolve its Node.js CLI entrypoint and retry`
54957
+ };
54958
+ }
54959
+ const result = spawnSync3(step.command, step.args, {
54960
+ cwd: step.cwd,
54961
+ env: (() => {
54962
+ const home = context.home ?? context.env?.HOME ?? os6.homedir();
54963
+ const env = { ...process.env, ...context.env, HOME: home };
54964
+ env.CODEX_HOME = context.env?.CODEX_HOME ?? path13.join(home, ".codex");
54965
+ env.PI_CODING_AGENT_DIR = context.env?.PI_CODING_AGENT_DIR ?? path13.join(home, ".pi", "agent");
54966
+ env.XDG_CONFIG_HOME = context.env?.XDG_CONFIG_HOME ?? path13.join(home, ".config");
54967
+ return env;
54968
+ })(),
54969
+ encoding: "utf8",
54970
+ timeout: 60000,
54971
+ windowsHide: true,
54972
+ shell: false
54973
+ });
54974
+ return {
54975
+ exitCode: result.status ?? (result.error ? 1 : 1),
54976
+ stdout: result.stdout ?? "",
54977
+ stderr: result.stderr ?? result.error?.message ?? ""
54978
+ };
54979
+ }
54980
+ var findNpmCli = (npm) => {
54981
+ const dir = path13.dirname(npm);
54982
+ const candidates = [
54983
+ path13.join(dir, "node_modules", "npm", "bin", "npm-cli.js"),
54984
+ path13.join(dir, "..", "node_modules", "npm", "bin", "npm-cli.js"),
54985
+ path13.join(dir, "..", "lib", "node_modules", "npm", "bin", "npm-cli.js")
54986
+ ];
54987
+ return candidates.find((candidate) => existsSync10(candidate)) ?? null;
54988
+ };
54989
+
54606
54990
  // packages/workit-core/src/core/setup.ts
54607
54991
  var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE";
54608
54992
  function parseList(raw) {
@@ -54682,7 +55066,7 @@ var resolveTokenPath = (configured, fallback, override) => {
54682
55066
  };
54683
55067
  var readConfigRecord = (file) => {
54684
55068
  try {
54685
- const value = JSON.parse(readFileSync11(file, "utf8"));
55069
+ const value = JSON.parse(readFileSync12(file, "utf8"));
54686
55070
  return isRecord2(value) ? value : null;
54687
55071
  } catch {
54688
55072
  return null;
@@ -54703,11 +55087,12 @@ function buildSetupPreview(values, opts = {}) {
54703
55087
  const current = readConfigFromDir(state.configDir);
54704
55088
  mutations.push({
54705
55089
  type: "merge-json",
54706
- path: path13.join(state.configDir, "config.json"),
55090
+ path: path14.join(state.configDir, "config.json"),
54707
55091
  value: {
54708
55092
  locale: values.locale,
54709
55093
  localeOptions: current.localeOptions,
54710
55094
  timezone: values.timezone,
55095
+ ...values.commitPolicy === undefined ? {} : { commitPolicy: values.commitPolicy },
54711
55096
  branchPolicy: mergePreset(values.branchPreset, {
54712
55097
  allowed: values.branchPreset === "custom" ? parseList(values.branchAllowed) : undefined,
54713
55098
  protectedNames: values.branchPreset === "custom" ? parseList(values.branchProtected) : undefined
@@ -54729,17 +55114,17 @@ function buildSetupPreview(values, opts = {}) {
54729
55114
  if (blocked.length === 0) {
54730
55115
  mutations.push({
54731
55116
  type: "update-workspaces",
54732
- path: path13.join(state.configDir, "workspaces.json"),
55117
+ path: path14.join(state.configDir, "workspaces.json"),
54733
55118
  entries: values.workspaces,
54734
55119
  expectedRevision: diskWorkspaceResult.revision
54735
55120
  });
54736
55121
  }
54737
55122
  }
54738
55123
  if (values.baseUrl.trim()) {
54739
- const ytPath = path13.join(state.configDir, "youtrack.json");
55124
+ const ytPath = path14.join(state.configDir, "youtrack.json");
54740
55125
  const ytExisting = readConfigRecord(ytPath);
54741
55126
  const ytConfigured = configuredTokenPath(ytExisting, "tokenFile");
54742
- const tokenPath = resolveTokenPath(ytConfigured, path13.join(state.configDir, "youtrack.token"), values.tokenPaths?.youtrack);
55127
+ const tokenPath = resolveTokenPath(ytConfigured, path14.join(state.configDir, "youtrack.token"), values.tokenPaths?.youtrack);
54743
55128
  const draft = youtrackDraft(values, tokenPath);
54744
55129
  if (ytConfigured !== null && tokenPath !== ytConfigured) {
54745
55130
  delete draft.tokenFile;
@@ -54755,7 +55140,7 @@ function buildSetupPreview(values, opts = {}) {
54755
55140
  path: ytPath,
54756
55141
  value: draft
54757
55142
  });
54758
- if (existsSync10(tokenPath))
55143
+ if (existsSync11(tokenPath))
54759
55144
  preserved.push(tokenPath);
54760
55145
  else
54761
55146
  mutations.push({
@@ -54767,7 +55152,7 @@ function buildSetupPreview(values, opts = {}) {
54767
55152
  });
54768
55153
  }
54769
55154
  if (values.vcsProvider !== "skip") {
54770
- const vcsPath = path13.join(state.configDir, "vcs.json");
55155
+ const vcsPath = path14.join(state.configDir, "vcs.json");
54771
55156
  mutations.push({
54772
55157
  type: "merge-json",
54773
55158
  path: vcsPath,
@@ -54781,16 +55166,45 @@ function buildSetupPreview(values, opts = {}) {
54781
55166
  if (platform === "opencode") {
54782
55167
  mutations.push({ type: "register-platform", platform, path: paths.opencodeConfig });
54783
55168
  } else if (platform === "cursor") {
55169
+ const cursorRoot = adapterRoot("cursor", {
55170
+ ...paths,
55171
+ dev: opts.dev ?? env.WORKFLOW_TOOLKIT_DEV ?? null
55172
+ });
55173
+ if (cursorRoot === null) {
55174
+ const commands = planHostInstall("cursor", {
55175
+ home: paths.home,
55176
+ cwd: paths.cwd,
55177
+ env,
55178
+ packageVersion: "latest",
55179
+ targetDir: paths.cursorPluginDir
55180
+ });
55181
+ mutations.push({
55182
+ type: "install-host",
55183
+ platform: "cursor",
55184
+ path: path14.join(paths.home, ".cursor", "plugins", "local", ".workit-package-bootstrap"),
55185
+ commands
55186
+ });
55187
+ }
54784
55188
  mutations.push({ type: "install-adapter", platform, path: paths.cursorPluginDir });
54785
55189
  mutations.push({ type: "register-platform", platform, path: paths.cursorSettings });
54786
55190
  mutations.push({ type: "register-platform", platform, path: paths.cursorMcp });
55191
+ } else if (platform === "codex" || platform === "pi") {
55192
+ const commands = planHostInstall(platform, { home: paths.home, cwd: paths.cwd, env });
55193
+ if (commands.length > 0) {
55194
+ mutations.push({
55195
+ type: "install-host",
55196
+ platform,
55197
+ path: platform === "codex" ? path14.join(env.CODEX_HOME ?? path14.join(paths.home, ".codex"), "plugins", "cache", "workflow-toolkit", "workit") : path14.join(env.PI_CODING_AGENT_DIR ?? path14.join(paths.home, ".pi", "agent"), "settings.json"),
55198
+ commands
55199
+ });
55200
+ }
54787
55201
  }
54788
55202
  }
54789
55203
  }
54790
55204
  if (values.applyProject) {
54791
- const root = path13.resolve(opts.cwd ?? process.cwd());
54792
- const gitignorePath = path13.join(root, ".gitignore");
54793
- const existing = existsSync10(gitignorePath) ? readFileSync11(gitignorePath, "utf8") : "";
55205
+ const root = path14.resolve(opts.cwd ?? process.cwd());
55206
+ const gitignorePath = path14.join(root, ".gitignore");
55207
+ const existing = existsSync11(gitignorePath) ? readFileSync12(gitignorePath, "utf8") : "";
54794
55208
  const existingLines = new Set(existing.split(`
54795
55209
  `).map((l) => l.trim()).filter(Boolean));
54796
55210
  const entries = GITIGNORE_ENTRIES.filter((e) => e.trim() !== "" && !existingLines.has(e.trim()));
@@ -54812,16 +55226,16 @@ function buildSetupPreview(values, opts = {}) {
54812
55226
  }
54813
55227
  var resolveSetupPaths = (options, configDir) => {
54814
55228
  const env = options.env ?? process.env;
54815
- const home = options.home ?? env.HOME ?? os6.homedir();
55229
+ const home = options.home ?? env.HOME ?? os7.homedir();
54816
55230
  return {
54817
55231
  home,
54818
55232
  configDir,
54819
55233
  cwd: options.cwd ?? process.cwd(),
54820
55234
  env,
54821
- opencodeConfig: options.opencodeConfig ?? path13.join(home, ".config", "opencode", "opencode.json"),
54822
- cursorSettings: options.cursorSettings ?? path13.join(home, ".cursor", "settings.json"),
54823
- cursorMcp: options.cursorMcp ?? path13.join(home, ".cursor", "mcp.json"),
54824
- cursorPluginDir: options.cursorPluginDir ?? path13.join(home, ".cursor", "plugins", "local", "workit")
55235
+ opencodeConfig: options.opencodeConfig ?? path14.join(home, ".config", "opencode", "opencode.json"),
55236
+ cursorSettings: options.cursorSettings ?? path14.join(home, ".cursor", "settings.json"),
55237
+ cursorMcp: options.cursorMcp ?? path14.join(home, ".cursor", "mcp.json"),
55238
+ cursorPluginDir: options.cursorPluginDir ?? path14.join(home, ".cursor", "plugins", "local", "workit")
54825
55239
  };
54826
55240
  };
54827
55241
  function resolveApply(preview, options) {
@@ -54835,9 +55249,9 @@ var isRecord2 = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
54835
55249
  var readExisting = (p) => {
54836
55250
  let raw;
54837
55251
  try {
54838
- raw = readFileSync11(p, "utf8");
55252
+ raw = readFileSync12(p, "utf8");
54839
55253
  } catch {
54840
- if (existsSync10(p))
55254
+ if (existsSync11(p))
54841
55255
  return { kind: "malformed", error: `${p} is not readable` };
54842
55256
  return { kind: "missing" };
54843
55257
  }
@@ -54861,7 +55275,7 @@ function deepMerge(base, patch) {
54861
55275
  }
54862
55276
  var readFileSafe = (p) => {
54863
55277
  try {
54864
- return readFileSync11(p, "utf8");
55278
+ return readFileSync12(p, "utf8");
54865
55279
  } catch {
54866
55280
  return null;
54867
55281
  }
@@ -54877,7 +55291,7 @@ var workspaceEntriesAlreadyMatch = (contents, file, entries) => {
54877
55291
  }
54878
55292
  };
54879
55293
  function applyMutation(m) {
54880
- const dir = path13.dirname(m.path);
55294
+ const dir = path14.dirname(m.path);
54881
55295
  switch (m.type) {
54882
55296
  case "create-file": {
54883
55297
  mkdirSync5(dir, { recursive: true });
@@ -55026,7 +55440,7 @@ function applyMutation(m) {
55026
55440
  detail: "already configured"
55027
55441
  };
55028
55442
  }
55029
- mkdirSync5(path13.dirname(m.path), { recursive: true });
55443
+ mkdirSync5(path14.dirname(m.path), { recursive: true });
55030
55444
  writeFileSync5(m.path, JSON.stringify(merged, null, 2) + `
55031
55445
  `, "utf8");
55032
55446
  return existing.kind === "record" ? { platform: "core", file: m.path, status: "Configured" } : { platform: "core", file: m.path, status: "Installed" };
@@ -55035,28 +55449,33 @@ function applyMutation(m) {
55035
55449
  }
55036
55450
  var isAdapter = (root, platform) => {
55037
55451
  try {
55038
- const pkg = JSON.parse(readFileSync11(path13.join(root, "package.json"), "utf8"));
55452
+ const pkg = JSON.parse(readFileSync12(path14.join(root, "package.json"), "utf8"));
55039
55453
  return pkg.name === `@brainervirus/workit-${platform}`;
55040
55454
  } catch {
55041
55455
  return false;
55042
55456
  }
55043
55457
  };
55044
- function adapterRoot(platform, res) {
55458
+ function adapterRoot(platform, res, preferInstalledAdapter = false) {
55045
55459
  const candidates = [];
55460
+ const stagedCursor = path14.join(res.home, ".cursor", "plugins", "local", ".workit-package-bootstrap", "node_modules", "@brainervirus", "workit-cursor");
55461
+ if (platform === "cursor" && preferInstalledAdapter)
55462
+ candidates.push(stagedCursor);
55046
55463
  if (res.dev) {
55047
- candidates.push(path13.join(res.dev, "packages", `workit-${platform}`));
55464
+ candidates.push(path14.join(res.dev, "packages", `workit-${platform}`));
55048
55465
  } else {
55049
- candidates.push(path13.join(packageRoot(), "..", `workit-${platform}`));
55050
- let dir = path13.resolve(res.cwd);
55466
+ candidates.push(path14.join(packageRoot(), "..", `workit-${platform}`));
55467
+ let dir = path14.resolve(res.cwd);
55051
55468
  while (true) {
55052
- candidates.push(path13.join(dir, "packages", `workit-${platform}`));
55053
- const parent = path13.dirname(dir);
55469
+ candidates.push(path14.join(dir, "packages", `workit-${platform}`));
55470
+ const parent = path14.dirname(dir);
55054
55471
  if (parent === dir)
55055
55472
  break;
55056
55473
  dir = parent;
55057
55474
  }
55058
55475
  }
55059
- candidates.push(path13.join(res.home, ".local", "share", "workit", "packages", `workit-${platform}`));
55476
+ candidates.push(path14.join(res.home, ".local", "share", "workit", "packages", `workit-${platform}`));
55477
+ if (platform === "cursor")
55478
+ candidates.push(stagedCursor);
55060
55479
  for (const candidate of candidates) {
55061
55480
  if (isAdapter(candidate, platform))
55062
55481
  return candidate;
@@ -55073,16 +55492,16 @@ var resolveOpenCodePin = (root, opts = {}) => {
55073
55492
  if (!isDevOpenCodeAdapterRoot(root, opts.dev))
55074
55493
  return OPENCODE_NPM_PIN;
55075
55494
  for (const rel of ["src/plugin.ts", "dist/plugin.js"]) {
55076
- const entry = path13.join(root, rel);
55077
- if (existsSync10(entry))
55495
+ const entry = path14.join(root, rel);
55496
+ if (existsSync11(entry))
55078
55497
  return `file://${entry}`;
55079
55498
  }
55080
55499
  return null;
55081
55500
  };
55082
55501
  var opencodePin = (root, res) => resolveOpenCodePin(root, { dev: res.dev });
55083
55502
  function applyOpenCode(root, res) {
55084
- const pin = opencodePin(root, res);
55085
- if (!pin) {
55503
+ const resolvedPin = opencodePin(root, res);
55504
+ if (!resolvedPin) {
55086
55505
  return {
55087
55506
  platform: "opencode",
55088
55507
  file: res.opencodeConfig,
@@ -55090,7 +55509,7 @@ function applyOpenCode(root, res) {
55090
55509
  detail: "workit-opencode package has no loadable plugin entry (src/plugin.ts or dist/plugin.js)"
55091
55510
  };
55092
55511
  }
55093
- mkdirSync5(path13.dirname(res.opencodeConfig), { recursive: true });
55512
+ mkdirSync5(path14.dirname(res.opencodeConfig), { recursive: true });
55094
55513
  const existing = readExisting(res.opencodeConfig);
55095
55514
  if (existing.kind === "malformed") {
55096
55515
  return {
@@ -55100,7 +55519,17 @@ function applyOpenCode(root, res) {
55100
55519
  detail: `cannot merge into malformed config: ${existing.error} — repair or remove the file`
55101
55520
  };
55102
55521
  }
55103
- const merged = mergeOpenCodeConfig(existing.kind === "record" ? existing.value : {}, pin);
55522
+ const configuredPlugins = existing.kind === "record" ? Array.isArray(existing.value.plugins) ? existing.value.plugins : typeof existing.value.plugins === "string" ? [existing.value.plugins] : typeof existing.value.plugin === "string" ? [existing.value.plugin] : Array.isArray(existing.value.plugin) ? existing.value.plugin : [] : [];
55523
+ const configuredPin = configuredPlugins.find((entry) => typeof entry === "string" && isWorkitPlugin(entry));
55524
+ const pin = configuredPin ?? resolvedPin;
55525
+ const base = existing.kind === "record" ? existing.value : {};
55526
+ const merged = existing.kind === "record" && Object.hasOwn(existing.value, "plugins") ? (() => {
55527
+ const plugins = mergeOpenCodePlugins(existing.value.plugins, pin);
55528
+ return {
55529
+ config: { ...existing.value, plugins: plugins.config },
55530
+ changed: plugins.changed.length > 0 ? ["plugins"] : []
55531
+ };
55532
+ })() : mergeOpenCodeConfig(base, pin);
55104
55533
  if (merged.changed.length === 0) {
55105
55534
  return {
55106
55535
  platform: "opencode",
@@ -55125,22 +55554,22 @@ function applyOpenCode(root, res) {
55125
55554
  }
55126
55555
  var samePluginContent = (src, dest, relative = "") => {
55127
55556
  try {
55128
- for (const entry of readdirSync4(src, { withFileTypes: true })) {
55557
+ for (const entry of readdirSync5(src, { withFileTypes: true })) {
55129
55558
  if (entry.name === "node_modules")
55130
55559
  continue;
55131
- const source = path13.join(src, entry.name);
55132
- const installed = path13.join(dest, entry.name);
55560
+ const source = path14.join(src, entry.name);
55561
+ const installed = path14.join(dest, entry.name);
55133
55562
  if (entry.isDirectory()) {
55134
- if (!statSync3(installed).isDirectory() || !samePluginContent(source, installed, path13.join(relative, entry.name)))
55563
+ if (!statSync4(installed).isDirectory() || !samePluginContent(source, installed, path14.join(relative, entry.name)))
55135
55564
  return false;
55136
- } else if (!readFileSync11(source).equals(readFileSync11(installed))) {
55565
+ } else if (!readFileSync12(source).equals(readFileSync12(installed))) {
55137
55566
  return false;
55138
55567
  }
55139
55568
  }
55140
- for (const entry of readdirSync4(dest, { withFileTypes: true })) {
55141
- if (existsSync10(path13.join(src, entry.name)))
55569
+ for (const entry of readdirSync5(dest, { withFileTypes: true })) {
55570
+ if (existsSync11(path14.join(src, entry.name)))
55142
55571
  continue;
55143
- const rel = path13.join(relative, entry.name);
55572
+ const rel = path14.join(relative, entry.name);
55144
55573
  if (rel === ".workit-root")
55145
55574
  continue;
55146
55575
  if (relative === "rules" && entry.isFile() && entry.name.endsWith(".mdc"))
@@ -55154,43 +55583,43 @@ var samePluginContent = (src, dest, relative = "") => {
55154
55583
  };
55155
55584
  var preservedCursorRules = (src, dest) => {
55156
55585
  const preserved = new Map;
55157
- const sourceRules = path13.join(src, "rules");
55158
- const installedRules = path13.join(dest, "rules");
55586
+ const sourceRules = path14.join(src, "rules");
55587
+ const installedRules = path14.join(dest, "rules");
55159
55588
  try {
55160
- for (const entry of readdirSync4(installedRules, { withFileTypes: true })) {
55589
+ for (const entry of readdirSync5(installedRules, { withFileTypes: true })) {
55161
55590
  if (!entry.isFile() || !entry.name.endsWith(".mdc"))
55162
55591
  continue;
55163
- if (existsSync10(path13.join(sourceRules, entry.name)))
55592
+ if (existsSync11(path14.join(sourceRules, entry.name)))
55164
55593
  continue;
55165
- preserved.set(entry.name, readFileSync11(path13.join(installedRules, entry.name)));
55594
+ preserved.set(entry.name, readFileSync12(path14.join(installedRules, entry.name)));
55166
55595
  }
55167
55596
  } catch {}
55168
55597
  return preserved;
55169
55598
  };
55170
55599
  function copyPluginDir(src, dest) {
55171
55600
  const realSrc = realpathSync2(src);
55172
- const marker = path13.join(dest, ".workit-root");
55173
- const destIsLink = existsSync10(dest) && lstatSync4(dest).isSymbolicLink();
55601
+ const marker = path14.join(dest, ".workit-root");
55602
+ const destIsLink = existsSync11(dest) && lstatSync4(dest).isSymbolicLink();
55174
55603
  const synced = !destIsLink && readFileSafe(marker)?.trim() === realSrc && samePluginContent(realSrc, dest);
55175
55604
  if (synced)
55176
55605
  return "Skipped";
55177
- const hadDir = existsSync10(dest);
55606
+ const hadDir = existsSync11(dest);
55178
55607
  const rules = preservedCursorRules(realSrc, dest);
55179
- const parent = path13.dirname(dest);
55608
+ const parent = path14.dirname(dest);
55180
55609
  mkdirSync5(parent, { recursive: true });
55181
- const swap = mkdtempSync(path13.join(parent, `.${path13.basename(dest)}.swap-`));
55182
- const stage = path13.join(swap, "stage");
55183
- const backup = path13.join(swap, "backup");
55610
+ const swap = mkdtempSync(path14.join(parent, `.${path14.basename(dest)}.swap-`));
55611
+ const stage = path14.join(swap, "stage");
55612
+ const backup = path14.join(swap, "backup");
55184
55613
  try {
55185
55614
  cpSync3(realSrc, stage, {
55186
55615
  recursive: true,
55187
- filter: (entry) => !path13.relative(realSrc, entry).split(path13.sep).includes("node_modules")
55616
+ filter: (entry) => !path14.relative(realSrc, entry).split(path14.sep).includes("node_modules")
55188
55617
  });
55189
55618
  for (const [name, content] of rules) {
55190
- mkdirSync5(path13.join(stage, "rules"), { recursive: true });
55191
- writeFileSync5(path13.join(stage, "rules", name), content);
55619
+ mkdirSync5(path14.join(stage, "rules"), { recursive: true });
55620
+ writeFileSync5(path14.join(stage, "rules", name), content);
55192
55621
  }
55193
- writeFileSync5(path13.join(stage, ".workit-root"), realSrc + `
55622
+ writeFileSync5(path14.join(stage, ".workit-root"), realSrc + `
55194
55623
  `, "utf8");
55195
55624
  if (!samePluginContent(realSrc, stage))
55196
55625
  throw new Error("staged adapter content is incomplete");
@@ -55202,7 +55631,7 @@ function copyPluginDir(src, dest) {
55202
55631
  try {
55203
55632
  renameSync2(stage, dest);
55204
55633
  } catch (error) {
55205
- if (hadDir && !existsSync10(dest))
55634
+ if (hadDir && !existsSync11(dest))
55206
55635
  renameSync2(backup, dest);
55207
55636
  throw error;
55208
55637
  }
@@ -55213,26 +55642,26 @@ function copyPluginDir(src, dest) {
55213
55642
  return hadDir ? "Configured" : "Installed";
55214
55643
  }
55215
55644
  var removeLegacyCursorDir = (res) => {
55216
- const legacy = path13.join(res.home, ".cursor", "plugins", "local", "workflow-toolkit");
55217
- if (legacy === res.cursorPluginDir || !existsSync10(legacy))
55645
+ const legacy = path14.join(res.home, ".cursor", "plugins", "local", "workflow-toolkit");
55646
+ if (legacy === res.cursorPluginDir || !existsSync11(legacy))
55218
55647
  return;
55219
- const legacyRules = path13.join(legacy, "rules");
55220
- if (existsSync10(legacyRules)) {
55648
+ const legacyRules = path14.join(legacy, "rules");
55649
+ if (existsSync11(legacyRules)) {
55221
55650
  try {
55222
- mkdirSync5(path13.join(res.cursorPluginDir, "rules"), { recursive: true });
55223
- for (const entry of readdirSync4(legacyRules, { withFileTypes: true })) {
55651
+ mkdirSync5(path14.join(res.cursorPluginDir, "rules"), { recursive: true });
55652
+ for (const entry of readdirSync5(legacyRules, { withFileTypes: true })) {
55224
55653
  if (!entry.isFile() || !entry.name.endsWith(".mdc"))
55225
55654
  continue;
55226
- const target = path13.join(res.cursorPluginDir, "rules", entry.name);
55227
- if (!existsSync10(target))
55228
- copyFileSync2(path13.join(legacyRules, entry.name), target);
55655
+ const target = path14.join(res.cursorPluginDir, "rules", entry.name);
55656
+ if (!existsSync11(target))
55657
+ copyFileSync2(path14.join(legacyRules, entry.name), target);
55229
55658
  }
55230
55659
  } catch {}
55231
55660
  }
55232
55661
  rmSync(legacy, { recursive: true, force: true });
55233
55662
  };
55234
55663
  function applyCursorSettings(root, res) {
55235
- mkdirSync5(path13.dirname(res.cursorSettings), { recursive: true });
55664
+ mkdirSync5(path14.dirname(res.cursorSettings), { recursive: true });
55236
55665
  const settingsExisting = readExisting(res.cursorSettings);
55237
55666
  if (settingsExisting.kind === "malformed") {
55238
55667
  return {
@@ -55260,7 +55689,7 @@ function applyCursorSettings(root, res) {
55260
55689
  };
55261
55690
  }
55262
55691
  function applyCursorMcp(root, res) {
55263
- mkdirSync5(path13.dirname(res.cursorMcp), { recursive: true });
55692
+ mkdirSync5(path14.dirname(res.cursorMcp), { recursive: true });
55264
55693
  const mcpExisting = readExisting(res.cursorMcp);
55265
55694
  if (mcpExisting.kind === "malformed") {
55266
55695
  return {
@@ -55270,7 +55699,18 @@ function applyCursorMcp(root, res) {
55270
55699
  detail: `cannot merge into malformed config: ${mcpExisting.error} — repair or remove the file`
55271
55700
  };
55272
55701
  }
55273
- const mcp = mergeCursorMcp(mcpExisting.kind === "record" ? mcpExisting.value : {}, "workit", cursorMcpServerEntry(res.cursorPluginDir));
55702
+ const mcpBase = mcpExisting.kind === "record" ? mcpExisting.value : {};
55703
+ const server = cursorMcpServerEntry(res.cursorPluginDir);
55704
+ const servers = isRecord2(mcpBase.mcpServers) ? mcpBase.mcpServers : {};
55705
+ const current = isRecord2(servers.workit) ? servers.workit : {};
55706
+ const previousArgs = Array.isArray(current.args) ? current.args.map(String) : [];
55707
+ const previousPackage = previousArgs.find((arg) => arg.startsWith("--package=@brainervirus/workit-cursor@") && arg !== `--package=${CURSOR_RUNTIME_PACKAGE}`);
55708
+ if (previousPackage) {
55709
+ const index = server.args.findIndex((arg) => arg.startsWith("--package="));
55710
+ if (index >= 0)
55711
+ server.args[index] = previousPackage;
55712
+ }
55713
+ const mcp = mergeCursorMcp(mcpBase, "workit", server);
55274
55714
  if (mcp.changed.length === 0) {
55275
55715
  return {
55276
55716
  platform: "cursor",
@@ -55288,9 +55728,9 @@ function applyCursorMcp(root, res) {
55288
55728
  };
55289
55729
  }
55290
55730
  function verifyPlatform(platform, res) {
55291
- if (platform === "opencode" && !existsSync10(res.opencodeConfig))
55731
+ if (platform === "opencode" && !existsSync11(res.opencodeConfig))
55292
55732
  return null;
55293
- if (platform === "cursor" && !existsSync10(res.cursorSettings) && !existsSync10(res.cursorMcp)) {
55733
+ if (platform === "cursor" && !existsSync11(res.cursorSettings) && !existsSync11(res.cursorMcp)) {
55294
55734
  return null;
55295
55735
  }
55296
55736
  return runDoctor({
@@ -55355,6 +55795,51 @@ function applySetupPreview(preview, options = {}) {
55355
55795
  const rootFor = new Map;
55356
55796
  let cursorCopyOk = false;
55357
55797
  for (const mutation of preview.mutations) {
55798
+ if (mutation.type === "install-host") {
55799
+ const expectedPath = mutation.platform === "cursor" ? path14.join(res.home, ".cursor", "plugins", "local", ".workit-package-bootstrap") : mutation.platform === "codex" ? path14.join(res.env.CODEX_HOME ?? path14.join(res.home, ".codex"), "plugins", "cache", "workflow-toolkit", "workit") : mutation.platform === "pi" ? path14.join(res.env.PI_CODING_AGENT_DIR ?? path14.join(res.home, ".pi", "agent"), "settings.json") : "";
55800
+ if (mutation.path !== expectedPath) {
55801
+ entries.push({
55802
+ platform: mutation.platform,
55803
+ file: mutation.path,
55804
+ status: "Failed",
55805
+ detail: `preview/apply path mismatch: apply resolved ${expectedPath}, the previewed host install was ${mutation.path} — rebuild the preview with the same options`
55806
+ });
55807
+ continue;
55808
+ }
55809
+ const run = options.runHostCommand ?? ((step) => runHostCommand(step, { home: res.home, env: res.env }));
55810
+ const installed = runHostInstall(mutation.commands, run);
55811
+ for (const { command, result } of installed.results) {
55812
+ entries.push({
55813
+ platform: mutation.platform,
55814
+ file: command.command,
55815
+ status: result.exitCode === 0 ? "Installed" : "Failed",
55816
+ detail: result.exitCode === 0 ? `${command.purpose} (${command.args.join(" ")})` : `${command.purpose} failed (${command.args.join(" ")}): ${result.stderr || `exit ${result.exitCode}`}`
55817
+ });
55818
+ }
55819
+ if (!installed.ok && installed.results.length === 0) {
55820
+ entries.push({
55821
+ platform: mutation.platform,
55822
+ file: mutation.platform,
55823
+ status: "Failed",
55824
+ detail: "host installation did not run"
55825
+ });
55826
+ }
55827
+ if (!installed.ok)
55828
+ continue;
55829
+ if (mutation.platform === "cursor" && installed.packageRoot) {
55830
+ rootFor.set("cursor", installed.packageRoot);
55831
+ }
55832
+ const verified = mutation.platform === "codex" ? isCodexWorkitInstalled(res.home, res.env) : mutation.platform === "pi" ? isPiWorkitInstalled(res.home, res.env) : true;
55833
+ if (!verified) {
55834
+ entries.push({
55835
+ platform: mutation.platform,
55836
+ file: mutation.platform,
55837
+ status: "Failed",
55838
+ detail: "the host command succeeded, but Workit registration was not found; inspect the host's native package list and retry"
55839
+ });
55840
+ }
55841
+ continue;
55842
+ }
55358
55843
  if (mutation.type !== "register-platform" && mutation.type !== "install-adapter") {
55359
55844
  entries.push(applyMutation(mutation));
55360
55845
  continue;
@@ -55372,7 +55857,7 @@ function applySetupPreview(preview, options = {}) {
55372
55857
  }
55373
55858
  let root = rootFor.get(platform);
55374
55859
  if (root === undefined) {
55375
- root = adapterRoot(platform, res);
55860
+ root = adapterRoot(platform, res, options.preferInstalledAdapter);
55376
55861
  rootFor.set(platform, root);
55377
55862
  }
55378
55863
  if (root === null) {
@@ -55468,7 +55953,7 @@ var setupCompletionGuidance = () => [
55468
55953
  ];
55469
55954
  function applyWorkspaceBranchPolicy(opts) {
55470
55955
  const { workspace_root, env = process.env } = opts;
55471
- const dir = path13.join(env.WORKFLOW_TOOLKIT_CONFIG ?? configDir());
55956
+ const dir = path14.join(env.WORKFLOW_TOOLKIT_CONFIG ?? configDir());
55472
55957
  const {
55473
55958
  status,
55474
55959
  path: wsPath,
@@ -55480,7 +55965,7 @@ function applyWorkspaceBranchPolicy(opts) {
55480
55965
  if (status === "malformed" || status === "invalid")
55481
55966
  return { ok: false, error: workspacesError ?? `invalid workspaces.json: ${wsPath}` };
55482
55967
  const detection = detectBranchPolicy(workspace_root);
55483
- const name = String(env.WORKFLOW_BP_NAME ?? path13.basename(workspace_root));
55968
+ const name = String(env.WORKFLOW_BP_NAME ?? path14.basename(workspace_root));
55484
55969
  const integration = env.WORKFLOW_BP_INTEGRATION ?? detection.integration;
55485
55970
  const policy = {
55486
55971
  preset: detection.preset,
@@ -55529,7 +56014,7 @@ function applyWorkspaceBranchPolicy(opts) {
55529
56014
  ok: false,
55530
56015
  error: `workspaces.json changed while applying the branch policy: ${wsPath}`
55531
56016
  };
55532
- mkdirSync5(path13.dirname(wsPath), { recursive: true });
56017
+ mkdirSync5(path14.dirname(wsPath), { recursive: true });
55533
56018
  const tmp = `${wsPath}.${process.pid}.tmp`;
55534
56019
  try {
55535
56020
  writeFileSync5(tmp, JSON.stringify({ ...document2, workspaces: next }, null, 2) + `
@@ -55588,6 +56073,201 @@ function validateBaseUrl(url) {
55588
56073
  return "base URL must use https";
55589
56074
  return null;
55590
56075
  }
56076
+ function workspaceEditorValue(workspace, field) {
56077
+ const [section, key, leaf] = field.split(".");
56078
+ if (section === "defaultProfile")
56079
+ return workspace.defaultProfile ?? "";
56080
+ if (section === "vcs") {
56081
+ if (key === "account")
56082
+ return workspace.vcs?.account ?? "";
56083
+ return workspace.vcs?.defaultTargetBranch ?? "";
56084
+ }
56085
+ if (section === "youtrack")
56086
+ return key === "baseUrl" ? workspace.youtrack?.baseUrl ?? "" : workspace.youtrack?.link_issues === undefined ? "inherit" : String(workspace.youtrack.link_issues);
56087
+ if (section === "issues")
56088
+ return workspace.issues?.link_on_pr === undefined ? "inherit" : String(workspace.issues.link_on_pr);
56089
+ if (section === "branchPolicy") {
56090
+ const value = workspace.branchPolicy;
56091
+ if (key === "preset")
56092
+ return value?.preset ?? "inherit";
56093
+ if (key === "allowed")
56094
+ return value?.allowed?.join(", ") ?? "";
56095
+ if (key === "protected")
56096
+ return value?.protected?.join(", ") ?? "";
56097
+ if (key === "developBranch")
56098
+ return value?.developBranch ?? "";
56099
+ if (key === "integration")
56100
+ return value?.integration ?? "merge";
56101
+ if (key === "prefixes")
56102
+ return value?.prefixes?.[leaf] ?? "";
56103
+ }
56104
+ if (section === "commitPolicy") {
56105
+ if (key === "preset")
56106
+ return workspace.commitPolicy?.preset ?? "inherit";
56107
+ if (key === "pattern")
56108
+ return workspace.commitPolicy?.pattern ?? "";
56109
+ }
56110
+ return "";
56111
+ }
56112
+ function setWorkspaceEditorValue(workspace, field, value, defaults = {}) {
56113
+ const [section, key, leaf] = field.split(".");
56114
+ const list = value.split(",").map((item) => item.trim()).filter(Boolean);
56115
+ if (section === "defaultProfile")
56116
+ return { ...workspace, defaultProfile: value.trim() || undefined };
56117
+ if (section === "vcs")
56118
+ return workspace.vcs ? { ...workspace, vcs: { ...workspace.vcs, [key]: value.trim() || undefined } } : workspace;
56119
+ if (section === "youtrack") {
56120
+ const current = workspace.youtrack ?? {};
56121
+ if (key === "baseUrl")
56122
+ return { ...workspace, youtrack: { ...current, baseUrl: value.trim() || undefined } };
56123
+ if (!value || value === "inherit") {
56124
+ const { link_issues: _discard, ...rest } = current;
56125
+ return { ...workspace, youtrack: Object.keys(rest).length ? rest : undefined };
56126
+ }
56127
+ return { ...workspace, youtrack: { ...current, link_issues: value === "true" } };
56128
+ }
56129
+ if (section === "issues") {
56130
+ const current = workspace.issues ?? { provider: "github" };
56131
+ if (!value || value === "inherit") {
56132
+ const { link_on_pr: _discard, ...rest } = current;
56133
+ return { ...workspace, issues: Object.keys(rest).length ? rest : undefined };
56134
+ }
56135
+ return {
56136
+ ...workspace,
56137
+ issues: { ...current, provider: "github", link_on_pr: value === "true" }
56138
+ };
56139
+ }
56140
+ if (section === "branchPolicy") {
56141
+ if (key === "preset" && (!value.trim() || value === "inherit")) {
56142
+ const { branchPolicy: _discard, ...rest } = workspace;
56143
+ return rest;
56144
+ }
56145
+ const current = workspace.branchPolicy ?? { preset: defaults.branchPreset ?? "gitflow" };
56146
+ const branchPolicy = key === "preset" ? { ...current, preset: value || "gitflow" } : key === "allowed" ? { ...current, allowed: list } : key === "protected" ? { ...current, protected: list } : key === "developBranch" ? { ...current, developBranch: value.trim() || undefined } : key === "integration" ? { ...current, integration: value === "pr" ? "pr" : "merge" } : key === "prefixes" ? {
56147
+ ...current,
56148
+ prefixes: {
56149
+ feature: "feature/",
56150
+ bugfix: "bugfix/",
56151
+ release: "release/",
56152
+ hotfix: "hotfix/",
56153
+ ...current.prefixes,
56154
+ [leaf]: value.trim()
56155
+ }
56156
+ } : current;
56157
+ return { ...workspace, branchPolicy };
56158
+ }
56159
+ if (section === "commitPolicy") {
56160
+ if (key === "preset" && (!value.trim() || value === "inherit")) {
56161
+ const { commitPolicy: _discard, ...rest } = workspace;
56162
+ return rest;
56163
+ }
56164
+ const current = workspace.commitPolicy ?? { preset: defaults.commitPreset ?? "conventional" };
56165
+ return {
56166
+ ...workspace,
56167
+ commitPolicy: key === "pattern" ? { ...current, pattern: value || undefined } : { ...current, preset: value }
56168
+ };
56169
+ }
56170
+ return workspace;
56171
+ }
56172
+ var splitList = (value) => value.split(",").map((item) => item.trim()).filter(Boolean);
56173
+ var emptyTrack = () => ({
56174
+ strategy: "gitflow",
56175
+ productionBranch: "main",
56176
+ integrationBranch: "develop",
56177
+ naming: { feature: "feature/{name}", release: "release/{version}", hotfix: "hotfix/{name}" },
56178
+ baseBranch: "develop",
56179
+ mergeBackBranches: ["develop"],
56180
+ pullRequestTarget: "develop",
56181
+ tagNamespace: "",
56182
+ versionSource: { kind: "git-tag" },
56183
+ requiredChecks: []
56184
+ });
56185
+ function profileEditorValue(profile, field) {
56186
+ const [section, key, leaf] = field.split(".");
56187
+ if (section === "branchPolicy") {
56188
+ const value = profile?.branchPolicy;
56189
+ if (key === "prefixes")
56190
+ return value?.prefixes?.[leaf] ?? "";
56191
+ if (key === "allowed" || key === "protected")
56192
+ return value?.[key]?.join(", ") ?? "";
56193
+ return String(value?.[key] ?? "");
56194
+ }
56195
+ return String(profile?.commitPolicy?.[key] ?? "");
56196
+ }
56197
+ function setProfileEditorValue(profile, field, value, defaults = {}) {
56198
+ const [section, key, leaf] = field.split(".");
56199
+ if (section === "branchPolicy") {
56200
+ if (key === "preset" && (!value.trim() || value === "inherit")) {
56201
+ const { branchPolicy: _discard, ...rest } = profile;
56202
+ return rest;
56203
+ }
56204
+ const current = profile.branchPolicy ?? { preset: defaults.branchPreset ?? "gitflow" };
56205
+ let branchPolicy;
56206
+ if (key === "preset")
56207
+ branchPolicy = { ...current, preset: value || "gitflow" };
56208
+ else if (key === "allowed" || key === "protected")
56209
+ branchPolicy = { ...current, [key]: splitList(value) };
56210
+ else if (key === "prefixes")
56211
+ branchPolicy = {
56212
+ ...current,
56213
+ prefixes: {
56214
+ feature: "feature/",
56215
+ bugfix: "bugfix/",
56216
+ release: "release/",
56217
+ hotfix: "hotfix/",
56218
+ ...current.prefixes,
56219
+ [leaf]: value
56220
+ }
56221
+ };
56222
+ else if (key === "developBranch")
56223
+ branchPolicy = { ...current, developBranch: value || undefined };
56224
+ else
56225
+ branchPolicy = { ...current, integration: value === "pr" ? "pr" : "merge" };
56226
+ return { ...profile, branchPolicy };
56227
+ }
56228
+ const current = profile.commitPolicy ?? { preset: defaults.commitPreset ?? "conventional" };
56229
+ if (key === "preset" && (!value.trim() || value === "inherit")) {
56230
+ const { commitPolicy: _discard, ...rest } = profile;
56231
+ return rest;
56232
+ }
56233
+ return {
56234
+ ...profile,
56235
+ commitPolicy: key === "pattern" ? { ...current, pattern: value || undefined } : { ...current, preset: value || "conventional" }
56236
+ };
56237
+ }
56238
+ function trackEditorValue(track, field) {
56239
+ if (field.startsWith("naming."))
56240
+ return track.naming[field.slice(7)];
56241
+ if (field === "mergeBackBranches" || field === "requiredChecks")
56242
+ return track[field].join(", ");
56243
+ if (field.startsWith("versionSource.")) {
56244
+ const key = field.slice(14);
56245
+ if (key === "kind")
56246
+ return track.versionSource.kind;
56247
+ if (key === "path")
56248
+ return track.versionSource.kind === "package-json" ? track.versionSource.path : "";
56249
+ return track.versionSource.kind === "package-json" ? track.versionSource.field : "";
56250
+ }
56251
+ return String(track[field] ?? "");
56252
+ }
56253
+ function setTrackEditorValue(track, field, value) {
56254
+ if (field.startsWith("naming."))
56255
+ return { ...track, naming: { ...track.naming, [field.slice(7)]: value } };
56256
+ if (field === "mergeBackBranches" || field === "requiredChecks")
56257
+ return { ...track, [field]: splitList(value) };
56258
+ if (field.startsWith("versionSource.")) {
56259
+ const key = field.slice(14);
56260
+ const kind = key === "kind" ? value : track.versionSource.kind;
56261
+ const versionSource = kind === "package-json" ? {
56262
+ kind,
56263
+ path: key === "path" ? value : track.versionSource.kind === kind ? track.versionSource.path : "package.json",
56264
+ field: key === "field" ? value : track.versionSource.kind === kind ? track.versionSource.field : "version"
56265
+ } : kind === "manual" ? { kind } : { kind: "git-tag" };
56266
+ return { ...track, versionSource };
56267
+ }
56268
+ return { ...track, [field]: value };
56269
+ }
56270
+ var createReleaseTrack = emptyTrack;
55591
56271
  function loadWorkspaces() {
55592
56272
  return loadWorkspacesFrom(configDir());
55593
56273
  }
@@ -55605,8 +56285,8 @@ function applyWizardBranchPolicy(branchPolicy, workspace_root, env = process.env
55605
56285
  }
55606
56286
 
55607
56287
  // packages/workit-cli/src/wizard-state.ts
55608
- import { existsSync as existsSync11, statSync as statSync4 } from "node:fs";
55609
- import path14 from "node:path";
56288
+ import { existsSync as existsSync12, statSync as statSync5 } from "node:fs";
56289
+ import path15 from "node:path";
55610
56290
  import { isDeepStrictEqual as isDeepStrictEqual3 } from "node:util";
55611
56291
  var NEXT = {
55612
56292
  platforms: "locale",
@@ -55625,6 +56305,10 @@ var NEXT = {
55625
56305
  workspaceName: "workspaceGlob",
55626
56306
  workspaceGlob: "workspaceProvider",
55627
56307
  workspaceProvider: null,
56308
+ workspaceAdvanced: null,
56309
+ workspaceAdvancedValue: null,
56310
+ globalCommitPolicy: null,
56311
+ globalCommitPattern: null,
55628
56312
  branchPolicy: "project",
55629
56313
  branchPolicyDevelop: "branchPolicy",
55630
56314
  project: "summary",
@@ -55648,6 +56332,10 @@ var PREV = {
55648
56332
  workspaceName: "workspaces",
55649
56333
  workspaceGlob: "workspaceName",
55650
56334
  workspaceProvider: "workspaceGlob",
56335
+ workspaceAdvanced: "workspaces",
56336
+ workspaceAdvancedValue: "workspaceAdvanced",
56337
+ globalCommitPolicy: "workspaces",
56338
+ globalCommitPattern: "globalCommitPolicy",
55651
56339
  branchPolicy: "workspaces",
55652
56340
  branchPolicyDevelop: "branchPolicy",
55653
56341
  project: "branchPolicy",
@@ -55662,7 +56350,7 @@ function resolveBasePath(values, env = process.env) {
55662
56350
  }
55663
56351
  var isDirectory = (p) => {
55664
56352
  try {
55665
- return statSync4(p).isDirectory();
56353
+ return statSync5(p).isDirectory();
55666
56354
  } catch {
55667
56355
  return false;
55668
56356
  }
@@ -55671,9 +56359,9 @@ var basePathMessage = (value) => {
55671
56359
  const p = value.trim();
55672
56360
  if (!p)
55673
56361
  return "workspace root path is required";
55674
- return !path14.isAbsolute(p) || !isDirectory(p) ? `invalid workspace root "${p}" — enter an existing absolute directory` : null;
56362
+ return !path15.isAbsolute(p) || !isDirectory(p) ? `invalid workspace root "${p}" — enter an existing absolute directory` : null;
55675
56363
  };
55676
- var skipsBranchPolicy = (screen, basePath) => (screen === "branchPolicy" || screen === "branchPolicyDevelop") && !existsSync11(path14.join(basePath, ".git"));
56364
+ var skipsBranchPolicy = (screen, basePath) => (screen === "branchPolicy" || screen === "branchPolicyDevelop") && !existsSync12(path15.join(basePath, ".git"));
55677
56365
  function nextScreen(screen, preset, tracker, basePath) {
55678
56366
  let next = NEXT[screen];
55679
56367
  while (next && (skipsCustomBranch(next, preset) || skipsBranchPolicy(next, basePath) || skipsYoutrack(next, tracker) || skipsBasePath(next, basePath)))
@@ -55756,6 +56444,7 @@ function createInitialDraft(config = readConfig(), opts = {}) {
55756
56444
  branchPreset: config.branchPolicy.preset,
55757
56445
  branchAllowed: policy.allowed.join(", "),
55758
56446
  branchProtected: policy.protected.join(", "),
56447
+ commitPolicy: { ...config.commitPolicy },
55759
56448
  baseUrl: "",
55760
56449
  basePath: process.env.WORKFLOW_WORKSPACE_ROOT ?? "",
55761
56450
  issueTracker: "youtrack",
@@ -55793,23 +56482,17 @@ function reducer3(draft, action) {
55793
56482
  const next = decodeIssueTracker(action.value, draft.values.issueTracker);
55794
56483
  if (next === draft.values.issueTracker)
55795
56484
  return draft;
55796
- const stripIssues = (workspace) => {
55797
- const rest = { ...workspace };
55798
- delete rest.issues;
55799
- return rest;
55800
- };
55801
- const needsStrip = next !== "github" && (draft.values.workspaces.some((w) => w.issues !== undefined) || draft.workspaceDraft?.issues !== undefined);
55802
56485
  const errors = { ...draft.errors };
55803
56486
  delete errors.baseUrl;
55804
56487
  return {
55805
56488
  ...draft,
55806
56489
  errors,
55807
- workspaceDraft: needsStrip && draft.workspaceDraft ? stripIssues(draft.workspaceDraft) : draft.workspaceDraft,
56490
+ workspaceDraft: draft.workspaceDraft,
55808
56491
  values: {
55809
56492
  ...draft.values,
55810
56493
  issueTracker: next,
55811
56494
  baseUrl: next === "youtrack" ? draft.values.baseUrl : "",
55812
- workspaces: needsStrip ? draft.values.workspaces.map(stripIssues) : draft.values.workspaces
56495
+ workspaces: draft.values.workspaces
55813
56496
  }
55814
56497
  };
55815
56498
  }
@@ -55939,6 +56622,18 @@ function reducer3(draft, action) {
55939
56622
  errors: {}
55940
56623
  };
55941
56624
  }
56625
+ case "workspaceAdvancedOpen": {
56626
+ const entry = draft.values.workspaces[action.index];
56627
+ if (!entry)
56628
+ return draft;
56629
+ return {
56630
+ ...draft,
56631
+ screen: "workspaceAdvanced",
56632
+ workspaceIndex: action.index,
56633
+ workspaceDraft: entry.vcs ? { ...entry, vcs: { ...entry.vcs } } : { ...entry },
56634
+ workspaceEditorField: undefined
56635
+ };
56636
+ }
55942
56637
  case "workspaceRemove":
55943
56638
  return {
55944
56639
  ...draft,
@@ -55962,34 +56657,165 @@ function reducer3(draft, action) {
55962
56657
  ...draft,
55963
56658
  workspaceDraft: {
55964
56659
  ...base,
55965
- vcs: provider === "skip" ? undefined : { provider, defaultTargetBranch: vcs?.defaultTargetBranch }
56660
+ vcs: provider === "skip" ? undefined : { ...vcs, provider }
55966
56661
  }
55967
56662
  };
55968
56663
  }
55969
- case "workspaceSave": {
55970
- const wd = draft.workspaceDraft;
55971
- if (!wd || !wd.name.trim() || !wd.glob.trim())
56664
+ case "workspaceAdvancedSelect":
56665
+ return { ...draft, screen: "workspaceAdvancedValue", workspaceEditorField: action.field };
56666
+ case "workspaceAdvancedSet": {
56667
+ if (!draft.workspaceDraft)
55972
56668
  return draft;
55973
- const entry = wd.vcs ? { ...wd, vcs: { ...wd.vcs } } : { ...wd, vcs: undefined };
55974
- const workspaces = draft.workspaceIndex === null ? [...draft.values.workspaces, entry] : draft.values.workspaces.map((w, i) => i === draft.workspaceIndex ? entry : w);
55975
56669
  return {
55976
56670
  ...draft,
55977
- screen: "workspaces",
55978
- workspaceDraft: null,
55979
- workspaceIndex: null,
55980
- values: { ...draft.values, workspaces },
55981
- errors: {}
56671
+ workspaceDraft: setWorkspaceEditorValue(draft.workspaceDraft, action.field, action.value, {
56672
+ branchPreset: draft.values.branchPreset,
56673
+ commitPreset: draft.values.commitPolicy.preset
56674
+ }),
56675
+ screen: "workspaceAdvanced"
55982
56676
  };
55983
56677
  }
55984
- }
55985
- }
55986
- function defaultWorkspaceProvider(vcs, tracker) {
55987
- if (tracker === "github")
55988
- return "github";
55989
- if (tracker === "gitlab")
55990
- return "gitlab";
55991
- return vcs === "gitlab" || vcs === "github" ? vcs : null;
55992
- }
56678
+ case "globalCommitOpen":
56679
+ return { ...draft, screen: "globalCommitPolicy" };
56680
+ case "globalCommitEditPattern":
56681
+ return { ...draft, screen: "globalCommitPattern" };
56682
+ case "globalCommitPreset": {
56683
+ const valid = [
56684
+ "conventional",
56685
+ "gitmoji",
56686
+ "ticket-prefix",
56687
+ "freeform",
56688
+ "custom",
56689
+ "auto"
56690
+ ];
56691
+ if (!valid.includes(action.value))
56692
+ return draft;
56693
+ return {
56694
+ ...draft,
56695
+ values: {
56696
+ ...draft.values,
56697
+ commitPolicy: {
56698
+ ...draft.values.commitPolicy,
56699
+ preset: action.value
56700
+ }
56701
+ }
56702
+ };
56703
+ }
56704
+ case "globalCommitPattern":
56705
+ return {
56706
+ ...draft,
56707
+ values: {
56708
+ ...draft.values,
56709
+ commitPolicy: { ...draft.values.commitPolicy, pattern: action.value || undefined }
56710
+ }
56711
+ };
56712
+ case "globalCommitPatternDone":
56713
+ return { ...draft, screen: "globalCommitPolicy" };
56714
+ case "workspaceProfileCreate": {
56715
+ const name = action.name.trim();
56716
+ if (!draft.workspaceDraft || !name || draft.workspaceDraft.profiles?.[name])
56717
+ return draft;
56718
+ const profiles = {
56719
+ ...draft.workspaceDraft.profiles,
56720
+ [name]: { commitPolicy: { preset: "conventional" } }
56721
+ };
56722
+ return { ...draft, workspaceDraft: { ...draft.workspaceDraft, profiles } };
56723
+ }
56724
+ case "workspaceProfileDelete": {
56725
+ if (!draft.workspaceDraft?.profiles?.[action.name])
56726
+ return draft;
56727
+ const profiles = { ...draft.workspaceDraft.profiles };
56728
+ delete profiles[action.name];
56729
+ return {
56730
+ ...draft,
56731
+ workspaceDraft: {
56732
+ ...draft.workspaceDraft,
56733
+ profiles,
56734
+ defaultProfile: draft.workspaceDraft.defaultProfile === action.name ? undefined : draft.workspaceDraft.defaultProfile
56735
+ }
56736
+ };
56737
+ }
56738
+ case "workspaceProfileDefault": {
56739
+ if (!draft.workspaceDraft?.profiles?.[action.name])
56740
+ return draft;
56741
+ return { ...draft, workspaceDraft: { ...draft.workspaceDraft, defaultProfile: action.name } };
56742
+ }
56743
+ case "workspaceProfileSet": {
56744
+ const profile = draft.workspaceDraft?.profiles?.[action.name];
56745
+ if (!draft.workspaceDraft || !profile)
56746
+ return draft;
56747
+ return {
56748
+ ...draft,
56749
+ workspaceDraft: {
56750
+ ...draft.workspaceDraft,
56751
+ profiles: {
56752
+ ...draft.workspaceDraft.profiles,
56753
+ [action.name]: setProfileEditorValue(profile, action.field, action.value, {
56754
+ branchPreset: draft.values.branchPreset,
56755
+ commitPreset: draft.values.commitPolicy.preset
56756
+ })
56757
+ }
56758
+ }
56759
+ };
56760
+ }
56761
+ case "workspaceTrackCreate": {
56762
+ const name = action.name.trim();
56763
+ if (!draft.workspaceDraft || !name || draft.workspaceDraft.releaseTracks?.[name])
56764
+ return draft;
56765
+ return {
56766
+ ...draft,
56767
+ workspaceDraft: {
56768
+ ...draft.workspaceDraft,
56769
+ releaseTracks: { ...draft.workspaceDraft.releaseTracks, [name]: createReleaseTrack() }
56770
+ }
56771
+ };
56772
+ }
56773
+ case "workspaceTrackDelete": {
56774
+ if (!draft.workspaceDraft?.releaseTracks?.[action.name])
56775
+ return draft;
56776
+ const releaseTracks = { ...draft.workspaceDraft.releaseTracks };
56777
+ delete releaseTracks[action.name];
56778
+ return { ...draft, workspaceDraft: { ...draft.workspaceDraft, releaseTracks } };
56779
+ }
56780
+ case "workspaceTrackSet": {
56781
+ const track = draft.workspaceDraft?.releaseTracks?.[action.name];
56782
+ if (!draft.workspaceDraft || !track)
56783
+ return draft;
56784
+ return {
56785
+ ...draft,
56786
+ workspaceDraft: {
56787
+ ...draft.workspaceDraft,
56788
+ releaseTracks: {
56789
+ ...draft.workspaceDraft.releaseTracks,
56790
+ [action.name]: setTrackEditorValue(track, action.field, action.value)
56791
+ }
56792
+ }
56793
+ };
56794
+ }
56795
+ case "workspaceSave": {
56796
+ const wd = draft.workspaceDraft;
56797
+ if (!wd || !wd.name.trim() || !wd.glob.trim())
56798
+ return draft;
56799
+ const entry = wd.vcs ? { ...wd, vcs: { ...wd.vcs } } : { ...wd, vcs: undefined };
56800
+ const workspaces = draft.workspaceIndex === null ? [...draft.values.workspaces, entry] : draft.values.workspaces.map((w, i) => i === draft.workspaceIndex ? entry : w);
56801
+ return {
56802
+ ...draft,
56803
+ screen: "workspaces",
56804
+ workspaceDraft: null,
56805
+ workspaceIndex: null,
56806
+ values: { ...draft.values, workspaces },
56807
+ errors: {}
56808
+ };
56809
+ }
56810
+ }
56811
+ }
56812
+ function defaultWorkspaceProvider(vcs, tracker) {
56813
+ if (tracker === "github")
56814
+ return "github";
56815
+ if (tracker === "gitlab")
56816
+ return "gitlab";
56817
+ return vcs === "gitlab" || vcs === "github" ? vcs : null;
56818
+ }
55993
56819
  function workspaceVcs(vcs, tracker) {
55994
56820
  const provider = defaultWorkspaceProvider(vcs, tracker);
55995
56821
  return provider ? { provider } : undefined;
@@ -56018,32 +56844,30 @@ function workspaceDraftText(draft, field, value) {
56018
56844
  }
56019
56845
 
56020
56846
  // packages/workit-core/src/core/detect-hosts.ts
56021
- import { existsSync as existsSync13, readdirSync as readdirSync5 } from "node:fs";
56022
- import os8 from "node:os";
56023
- import path16 from "node:path";
56847
+ import os9 from "node:os";
56024
56848
 
56025
56849
  // packages/workit-core/src/core/uninstall.ts
56026
- import { existsSync as existsSync12, readFileSync as readFileSync12, realpathSync as realpathSync3, rmSync as rmSync2, writeFileSync as writeFileSync6 } from "node:fs";
56027
- import os7 from "node:os";
56028
- import path15 from "node:path";
56850
+ import { existsSync as existsSync13, readFileSync as readFileSync13, realpathSync as realpathSync3, rmSync as rmSync2, writeFileSync as writeFileSync6 } from "node:fs";
56851
+ import os8 from "node:os";
56852
+ import path16 from "node:path";
56029
56853
  var resolveUninstallPaths = (options = {}) => {
56030
- const home = options.home ?? options.env?.HOME ?? os7.homedir();
56854
+ const home = options.home ?? options.env?.HOME ?? os8.homedir();
56031
56855
  return {
56032
- opencodeConfig: options.opencodeConfig ?? path15.join(home, ".config", "opencode", "opencode.json"),
56033
- cursorSettings: options.cursorSettings ?? path15.join(home, ".cursor", "settings.json"),
56034
- cursorMcp: options.cursorMcp ?? path15.join(home, ".cursor", "mcp.json"),
56035
- cursorPluginDir: options.cursorPluginDir ?? path15.join(home, ".cursor", "plugins", "local", "workit"),
56036
- codexPluginDir: path15.join(home, ".codex", "plugins", "workit"),
56037
- piConfig: path15.join(home, ".pi", "config.json")
56856
+ opencodeConfig: options.opencodeConfig ?? path16.join(home, ".config", "opencode", "opencode.json"),
56857
+ cursorSettings: options.cursorSettings ?? path16.join(home, ".cursor", "settings.json"),
56858
+ cursorMcp: options.cursorMcp ?? path16.join(home, ".cursor", "mcp.json"),
56859
+ cursorPluginDir: options.cursorPluginDir ?? path16.join(home, ".cursor", "plugins", "local", "workit"),
56860
+ codexPluginDir: path16.join(home, ".codex", "plugins", "workit"),
56861
+ piConfig: path16.join(home, ".pi", "config.json")
56038
56862
  };
56039
56863
  };
56040
56864
  var isRecord3 = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
56041
56865
  var readJsonRecord = (file) => {
56042
56866
  let raw;
56043
56867
  try {
56044
- raw = readFileSync12(file, "utf8");
56868
+ raw = readFileSync13(file, "utf8");
56045
56869
  } catch {
56046
- if (existsSync12(file))
56870
+ if (existsSync13(file))
56047
56871
  return { kind: "malformed", error: `${file} is not readable` };
56048
56872
  return { kind: "missing" };
56049
56873
  }
@@ -56055,8 +56879,8 @@ var readJsonRecord = (file) => {
56055
56879
  }
56056
56880
  };
56057
56881
  var stripTrailingSep = (p) => {
56058
- const j = path15.join(p);
56059
- return path15.dirname(j) === j ? j : j.replace(/[\\/]+$/, "");
56882
+ const j = path16.join(p);
56883
+ return path16.dirname(j) === j ? j : j.replace(/[\\/]+$/, "");
56060
56884
  };
56061
56885
  var CURSOR_LEGACY_IDENTITIES = ["workflow-toolkit", "local/workflow-toolkit"];
56062
56886
  function cleanCursorSettings(settings, pluginDir) {
@@ -56167,7 +56991,7 @@ function planUninstall(paths = {}) {
56167
56991
  const mcpExisting = readJsonRecord(res.cursorMcp);
56168
56992
  const settingsDirty = settingsExisting.kind === "malformed" || settingsExisting.kind === "record" && cleanCursorSettings(settingsExisting.value, res.cursorPluginDir).changed;
56169
56993
  const mcpDirty = mcpExisting.kind === "malformed" || mcpExisting.kind === "record" && cleanCursorMcp(mcpExisting.value).changed;
56170
- const dirExists = existsSync12(res.cursorPluginDir);
56994
+ const dirExists = existsSync13(res.cursorPluginDir);
56171
56995
  const actions = [];
56172
56996
  if (settingsDirty) {
56173
56997
  actions.push({
@@ -56195,7 +57019,7 @@ function planUninstall(paths = {}) {
56195
57019
  installed: settingsDirty || mcpDirty || dirExists,
56196
57020
  actions
56197
57021
  };
56198
- const codexInstalled = existsSync12(res.codexPluginDir);
57022
+ const codexInstalled = existsSync13(res.codexPluginDir);
56199
57023
  const codex = {
56200
57024
  host: "codex",
56201
57025
  installed: codexInstalled,
@@ -56217,8 +57041,8 @@ function planUninstall(paths = {}) {
56217
57041
  return { hosts: [opencode, cursor, codex, pi] };
56218
57042
  }
56219
57043
  var canonicalRemoveDirAllowed = (actionPath, res) => {
56220
- const expected = path15.resolve(res.cursorPluginDir);
56221
- if (!(actionPath === expected && path15.basename(expected) === "workit" && path15.basename(path15.dirname(expected)) === "local" && path15.basename(path15.dirname(path15.dirname(expected))) === "plugins")) {
57044
+ const expected = path16.resolve(res.cursorPluginDir);
57045
+ if (!(actionPath === expected && path16.basename(expected) === "workit" && path16.basename(path16.dirname(expected)) === "local" && path16.basename(path16.dirname(path16.dirname(expected))) === "plugins")) {
56222
57046
  return false;
56223
57047
  }
56224
57048
  try {
@@ -56242,7 +57066,7 @@ var applyEditJsonRemove = (target, cleaner) => {
56242
57066
  `;
56243
57067
  let current;
56244
57068
  try {
56245
- current = readFileSync12(target, "utf8");
57069
+ current = readFileSync13(target, "utf8");
56246
57070
  } catch (error) {
56247
57071
  return {
56248
57072
  status: "failed",
@@ -56270,12 +57094,12 @@ function applyUninstall(plan, paths = {}) {
56270
57094
  let status;
56271
57095
  let detail;
56272
57096
  if (action.kind === "remove-dir") {
56273
- const resolved = path15.resolve(action.path);
56274
- const allowed = canonicalRemoveDirAllowed(resolved, res) || resolved === path15.resolve(res.codexPluginDir) && path15.basename(resolved) === "workit" && path15.basename(path15.dirname(resolved)) === "plugins";
57097
+ const resolved = path16.resolve(action.path);
57098
+ const allowed = canonicalRemoveDirAllowed(resolved, res) || resolved === path16.resolve(res.codexPluginDir) && path16.basename(resolved) === "workit" && path16.basename(path16.dirname(resolved)) === "plugins";
56275
57099
  if (!allowed) {
56276
57100
  status = "failed";
56277
57101
  detail = `refusing to remove non-canonical plugin directory: ${resolved}`;
56278
- } else if (!existsSync12(resolved)) {
57102
+ } else if (!existsSync13(resolved)) {
56279
57103
  status = "skipped";
56280
57104
  detail = "directory already absent";
56281
57105
  } else {
@@ -56306,13 +57130,7 @@ function applyUninstall(plan, paths = {}) {
56306
57130
 
56307
57131
  // packages/workit-core/src/core/detect-hosts.ts
56308
57132
  var HOSTS = ["opencode", "cursor", "codex", "pi"];
56309
- var HOME_MARKERS = {
56310
- opencode: [path16.join(".config", "opencode")],
56311
- cursor: [".cursor"],
56312
- codex: [".codex"],
56313
- pi: [".pi"]
56314
- };
56315
- var WIZARD_HOSTS = ["opencode", "cursor"];
57133
+ var WIZARD_HOSTS = [...HOSTS];
56316
57134
  function preselectedPlatforms(detection) {
56317
57135
  return WIZARD_HOSTS.filter((host) => detection[host].detected);
56318
57136
  }
@@ -56326,52 +57144,19 @@ function emptyDetection() {
56326
57144
  }
56327
57145
  function detectHosts(options = {}) {
56328
57146
  const env = options.env ?? process.env;
56329
- const home = options.home ?? env.HOME ?? os8.homedir();
56330
- const plan = planUninstall(options);
57147
+ const home = options.home ?? env.HOME ?? os9.homedir();
57148
+ const plan = planUninstall({ ...options, home, env });
56331
57149
  const configuredByHost = new Map(plan.hosts.map((h) => [h.host, h.installed]));
56332
57150
  const found = emptyDetection();
56333
57151
  for (const host of HOSTS) {
56334
- const detected = cliFound(host, env, home) || HOME_MARKERS[host].some((m) => existsSync13(path16.join(home, m)));
57152
+ const detected = findHostExecutable(host, { home, env }) !== null || installedHostApp(host, home, env);
56335
57153
  found[host] = {
56336
57154
  detected,
56337
- configured: WIZARD_HOSTS.includes(host) && (configuredByHost.get(host) ?? false)
57155
+ configured: host === "codex" ? isCodexWorkitInstalled(home, env) || (configuredByHost.get(host) ?? false) : host === "pi" ? isPiWorkitInstalled(home, env) || (configuredByHost.get(host) ?? false) : configuredByHost.get(host) ?? false
56338
57156
  };
56339
57157
  }
56340
57158
  return found;
56341
57159
  }
56342
- function cliFound(name, env, home) {
56343
- if (commandOnPath(name, env))
56344
- return true;
56345
- const extra = managerBinDirs(home);
56346
- if (extra.length === 0)
56347
- return false;
56348
- return commandOnPath(name, {
56349
- ...env,
56350
- PATH: [...(env.PATH ?? "").split(path16.delimiter), ...extra].join(path16.delimiter)
56351
- });
56352
- }
56353
- var managerBinDirs = (home) => {
56354
- const dirs = [path16.join(home, ".local", "bin"), path16.join(home, ".asdf", "shims")];
56355
- const versioned = [
56356
- [path16.join(home, ".local", "share", "fnm", "node-versions"), path16.join("installation", "bin")],
56357
- [path16.join(home, ".nvm", "versions", "node"), "bin"]
56358
- ];
56359
- for (const [base, tail] of versioned) {
56360
- let entries;
56361
- try {
56362
- entries = readdirSync5(base, { withFileTypes: true });
56363
- } catch {
56364
- continue;
56365
- }
56366
- for (const entry of entries) {
56367
- try {
56368
- if (entry.isDirectory())
56369
- dirs.push(path16.join(base, entry.name, tail));
56370
- } catch {}
56371
- }
56372
- }
56373
- return dirs;
56374
- };
56375
57160
 
56376
57161
  // packages/workit-cli/src/search-select.tsx
56377
57162
  var import_react65 = __toESM(require_react(), 1);
@@ -56469,22 +57254,10 @@ function SearchSelect({
56469
57254
  var jsx_dev_runtime2 = __toESM(require_jsx_dev_runtime(), 1);
56470
57255
  var PLATFORM_LABELS = [
56471
57256
  { label: "OpenCode", value: "opencode" },
56472
- { label: "Cursor", value: "cursor" }
57257
+ { label: "Cursor", value: "cursor" },
57258
+ { label: "Codex", value: "codex" },
57259
+ { label: "Pi", value: "pi" }
56473
57260
  ];
56474
- function platformOptions(detection) {
56475
- return PLATFORM_LABELS.map(({ label, value }) => {
56476
- const found = detection[value];
56477
- const tag = found.configured ? " · already configured" : found.detected ? " · detected" : "";
56478
- return { label: `${label}${tag}`, value };
56479
- });
56480
- }
56481
- function externalHostGuidance(detection) {
56482
- return ["codex", "pi"].map((host) => {
56483
- const found = detection[host];
56484
- const status = found.configured ? "already configured" : found.detected ? "detected" : "not detected";
56485
- return host === "codex" ? `Codex · ${status} — plugin/hooks setup: https://github.com/BrainerVirus/workit#readme (Codex CLI / desktop)` : `Pi · ${status} — install separately: pi install @brainervirus/workit-pi`;
56486
- });
56487
- }
56488
57261
  var BRANCH_PRESETS = [
56489
57262
  { label: "GitFlow", value: "gitflow" },
56490
57263
  { label: "GitHub Flow", value: "github-flow" },
@@ -56522,6 +57295,99 @@ var ISSUE_TRACKERS = [
56522
57295
  { label: "GitLab Issues", value: "gitlab" },
56523
57296
  { label: "None", value: "none" }
56524
57297
  ];
57298
+ var WORKSPACE_ADVANCED_FIELDS = [
57299
+ { label: "Hosting provider account", value: "vcs.account" },
57300
+ { label: "Default target branch", value: "vcs.defaultTargetBranch" },
57301
+ { label: "Workspace YouTrack URL", value: "youtrack.baseUrl" },
57302
+ { label: "Link issues in YouTrack", value: "youtrack.link_issues" },
57303
+ { label: "Link GitHub issues on pull requests", value: "issues.link_on_pr" },
57304
+ { label: "Branch preset", value: "branchPolicy.preset" },
57305
+ { label: "Allowed branch patterns", value: "branchPolicy.allowed" },
57306
+ { label: "Protected branches", value: "branchPolicy.protected" },
57307
+ { label: "Develop branch", value: "branchPolicy.developBranch" },
57308
+ { label: "Integration method (pr or merge)", value: "branchPolicy.integration" },
57309
+ { label: "Feature prefix", value: "branchPolicy.prefixes.feature" },
57310
+ { label: "Bugfix prefix", value: "branchPolicy.prefixes.bugfix" },
57311
+ { label: "Release prefix", value: "branchPolicy.prefixes.release" },
57312
+ { label: "Hotfix prefix", value: "branchPolicy.prefixes.hotfix" },
57313
+ { label: "Commit policy preset", value: "commitPolicy.preset" },
57314
+ { label: "Custom commit pattern", value: "commitPolicy.pattern" },
57315
+ { label: "Default profile name", value: "defaultProfile" }
57316
+ ];
57317
+ var PROFILE_FIELDS = [
57318
+ { label: "Branch preset", value: "branchPolicy.preset" },
57319
+ { label: "Allowed patterns", value: "branchPolicy.allowed" },
57320
+ { label: "Protected branches", value: "branchPolicy.protected" },
57321
+ { label: "Develop branch", value: "branchPolicy.developBranch" },
57322
+ { label: "Integration (pr or merge)", value: "branchPolicy.integration" },
57323
+ { label: "Feature prefix", value: "branchPolicy.prefixes.feature" },
57324
+ { label: "Bugfix prefix", value: "branchPolicy.prefixes.bugfix" },
57325
+ { label: "Release prefix", value: "branchPolicy.prefixes.release" },
57326
+ { label: "Hotfix prefix", value: "branchPolicy.prefixes.hotfix" },
57327
+ { label: "Commit preset", value: "commitPolicy.preset" },
57328
+ { label: "Commit pattern", value: "commitPolicy.pattern" }
57329
+ ];
57330
+ var TRACK_FIELDS = [
57331
+ { label: "Strategy", value: "strategy" },
57332
+ { label: "Production branch", value: "productionBranch" },
57333
+ { label: "Integration branch", value: "integrationBranch" },
57334
+ { label: "Feature branch naming", value: "naming.feature" },
57335
+ { label: "Release branch naming", value: "naming.release" },
57336
+ { label: "Hotfix branch naming", value: "naming.hotfix" },
57337
+ { label: "Base branch", value: "baseBranch" },
57338
+ { label: "Merge-back branches", value: "mergeBackBranches" },
57339
+ { label: "Pull request target", value: "pullRequestTarget" },
57340
+ { label: "Tag namespace", value: "tagNamespace" },
57341
+ { label: "Version source (manual, git-tag, package-json)", value: "versionSource.kind" },
57342
+ { label: "Package.json path", value: "versionSource.path" },
57343
+ { label: "Package version field", value: "versionSource.field" },
57344
+ { label: "Required checks", value: "requiredChecks" }
57345
+ ];
57346
+ var enumFieldOptions = (field) => {
57347
+ if (field === "commitPolicy.preset")
57348
+ return [{ label: "Inherit global", value: "inherit" }, ...commitPresetOptions];
57349
+ if (field === "branchPolicy.preset")
57350
+ return [
57351
+ { label: "Inherit global", value: "inherit" },
57352
+ { label: "GitFlow", value: "gitflow" },
57353
+ { label: "GitHub Flow", value: "github-flow" },
57354
+ { label: "Trunk-based", value: "trunk-based" },
57355
+ { label: "Custom", value: "custom" }
57356
+ ];
57357
+ if (field === "strategy")
57358
+ return [
57359
+ { label: "GitFlow", value: "gitflow" },
57360
+ { label: "GitHub Flow", value: "github-flow" },
57361
+ { label: "Trunk-based", value: "trunk-based" },
57362
+ { label: "Custom", value: "custom" }
57363
+ ];
57364
+ if (field.endsWith(".integration"))
57365
+ return [
57366
+ { label: "Pull requests", value: "pr" },
57367
+ { label: "Merge commits", value: "merge" }
57368
+ ];
57369
+ if (field === "versionSource.kind")
57370
+ return [
57371
+ { label: "Package.json", value: "package-json" },
57372
+ { label: "Git tag", value: "git-tag" },
57373
+ { label: "Manual", value: "manual" }
57374
+ ];
57375
+ if (field === "youtrack.link_issues" || field === "issues.link_on_pr")
57376
+ return [
57377
+ { label: "Inherit global behavior", value: "inherit" },
57378
+ { label: "Enabled", value: "true" },
57379
+ { label: "Disabled", value: "false" }
57380
+ ];
57381
+ return null;
57382
+ };
57383
+ var commitPresetOptions = [
57384
+ { label: "Conventional commits", value: "conventional" },
57385
+ { label: "Gitmoji", value: "gitmoji" },
57386
+ { label: "Ticket prefix", value: "ticket-prefix" },
57387
+ { label: "Freeform", value: "freeform" },
57388
+ { label: "Custom pattern", value: "custom" },
57389
+ { label: "Auto-detect", value: "auto" }
57390
+ ];
56525
57391
  var TIMEZONE_FALLBACK = [
56526
57392
  "UTC",
56527
57393
  "America/New_York",
@@ -56560,6 +57426,8 @@ var TEXT_SCREENS = new Set([
56560
57426
  "basePath",
56561
57427
  "workspaceName",
56562
57428
  "workspaceGlob",
57429
+ "workspaceAdvancedValue",
57430
+ "globalCommitPattern",
56563
57431
  "branchPolicyDevelop"
56564
57432
  ]);
56565
57433
  var SEARCH_SCREENS = new Set(["locale", "timezone"]);
@@ -56604,6 +57472,479 @@ function SelectList({
56604
57472
  }, option.value, true, undefined, this))
56605
57473
  }, undefined, false, undefined, this);
56606
57474
  }
57475
+ function HostPicker({
57476
+ detection,
57477
+ selected,
57478
+ onChange,
57479
+ onSubmit
57480
+ }) {
57481
+ const available = PLATFORM_LABELS.filter(({ value }) => detection[value].detected).map(({ value }) => value);
57482
+ const options = [
57483
+ { label: "Select all available", value: "__all" },
57484
+ { label: "Clear all", value: "__none" },
57485
+ ...PLATFORM_LABELS.map(({ label, value }) => ({ label, value }))
57486
+ ];
57487
+ const [index, setIndex] = import_react66.useState(0);
57488
+ const indexRef = import_react66.useRef(index);
57489
+ const [chosen, setChosen] = import_react66.useState(selected);
57490
+ const chosenRef = import_react66.useRef(chosen);
57491
+ import_react66.useEffect(() => {
57492
+ indexRef.current = index;
57493
+ }, [index]);
57494
+ import_react66.useEffect(() => {
57495
+ chosenRef.current = chosen;
57496
+ }, [chosen]);
57497
+ const update = (next) => {
57498
+ chosenRef.current = next;
57499
+ setChosen(next);
57500
+ onChange(next);
57501
+ };
57502
+ use_input_default((input, key) => {
57503
+ if (key.upArrow || key.downArrow) {
57504
+ const next = Math.max(0, Math.min(options.length - 1, indexRef.current + (key.downArrow ? 1 : -1)));
57505
+ indexRef.current = next;
57506
+ setIndex(next);
57507
+ return;
57508
+ }
57509
+ if (input === " ") {
57510
+ const option = options[indexRef.current];
57511
+ if (option.value === "__all")
57512
+ update([...available]);
57513
+ else if (option.value === "__none")
57514
+ update([]);
57515
+ else if (detection[option.value].detected) {
57516
+ const next = chosenRef.current.includes(option.value) ? chosenRef.current.filter((host) => host !== option.value) : [...chosenRef.current, option.value];
57517
+ update(next);
57518
+ }
57519
+ } else if (key.return) {
57520
+ const option = options[indexRef.current];
57521
+ if (option.value === "__all")
57522
+ update([...available]);
57523
+ else if (option.value === "__none")
57524
+ update([]);
57525
+ onSubmit(chosenRef.current);
57526
+ }
57527
+ });
57528
+ return /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Box_default, {
57529
+ flexDirection: "column",
57530
+ gap: 0,
57531
+ children: options.map((option, row) => {
57532
+ const host = option.value === "__all" || option.value === "__none" ? null : option.value;
57533
+ const detected = host === null || detection[host].detected;
57534
+ const checked = host !== null && chosen.includes(host);
57535
+ const label = host === null ? option.label : `${checked ? "[✓]" : "[ ]"} ${option.label}${detection[host].configured ? " · already configured" : detection[host].detected ? " · detected" : " · unavailable"}`;
57536
+ return /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
57537
+ color: row === index ? "cyan" : undefined,
57538
+ dimColor: !detected,
57539
+ children: [
57540
+ row === index ? "❯ " : " ",
57541
+ label
57542
+ ]
57543
+ }, option.value, true, undefined, this);
57544
+ })
57545
+ }, undefined, false, undefined, this);
57546
+ }
57547
+ function WorkspaceCollectionEditor({
57548
+ draft,
57549
+ dispatch
57550
+ }) {
57551
+ const [mode, setMode] = import_react66.useState("main");
57552
+ const [name, setName] = import_react66.useState("");
57553
+ const [field, setField] = import_react66.useState(null);
57554
+ const [nameError, setNameError] = import_react66.useState("");
57555
+ const workspace = draft.workspaceDraft;
57556
+ const profileNames = Object.keys(workspace?.profiles ?? {});
57557
+ const trackNames = Object.keys(workspace?.releaseTracks ?? {});
57558
+ if (!workspace)
57559
+ return /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
57560
+ color: "red",
57561
+ children: "Workspace draft is unavailable."
57562
+ }, undefined, false, undefined, this);
57563
+ if (mode === "profiles") {
57564
+ const options = [
57565
+ ...profileNames.flatMap((profile) => [
57566
+ { label: `Edit profile ${profile}`, value: `edit:${profile}` },
57567
+ { label: `Set ${profile} as default`, value: `default:${profile}` },
57568
+ { label: `Delete profile ${profile}`, value: `delete:${profile}` }
57569
+ ]),
57570
+ { label: "Create profile", value: "create" },
57571
+ { label: "Back to advanced settings", value: "back" }
57572
+ ];
57573
+ return /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Box_default, {
57574
+ flexDirection: "column",
57575
+ gap: 1,
57576
+ children: [
57577
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
57578
+ bold: true,
57579
+ children: [
57580
+ "Profiles · default: ",
57581
+ workspace.defaultProfile ?? "none"
57582
+ ]
57583
+ }, undefined, true, undefined, this),
57584
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
57585
+ dimColor: true,
57586
+ children: "Profiles can override branch rules and commit conventions."
57587
+ }, undefined, false, undefined, this),
57588
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(SelectList, {
57589
+ options,
57590
+ value: "back",
57591
+ onSelect: (value) => {
57592
+ const profileName = value.slice(value.indexOf(":") + 1);
57593
+ if (value === "create") {
57594
+ setNameError("");
57595
+ setName("");
57596
+ setMode("profileName");
57597
+ } else if (value.startsWith("edit:")) {
57598
+ setName(profileName);
57599
+ setMode("profileEdit");
57600
+ } else if (value.startsWith("default:"))
57601
+ dispatch({ type: "workspaceProfileDefault", name: profileName });
57602
+ else if (value.startsWith("delete:"))
57603
+ dispatch({ type: "workspaceProfileDelete", name: profileName });
57604
+ else
57605
+ setMode("main");
57606
+ }
57607
+ }, undefined, false, undefined, this)
57608
+ ]
57609
+ }, undefined, true, undefined, this);
57610
+ }
57611
+ if (mode === "profileName")
57612
+ return /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Box_default, {
57613
+ flexDirection: "column",
57614
+ gap: 1,
57615
+ children: [
57616
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
57617
+ bold: true,
57618
+ children: "Create profile"
57619
+ }, undefined, false, undefined, this),
57620
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
57621
+ dimColor: true,
57622
+ children: "Profile name:"
57623
+ }, undefined, false, undefined, this),
57624
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(TextInput, {
57625
+ onSubmit: (value) => {
57626
+ const next = value.trim();
57627
+ if (!next) {
57628
+ setNameError("Enter a profile name.");
57629
+ return;
57630
+ }
57631
+ if (workspace.profiles?.[next]) {
57632
+ setNameError(`Profile ${next} already exists. Choose a different name or edit it from the list.`);
57633
+ return;
57634
+ }
57635
+ dispatch({ type: "workspaceProfileCreate", name: next });
57636
+ setName(next);
57637
+ setMode("profileEdit");
57638
+ }
57639
+ }, undefined, false, undefined, this),
57640
+ nameError && /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
57641
+ color: "red",
57642
+ children: nameError
57643
+ }, undefined, false, undefined, this),
57644
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
57645
+ dimColor: true,
57646
+ children: "Enter to create · Esc Back"
57647
+ }, undefined, false, undefined, this)
57648
+ ]
57649
+ }, undefined, true, undefined, this);
57650
+ if (mode === "profileEdit" || mode === "profileValue") {
57651
+ const selected = workspace.profiles?.[name];
57652
+ if (mode === "profileValue" && field) {
57653
+ const item = PROFILE_FIELDS.find((entry) => entry.value === field);
57654
+ const choices = enumFieldOptions(field);
57655
+ return /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Box_default, {
57656
+ flexDirection: "column",
57657
+ gap: 1,
57658
+ children: [
57659
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
57660
+ bold: true,
57661
+ children: [
57662
+ "Profile ",
57663
+ name,
57664
+ " · ",
57665
+ item?.label
57666
+ ]
57667
+ }, undefined, true, undefined, this),
57668
+ choices ? /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(SelectList, {
57669
+ options: choices,
57670
+ value: selected ? profileEditorValue(selected, field) : choices[0].value,
57671
+ onSelect: (value) => {
57672
+ dispatch({
57673
+ type: "workspaceProfileSet",
57674
+ name,
57675
+ field,
57676
+ value
57677
+ });
57678
+ setMode("profileEdit");
57679
+ }
57680
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(TextInput, {
57681
+ defaultValue: selected ? profileEditorValue(selected, field) : "",
57682
+ onSubmit: (value) => {
57683
+ dispatch({
57684
+ type: "workspaceProfileSet",
57685
+ name,
57686
+ field,
57687
+ value
57688
+ });
57689
+ setMode("profileEdit");
57690
+ }
57691
+ }, undefined, false, undefined, this),
57692
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
57693
+ dimColor: true,
57694
+ children: "Enter to save · Esc Back"
57695
+ }, undefined, false, undefined, this)
57696
+ ]
57697
+ }, undefined, true, undefined, this);
57698
+ }
57699
+ const options = [
57700
+ ...PROFILE_FIELDS,
57701
+ { label: "Done editing", value: "done" }
57702
+ ];
57703
+ return /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Box_default, {
57704
+ flexDirection: "column",
57705
+ gap: 1,
57706
+ children: [
57707
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
57708
+ bold: true,
57709
+ children: [
57710
+ "Edit profile · ",
57711
+ name
57712
+ ]
57713
+ }, undefined, true, undefined, this),
57714
+ PROFILE_FIELDS.map((item) => /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
57715
+ dimColor: true,
57716
+ children: [
57717
+ item.label,
57718
+ ": ",
57719
+ selected ? profileEditorValue(selected, item.value) || "unset" : ""
57720
+ ]
57721
+ }, item.value, true, undefined, this)),
57722
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(SelectList, {
57723
+ options,
57724
+ value: "done",
57725
+ onSelect: (value) => {
57726
+ if (value === "done")
57727
+ setMode("profiles");
57728
+ else {
57729
+ setField(value);
57730
+ setMode("profileValue");
57731
+ }
57732
+ }
57733
+ }, undefined, false, undefined, this)
57734
+ ]
57735
+ }, undefined, true, undefined, this);
57736
+ }
57737
+ if (mode === "tracks") {
57738
+ const options = [
57739
+ ...trackNames.flatMap((track) => [
57740
+ { label: `Edit release track ${track}`, value: `edit:${track}` },
57741
+ { label: `Delete release track ${track}`, value: `delete:${track}` }
57742
+ ]),
57743
+ { label: "Create release track", value: "create" },
57744
+ { label: "Back to advanced settings", value: "back" }
57745
+ ];
57746
+ return /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Box_default, {
57747
+ flexDirection: "column",
57748
+ gap: 1,
57749
+ children: [
57750
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
57751
+ bold: true,
57752
+ children: "Named release tracks"
57753
+ }, undefined, false, undefined, this),
57754
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
57755
+ dimColor: true,
57756
+ children: "Each track has its own branches, naming, version source, and checks."
57757
+ }, undefined, false, undefined, this),
57758
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(SelectList, {
57759
+ options,
57760
+ value: "back",
57761
+ onSelect: (value) => {
57762
+ const trackName = value.slice(value.indexOf(":") + 1);
57763
+ if (value === "create") {
57764
+ setNameError("");
57765
+ setName("");
57766
+ setMode("trackName");
57767
+ } else if (value.startsWith("edit:")) {
57768
+ setName(trackName);
57769
+ setMode("trackEdit");
57770
+ } else if (value.startsWith("delete:"))
57771
+ dispatch({ type: "workspaceTrackDelete", name: trackName });
57772
+ else
57773
+ setMode("main");
57774
+ }
57775
+ }, undefined, false, undefined, this)
57776
+ ]
57777
+ }, undefined, true, undefined, this);
57778
+ }
57779
+ if (mode === "trackName")
57780
+ return /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Box_default, {
57781
+ flexDirection: "column",
57782
+ gap: 1,
57783
+ children: [
57784
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
57785
+ bold: true,
57786
+ children: "Create release track"
57787
+ }, undefined, false, undefined, this),
57788
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
57789
+ dimColor: true,
57790
+ children: "Track name:"
57791
+ }, undefined, false, undefined, this),
57792
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(TextInput, {
57793
+ onSubmit: (value) => {
57794
+ const next = value.trim();
57795
+ if (!next) {
57796
+ setNameError("Enter a release-track name.");
57797
+ return;
57798
+ }
57799
+ if (workspace.releaseTracks?.[next]) {
57800
+ setNameError(`Release track ${next} already exists. Choose a different name or edit it from the list.`);
57801
+ return;
57802
+ }
57803
+ dispatch({ type: "workspaceTrackCreate", name: next });
57804
+ setName(next);
57805
+ setMode("trackEdit");
57806
+ }
57807
+ }, undefined, false, undefined, this),
57808
+ nameError && /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
57809
+ color: "red",
57810
+ children: nameError
57811
+ }, undefined, false, undefined, this),
57812
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
57813
+ dimColor: true,
57814
+ children: "Enter to create · Esc Back"
57815
+ }, undefined, false, undefined, this)
57816
+ ]
57817
+ }, undefined, true, undefined, this);
57818
+ if (mode === "trackEdit" || mode === "trackValue") {
57819
+ const selected = workspace.releaseTracks?.[name];
57820
+ if (mode === "trackValue" && field) {
57821
+ const item = TRACK_FIELDS.find((entry) => entry.value === field);
57822
+ const choices = enumFieldOptions(field);
57823
+ return /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Box_default, {
57824
+ flexDirection: "column",
57825
+ gap: 1,
57826
+ children: [
57827
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
57828
+ bold: true,
57829
+ children: [
57830
+ "Release track ",
57831
+ name,
57832
+ " · ",
57833
+ item?.label
57834
+ ]
57835
+ }, undefined, true, undefined, this),
57836
+ choices ? /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(SelectList, {
57837
+ options: choices,
57838
+ value: selected ? trackEditorValue(selected, field) : choices[0].value,
57839
+ onSelect: (value) => {
57840
+ dispatch({
57841
+ type: "workspaceTrackSet",
57842
+ name,
57843
+ field,
57844
+ value
57845
+ });
57846
+ setMode("trackEdit");
57847
+ }
57848
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(TextInput, {
57849
+ defaultValue: selected ? trackEditorValue(selected, field) : "",
57850
+ onSubmit: (value) => {
57851
+ dispatch({
57852
+ type: "workspaceTrackSet",
57853
+ name,
57854
+ field,
57855
+ value
57856
+ });
57857
+ setMode("trackEdit");
57858
+ }
57859
+ }, undefined, false, undefined, this),
57860
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
57861
+ dimColor: true,
57862
+ children: "Lists use comma-separated values · Enter to save · Esc Back"
57863
+ }, undefined, false, undefined, this)
57864
+ ]
57865
+ }, undefined, true, undefined, this);
57866
+ }
57867
+ const options = [...TRACK_FIELDS, { label: "Done editing", value: "done" }];
57868
+ return /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Box_default, {
57869
+ flexDirection: "column",
57870
+ gap: 1,
57871
+ children: [
57872
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
57873
+ bold: true,
57874
+ children: [
57875
+ "Edit release track · ",
57876
+ name
57877
+ ]
57878
+ }, undefined, true, undefined, this),
57879
+ TRACK_FIELDS.map((item) => /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
57880
+ dimColor: true,
57881
+ children: [
57882
+ item.label,
57883
+ ": ",
57884
+ selected ? trackEditorValue(selected, item.value) || "unset" : ""
57885
+ ]
57886
+ }, item.value, true, undefined, this)),
57887
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(SelectList, {
57888
+ options,
57889
+ value: "done",
57890
+ onSelect: (value) => {
57891
+ if (value === "done")
57892
+ setMode("tracks");
57893
+ else {
57894
+ setField(value);
57895
+ setMode("trackValue");
57896
+ }
57897
+ }
57898
+ }, undefined, false, undefined, this)
57899
+ ]
57900
+ }, undefined, true, undefined, this);
57901
+ }
57902
+ const options = [
57903
+ ...WORKSPACE_ADVANCED_FIELDS,
57904
+ { label: "Manage profiles", value: "profiles" },
57905
+ { label: "Manage release tracks", value: "tracks" },
57906
+ { label: "Save workspace settings", value: "save" }
57907
+ ];
57908
+ return /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Box_default, {
57909
+ flexDirection: "column",
57910
+ gap: 1,
57911
+ children: [
57912
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
57913
+ bold: true,
57914
+ children: [
57915
+ "Advanced workspace settings · ",
57916
+ workspace.name
57917
+ ]
57918
+ }, undefined, true, undefined, this),
57919
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
57920
+ dimColor: true,
57921
+ children: "Choose a setting to edit. Lists use comma-separated values; blank clears an override."
57922
+ }, undefined, false, undefined, this),
57923
+ [...WORKSPACE_ADVANCED_FIELDS].map(({ label, value }) => /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
57924
+ dimColor: true,
57925
+ children: [
57926
+ label,
57927
+ ": ",
57928
+ workspaceEditorValue(workspace, value) || "inherited / unset"
57929
+ ]
57930
+ }, value, true, undefined, this)),
57931
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(SelectList, {
57932
+ options,
57933
+ value: "save",
57934
+ onSelect: (value) => {
57935
+ if (value === "save")
57936
+ dispatch({ type: "workspaceSave" });
57937
+ else if (value === "profiles")
57938
+ setMode("profiles");
57939
+ else if (value === "tracks")
57940
+ setMode("tracks");
57941
+ else
57942
+ dispatch({ type: "workspaceAdvancedSelect", field: value });
57943
+ }
57944
+ }, undefined, false, undefined, this)
57945
+ ]
57946
+ }, undefined, true, undefined, this);
57947
+ }
56607
57948
  function effectivePolicy(values) {
56608
57949
  return mergePreset(values.branchPreset, {
56609
57950
  allowed: parseList(values.branchAllowed),
@@ -56815,6 +58156,8 @@ function describeMutation(m) {
56815
58156
  return `+ register ${m.platform}: ${m.path}`;
56816
58157
  case "install-adapter":
56817
58158
  return `+ copy adapter ${m.platform}: ${m.path}`;
58159
+ case "install-host":
58160
+ return `+ install ${m.platform}: ${m.commands.map((command) => command.command).join(", ")}`;
56818
58161
  case "set-token-path":
56819
58162
  return `+ change ${m.key} in ${m.path} → ${m.value}`;
56820
58163
  }
@@ -56870,28 +58213,21 @@ function Screen({
56870
58213
  children: "Step 1 — Platforms"
56871
58214
  }, undefined, false, undefined, this),
56872
58215
  /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
56873
- children: "This wizard configures OpenCode and Cursor only."
58216
+ children: "Choose the detected tools where Workit should be configured."
56874
58217
  }, undefined, false, undefined, this),
56875
58218
  /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
56876
58219
  dimColor: true,
56877
- children: "Select the tools to configure (space to toggle):"
58220
+ children: "Use Space to toggle, or choose Select all available / Clear all."
56878
58221
  }, undefined, false, undefined, this),
56879
- /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(MultiSelect, {
56880
- options: platformOptions(detection),
56881
- defaultValue: draft.values.platforms,
58222
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(HostPicker, {
58223
+ detection,
58224
+ selected: draft.values.platforms,
56882
58225
  onChange: (values) => dispatch({ type: "set", field: "platforms", value: values }),
56883
58226
  onSubmit: (values) => {
56884
58227
  dispatch({ type: "set", field: "platforms", value: values });
56885
58228
  dispatch({ type: "next" });
56886
58229
  }
56887
58230
  }, undefined, false, undefined, this),
56888
- externalHostGuidance(detection).map((guidance) => /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
56889
- children: guidance
56890
- }, guidance, false, undefined, this)),
56891
- /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
56892
- dimColor: true,
56893
- children: "For legacy installations, use `workit cutover` to preview migration."
56894
- }, undefined, false, undefined, this),
56895
58231
  draft.errors.platforms && /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
56896
58232
  color: "red",
56897
58233
  children: draft.errors.platforms
@@ -57260,7 +58596,12 @@ function Screen({
57260
58596
  })),
57261
58597
  { label: "Add workspace", value: "add" },
57262
58598
  { label: `Use current project (${base})`, value: "current" },
57263
- { label: "Done", value: "done" }
58599
+ { label: "Done", value: "done" },
58600
+ { label: "Advanced global commit policy", value: "global-commit" },
58601
+ ...draft.values.workspaces.map((w, i) => ({
58602
+ label: `Advanced settings for ${w.name}`,
58603
+ value: `advanced:${i}`
58604
+ }))
57264
58605
  ];
57265
58606
  return /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Box_default, {
57266
58607
  flexDirection: "column",
@@ -57300,6 +58641,10 @@ function Screen({
57300
58641
  dispatch({ type: "workspaceEdit", index: Number(value.slice(5)) });
57301
58642
  else if (value.startsWith("remove:"))
57302
58643
  dispatch({ type: "workspaceRemove", index: Number(value.slice(7)) });
58644
+ else if (value.startsWith("advanced:"))
58645
+ dispatch({ type: "workspaceAdvancedOpen", index: Number(value.slice(9)) });
58646
+ else if (value === "global-commit")
58647
+ dispatch({ type: "globalCommitOpen" });
57303
58648
  else if (value === "add")
57304
58649
  dispatch({ type: "workspaceAdd" });
57305
58650
  else if (value === "current")
@@ -57334,93 +58679,222 @@ function Screen({
57334
58679
  onChange: (value) => dispatch({ type: "workspaceDraftName", value }),
57335
58680
  onSubmit: () => dispatch({ type: "next" })
57336
58681
  }, undefined, false, undefined, this),
57337
- draft.errors.workspaceName && /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
57338
- color: "red",
57339
- children: draft.errors.workspaceName
58682
+ draft.errors.workspaceName && /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
58683
+ color: "red",
58684
+ children: draft.errors.workspaceName
58685
+ }, undefined, false, undefined, this),
58686
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
58687
+ dimColor: true,
58688
+ children: "Enter to continue · Esc Back"
58689
+ }, undefined, false, undefined, this)
58690
+ ]
58691
+ }, undefined, true, undefined, this);
58692
+ case "workspaceGlob": {
58693
+ const glob = draft.workspaceDraft?.glob ?? "";
58694
+ return /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Box_default, {
58695
+ flexDirection: "column",
58696
+ gap: 1,
58697
+ children: [
58698
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
58699
+ bold: true,
58700
+ children: "Step 5 — Workspaces · Pattern"
58701
+ }, undefined, false, undefined, this),
58702
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
58703
+ dimColor: true,
58704
+ children: "Workspace pattern (glob, e.g. /work/**):"
58705
+ }, undefined, false, undefined, this),
58706
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(TextInput, {
58707
+ placeholder: SCREEN_PLACEHOLDERS.workspaceGlob,
58708
+ defaultValue: glob,
58709
+ onChange: (value) => dispatch({ type: "workspaceDraftGlob", value }),
58710
+ onSubmit: () => dispatch({ type: "next" })
58711
+ }, undefined, false, undefined, this),
58712
+ glob.trim() !== "" && /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Box_default, {
58713
+ flexDirection: "column",
58714
+ gap: 0,
58715
+ children: [
58716
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
58717
+ bold: true,
58718
+ children: "Match preview (shared matcher):"
58719
+ }, undefined, false, undefined, this),
58720
+ workspacePreviewTargets(resolveBasePath(draft.values)).map((target) => {
58721
+ const matches = matchWorkspace(glob, target);
58722
+ return /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
58723
+ color: matches ? "green" : "red",
58724
+ children: [
58725
+ matches ? "✓ matches" : "✗ no match",
58726
+ " ",
58727
+ target
58728
+ ]
58729
+ }, target, true, undefined, this);
58730
+ })
58731
+ ]
58732
+ }, undefined, true, undefined, this),
58733
+ draft.errors.workspaceGlob && /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
58734
+ color: "red",
58735
+ children: draft.errors.workspaceGlob
58736
+ }, undefined, false, undefined, this),
58737
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
58738
+ dimColor: true,
58739
+ children: "Enter to continue · Esc Back"
58740
+ }, undefined, false, undefined, this)
58741
+ ]
58742
+ }, undefined, true, undefined, this);
58743
+ }
58744
+ case "workspaceProvider":
58745
+ return /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Box_default, {
58746
+ flexDirection: "column",
58747
+ gap: 1,
58748
+ children: [
58749
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
58750
+ bold: true,
58751
+ children: "Step 5 — Workspaces · Provider"
58752
+ }, undefined, false, undefined, this),
58753
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
58754
+ dimColor: true,
58755
+ children: "Version control provider for this workspace:"
58756
+ }, undefined, false, undefined, this),
58757
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(SelectList, {
58758
+ options: VCS_PROVIDERS,
58759
+ value: draft.workspaceDraft?.vcs?.provider ?? "skip",
58760
+ onChange: (value) => dispatch({ type: "workspaceDraftProvider", value }),
58761
+ onSelect: (value) => {
58762
+ dispatch({ type: "workspaceDraftProvider", value });
58763
+ dispatch({ type: "workspaceSave" });
58764
+ }
58765
+ }, undefined, false, undefined, this),
58766
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
58767
+ dimColor: true,
58768
+ children: "Enter to save · b Back · Esc Cancel"
58769
+ }, undefined, false, undefined, this)
58770
+ ]
58771
+ }, undefined, true, undefined, this);
58772
+ case "workspaceAdvanced": {
58773
+ return /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(WorkspaceCollectionEditor, {
58774
+ draft,
58775
+ dispatch
58776
+ }, undefined, false, undefined, this);
58777
+ }
58778
+ case "workspaceAdvancedValue": {
58779
+ const field = draft.workspaceEditorField;
58780
+ const current = draft.workspaceDraft && field ? workspaceEditorValue(draft.workspaceDraft, field) : "";
58781
+ const label = WORKSPACE_ADVANCED_FIELDS.find((entry) => entry.value === field)?.label ?? "Setting";
58782
+ const choices = field ? enumFieldOptions(field) : null;
58783
+ const profileChoices = field === "defaultProfile" ? [
58784
+ { label: "Inherit / no default profile", value: "" },
58785
+ ...Object.keys(draft.workspaceDraft?.profiles ?? {}).map((name) => ({
58786
+ label: name,
58787
+ value: name
58788
+ }))
58789
+ ] : null;
58790
+ return /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Box_default, {
58791
+ flexDirection: "column",
58792
+ gap: 1,
58793
+ children: [
58794
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
58795
+ bold: true,
58796
+ children: [
58797
+ "Advanced workspace settings · ",
58798
+ label
58799
+ ]
58800
+ }, undefined, true, undefined, this),
58801
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
58802
+ dimColor: true,
58803
+ children: "Leave empty to clear this override. Use comma-separated values for lists."
58804
+ }, undefined, false, undefined, this),
58805
+ profileChoices ? /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(SelectList, {
58806
+ options: profileChoices,
58807
+ value: current,
58808
+ onSelect: (value) => field && dispatch({ type: "workspaceAdvancedSet", field, value })
58809
+ }, undefined, false, undefined, this) : choices ? /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(SelectList, {
58810
+ options: choices,
58811
+ value: current || choices[0].value,
58812
+ onSelect: (value) => field && dispatch({ type: "workspaceAdvancedSet", field, value })
58813
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(TextInput, {
58814
+ defaultValue: current,
58815
+ onSubmit: (value) => field && dispatch({ type: "workspaceAdvancedSet", field, value })
58816
+ }, undefined, false, undefined, this),
58817
+ field?.endsWith("integration") && /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
58818
+ dimColor: true,
58819
+ children: "Enter pr or merge."
57340
58820
  }, undefined, false, undefined, this),
57341
58821
  /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
57342
58822
  dimColor: true,
57343
- children: "Enter to continue · Esc Back"
58823
+ children: "Enter to save · Esc Back"
57344
58824
  }, undefined, false, undefined, this)
57345
58825
  ]
57346
58826
  }, undefined, true, undefined, this);
57347
- case "workspaceGlob": {
57348
- const glob = draft.workspaceDraft?.glob ?? "";
58827
+ }
58828
+ case "globalCommitPolicy": {
58829
+ const options = [
58830
+ { label: "Conventional commits", value: "conventional" },
58831
+ { label: "Gitmoji", value: "gitmoji" },
58832
+ { label: "Ticket prefix", value: "ticket-prefix" },
58833
+ { label: "Freeform", value: "freeform" },
58834
+ { label: "Custom pattern", value: "custom" },
58835
+ { label: "Auto-detect", value: "auto" },
58836
+ { label: "Edit custom pattern…", value: "pattern" },
58837
+ { label: "Done", value: "done" }
58838
+ ];
57349
58839
  return /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Box_default, {
57350
58840
  flexDirection: "column",
57351
58841
  gap: 1,
57352
58842
  children: [
57353
58843
  /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
57354
58844
  bold: true,
57355
- children: "Step 5 — Workspaces · Pattern"
58845
+ children: "Advanced global commit policy"
57356
58846
  }, undefined, false, undefined, this),
57357
58847
  /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
57358
58848
  dimColor: true,
57359
- children: "Workspace pattern (glob, e.g. /work/**):"
57360
- }, undefined, false, undefined, this),
57361
- /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(TextInput, {
57362
- placeholder: SCREEN_PLACEHOLDERS.workspaceGlob,
57363
- defaultValue: glob,
57364
- onChange: (value) => dispatch({ type: "workspaceDraftGlob", value }),
57365
- onSubmit: () => dispatch({ type: "next" })
58849
+ children: "Default convention used when a workspace has no commit override."
57366
58850
  }, undefined, false, undefined, this),
57367
- glob.trim() !== "" && /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Box_default, {
57368
- flexDirection: "column",
57369
- gap: 0,
58851
+ draft.values.commitPolicy.pattern && /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
58852
+ dimColor: true,
57370
58853
  children: [
57371
- /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
57372
- bold: true,
57373
- children: "Match preview (shared matcher):"
57374
- }, undefined, false, undefined, this),
57375
- workspacePreviewTargets(resolveBasePath(draft.values)).map((target) => {
57376
- const matches = matchWorkspace(glob, target);
57377
- return /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
57378
- color: matches ? "green" : "red",
57379
- children: [
57380
- matches ? "✓ matches" : "✗ no match",
57381
- " ",
57382
- target
57383
- ]
57384
- }, target, true, undefined, this);
57385
- })
58854
+ "Custom pattern: ",
58855
+ draft.values.commitPolicy.pattern
57386
58856
  ]
57387
58857
  }, undefined, true, undefined, this),
57388
- draft.errors.workspaceGlob && /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
57389
- color: "red",
57390
- children: draft.errors.workspaceGlob
58858
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(SelectList, {
58859
+ options,
58860
+ value: draft.values.commitPolicy.preset,
58861
+ onSelect: (value) => {
58862
+ if (value === "pattern")
58863
+ dispatch({ type: "globalCommitEditPattern" });
58864
+ else if (value === "done")
58865
+ dispatch({ type: "back" });
58866
+ else
58867
+ dispatch({ type: "globalCommitPreset", value });
58868
+ }
57391
58869
  }, undefined, false, undefined, this),
57392
58870
  /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
57393
58871
  dimColor: true,
57394
- children: "Enter to continue · Esc Back"
58872
+ children: "Enter to select · b Back"
57395
58873
  }, undefined, false, undefined, this)
57396
58874
  ]
57397
58875
  }, undefined, true, undefined, this);
57398
58876
  }
57399
- case "workspaceProvider":
58877
+ case "globalCommitPattern":
57400
58878
  return /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Box_default, {
57401
58879
  flexDirection: "column",
57402
58880
  gap: 1,
57403
58881
  children: [
57404
58882
  /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
57405
58883
  bold: true,
57406
- children: "Step 5 — Workspaces · Provider"
58884
+ children: "Advanced global commit policy · Custom pattern"
57407
58885
  }, undefined, false, undefined, this),
57408
58886
  /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
57409
58887
  dimColor: true,
57410
- children: "Version control provider for this workspace:"
58888
+ children: "Regular expression matched against the commit subject line."
57411
58889
  }, undefined, false, undefined, this),
57412
- /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(SelectList, {
57413
- options: VCS_PROVIDERS,
57414
- value: draft.workspaceDraft?.vcs?.provider ?? "skip",
57415
- onChange: (value) => dispatch({ type: "workspaceDraftProvider", value }),
57416
- onSelect: (value) => {
57417
- dispatch({ type: "workspaceDraftProvider", value });
57418
- dispatch({ type: "workspaceSave" });
57419
- }
58890
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(TextInput, {
58891
+ defaultValue: draft.values.commitPolicy.pattern ?? "",
58892
+ onChange: (value) => dispatch({ type: "globalCommitPattern", value }),
58893
+ onSubmit: () => dispatch({ type: "globalCommitPatternDone" })
57420
58894
  }, undefined, false, undefined, this),
57421
58895
  /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
57422
58896
  dimColor: true,
57423
- children: "Enter to save · b Back · Esc Cancel"
58897
+ children: "Enter to save · Esc Back"
57424
58898
  }, undefined, false, undefined, this)
57425
58899
  ]
57426
58900
  }, undefined, true, undefined, this);
@@ -57494,6 +58968,36 @@ function Screen({
57494
58968
  case "summary": {
57495
58969
  const policy = effectivePolicy(draft.values);
57496
58970
  const preview = buildSetupPreview(draft.values, { cwd: resolveBasePath(draft.values) });
58971
+ const policyPreview = (() => {
58972
+ try {
58973
+ const current = readConfig();
58974
+ const config = {
58975
+ ...current,
58976
+ branchPolicy: mergePreset(draft.values.branchPreset, {
58977
+ allowed: parseList(draft.values.branchAllowed),
58978
+ protectedNames: parseList(draft.values.branchProtected)
58979
+ }, current),
58980
+ commitPolicy: draft.values.commitPolicy
58981
+ };
58982
+ const checkout = resolveBasePath(draft.values);
58983
+ const workspace = resolveWorkspaceFromEntries(checkout, draft.values.workspaces);
58984
+ const resolved = resolveWorkspacePolicy(config, workspace);
58985
+ if (resolved.status === "invalid")
58986
+ return { error: resolved.error };
58987
+ return {
58988
+ error: null,
58989
+ workspace: workspace?.name ?? null,
58990
+ branchSource: resolved.provenance.branchPolicy,
58991
+ commitSource: resolved.provenance.commitPolicy,
58992
+ target: resolved.branchPolicy.defaultTargetBranch,
58993
+ branchPreset: resolved.branchPolicy.preset,
58994
+ commitPreset: resolved.commitPolicy.preset
58995
+ };
58996
+ } catch (error) {
58997
+ return { error: error instanceof Error ? error.message : String(error) };
58998
+ }
58999
+ })();
59000
+ const canApply = preview.ok && policyPreview.error === null;
57497
59001
  return /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Box_default, {
57498
59002
  flexDirection: "column",
57499
59003
  gap: 1,
@@ -57544,6 +59048,52 @@ function Screen({
57544
59048
  policy.protected.join(", ")
57545
59049
  ]
57546
59050
  }, undefined, true, undefined, this),
59051
+ policyPreview.error === null ? /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Box_default, {
59052
+ flexDirection: "column",
59053
+ gap: 0,
59054
+ children: [
59055
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
59056
+ children: [
59057
+ "Sample checkout: ",
59058
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
59059
+ color: "green",
59060
+ children: resolveBasePath(draft.values)
59061
+ }, undefined, false, undefined, this),
59062
+ policyPreview.workspace ? ` · workspace ${policyPreview.workspace}` : " · no matching workspace"
59063
+ ]
59064
+ }, undefined, true, undefined, this),
59065
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
59066
+ children: [
59067
+ "Effective branch policy: ",
59068
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
59069
+ color: "green",
59070
+ children: policyPreview.branchPreset
59071
+ }, undefined, false, undefined, this),
59072
+ " · target ",
59073
+ policyPreview.target,
59074
+ " · source ",
59075
+ policyPreview.branchSource
59076
+ ]
59077
+ }, undefined, true, undefined, this),
59078
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
59079
+ children: [
59080
+ "Effective commit policy: ",
59081
+ /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
59082
+ color: "green",
59083
+ children: policyPreview.commitPreset
59084
+ }, undefined, false, undefined, this),
59085
+ " · source ",
59086
+ policyPreview.commitSource
59087
+ ]
59088
+ }, undefined, true, undefined, this)
59089
+ ]
59090
+ }, undefined, true, undefined, this) : /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
59091
+ color: "red",
59092
+ children: [
59093
+ "Policy preview failed: ",
59094
+ policyPreview.error
59095
+ ]
59096
+ }, undefined, true, undefined, this),
57547
59097
  /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
57548
59098
  children: [
57549
59099
  "YouTrack base URL:",
@@ -57592,7 +59142,7 @@ function Screen({
57592
59142
  }, o.envKey, true, undefined, this))
57593
59143
  ]
57594
59144
  }, undefined, true, undefined, this),
57595
- preview.ok ? /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Box_default, {
59145
+ canApply ? /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Box_default, {
57596
59146
  flexDirection: "column",
57597
59147
  gap: 0,
57598
59148
  children: [
@@ -57600,9 +59150,9 @@ function Screen({
57600
59150
  bold: true,
57601
59151
  children: "Will apply:"
57602
59152
  }, undefined, false, undefined, this),
57603
- preview.mutations.map((m) => /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
59153
+ preview.mutations.map((m, index) => /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
57604
59154
  children: describeMutation(m)
57605
- }, `${m.type}:${m.path}`, false, undefined, this)),
59155
+ }, `${m.type}:${index}`, false, undefined, this)),
57606
59156
  preview.preserved.map((p) => /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
57607
59157
  color: "green",
57608
59158
  children: [
@@ -57631,7 +59181,7 @@ function Screen({
57631
59181
  }, undefined, false, undefined, this)
57632
59182
  ]
57633
59183
  }, undefined, true, undefined, this),
57634
- preview.ok && /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(ConfirmInput, {
59184
+ canApply && /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(ConfirmInput, {
57635
59185
  defaultChoice: "confirm",
57636
59186
  submitOnEnter: false,
57637
59187
  onConfirm: () => dispatch({ type: "apply" }),
@@ -57639,7 +59189,7 @@ function Screen({
57639
59189
  }, undefined, false, undefined, this),
57640
59190
  /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
57641
59191
  dimColor: true,
57642
- children: preview.ok ? "y to apply · b Back · Esc Cancel" : "b Back · Esc Cancel"
59192
+ children: canApply ? "y to apply · b Back · Esc Cancel" : "b Back · Esc Cancel"
57643
59193
  }, undefined, false, undefined, this)
57644
59194
  ]
57645
59195
  }, undefined, true, undefined, this);
@@ -57658,7 +59208,7 @@ function Screen({
57658
59208
 
57659
59209
  // packages/workit-core/src/core/logger.ts
57660
59210
  import { appendFileSync, mkdirSync as mkdirSync6, readdirSync as readdirSync6, unlinkSync as unlinkSync4 } from "node:fs";
57661
- import os9 from "node:os";
59211
+ import os10 from "node:os";
57662
59212
  import path17 from "node:path";
57663
59213
  var REDACTED = "[REDACTED]";
57664
59214
  var DEFAULT_MAX_RATE = 20;
@@ -57713,10 +59263,10 @@ var resolveStateDir = () => {
57713
59263
  const override = process.env.WORKFLOW_TOOLKIT_STATE;
57714
59264
  if (override)
57715
59265
  return override;
57716
- const home = os9.homedir();
59266
+ const home = os10.homedir();
57717
59267
  if (process.env.XDG_STATE_HOME)
57718
59268
  return path17.join(process.env.XDG_STATE_HOME, "workit");
57719
- switch (os9.platform()) {
59269
+ switch (os10.platform()) {
57720
59270
  case "darwin":
57721
59271
  return path17.join(home, "Library", "Application Support", "workit");
57722
59272
  case "win32":
@@ -57725,7 +59275,7 @@ var resolveStateDir = () => {
57725
59275
  return path17.join(process.env.HOME ?? home, ".local", "state", "workit");
57726
59276
  }
57727
59277
  };
57728
- var homePattern = new RegExp(`${escapeRegExp2(os9.homedir())}(?=[/\\\\]|$)|\\$HOME(?=[/\\\\]|$)`, "g");
59278
+ var homePattern = new RegExp(`${escapeRegExp2(os10.homedir())}(?=[/\\\\]|$)|\\$HOME(?=[/\\\\]|$)`, "g");
57729
59279
  var applyPatterns = (value) => {
57730
59280
  let out = value;
57731
59281
  out = out.replace(SECRET_VALUE, REDACTED);
@@ -58115,11 +59665,11 @@ async function runCutoverCommand(argv, deps = {}) {
58115
59665
  }
58116
59666
 
58117
59667
  // packages/workit-cli/src/task.ts
58118
- import { readFileSync as readFileSync19 } from "node:fs";
59668
+ import { readFileSync as readFileSync20 } from "node:fs";
58119
59669
  import { createInterface as createInterface2 } from "node:readline/promises";
58120
59670
 
58121
59671
  // packages/workit-core/src/core.ts
58122
- import { spawnSync as spawnSync5 } from "node:child_process";
59672
+ import { spawnSync as spawnSync6 } from "node:child_process";
58123
59673
  import { existsSync as existsSync17, realpathSync as realpathSync10 } from "node:fs";
58124
59674
  import path27 from "node:path";
58125
59675
 
@@ -59792,7 +61342,7 @@ function assertProductWriteAllowed(input) {
59792
61342
  // packages/workit-core/src/core/task-evaluation.ts
59793
61343
  import { createHash as createHash10 } from "node:crypto";
59794
61344
  import * as fs10 from "node:fs";
59795
- import { spawnSync as spawnSync4 } from "node:child_process";
61345
+ import { spawnSync as spawnSync5 } from "node:child_process";
59796
61346
  import path25 from "node:path";
59797
61347
 
59798
61348
  // packages/workit-core/src/core/authority.ts
@@ -59802,11 +61352,11 @@ import path24 from "node:path";
59802
61352
 
59803
61353
  // packages/workit-core/src/core/auto-approval.ts
59804
61354
  import { createHash as createHash8 } from "node:crypto";
59805
- import { readFileSync as readFileSync15, realpathSync as realpathSync5 } from "node:fs";
61355
+ import { readFileSync as readFileSync16, realpathSync as realpathSync5 } from "node:fs";
59806
61356
  import path23 from "node:path";
59807
61357
 
59808
61358
  // packages/workit-core/src/core/branch.ts
59809
- import { existsSync as existsSync15, mkdirSync as mkdirSync8, readFileSync as readFileSync14, writeFileSync as writeFileSync8 } from "node:fs";
61359
+ import { existsSync as existsSync15, mkdirSync as mkdirSync8, readFileSync as readFileSync15, writeFileSync as writeFileSync8 } from "node:fs";
59810
61360
  import { execFileSync as execFileSync3 } from "node:child_process";
59811
61361
  import { createHash as createHash7 } from "node:crypto";
59812
61362
  import path22 from "node:path";
@@ -59911,11 +61461,11 @@ var gitContext = (workspaceRoot, paths = []) => {
59911
61461
  // packages/workit-core/src/core/vcs-config.ts
59912
61462
  import fs8 from "node:fs";
59913
61463
  import path21 from "node:path";
59914
- import { spawnSync as spawnSync3 } from "node:child_process";
61464
+ import { spawnSync as spawnSync4 } from "node:child_process";
59915
61465
  var vcsConfigPath = () => process.env.WORKFLOW_VCS_CONFIG ?? path21.join(configDir(), "vcs.json");
59916
61466
  var vcsCwd = (cwd) => cwd ?? process.env.WORKFLOW_WORKSPACE_ROOT ?? process.cwd();
59917
61467
  var remoteProvider = (cwd) => {
59918
- const r = spawnSync3("git", ["remote", "get-url", "origin"], { cwd, encoding: "utf8" });
61468
+ const r = spawnSync4("git", ["remote", "get-url", "origin"], { cwd, encoding: "utf8" });
59919
61469
  if (r.status !== 0)
59920
61470
  return null;
59921
61471
  const url = (r.stdout ?? "").trim();
@@ -59952,7 +61502,7 @@ var remoteEndpoint = (remote) => {
59952
61502
  var configuredSshEndpoint = (host) => {
59953
61503
  if (host.startsWith("-"))
59954
61504
  return null;
59955
- const resolved = spawnSync3("ssh", ["-G", host], {
61505
+ const resolved = spawnSync4("ssh", ["-G", host], {
59956
61506
  encoding: "utf8",
59957
61507
  timeout: 5000,
59958
61508
  maxBuffer: 1024 * 1024,
@@ -60031,8 +61581,8 @@ function vcsConfig(mode, cwd) {
60031
61581
  const hasWorkspacePolicy = typeof wp.preset === "string" && Object.hasOwn(PRESETS, wp.preset);
60032
61582
  const policyDefault = resolveBranchPolicyFor(root).defaultTargetBranch;
60033
61583
  const defaultTarget = String(wsVcs.defaultTargetBranch ?? (hasWorkspacePolicy ? policyDefault : cfg.defaultTargetBranch ?? policyDefault) ?? "develop");
60034
- const linkIssues = provider === "gitlab" && typeof wsYt.link_issues === "boolean" ? wsYt.link_issues : null;
60035
- const youtrackBaseUrl = provider === "gitlab" && typeof wsYt.baseUrl === "string" ? wsYt.baseUrl : null;
61584
+ const linkIssues = typeof wsYt.link_issues === "boolean" ? wsYt.link_issues : null;
61585
+ const youtrackBaseUrl = typeof wsYt.baseUrl === "string" ? wsYt.baseUrl : null;
60036
61586
  let issuesProvider = null;
60037
61587
  let linkOnPr = null;
60038
61588
  if (provider === "github" && typeof wsIssues.provider === "string" && wsIssues.provider.toLowerCase() === "github") {
@@ -60103,7 +61653,7 @@ function vcsCliIdentity(cwd) {
60103
61653
  PATH: process.env.PATH ?? "",
60104
61654
  ...provider === "github" ? { GH_HOST: host } : { GITLAB_HOST: host }
60105
61655
  };
60106
- const result = spawnSync3(bin, ["api", "user"], {
61656
+ const result = spawnSync4(bin, ["api", "user"], {
60107
61657
  cwd: root,
60108
61658
  encoding: "utf8",
60109
61659
  env
@@ -60145,7 +61695,7 @@ function mergedPrStyle(limit = 6, cwd) {
60145
61695
  `).filter((l) => l.startsWith("## ")).map((l) => l.trim()),
60146
61696
  descriptionPreview: desc.slice(0, 600)
60147
61697
  });
60148
- const remoteResult = spawnSync3("git", ["remote", "get-url", "--push", "--all", "origin"], {
61698
+ const remoteResult = spawnSync4("git", ["remote", "get-url", "--push", "--all", "origin"], {
60149
61699
  cwd,
60150
61700
  encoding: "utf8"
60151
61701
  });
@@ -60161,7 +61711,7 @@ function mergedPrStyle(limit = 6, cwd) {
60161
61711
  if (!hostingApiHostMatches(remote, host))
60162
61712
  return { ok: false, error: "PR style target does not match the provider host" };
60163
61713
  if (provider === "gitlab") {
60164
- const run = (args) => spawnSync3("glab", ["api", ...args], {
61714
+ const run = (args) => spawnSync4("glab", ["api", ...args], {
60165
61715
  cwd,
60166
61716
  encoding: "utf8",
60167
61717
  env
@@ -60181,7 +61731,7 @@ function mergedPrStyle(limit = 6, cwd) {
60181
61731
  });
60182
61732
  }
60183
61733
  } else if (provider === "github") {
60184
- const r = spawnSync3("gh", [
61734
+ const r = spawnSync4("gh", [
60185
61735
  "pr",
60186
61736
  "list",
60187
61737
  "--repo",
@@ -60454,7 +62004,7 @@ var branchSetup = ({
60454
62004
  };
60455
62005
  const readManifest = () => {
60456
62006
  try {
60457
- return JSON.parse(readFileSync14(manifestPath, "utf8"));
62007
+ return JSON.parse(readFileSync15(manifestPath, "utf8"));
60458
62008
  } catch {
60459
62009
  return {};
60460
62010
  }
@@ -60650,7 +62200,7 @@ var OPERATION_CLASS = {
60650
62200
  var operationAutoClass = (operation) => typeof operation === "string" ? OPERATION_CLASS[operation] ?? null : null;
60651
62201
  var digestOf = (configDirPath) => {
60652
62202
  try {
60653
- const raw = readFileSync15(`${configDirPath}/workspaces.json`, "utf8");
62203
+ const raw = readFileSync16(`${configDirPath}/workspaces.json`, "utf8");
60654
62204
  return createHash8("sha256").update(raw).digest("hex");
60655
62205
  } catch {
60656
62206
  return null;
@@ -61458,7 +63008,7 @@ var compareCodeUnits2 = (left, right) => {
61458
63008
  return left.length - right.length;
61459
63009
  };
61460
63010
  var gitPaths = (root) => {
61461
- const result = spawnSync4("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
63011
+ const result = spawnSync5("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
61462
63012
  cwd: root,
61463
63013
  encoding: "buffer"
61464
63014
  });
@@ -61471,7 +63021,7 @@ var gitPaths = (root) => {
61471
63021
  };
61472
63022
  }
61473
63023
  const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path25.sep).join("/")) : [];
61474
- const stagedDeleted = spawnSync4("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
63024
+ const stagedDeleted = spawnSync5("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
61475
63025
  if (stagedDeleted.status !== 0) {
61476
63026
  const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
61477
63027
  if (!/not a git repository/i.test(stderr))
@@ -61602,7 +63152,7 @@ function captureCandidate(root, scope, environment = []) {
61602
63152
  const values = supplied.sort((left, right) => compareCodeUnits2(left.name, right.name)).map(({ name, value }) => ({ name, value, refs: [] }));
61603
63153
  if (values.some(({ value }) => value === null))
61604
63154
  completeness = "uncertain";
61605
- const headResult = spawnSync4("git", ["rev-parse", "HEAD"], { cwd: checkout, encoding: "utf8" });
63155
+ const headResult = spawnSync5("git", ["rev-parse", "HEAD"], { cwd: checkout, encoding: "utf8" });
61606
63156
  if (headResult.status !== 0 && !/not a git repository/i.test(headResult.stderr ?? ""))
61607
63157
  completeness = "uncertain";
61608
63158
  const head = headResult.status === 0 ? headResult.stdout.trim() : null;
@@ -61990,6 +63540,13 @@ var compactText = (value, limit) => value === null ? null : value.replace(privat
61990
63540
  return `${match.slice(0, space)} <redacted>`;
61991
63541
  }).slice(0, limit);
61992
63542
  var compactBytes = (value) => new TextEncoder().encode(value).byteLength;
63543
+ var compactTimestamp = (value) => `${value.slice(0, 19)}.${value.slice(20, -1).padEnd(9, "0")}`;
63544
+ var compactDecisionChoice = (entry) => {
63545
+ const stated = entry.data.binding.statedChoice?.text;
63546
+ const approved = entry.data.response === "approved" ? entry.data.binding.approvedContent : "";
63547
+ const source = stated?.trim() || approved.trim() || null;
63548
+ return compactText(source, COMPACT_TEXT_BYTES);
63549
+ };
61993
63550
  var compactReference = (ref) => {
61994
63551
  if (ref.kind === "file")
61995
63552
  return {
@@ -62005,11 +63562,18 @@ var compactReference = (ref) => {
62005
63562
  return { kind: ref.kind };
62006
63563
  };
62007
63564
  function compactTaskContext(view) {
62008
- const decisions = view.task.decisions.slice().sort((left, right) => left.id < right.id ? -1 : left.id > right.id ? 1 : 0).slice(0, COMPACT_MAX_ITEMS).map((entry) => ({
63565
+ const decisions = view.task.decisions.slice().sort((left, right) => {
63566
+ const leftAt = compactTimestamp(left.recordedAt);
63567
+ const rightAt = compactTimestamp(right.recordedAt);
63568
+ if (leftAt !== rightAt)
63569
+ return leftAt > rightAt ? -1 : 1;
63570
+ return left.id < right.id ? -1 : left.id > right.id ? 1 : 0;
63571
+ }).slice(0, COMPACT_MAX_ITEMS).map((entry) => ({
62009
63572
  id: entry.id,
62010
63573
  purpose: entry.data.purpose,
62011
63574
  status: entry.data.response,
62012
63575
  digest: entry.data.digest,
63576
+ choice: compactDecisionChoice(entry) || `task.inspect taskId=${view.task.id} view=full; decision=${entry.id}`,
62013
63577
  references: entry.data.binding.contentRefs.slice(0, 1).map(compactReference)
62014
63578
  }));
62015
63579
  const methods = view.task.policy ? selectMethods(view.task.policy, view.capabilities).map((method) => ({
@@ -65076,7 +66640,7 @@ function resolveInside(root, candidate) {
65076
66640
  }
65077
66641
  function run2(root, executable, args, env = {}) {
65078
66642
  const cwd = realpathSync10(root);
65079
- const result = spawnSync5(executable, args, {
66643
+ const result = spawnSync6(executable, args, {
65080
66644
  cwd,
65081
66645
  encoding: "utf8",
65082
66646
  env: { ...process.env, ...env }
@@ -65092,7 +66656,7 @@ function run2(root, executable, args, env = {}) {
65092
66656
  // packages/workit-core/src/core/pr-create.ts
65093
66657
  import fs11 from "node:fs";
65094
66658
  import path28 from "node:path";
65095
- import { spawnSync as spawnSync6 } from "node:child_process";
66659
+ import { spawnSync as spawnSync7 } from "node:child_process";
65096
66660
  import { pathToFileURL } from "node:url";
65097
66661
  var remoteRepoPath = (remote) => {
65098
66662
  const raw = remote.trim();
@@ -65194,11 +66758,11 @@ function whichOnPath(tool) {
65194
66758
  }
65195
66759
  var hostingCliAvailable = (provider) => whichOnPath(provider === "gitlab" ? "glab" : "gh") !== null;
65196
66760
  function repoRoot2(cwd) {
65197
- const result = spawnSync6("git", ["rev-parse", "--show-toplevel"], { cwd, encoding: "utf8" });
66761
+ const result = spawnSync7("git", ["rev-parse", "--show-toplevel"], { cwd, encoding: "utf8" });
65198
66762
  return result.status === 0 ? (result.stdout ?? "").trim() : cwd;
65199
66763
  }
65200
66764
  var pushRemote = (cwd) => {
65201
- const result = spawnSync6("git", ["remote", "get-url", "--push", "--all", "origin"], {
66765
+ const result = spawnSync7("git", ["remote", "get-url", "--push", "--all", "origin"], {
65202
66766
  cwd,
65203
66767
  encoding: "utf8"
65204
66768
  });
@@ -65258,7 +66822,7 @@ var pushTargetIsStable = (cwd, remote) => {
65258
66822
  const target = safePushUrl(remote);
65259
66823
  if (!target)
65260
66824
  return false;
65261
- const result = spawnSync6("git", ["config", "--get-regexp", "^url\\..*\\.(insteadof|pushinsteadof)$"], { cwd, encoding: "utf8" });
66825
+ const result = spawnSync7("git", ["config", "--get-regexp", "^url\\..*\\.(insteadof|pushinsteadof)$"], { cwd, encoding: "utf8" });
65262
66826
  if (result.status !== 0 && result.status !== 1)
65263
66827
  return false;
65264
66828
  const prefixes = (result.stdout ?? "").split(/\r?\n/u).map((line) => line.trim().slice(line.trim().indexOf(" ") + 1)).filter(Boolean);
@@ -65306,12 +66870,12 @@ function prCreate(env, cwd) {
65306
66870
  const resolvedDefault = String(cfg.defaultTargetBranch ?? policy.defaultTargetBranch ?? "develop");
65307
66871
  const target = targetOverride || resolvedDefault;
65308
66872
  const title = String(env.WF_PR_TITLE ?? "");
65309
- const br = spawnSync6("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
66873
+ const br = spawnSync7("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
65310
66874
  cwd: root,
65311
66875
  encoding: "utf8"
65312
66876
  });
65313
66877
  const branch = br.status === 0 ? (br.stdout ?? "").trim() : "";
65314
- const sourceResult = branch ? spawnSync6("git", ["rev-parse", "--verify", `refs/heads/${branch}`], {
66878
+ const sourceResult = branch ? spawnSync7("git", ["rev-parse", "--verify", `refs/heads/${branch}`], {
65315
66879
  cwd: root,
65316
66880
  encoding: "utf8"
65317
66881
  }) : null;
@@ -65320,7 +66884,7 @@ function prCreate(env, cwd) {
65320
66884
  return { error: "approved PR source branch and commit are incomplete" };
65321
66885
  if (env.WORKIT_EXPECTED_SOURCE_BRANCH && env.WORKIT_EXPECTED_SOURCE_BRANCH !== branch || env.WORKIT_EXPECTED_SOURCE_COMMIT && env.WORKIT_EXPECTED_SOURCE_COMMIT !== sourceCommit)
65322
66886
  return { error: "approved PR source branch or commit changed before execution" };
65323
- const mergeBaseResult = policy.integration === "merge" ? spawnSync6("git", ["rev-parse", "--verify", `refs/heads/${target}`], {
66887
+ const mergeBaseResult = policy.integration === "merge" ? spawnSync7("git", ["rev-parse", "--verify", `refs/heads/${target}`], {
65324
66888
  cwd: root,
65325
66889
  encoding: "utf8"
65326
66890
  }) : null;
@@ -65336,7 +66900,7 @@ function prCreate(env, cwd) {
65336
66900
  targetBranch: target,
65337
66901
  stderr: "source or target branch commit could not be resolved"
65338
66902
  };
65339
- const merge = spawnSync6("git", ["merge", "--no-ff", sourceCommit, "-m", title], {
66903
+ const merge = spawnSync7("git", ["merge", "--no-ff", sourceCommit, "-m", title], {
65340
66904
  cwd: root,
65341
66905
  encoding: "utf8"
65342
66906
  });
@@ -65347,16 +66911,16 @@ function prCreate(env, cwd) {
65347
66911
  targetBranch: target,
65348
66912
  stderr: (merge.stderr ?? "").slice(0, 800)
65349
66913
  };
65350
- const mergedCommit = spawnSync6("git", ["rev-parse", "HEAD"], {
66914
+ const mergedCommit = spawnSync7("git", ["rev-parse", "HEAD"], {
65351
66915
  cwd: root,
65352
66916
  encoding: "utf8"
65353
66917
  });
65354
66918
  if (mergedCommit.status !== 0)
65355
66919
  return { error: "merge commit could not be resolved", mode: "merge" };
65356
- const updateTarget = spawnSync6("git", ["update-ref", `refs/heads/${target}`, mergedCommit.stdout.trim(), mergeBaseCommit], { cwd: root, encoding: "utf8" });
66920
+ const updateTarget = spawnSync7("git", ["update-ref", `refs/heads/${target}`, mergedCommit.stdout.trim(), mergeBaseCommit], { cwd: root, encoding: "utf8" });
65357
66921
  if (updateTarget.status !== 0)
65358
66922
  return { error: "merge target branch changed before update", mode: "merge" };
65359
- const push = spawnSync6("git", ["push", targetUrl, `${mergedCommit.stdout.trim()}:refs/heads/${target}`], { cwd: root, encoding: "utf8", env: { ...process.env, PATH: process.env.PATH ?? "" } });
66923
+ const push = spawnSync7("git", ["push", targetUrl, `${mergedCommit.stdout.trim()}:refs/heads/${target}`], { cwd: root, encoding: "utf8", env: { ...process.env, PATH: process.env.PATH ?? "" } });
65360
66924
  if (push.status !== 0)
65361
66925
  return {
65362
66926
  error: "push failed",
@@ -65373,7 +66937,7 @@ function prCreate(env, cwd) {
65373
66937
  output: (push.stdout ?? "").trim()
65374
66938
  };
65375
66939
  };
65376
- const co = spawnSync6("git", ["checkout", "--detach", mergeBaseCommit], {
66940
+ const co = spawnSync7("git", ["checkout", "--detach", mergeBaseCommit], {
65377
66941
  cwd: root,
65378
66942
  encoding: "utf8"
65379
66943
  });
@@ -65385,7 +66949,7 @@ function prCreate(env, cwd) {
65385
66949
  stderr: (co.stderr ?? "").slice(0, 800)
65386
66950
  };
65387
66951
  const result = finish();
65388
- spawnSync6("git", ["checkout", branch], { cwd: root, encoding: "utf8" });
66952
+ spawnSync7("git", ["checkout", branch], { cwd: root, encoding: "utf8" });
65389
66953
  return result;
65390
66954
  }
65391
66955
  const pr = cfg.pr ?? {};
@@ -65411,7 +66975,7 @@ function prCreate(env, cwd) {
65411
66975
  const push = pr.pushBranch !== false;
65412
66976
  const skipConfirm = pr.confirmSkip !== false;
65413
66977
  const remoteBranchTip = () => {
65414
- const read = spawnSync6("git", ["ls-remote", targetUrl, `refs/heads/${branch}`], {
66978
+ const read = spawnSync7("git", ["ls-remote", targetUrl, `refs/heads/${branch}`], {
65415
66979
  cwd: root,
65416
66980
  encoding: "utf8",
65417
66981
  env: { ...process.env, PATH: process.env.PATH ?? "" }
@@ -65427,7 +66991,7 @@ function prCreate(env, cwd) {
65427
66991
  stderr: branch ? "source branch commit could not be resolved" : "empty current branch (detached HEAD or unborn HEAD)"
65428
66992
  };
65429
66993
  if (push) {
65430
- const pushed = spawnSync6("git", ["push", "-u", targetUrl, `${sourceCommit}:refs/heads/${branch}`], { cwd: root, encoding: "utf8", env: { ...process.env, PATH: process.env.PATH ?? "" } });
66994
+ const pushed = spawnSync7("git", ["push", "-u", targetUrl, `${sourceCommit}:refs/heads/${branch}`], { cwd: root, encoding: "utf8", env: { ...process.env, PATH: process.env.PATH ?? "" } });
65431
66995
  if (pushed.status !== 0)
65432
66996
  return { error: "push failed", provider, stderr: (pushed.stderr ?? "").slice(0, 800) };
65433
66997
  }
@@ -65472,12 +67036,12 @@ function prCreate(env, cwd) {
65472
67036
  cmd.push("--draft");
65473
67037
  cmdEnv = { ...process.env, PATH: process.env.PATH ?? "", GH_HOST: identity.host };
65474
67038
  }
65475
- const result = spawnSync6(cmd[0], cmd.slice(1), { cwd: root, encoding: "utf8", env: cmdEnv });
67039
+ const result = spawnSync7(cmd[0], cmd.slice(1), { cwd: root, encoding: "utf8", env: cmdEnv });
65476
67040
  if (result.status !== 0) {
65477
67041
  const err = (result.stderr ?? result.stdout ?? "").trim();
65478
67042
  let hint = null;
65479
67043
  if (provider === "gitlab" && (err.includes("409") || err.toLowerCase().includes("already exists"))) {
65480
- const list = spawnSync6("glab", ["mr", "list", `--source-branch=${branch}`, "--output=json"], {
67044
+ const list = spawnSync7("glab", ["mr", "list", `--source-branch=${branch}`, "--output=json"], {
65481
67045
  cwd: root,
65482
67046
  encoding: "utf8",
65483
67047
  env: cmdEnv
@@ -65536,7 +67100,7 @@ function mergePr(cwd, opts) {
65536
67100
  return { error: "approved hosting account changed before execution" };
65537
67101
  if (!/^[a-f0-9]{40,64}$/i.test(opts.sourceCommit))
65538
67102
  return { error: "approved merge source commit is invalid" };
65539
- const currentCommit = spawnSync6("git", ["rev-parse", "--verify", `refs/heads/${opts.source}`], {
67103
+ const currentCommit = spawnSync7("git", ["rev-parse", "--verify", `refs/heads/${opts.source}`], {
65540
67104
  cwd: root,
65541
67105
  encoding: "utf8"
65542
67106
  });
@@ -65550,7 +67114,7 @@ function mergePr(cwd, opts) {
65550
67114
  return { error: `workflow CLI missing (required for ${provider})` };
65551
67115
  const owner = repoPath.split("/")[0];
65552
67116
  const endpoint = provider === "github" ? `repos/${repository.replace(/^[^/]+\//u, "")}/pulls?state=open&head=${encodeURIComponent(`${owner}:${opts.source}`)}&base=${encodeURIComponent(opts.target)}&per_page=100` : `projects/${encodeURIComponent(repoPath)}/merge_requests?state=opened&source_branch=${encodeURIComponent(opts.source)}&target_branch=${encodeURIComponent(opts.target)}&per_page=100`;
65553
- const listOpen = () => spawnSync6(provider === "github" ? "gh" : "glab", ["api", endpoint], {
67117
+ const listOpen = () => spawnSync7(provider === "github" ? "gh" : "glab", ["api", endpoint], {
65554
67118
  cwd: root,
65555
67119
  encoding: "utf8",
65556
67120
  env: {
@@ -65610,7 +67174,7 @@ function mergePr(cwd, opts) {
65610
67174
  cmd.push("--delete-branch");
65611
67175
  cmdEnv = { ...process.env, PATH: process.env.PATH ?? "", GH_HOST: identity.host };
65612
67176
  }
65613
- const result = spawnSync6(cmd[0], cmd.slice(1), { cwd: root, encoding: "utf8", env: cmdEnv });
67177
+ const result = spawnSync7(cmd[0], cmd.slice(1), { cwd: root, encoding: "utf8", env: cmdEnv });
65614
67178
  if (result.status !== 0)
65615
67179
  return {
65616
67180
  error: "merge failed",
@@ -65627,8 +67191,8 @@ function mergePr(cwd, opts) {
65627
67191
  }
65628
67192
 
65629
67193
  // packages/workit-core/src/core/repo-context.ts
65630
- import { spawnSync as spawnSync7 } from "node:child_process";
65631
- import { existsSync as existsSync18, readFileSync as readFileSync18, readdirSync as readdirSync9 } from "node:fs";
67194
+ import { spawnSync as spawnSync8 } from "node:child_process";
67195
+ import { existsSync as existsSync18, readFileSync as readFileSync19, readdirSync as readdirSync9 } from "node:fs";
65632
67196
  import path29 from "node:path";
65633
67197
  var isSafeContextRange = (value) => value.length <= 4096 && (() => {
65634
67198
  try {
@@ -65656,7 +67220,7 @@ var invalidRange = (cwd) => ({
65656
67220
  cwd
65657
67221
  });
65658
67222
  var runGit = (cwd, args) => {
65659
- const result = spawnSync7("git", args, { cwd, encoding: "utf8" });
67223
+ const result = spawnSync8("git", args, { cwd, encoding: "utf8" });
65660
67224
  return {
65661
67225
  stdout: (result.stdout ?? "").trimEnd(),
65662
67226
  stderr: (result.stderr ?? "").trimEnd(),
@@ -65798,7 +67362,7 @@ var packageScripts = (cwd, keys) => {
65798
67362
  if (!existsSync18(pkgPath))
65799
67363
  return [];
65800
67364
  try {
65801
- const pkg = JSON.parse(readFileSync18(pkgPath, "utf8"));
67365
+ const pkg = JSON.parse(readFileSync19(pkgPath, "utf8"));
65802
67366
  return keys.filter((k) => pkg.scripts && typeof pkg.scripts[k] === "string").map((k) => `${k}: ${pkg.scripts?.[k]}`);
65803
67367
  } catch {
65804
67368
  return [];
@@ -65832,7 +67396,7 @@ var documentationFiles = (cwd) => {
65832
67396
  var readTrimmed = (file, maxLines) => {
65833
67397
  if (!existsSync18(file))
65834
67398
  return "";
65835
- const lines = readFileSync18(file, "utf8").split(`
67399
+ const lines = readFileSync19(file, "utf8").split(`
65836
67400
  `);
65837
67401
  return lines.slice(0, maxLines).join(`
65838
67402
  `);
@@ -66020,7 +67584,7 @@ function docsRefreshContext(root, range) {
66020
67584
  const pkgPath = path29.join(cwd, "package.json");
66021
67585
  if (existsSync18(pkgPath)) {
66022
67586
  try {
66023
- const pkg = JSON.parse(readFileSync18(pkgPath, "utf8"));
67587
+ const pkg = JSON.parse(readFileSync19(pkgPath, "utf8"));
66024
67588
  stdout += JSON.stringify({ name: pkg.name, version: pkg.version, scripts: pkg.scripts }, null, 2) + `
66025
67589
  `;
66026
67590
  } catch {}
@@ -66097,7 +67661,7 @@ function releaseNotesContext(root, rangeOrTag) {
66097
67661
  }
66098
67662
 
66099
67663
  // packages/workit-core/src/core/tracker-issues.ts
66100
- import { spawnSync as spawnSync8 } from "node:child_process";
67664
+ import { spawnSync as spawnSync9 } from "node:child_process";
66101
67665
  var repoPathFromRemote = (remote) => {
66102
67666
  let rest = (remote || "").trim().replace(/\/+$/, "");
66103
67667
  if (!rest)
@@ -66118,7 +67682,7 @@ var defaultCli = (bin, available) => {
66118
67682
  if (!available)
66119
67683
  return null;
66120
67684
  return (args, root) => {
66121
- const out = spawnSync8(bin, args, {
67685
+ const out = spawnSync9(bin, args, {
66122
67686
  cwd: root,
66123
67687
  encoding: "utf8",
66124
67688
  env: { ...process.env, PATH: process.env.PATH ?? "" }
@@ -66851,7 +68415,7 @@ async function logTimeUpdate(input, operation = defaultOperations) {
66851
68415
  // packages/workit-core/src/core/external-action-effects.ts
66852
68416
  import fs13 from "node:fs";
66853
68417
  import path31 from "node:path";
66854
- import { spawnSync as spawnSync9 } from "node:child_process";
68418
+ import { spawnSync as spawnSync10 } from "node:child_process";
66855
68419
  var run3 = (root, args) => {
66856
68420
  return run2(root, "git", args);
66857
68421
  };
@@ -67251,7 +68815,7 @@ var hostingProvider = (root, remote) => {
67251
68815
  };
67252
68816
  var remoteBranchTip = (root, provider, host, repo, branch) => {
67253
68817
  const endpoint = provider === "github" ? `repos/${repo}/git/ref/heads/${branch.split("/").map(encodeURIComponent).join("/")}` : `projects/${encodeURIComponent(repo)}/repository/branches/${encodeURIComponent(branch)}`;
67254
- const response = spawnSync9(provider === "github" ? "gh" : "glab", ["api", endpoint], {
68818
+ const response = spawnSync10(provider === "github" ? "gh" : "glab", ["api", endpoint], {
67255
68819
  cwd: root,
67256
68820
  encoding: "utf8",
67257
68821
  env: hostingCliEnv(provider, host)
@@ -67259,7 +68823,7 @@ var remoteBranchTip = (root, provider, host, repo, branch) => {
67259
68823
  if (response.status !== 0) {
67260
68824
  if (/\b404\b/.test(response.stderr ?? "")) {
67261
68825
  const project = provider === "github" ? `repos/${repo}` : `projects/${encodeURIComponent(repo)}`;
67262
- const visible = spawnSync9(provider === "github" ? "gh" : "glab", ["api", project], {
68826
+ const visible = spawnSync10(provider === "github" ? "gh" : "glab", ["api", project], {
67263
68827
  cwd: root,
67264
68828
  encoding: "utf8",
67265
68829
  env: hostingCliEnv(provider, host)
@@ -67315,7 +68879,7 @@ var mergedBranch = (root, branch) => {
67315
68879
  outcome: "not_started"
67316
68880
  });
67317
68881
  const endpoint = provider === "github" ? `repos/${repo}/pulls?state=closed&head=${encodeURIComponent(`${repo.split("/")[0]}:${branch}`)}&per_page=100` : `projects/${encodeURIComponent(repo)}/merge_requests?state=merged&source_branch=${encodeURIComponent(branch)}&per_page=100`;
67318
- const response = spawnSync9(provider === "github" ? "gh" : "glab", ["api", endpoint], {
68882
+ const response = spawnSync10(provider === "github" ? "gh" : "glab", ["api", endpoint], {
67319
68883
  cwd: root,
67320
68884
  encoding: "utf8",
67321
68885
  env: hostingCliEnv(provider, auth.host)
@@ -67377,7 +68941,7 @@ var readHostingAction = async (root, resolved, actionRef) => {
67377
68941
  return unknownHostingEvidence(operation);
67378
68942
  const endpoint = cfg.provider === "github" ? `repos/${repo}/pulls?state=all&head=${encodeURIComponent(`${repo.split("/")[0]}:${branch}`)}&base=${encodeURIComponent(target)}&per_page=100&page=1` : `projects/${encodeURIComponent(repo)}/merge_requests?state=all&source_branch=${encodeURIComponent(branch)}&target_branch=${encodeURIComponent(target)}&per_page=100&page=1`;
67379
68943
  const cli = cfg.provider === "github" ? "gh" : "glab";
67380
- const response = spawnSync9(cli, ["api", endpoint], {
68944
+ const response = spawnSync10(cli, ["api", endpoint], {
67381
68945
  cwd: root,
67382
68946
  encoding: "utf8",
67383
68947
  env: hostingCliEnv(cfg.provider, auth.host)
@@ -68464,7 +70028,7 @@ async function payloadValue(raw, deps) {
68464
70028
  return { ok: true, value: {} };
68465
70029
  let source;
68466
70030
  try {
68467
- source = raw === "-" ? await readInput(deps.stdin ?? process.stdin) : raw.startsWith("@") ? decodeUtf8(readFileSync19(raw.slice(1))) : raw;
70031
+ source = raw === "-" ? await readInput(deps.stdin ?? process.stdin) : raw.startsWith("@") ? decodeUtf8(readFileSync20(raw.slice(1))) : raw;
68468
70032
  } catch (error) {
68469
70033
  return {
68470
70034
  ok: false,
@@ -69177,6 +70741,498 @@ async function runActionCommand(argv, deps = {}) {
69177
70741
  return result.ok ? 0 : 1;
69178
70742
  }
69179
70743
 
70744
+ // packages/workit-cli/src/upgrade.ts
70745
+ import { spawnSync as spawnSync11 } from "node:child_process";
70746
+ import {
70747
+ existsSync as existsSync19,
70748
+ readFileSync as readFileSync21,
70749
+ mkdirSync as mkdirSync9,
70750
+ copyFileSync as copyFileSync3,
70751
+ chmodSync,
70752
+ cpSync as cpSync4,
70753
+ lstatSync as lstatSync8
70754
+ } from "node:fs";
70755
+ import path32 from "node:path";
70756
+ import os11 from "node:os";
70757
+ import { createHash as createHash11 } from "node:crypto";
70758
+ var HOSTS2 = ["opencode", "cursor", "codex", "pi"];
70759
+ var upgradePaths = (deps) => {
70760
+ const home = deps.home ?? deps.env?.HOME ?? os11.homedir();
70761
+ const env = {
70762
+ ...process.env,
70763
+ ...deps.home || deps.env?.HOME ? {
70764
+ HOME: home,
70765
+ CODEX_HOME: path32.join(home, ".codex"),
70766
+ PI_CODING_AGENT_DIR: path32.join(home, ".pi/agent"),
70767
+ XDG_CONFIG_HOME: path32.join(home, ".config"),
70768
+ WORKFLOW_TOOLKIT_CONFIG: undefined,
70769
+ WORKFLOW_TOOLKIT_CONFIG_DIR: undefined
70770
+ } : {},
70771
+ ...deps.env,
70772
+ HOME: home
70773
+ };
70774
+ const xdg = env.XDG_CONFIG_HOME || path32.join(home, ".config");
70775
+ return {
70776
+ home,
70777
+ env,
70778
+ configDir: env.WORKFLOW_TOOLKIT_CONFIG ?? env.WORKFLOW_TOOLKIT_CONFIG_DIR ?? path32.join(xdg, "workit"),
70779
+ opencodeConfig: path32.join(xdg, "opencode/opencode.json"),
70780
+ piSettings: path32.join(env.PI_CODING_AGENT_DIR ?? path32.join(home, ".pi/agent"), "settings.json"),
70781
+ codexConfig: path32.join(env.CODEX_HOME ?? path32.join(home, ".codex"), "config.toml")
70782
+ };
70783
+ };
70784
+ var object2 = (file) => {
70785
+ if (!existsSync19(file))
70786
+ return null;
70787
+ let data;
70788
+ try {
70789
+ data = JSON.parse(readFileSync21(file, "utf8"));
70790
+ } catch {
70791
+ throw new Error(`${file} is not valid JSON; use the native host to inspect JSONC settings`);
70792
+ }
70793
+ if (!data || typeof data !== "object" || Array.isArray(data))
70794
+ throw new Error(`${file} is not a configuration object`);
70795
+ return data;
70796
+ };
70797
+ var digest2 = (file) => existsSync19(file) ? createHash11("sha256").update(readFileSync21(file)).digest("hex") : "missing";
70798
+ var runner = (deps) => deps.run ?? ((command, args) => {
70799
+ const { home, env } = upgradePaths(deps);
70800
+ let step;
70801
+ try {
70802
+ step = hostCommand(command, args, { home, env });
70803
+ } catch {
70804
+ return { status: 1, stdout: "" };
70805
+ }
70806
+ const result = runHostCommand(step, { home, env });
70807
+ return { status: result.exitCode, stdout: result.stdout };
70808
+ });
70809
+ var classify = (host, selector) => ({
70810
+ host,
70811
+ selector,
70812
+ kind: selector === `@brainervirus/workit-${host}` || selector === `@brainervirus/workit-${host}@latest` || selector === `npm:@brainervirus/workit-${host}` || selector === `npm:@brainervirus/workit-${host}@latest` ? "floating" : new RegExp(`^(?:npm:)?@brainervirus/workit-${host}@[^/]+$`).test(selector) ? "pinned" : "local"
70813
+ });
70814
+ var sources = (paths, run, hosts) => {
70815
+ const { home, opencodeConfig, piSettings } = paths;
70816
+ const result = [];
70817
+ if (hosts.includes("opencode") && existsSync19(opencodeConfig + "c"))
70818
+ throw new Error("OpenCode JSONC config is present; inspect effective native configuration before updating");
70819
+ const oc = hosts.includes("opencode") ? object2(opencodeConfig) : null;
70820
+ if (oc && [oc.plugins, oc.plugin].some((value) => value !== undefined && !Array.isArray(value)))
70821
+ throw new Error("OpenCode plugin registrations must be arrays");
70822
+ const plugins = [...oc?.plugins ?? [], ...oc?.plugin ?? []];
70823
+ for (const pin of plugins)
70824
+ if (typeof pin === "string" && isWorkitPlugin(pin))
70825
+ result.push(classify("opencode", pin));
70826
+ const cursor = hosts.includes("cursor") ? object2(path32.join(home, ".cursor/plugins/local/workit/package.json")) : null;
70827
+ if (cursor?.name === "@brainervirus/workit-cursor") {
70828
+ const mcp = object2(path32.join(home, ".cursor/mcp.json"));
70829
+ const args = mcp?.mcpServers?.workit?.args ?? [];
70830
+ const selector = args.find((arg) => typeof arg === "string" && arg.startsWith("--package="));
70831
+ result.push(classify("cursor", typeof selector === "string" ? selector.slice(10) : "local"));
70832
+ }
70833
+ const pi = hosts.includes("pi") ? object2(piSettings) : null;
70834
+ if (pi?.packages !== undefined && !Array.isArray(pi.packages))
70835
+ throw new Error("Pi package registrations must be an array");
70836
+ for (const entry of pi?.packages ?? []) {
70837
+ const source = typeof entry === "string" ? entry : entry?.source;
70838
+ if (typeof source !== "string")
70839
+ continue;
70840
+ const npm = /^(?:npm:)?@brainervirus\/workit-pi(?:@[^/]+)?$/.test(source);
70841
+ const local = path32.isAbsolute(source) && source.replaceAll("\\", "/").endsWith("/packages/workit-pi") && object2(path32.join(source, "package.json"))?.name === "@brainervirus/workit-pi";
70842
+ if (npm || local)
70843
+ result.push(classify("pi", source));
70844
+ }
70845
+ const listing = hosts.includes("codex") ? run("codex", ["plugin", "list", "--json"]) : { status: 1, stdout: "" };
70846
+ if (hosts.includes("codex") && listing.status !== 0) {
70847
+ let available = false;
70848
+ try {
70849
+ hostCommand("codex", [], { home, env: paths.env });
70850
+ available = true;
70851
+ } catch {}
70852
+ if (available || existsSync19(paths.codexConfig))
70853
+ throw new Error("Codex plugin inventory failed; inspect the native host before upgrading");
70854
+ }
70855
+ if (listing.status === 0) {
70856
+ let installed;
70857
+ try {
70858
+ installed = JSON.parse(listing.stdout).installed ?? [];
70859
+ } catch {
70860
+ throw new Error("Codex plugin inventory could not be parsed");
70861
+ }
70862
+ if (!Array.isArray(installed))
70863
+ throw new Error("Codex plugin inventory is malformed");
70864
+ for (const plugin of installed) {
70865
+ if (plugin.name !== "workit")
70866
+ continue;
70867
+ const source = plugin.source;
70868
+ const npm = plugin.marketplaceName === "workflow-toolkit" && source?.source === "npm" && source?.package === "@brainervirus/workit-codex";
70869
+ result.push({
70870
+ ...classify("codex", npm ? `${source.package}${source.version ? `@${source.version}` : ""}` : "local"),
70871
+ installedVersion: plugin.version
70872
+ });
70873
+ }
70874
+ }
70875
+ for (const source of result) {
70876
+ if (source.host === "cursor")
70877
+ source.installedVersion = cursor?.version;
70878
+ if (source.host === "pi" && source.kind === "floating") {
70879
+ const inventory = run("pi", ["list"]);
70880
+ if (inventory.status === 0)
70881
+ source.installedVersion = piInstalledVersion(inventory.stdout, source.selector);
70882
+ }
70883
+ }
70884
+ return result;
70885
+ };
70886
+ var piInstalledVersion = (listing, selector) => {
70887
+ const lines = listing.split(/\r?\n/).map((line) => line.trim());
70888
+ const index = lines.indexOf(selector);
70889
+ if (index < 0 || !lines[index + 1] || !path32.isAbsolute(lines[index + 1]))
70890
+ return;
70891
+ const pkg = object2(path32.join(lines[index + 1], "package.json"));
70892
+ return pkg?.name === "@brainervirus/workit-pi" ? pkg.version : undefined;
70893
+ };
70894
+ var versionAtLeast = (installed, latest) => {
70895
+ if (!/^\d+\.\d+\.\d+$/.test(installed))
70896
+ return false;
70897
+ const current = installed.split(".").map(Number);
70898
+ const target = latest.split(".").map(Number);
70899
+ for (let index = 0;index < 3; index++) {
70900
+ if (current[index] !== target[index])
70901
+ return current[index] > target[index];
70902
+ }
70903
+ return true;
70904
+ };
70905
+ function previewUpgrade(hosts = HOSTS2, deps = {}) {
70906
+ const paths = upgradePaths(deps);
70907
+ const { home, configDir } = paths;
70908
+ const run = runner(deps);
70909
+ const state = readSetupState(configDir);
70910
+ const plan = {
70911
+ ok: true,
70912
+ blocked: [],
70913
+ skipped: [],
70914
+ entries: [],
70915
+ configDigests: {},
70916
+ migrations: []
70917
+ };
70918
+ for (const file of [state.config, state.workspaces, state.youtrack, state.vcs]) {
70919
+ if (file.status === "malformed")
70920
+ plan.blocked.push(file.error ?? file.file);
70921
+ plan.configDigests[file.file] = digest2(file.file);
70922
+ }
70923
+ for (const file of [
70924
+ paths.opencodeConfig,
70925
+ paths.opencodeConfig + "c",
70926
+ path32.join(home, ".cursor/settings.json"),
70927
+ path32.join(home, ".cursor/mcp.json"),
70928
+ path32.join(home, ".cursor/plugins/local/workit/package.json"),
70929
+ paths.codexConfig,
70930
+ paths.piSettings
70931
+ ])
70932
+ plan.configDigests[file] = digest2(file);
70933
+ try {
70934
+ const config = object2(state.config.file);
70935
+ if (config && Object.hasOwn(config, "trustedPaths"))
70936
+ plan.migrations.push({
70937
+ id: "2.0-remove-ignored-trusted-paths",
70938
+ file: state.config.file,
70939
+ description: "Remove ignored Workit trustedPaths; native host permissions remain unchanged"
70940
+ });
70941
+ const installedSources = sources(paths, run, hosts);
70942
+ for (const host of ["opencode", "pi"]) {
70943
+ if (new Set(installedSources.filter((source) => source.host === host).map((source) => source.selector)).size > 1)
70944
+ plan.blocked.push(`${host}: conflicting Workit registrations; reconcile them in the native host before upgrading`);
70945
+ }
70946
+ for (const source of installedSources.filter((source, index, list) => list.findIndex((entry) => entry.host === source.host && entry.selector === source.selector) === index)) {
70947
+ if (source.kind !== "floating") {
70948
+ plan.skipped.push({
70949
+ host: source.host,
70950
+ reason: `${source.kind} source preserved; change its source explicitly to upgrade`
70951
+ });
70952
+ continue;
70953
+ }
70954
+ if (source.host === "opencode") {
70955
+ plan.skipped.push({
70956
+ host: source.host,
70957
+ reason: "OpenCode server plugin preserved: scoped native updates are unavailable in qualified 2.0.21; Workit never updates unrelated plugins"
70958
+ });
70959
+ continue;
70960
+ }
70961
+ const latest = run("npm", ["view", `@brainervirus/workit-${source.host}`, "version"]);
70962
+ if (latest.status !== 0 || !/^\d+\.\d+\.\d+$/.test(latest.stdout.trim())) {
70963
+ plan.blocked.push(`${source.host}: registry version unavailable; existing installation preserved`);
70964
+ continue;
70965
+ }
70966
+ if (source.installedVersion && versionAtLeast(source.installedVersion, latest.stdout.trim())) {
70967
+ plan.skipped.push({
70968
+ host: source.host,
70969
+ reason: `already current (${source.installedVersion})`
70970
+ });
70971
+ continue;
70972
+ }
70973
+ const commands = source.host === "pi" ? [{ command: "pi", args: ["update", "--extension", source.selector] }] : source.host === "codex" ? [{ command: "codex", args: ["plugin", "add", "workit@workflow-toolkit", "--json"] }] : planHostInstall("cursor", {
70974
+ home,
70975
+ env: deps.env,
70976
+ cwd: home,
70977
+ packageVersion: latest.stdout.trim(),
70978
+ targetDir: path32.join(home, ".cursor/plugins/local/workit")
70979
+ });
70980
+ plan.entries.push({
70981
+ host: source.host,
70982
+ selector: source.selector,
70983
+ latest: latest.stdout.trim(),
70984
+ commands
70985
+ });
70986
+ }
70987
+ } catch (error) {
70988
+ plan.blocked.push(error instanceof Error ? error.message : "installed source could not be read");
70989
+ }
70990
+ plan.ok = plan.blocked.length === 0;
70991
+ return plan;
70992
+ }
70993
+ function applyUpgrade(plan, deps = {}) {
70994
+ if (!plan.ok)
70995
+ return { ok: false, error: plan.blocked.join("; ") };
70996
+ if (!plan.entries.length && !plan.migrations.length && !plan.cli)
70997
+ return { ok: true };
70998
+ for (const [file, expected] of Object.entries(plan.configDigests))
70999
+ if (digest2(file) !== expected)
71000
+ return { ok: false, error: `configuration changed; preview again: ${file}` };
71001
+ const { home, env, configDir } = upgradePaths(deps);
71002
+ const active = deps.activeHosts?.() ?? runningHosts(deps);
71003
+ if (plan.entries.some((entry) => active.includes(entry.host)))
71004
+ return {
71005
+ ok: false,
71006
+ error: "Stop the selected running hosts before upgrading their loaded plugins"
71007
+ };
71008
+ const backup = path32.join(home, ".local/state/workit/upgrades", new Date().toISOString().replaceAll(":", "-"));
71009
+ mkdirSync9(backup, { recursive: true, mode: 448 });
71010
+ for (const file of Object.keys(plan.configDigests))
71011
+ if (existsSync19(file)) {
71012
+ const target = path32.join(backup, createHash11("sha256").update(file).digest("hex").slice(0, 12) + "-" + path32.basename(file));
71013
+ copyFileSync3(file, target);
71014
+ chmodSync(target, 384);
71015
+ }
71016
+ for (const migration of plan.migrations) {
71017
+ const value = object2(migration.file);
71018
+ if (migration.id !== "2.0-remove-ignored-trusted-paths" || !value)
71019
+ return {
71020
+ ok: false,
71021
+ backup,
71022
+ error: "Unknown configuration migration; upgrade the CLI before applying"
71023
+ };
71024
+ delete value.trustedPaths;
71025
+ writeFileAtomic(migration.file, JSON.stringify(value, null, 2) + `
71026
+ `, 384);
71027
+ }
71028
+ const run = runner(deps);
71029
+ const cursorRoot = path32.join(home, ".cursor/plugins/local/workit");
71030
+ if (plan.entries.some((entry) => entry.host === "cursor") && existsSync19(cursorRoot))
71031
+ cpSync4(cursorRoot, path32.join(backup, "cursor-package"), { recursive: true });
71032
+ for (const entry of plan.entries) {
71033
+ if (entry.host === "opencode")
71034
+ return {
71035
+ ok: false,
71036
+ backup,
71037
+ error: "Scoped OpenCode server-plugin updates are unsupported; registration preserved"
71038
+ };
71039
+ if (entry.host === "cursor") {
71040
+ const preview = {
71041
+ ok: true,
71042
+ blocked: [],
71043
+ preserved: [],
71044
+ overrides: [],
71045
+ platforms: ["cursor"],
71046
+ state: readSetupState(configDir),
71047
+ mutations: [
71048
+ {
71049
+ type: "install-host",
71050
+ platform: "cursor",
71051
+ path: path32.join(home, ".cursor/plugins/local/.workit-package-bootstrap"),
71052
+ commands: entry.commands.map((command) => ({
71053
+ ...command,
71054
+ purpose: command.purpose ?? "Upgrade Cursor"
71055
+ }))
71056
+ },
71057
+ {
71058
+ type: "install-adapter",
71059
+ platform: "cursor",
71060
+ path: path32.join(home, ".cursor/plugins/local/workit")
71061
+ }
71062
+ ]
71063
+ };
71064
+ const result = applySetupPreview(preview, {
71065
+ home,
71066
+ env,
71067
+ cwd: home,
71068
+ preferInstalledAdapter: true,
71069
+ runHostCommand: (command) => {
71070
+ const outcome = run(command.command, command.args);
71071
+ return { exitCode: outcome.status ?? 1, stdout: outcome.stdout, stderr: "" };
71072
+ }
71073
+ });
71074
+ const pkg = object2(path32.join(home, ".cursor/plugins/local/workit/package.json"));
71075
+ if (!result.ok || pkg?.version !== entry.latest)
71076
+ return {
71077
+ ok: false,
71078
+ backup,
71079
+ error: "Cursor update did not verify the requested package version; inspect the installer before retrying"
71080
+ };
71081
+ continue;
71082
+ }
71083
+ for (const command of entry.commands)
71084
+ if (run(command.command, command.args).status !== 0)
71085
+ return {
71086
+ ok: false,
71087
+ backup,
71088
+ error: `${entry.host}: update failed; inspect the native host before retrying`
71089
+ };
71090
+ const verify = entry.host === "codex" ? run("codex", ["plugin", "list", "--json"]) : run("pi", ["list"]);
71091
+ if (verify.status !== 0)
71092
+ return { ok: false, backup, error: `${entry.host}: update verification failed` };
71093
+ if (entry.host === "codex") {
71094
+ let installed = [];
71095
+ try {
71096
+ installed = JSON.parse(verify.stdout).installed ?? [];
71097
+ } catch {}
71098
+ if (!installed.some((plugin) => plugin.name === "workit" && plugin.marketplaceName === "workflow-toolkit" && plugin.version === entry.latest))
71099
+ return { ok: false, backup, error: "Codex did not report the requested Workit version" };
71100
+ }
71101
+ if (entry.host === "pi" && piInstalledVersion(verify.stdout, entry.selector) !== entry.latest)
71102
+ return { ok: false, backup, error: "Pi did not report the requested Workit package version" };
71103
+ }
71104
+ if (plan.cli) {
71105
+ const currentRoot = run("npm", ["root", "--global"]);
71106
+ if (currentRoot.status !== 0 || currentRoot.stdout.trim() !== plan.cli.root)
71107
+ return { ok: false, backup, error: "Global npm location changed; preview again" };
71108
+ const cliBackup = path32.join(backup, "cli-package");
71109
+ cpSync4(path32.join(plan.cli.root, "@brainervirus/workit-cli"), cliBackup, { recursive: true });
71110
+ if (run("npm", ["install", "--global", `@brainervirus/workit-cli@${plan.cli.latest}`]).status !== 0)
71111
+ return {
71112
+ ok: false,
71113
+ backup,
71114
+ error: `CLI update failed; preserved package at ${cliBackup}; use npm to repair or run node ${path32.join(cliBackup, "dist/index.js")}`
71115
+ };
71116
+ const installed = object2(path32.join(plan.cli.root, "@brainervirus/workit-cli/package.json"));
71117
+ if (installed?.name !== "@brainervirus/workit-cli" || installed.version !== plan.cli.latest)
71118
+ return {
71119
+ ok: false,
71120
+ backup,
71121
+ error: `CLI update did not verify the requested version; preserved package at ${cliBackup}`
71122
+ };
71123
+ }
71124
+ return { ok: true, backup };
71125
+ }
71126
+ async function runUpgradeCommand(argv, deps = {}) {
71127
+ const out = deps.out ?? process.stdout;
71128
+ const flags = new Set(argv);
71129
+ const unknown = argv.filter((arg) => !["--apply", "--confirm", "--json", "--cli"].includes(arg) && !arg.startsWith("--hosts="));
71130
+ const hostFlag = argv.find((arg) => arg.startsWith("--hosts="))?.slice(8);
71131
+ const raw = hostFlag === "none" ? [] : hostFlag?.split(",") ?? HOSTS2;
71132
+ if (unknown.length || raw.some((host) => !HOSTS2.includes(host))) {
71133
+ out.write(`Usage: workit upgrade [--hosts=opencode,cursor,codex,pi|none] [--cli] [--apply --confirm] [--json]
71134
+ `);
71135
+ return 2;
71136
+ }
71137
+ const plan = previewUpgrade(raw, deps);
71138
+ if (flags.has("--cli")) {
71139
+ const run = runner(deps);
71140
+ const root = run("npm", ["root", "--global"]);
71141
+ if (root.status !== 0 || !path32.isAbsolute(root.stdout.trim())) {
71142
+ plan.blocked.push("Global npm location could not be verified");
71143
+ } else {
71144
+ const packageFile = path32.join(root.stdout.trim(), "@brainervirus/workit-cli/package.json");
71145
+ const pkg = object2(packageFile);
71146
+ if (pkg?.name !== "@brainervirus/workit-cli" || lstatSync8(path32.dirname(packageFile)).isSymbolicLink()) {
71147
+ plan.skipped.push({
71148
+ host: "cli",
71149
+ reason: "No managed global CLI install (local links preserved); use npx @brainervirus/workit-cli@latest"
71150
+ });
71151
+ } else {
71152
+ const latest = run("npm", ["view", "@brainervirus/workit-cli", "version"]);
71153
+ if (latest.status !== 0 || !/^\d+\.\d+\.\d+$/.test(latest.stdout.trim()))
71154
+ plan.blocked.push("CLI registry version unavailable; existing installation preserved");
71155
+ else if (!versionAtLeast(pkg.version, latest.stdout.trim())) {
71156
+ plan.cli = { latest: latest.stdout.trim(), root: root.stdout.trim() };
71157
+ plan.configDigests[packageFile] = digest2(packageFile);
71158
+ } else
71159
+ plan.skipped.push({ host: "cli", reason: `already current (${pkg.version})` });
71160
+ }
71161
+ }
71162
+ plan.ok = plan.blocked.length === 0;
71163
+ }
71164
+ if (!flags.has("--apply")) {
71165
+ out.write(JSON.stringify(plan, null, 2) + `
71166
+ `);
71167
+ return plan.ok ? 0 : 1;
71168
+ }
71169
+ if (!flags.has("--confirm")) {
71170
+ out.write(`Apply requires --confirm after reviewing the upgrade preview.
71171
+ `);
71172
+ return 2;
71173
+ }
71174
+ const result = applyUpgrade(plan, deps);
71175
+ out.write(JSON.stringify(result, null, 2) + `
71176
+ `);
71177
+ return result.ok ? 0 : 1;
71178
+ }
71179
+ async function runLaunchCommand(argv, deps = {}) {
71180
+ const [host, ...args] = argv;
71181
+ if (!HOSTS2.includes(host)) {
71182
+ (deps.out ?? process.stdout).write(`Usage: workit launch <opencode|cursor|codex|pi> [--auto-upgrade] [-- host arguments]
71183
+ `);
71184
+ return 2;
71185
+ }
71186
+ const auto = args[0] === "--auto-upgrade";
71187
+ const rest = auto ? args.slice(1) : args;
71188
+ const hostArgs = rest[0] === "--" ? rest.slice(1) : rest;
71189
+ const { home, env } = upgradePaths(deps);
71190
+ let launch;
71191
+ if (!deps.run) {
71192
+ try {
71193
+ launch = hostCommand(host, hostArgs, { home, env });
71194
+ } catch (error) {
71195
+ (deps.out ?? process.stderr).write(`${error instanceof Error ? error.message : "Host executable unavailable"}
71196
+ `);
71197
+ return 1;
71198
+ }
71199
+ }
71200
+ if (auto) {
71201
+ const plan = previewUpgrade([host], deps);
71202
+ if (plan.ok) {
71203
+ for (const skipped of plan.skipped)
71204
+ (deps.out ?? process.stderr).write(`${skipped.host}: ${skipped.reason}
71205
+ `);
71206
+ const result = applyUpgrade(plan, deps);
71207
+ if (!result.ok) {
71208
+ (deps.out ?? process.stdout).write(`${result.error}
71209
+ `);
71210
+ return 1;
71211
+ }
71212
+ } else if (plan.blocked.every((reason) => reason.includes("registry version unavailable"))) {
71213
+ (deps.out ?? process.stderr).write(`Upgrade unavailable: ${plan.blocked.join("; ")}. Starting the unchanged host.
71214
+ `);
71215
+ } else {
71216
+ (deps.out ?? process.stderr).write(`Upgrade blocked: ${plan.blocked.join("; ")}
71217
+ `);
71218
+ return 1;
71219
+ }
71220
+ }
71221
+ if (deps.run)
71222
+ return deps.run(host, hostArgs).status ?? 1;
71223
+ return spawnSync11(launch.command, launch.args, {
71224
+ stdio: "inherit",
71225
+ env
71226
+ }).status ?? 1;
71227
+ }
71228
+ var runningHosts = (deps) => {
71229
+ const run = runner(deps);
71230
+ const list = process.platform === "win32" ? run("tasklist", ["/fo", "csv", "/nh"]) : run("ps", ["-eo", "comm="]);
71231
+ if (list.status !== 0)
71232
+ return HOSTS2;
71233
+ return HOSTS2.filter((host) => new RegExp(`(?:^|[\\/",\\s])${host}(?:\\.exe)?(?:[",\\s]|$)`, "im").test(list.stdout));
71234
+ };
71235
+
69180
71236
  // packages/workit-cli/src/index.tsx
69181
71237
  var jsx_dev_runtime3 = __toESM(require_jsx_dev_runtime(), 1);
69182
71238
  var logger = createLogger({
@@ -69201,6 +71257,8 @@ var HELP = `workit — workflow rails for agentic coding
69201
71257
 
69202
71258
  Usage:
69203
71259
  workit init Run the interactive setup wizard
71260
+ workit upgrade Preview upgrades (--apply --confirm; --hosts=a,b; --cli for the CLI)
71261
+ workit launch <host> [--auto-upgrade] [-- args] Upgrade before host startup
69204
71262
  workit doctor Verify the offline installation health (add --json for a machine-readable report)
69205
71263
  workit uninstall Remove workit host registrations interactively (~/.config/workit is kept)
69206
71264
  workit cutover Preview or apply an explicit v1 cutover (apply requires --confirm)
@@ -69209,7 +71267,7 @@ ${COMMAND_DESCRIPTIONS.map(([cmd, desc]) => ` ${cmd.padEnd(helpColumn)}${desc}`
69209
71267
  action payloads: ${externalActionHelp}
69210
71268
  workit Show this help
69211
71269
 
69212
- Run \`npx workit init\` to configure platforms, YouTrack, VCS and project hygiene.
71270
+ Run \`npx @brainervirus/workit-cli init\` to configure platforms, YouTrack, VCS and project hygiene.
69213
71271
  `;
69214
71272
  function printApplySummary(result) {
69215
71273
  for (const entry of result.entries) {
@@ -69423,7 +71481,11 @@ if (__require.main == __require.module) {
69423
71481
  logger.error(EVENT.uncaughtFailure, { phase: "uncaughtException", ...errorDetail(err) });
69424
71482
  process.exit(1);
69425
71483
  });
69426
- if (subcommand === "init") {
71484
+ if (subcommand === "upgrade") {
71485
+ process.exit(await runUpgradeCommand(args.slice(1)));
71486
+ } else if (subcommand === "launch") {
71487
+ process.exit(await runLaunchCommand(args.slice(1)));
71488
+ } else if (subcommand === "init") {
69427
71489
  await runInit();
69428
71490
  } else if (subcommand === "doctor") {
69429
71491
  runDoctorCommand(args);