@brainervirus/workit-claude-code 8.0.1 → 8.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.
package/dist/workit.js CHANGED
@@ -65,7 +65,7 @@ var package_default;
65
65
  var init_package = __esm(() => {
66
66
  package_default = {
67
67
  name: "@brainervirus/workit-cli",
68
- version: "8.0.1",
68
+ version: "8.1.0",
69
69
  private: false,
70
70
  description: "Workit CLI — setup wizard, doctor, and task control for agentic coding workflows",
71
71
  keywords: [
@@ -103,9 +103,9 @@ var init_package = __esm(() => {
103
103
  },
104
104
  dependencies: {
105
105
  "@babel/parser": "7.29.9",
106
- "@brainervirus/workit-core": "^8.0.1",
107
- "@brainervirus/workit-cursor": "^8.0.1",
108
- "@brainervirus/workit-opencode": "^8.0.1",
106
+ "@brainervirus/workit-core": "^8.1.0",
107
+ "@brainervirus/workit-cursor": "^8.1.0",
108
+ "@brainervirus/workit-opencode": "^8.1.0",
109
109
  "@inkjs/ui": "2.0.0",
110
110
  ink: "7.1.1",
111
111
  react: "19.2.8"
@@ -14919,60 +14919,6 @@ var init_task_context = __esm(() => {
14919
14919
  privateKey = /-----BEGIN [^-]*PRIVATE KEY-----[\s\S]*?-----END [^-]*PRIVATE KEY-----/gi;
14920
14920
  });
14921
14921
 
14922
- // packages/workit-core/src/core/commit-flavors.ts
14923
- var CONVENTIONAL_RE, TICKET_RE, GITMOJI_RE, matchCommitFlavor = (message, flavor, pattern) => {
14924
- const subject = message.split(`
14925
- `, 1)[0] ?? "";
14926
- switch (flavor) {
14927
- case "conventional":
14928
- return CONVENTIONAL_RE.test(subject);
14929
- case "gitmoji":
14930
- return GITMOJI_RE.test(subject);
14931
- case "ticket-prefix":
14932
- return TICKET_RE.test(subject);
14933
- case "freeform":
14934
- return subject.trim().length > 0;
14935
- case "custom":
14936
- if (!pattern)
14937
- return false;
14938
- try {
14939
- return new RegExp(pattern).test(subject);
14940
- } catch {
14941
- return false;
14942
- }
14943
- case "auto":
14944
- return subject.trim().length > 0;
14945
- }
14946
- }, DETECTABLE, detectCommitFlavor = (subjects, threshold = 0.7) => {
14947
- if (subjects.length === 0)
14948
- return { flavor: null, confidence: 0 };
14949
- const votes = new Map;
14950
- for (const subject of subjects) {
14951
- const hit = DETECTABLE.find((flavor) => matchCommitFlavor(subject, flavor));
14952
- if (hit)
14953
- votes.set(hit, (votes.get(hit) ?? 0) + 1);
14954
- }
14955
- let best = null;
14956
- let count = 0;
14957
- for (const [flavor, votesFor] of votes) {
14958
- if (votesFor > count) {
14959
- best = flavor;
14960
- count = votesFor;
14961
- }
14962
- }
14963
- const confidence = count / subjects.length;
14964
- return best !== null && confidence >= threshold ? { flavor: best, confidence } : { flavor: null, confidence };
14965
- };
14966
- var init_commit_flavors = __esm(() => {
14967
- CONVENTIONAL_RE = /^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\(.+\))?(!)?: .+/;
14968
- TICKET_RE = /^[A-Z]{2,}-\d+\b/;
14969
- GITMOJI_RE = /^\p{Extended_Pictographic}/u;
14970
- DETECTABLE = ["conventional", "gitmoji", "ticket-prefix"];
14971
- });
14972
-
14973
- // packages/workit-core/src/core/git.ts
14974
- var init_git = () => {};
14975
-
14976
14922
  // packages/workit-core/src/core/boundary.ts
14977
14923
  var EVENT, errorDetail = (err) => {
14978
14924
  if (err instanceof Error) {
@@ -15057,7 +15003,7 @@ var diagnosticLogger, setDiagnosticLogger = (logger) => {
15057
15003
  if (!migrationFailed)
15058
15004
  migratedDir = dir;
15059
15005
  return dir;
15060
- }, configDir = () => ensureConfigDir(), LOCALE_RE, DEFAULTS, readSafe = (p) => {
15006
+ }, configDir = () => ensureConfigDir(), LOCALE_RE, DEFAULTS, unchosenBranchPolicy, readSafe = (p) => {
15061
15007
  try {
15062
15008
  return readFileSync3(p, "utf8");
15063
15009
  } catch {
@@ -15079,22 +15025,21 @@ var diagnosticLogger, setDiagnosticLogger = (logger) => {
15079
15025
  const locale = LOCALE_RE.test(String(input.locale ?? "")) ? input.locale : DEFAULTS.locale;
15080
15026
  const preset = Object.hasOwn(PRESETS, input.branchPolicy?.preset) ? input.branchPolicy?.preset : "gitflow";
15081
15027
  const commitPreset = COMMIT_PRESETS.includes(String(input.commitPolicy?.preset ?? "")) ? input.commitPolicy?.preset : "conventional";
15082
- return {
15083
- status: "valid",
15084
- path: file,
15085
- config: {
15086
- locale,
15087
- localeOptions: Array.isArray(input.localeOptions) ? input.localeOptions : DEFAULTS.localeOptions,
15088
- branchPolicy: mergePreset(preset, {
15089
- allowed: Array.isArray(input.branchPolicy?.allowed) ? input.branchPolicy.allowed : undefined,
15090
- protectedNames: Array.isArray(input.branchPolicy?.protected) ? input.branchPolicy.protected : undefined
15091
- }, DEFAULTS),
15092
- commitPolicy: {
15093
- preset: commitPreset,
15094
- ...typeof input.commitPolicy?.pattern === "string" ? { pattern: input.commitPolicy.pattern } : {}
15095
- }
15028
+ const config = {
15029
+ locale,
15030
+ localeOptions: Array.isArray(input.localeOptions) ? input.localeOptions : DEFAULTS.localeOptions,
15031
+ branchPolicy: mergePreset(preset, {
15032
+ allowed: Array.isArray(input.branchPolicy?.allowed) ? input.branchPolicy.allowed : undefined,
15033
+ protectedNames: Array.isArray(input.branchPolicy?.protected) ? input.branchPolicy.protected : undefined
15034
+ }, DEFAULTS),
15035
+ commitPolicy: {
15036
+ preset: commitPreset,
15037
+ ...typeof input.commitPolicy?.pattern === "string" ? { pattern: input.commitPolicy.pattern } : {}
15096
15038
  }
15097
15039
  };
15040
+ if (!Object.hasOwn(PRESETS, input.branchPolicy?.preset))
15041
+ unchosenBranchPolicy.add(config);
15042
+ return { status: "valid", path: file, config };
15098
15043
  }, readConfigTyped = (dir) => {
15099
15044
  const file = path13.join(dir ?? configDir(), "config.json");
15100
15045
  return parseConfigResult(readSafe(file), file);
@@ -15108,9 +15053,11 @@ var diagnosticLogger, setDiagnosticLogger = (logger) => {
15108
15053
  if (result.status === "malformed")
15109
15054
  throw new Error(result.error);
15110
15055
  return result.config ?? DEFAULTS;
15111
- }, vcsConfigPath = () => process.env.WORKFLOW_VCS_CONFIG ?? path13.join(configDir(), "vcs.json"), branchGlobPattern = (glob) => new RegExp(`^${glob.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*")}$`, "i"), resolveBranchPolicy = (config, workspace) => {
15056
+ }, vcsConfigPath = () => process.env.WORKFLOW_VCS_CONFIG ?? path13.join(configDir(), "vcs.json"), branchGlobPattern = (glob) => new RegExp(`^${glob.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*")}$`, "i"), resolveBranchPolicy = (config, workspace, repoPreset) => {
15112
15057
  const wp = workspace?.branchPolicy ?? {};
15113
- const preset = Object.hasOwn(PRESETS, wp.preset) ? wp.preset : config.branchPolicy?.preset ?? "gitflow";
15058
+ const workspacePreset = Object.hasOwn(PRESETS, wp.preset);
15059
+ const detected = !workspacePreset && unchosenBranchPolicy.has(config) ? repoPreset?.() ?? null : null;
15060
+ const preset = workspacePreset ? wp.preset : detected ?? config.branchPolicy?.preset ?? "gitflow";
15114
15061
  const merged = mergePreset(preset, {
15115
15062
  allowed: wp.allowed,
15116
15063
  protectedNames: wp.protected
@@ -15119,9 +15066,10 @@ var diagnosticLogger, setDiagnosticLogger = (logger) => {
15119
15066
  return {
15120
15067
  preset,
15121
15068
  allowed,
15122
- protected: new Set(merged.protected.map((p) => p.toLowerCase())),
15069
+ protected: new Set([...merged.protected, ...detected ? PRESETS.gitflow.protected : []].map((p) => p.toLowerCase())),
15123
15070
  integration: wp.integration === "merge" ? "merge" : "pr",
15124
- defaultTargetBranch: preset === "github-flow" ? "main" : preset === "trunk-based" ? "master" : "develop"
15071
+ defaultTargetBranch: preset === "github-flow" ? "main" : preset === "trunk-based" ? "master" : "develop",
15072
+ detected: detected !== null
15125
15073
  };
15126
15074
  }, COMMIT_PRESETS, resolveCommitPolicy = (config, workspace) => {
15127
15075
  const wp = workspace?.commitPolicy ?? {};
@@ -15155,6 +15103,7 @@ var init_config = __esm(() => {
15155
15103
  },
15156
15104
  commitPolicy: { preset: "conventional" }
15157
15105
  };
15106
+ unchosenBranchPolicy = new WeakSet([DEFAULTS]);
15158
15107
  COMMIT_PRESETS = [
15159
15108
  "conventional",
15160
15109
  "gitmoji",
@@ -15165,6 +15114,126 @@ var init_config = __esm(() => {
15165
15114
  ];
15166
15115
  });
15167
15116
 
15117
+ // packages/workit-core/src/core/branch-policy.ts
15118
+ import { spawnSync as spawnSync7 } from "node:child_process";
15119
+ var PREFIXES, branchNames = (workspaceRoot) => {
15120
+ const r = spawnSync7("git", ["for-each-ref", "--format=%(refname)", "refs/heads", "refs/remotes/origin"], { cwd: workspaceRoot, encoding: "utf8" });
15121
+ if (r.status !== 0)
15122
+ return new Set;
15123
+ return new Set((r.stdout ?? "").split(`
15124
+ `).filter(Boolean).map((ref) => ref.startsWith("refs/heads/") ? ref.slice("refs/heads/".length) : ref.slice("refs/remotes/origin/".length)));
15125
+ }, detectBranchPolicy = (workspaceRoot) => {
15126
+ const names = branchNames(workspaceRoot);
15127
+ const develop = names.has("develop");
15128
+ const main = names.has("main");
15129
+ const master = names.has("master");
15130
+ const root = main ? "main" : master ? "master" : null;
15131
+ if (develop) {
15132
+ return {
15133
+ preset: "gitflow",
15134
+ developBranch: "develop",
15135
+ integration: "merge",
15136
+ protected: root ? [root, "develop"] : ["develop"],
15137
+ allowed: [...PRESETS.gitflow.allowed],
15138
+ prefixes: PREFIXES
15139
+ };
15140
+ }
15141
+ if (root === "main") {
15142
+ return {
15143
+ preset: "github-flow",
15144
+ developBranch: null,
15145
+ integration: "pr",
15146
+ protected: [root],
15147
+ allowed: ["*"],
15148
+ prefixes: PREFIXES
15149
+ };
15150
+ }
15151
+ if (root === "master") {
15152
+ return {
15153
+ preset: "trunk-based",
15154
+ developBranch: null,
15155
+ integration: "pr",
15156
+ protected: [root],
15157
+ allowed: ["*"],
15158
+ prefixes: PREFIXES
15159
+ };
15160
+ }
15161
+ return {
15162
+ preset: "gitflow",
15163
+ developBranch: null,
15164
+ integration: "merge",
15165
+ protected: [],
15166
+ allowed: [],
15167
+ prefixes: PREFIXES
15168
+ };
15169
+ }, repoBranchPreset = (workspaceRoot) => {
15170
+ const detected = detectBranchPolicy(workspaceRoot);
15171
+ return detected.developBranch || detected.allowed.length ? detected.preset : null;
15172
+ };
15173
+ var init_branch_policy = __esm(() => {
15174
+ init_config();
15175
+ PREFIXES = {
15176
+ feature: "feature/*",
15177
+ bugfix: "bugfix/*",
15178
+ release: "release/*",
15179
+ hotfix: "hotfix/*"
15180
+ };
15181
+ });
15182
+
15183
+ // packages/workit-core/src/core/commit-flavors.ts
15184
+ var CONVENTIONAL_RE, TICKET_RE, GITMOJI_RE, matchCommitFlavor = (message, flavor, pattern) => {
15185
+ const subject = message.split(`
15186
+ `, 1)[0] ?? "";
15187
+ switch (flavor) {
15188
+ case "conventional":
15189
+ return CONVENTIONAL_RE.test(subject);
15190
+ case "gitmoji":
15191
+ return GITMOJI_RE.test(subject);
15192
+ case "ticket-prefix":
15193
+ return TICKET_RE.test(subject);
15194
+ case "freeform":
15195
+ return subject.trim().length > 0;
15196
+ case "custom":
15197
+ if (!pattern)
15198
+ return false;
15199
+ try {
15200
+ return new RegExp(pattern).test(subject);
15201
+ } catch {
15202
+ return false;
15203
+ }
15204
+ case "auto":
15205
+ return subject.trim().length > 0;
15206
+ }
15207
+ }, DETECTABLE, detectCommitFlavor = (subjects, threshold = 0.7) => {
15208
+ if (subjects.length === 0)
15209
+ return { flavor: null, confidence: 0 };
15210
+ const votes = new Map;
15211
+ for (const subject of subjects) {
15212
+ const hit = DETECTABLE.find((flavor) => matchCommitFlavor(subject, flavor));
15213
+ if (hit)
15214
+ votes.set(hit, (votes.get(hit) ?? 0) + 1);
15215
+ }
15216
+ let best = null;
15217
+ let count = 0;
15218
+ for (const [flavor, votesFor] of votes) {
15219
+ if (votesFor > count) {
15220
+ best = flavor;
15221
+ count = votesFor;
15222
+ }
15223
+ }
15224
+ const confidence = count / subjects.length;
15225
+ return best !== null && confidence >= threshold ? { flavor: best, confidence } : { flavor: null, confidence };
15226
+ };
15227
+ var init_commit_flavors = __esm(() => {
15228
+ CONVENTIONAL_RE = /^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\(.+\))?(!)?: .+/;
15229
+ TICKET_RE = /^[A-Z]{2,}-\d+\b/;
15230
+ GITMOJI_RE = /^\p{Extended_Pictographic}/u;
15231
+ DETECTABLE = ["conventional", "gitmoji", "ticket-prefix"];
15232
+ });
15233
+
15234
+ // packages/workit-core/src/core/git.ts
15235
+ var init_git = () => {};
15236
+
15168
15237
  // packages/workit-core/src/core/workspaces.ts
15169
15238
  import { createHash as createHash8 } from "node:crypto";
15170
15239
  import { readFileSync as readFileSync4, realpathSync as realpathSync3 } from "node:fs";
@@ -15174,7 +15243,7 @@ function workspaceDefaultTarget(cwd, globalDefault) {
15174
15243
  const wp = ws?.branchPolicy ?? {};
15175
15244
  const hasWorkspacePolicy = typeof wp.preset === "string" && Object.hasOwn(PRESETS, wp.preset);
15176
15245
  const selected = resolveRuntimeWorkspacePolicy(cwd, "branch", process.env.WORKFLOW_PROFILE?.trim() || undefined);
15177
- const policyDefault = resolveBranchPolicy(readConfig(), selected.policy ? { branchPolicy: selected.policy } : null).defaultTargetBranch;
15246
+ const policyDefault = resolveBranchPolicy(readConfig(), selected.policy ? { branchPolicy: selected.policy } : null, () => repoBranchPreset(cwd)).defaultTargetBranch;
15178
15247
  return String(ws?.vcs?.defaultTargetBranch ?? (hasWorkspacePolicy ? policyDefault : globalDefault ?? policyDefault) ?? "develop");
15179
15248
  }
15180
15249
  var nonBlank, branchPreset, commitPreset, workspaceBranchPolicySchema, workspaceCommitPolicySchema, workspaceProfileSchema, versionSourceSchema, releaseTrackSchema, workspaceConfigSchema, workspacesFileSchema, workspacesRevision = (contents) => createHash8("sha256").update(contents ?? "<missing>").digest("hex"), validateWorkspacesDocument = (parsed, file) => {
@@ -15456,7 +15525,7 @@ var nonBlank, branchPreset, commitPreset, workspaceBranchPolicySchema, workspace
15456
15525
  commitPolicy: profile?.commitPolicy ? `profile:${profileName}` : workspace.commitPolicy ? `workspace:${workspace.name}` : "user-default"
15457
15526
  }
15458
15527
  };
15459
- }, resolveWorkspacePolicy = (config, workspace, profileName) => {
15528
+ }, resolveWorkspacePolicy = (config, workspace, profileName, repoPreset) => {
15460
15529
  const selected = workspace ? resolveWorkspaceProfile(workspace, profileName) : {
15461
15530
  status: "resolved",
15462
15531
  profileName: null,
@@ -15469,7 +15538,7 @@ var nonBlank, branchPreset, commitPreset, workspaceBranchPolicySchema, workspace
15469
15538
  status: "resolved",
15470
15539
  workspace: selected.workspace,
15471
15540
  profileName: selected.profileName,
15472
- branchPolicy: resolveBranchPolicy(config, selected.workspace),
15541
+ branchPolicy: resolveBranchPolicy(config, selected.workspace, repoPreset),
15473
15542
  commitPolicy: resolveCommitPolicy(config, selected.workspace),
15474
15543
  provenance: selected.provenance
15475
15544
  };
@@ -15477,6 +15546,7 @@ var nonBlank, branchPreset, commitPreset, workspaceBranchPolicySchema, workspace
15477
15546
  var init_workspaces = __esm(() => {
15478
15547
  init_zod();
15479
15548
  init_config();
15549
+ init_branch_policy();
15480
15550
  nonBlank = string2().refine((value) => value.trim().length > 0, "must not be blank");
15481
15551
  branchPreset = custom((value) => typeof value === "string" && Object.hasOwn(PRESETS, value), "unsupported branch preset");
15482
15552
  commitPreset = custom((value) => typeof value === "string" && COMMIT_PRESETS.includes(value), "unsupported commit preset");
@@ -15603,7 +15673,7 @@ var init_workspaces = __esm(() => {
15603
15673
 
15604
15674
  // packages/workit-core/src/core/release-tracks.ts
15605
15675
  import { createHash as createHash9 } from "node:crypto";
15606
- import { spawnSync as spawnSync7 } from "node:child_process";
15676
+ import { spawnSync as spawnSync8 } from "node:child_process";
15607
15677
  import { readFileSync as readFileSync5 } from "node:fs";
15608
15678
  function readReleaseTracks(raw) {
15609
15679
  const out = { tracks: [], issues: [], error: null, protectedBranches: [] };
@@ -16005,7 +16075,7 @@ var ReleaseTrackError, KNOWN_FIELDS, BRANCH_FIELDS, PLACEHOLDER, HAS_PLACEHOLDER
16005
16075
  const remote = remotes.find((name) => bare.startsWith(`${name}/`));
16006
16076
  return remote ? bare.slice(remote.length + 1) : bare;
16007
16077
  }, MAX_RECORDED_HOPS = 10, git4 = (cwd, args) => {
16008
- const run = spawnSync7("git", args, {
16078
+ const run = spawnSync8("git", args, {
16009
16079
  cwd,
16010
16080
  encoding: "utf8",
16011
16081
  timeout: 20000,
@@ -16066,7 +16136,7 @@ var init_release_tracks = __esm(() => {
16066
16136
  // packages/workit-core/src/core/vcs-config.ts
16067
16137
  import fs12 from "node:fs";
16068
16138
  import path15 from "node:path";
16069
- import { spawnSync as spawnSync8 } from "node:child_process";
16139
+ import { spawnSync as spawnSync9 } from "node:child_process";
16070
16140
  function vcsConfig(mode, cwd, options = {}) {
16071
16141
  const ws = resolveRuntimeWorkspaceVcs(vcsCwd(cwd));
16072
16142
  const wsVcs = ws?.vcs ?? {};
@@ -16149,7 +16219,7 @@ function vcsConfig(mode, cwd, options = {}) {
16149
16219
  return out;
16150
16220
  }
16151
16221
  var workspacesPath = () => path15.join(configDir(), "workspaces.json"), vcsCwd = (cwd) => cwd ?? process.env.WORKFLOW_WORKSPACE_ROOT ?? process.cwd(), remoteProvider = (cwd) => {
16152
- const r = spawnSync8("git", ["remote", "get-url", "origin"], { cwd, encoding: "utf8" });
16222
+ const r = spawnSync9("git", ["remote", "get-url", "origin"], { cwd, encoding: "utf8" });
16153
16223
  if (r.status !== 0)
16154
16224
  return null;
16155
16225
  const url = (r.stdout ?? "").trim();
@@ -16199,7 +16269,7 @@ function effectiveWorkspacePolicy(workspaceRoot, kind, profileName) {
16199
16269
  const selected = resolveRuntimeWorkspacePolicy(workspaceRoot, kind, profileName?.trim() || process.env.WORKFLOW_PROFILE?.trim() || undefined);
16200
16270
  const config = readConfig();
16201
16271
  return kind === "branch" ? {
16202
- branchPolicy: withReleaseTracks(workspaceRoot, resolveBranchPolicy(config, selected.policy ? { branchPolicy: selected.policy } : null)),
16272
+ branchPolicy: withReleaseTracks(workspaceRoot, resolveBranchPolicy(config, selected.policy ? { branchPolicy: selected.policy } : null, () => repoBranchPreset(workspaceRoot))),
16203
16273
  provenance: { branchPolicy: selected.source }
16204
16274
  } : {
16205
16275
  commitPolicy: resolveCommitPolicy(config, selected.policy ? { commitPolicy: selected.policy } : null),
@@ -16356,6 +16426,7 @@ var withReleaseTracks = (workspaceRoot, policy) => {
16356
16426
  return dirty ? "carry" : "clean";
16357
16427
  };
16358
16428
  var init_branch = __esm(() => {
16429
+ init_branch_policy();
16359
16430
  init_commit_flavors();
16360
16431
  init_git();
16361
16432
  init_config();
@@ -18464,7 +18535,7 @@ var init_pr_body = __esm(() => {
18464
18535
 
18465
18536
  // packages/workit-core/src/core/pr-create.ts
18466
18537
  import path19 from "node:path";
18467
- import { spawnSync as spawnSync9 } from "node:child_process";
18538
+ import { spawnSync as spawnSync10 } from "node:child_process";
18468
18539
  import { pathToFileURL } from "node:url";
18469
18540
  var safePushUrl = (remote) => {
18470
18541
  const raw = remote.trim();
@@ -18492,7 +18563,7 @@ var safePushUrl = (remote) => {
18492
18563
  const target = safePushUrl(remote);
18493
18564
  if (!target)
18494
18565
  return false;
18495
- const result = spawnSync9("git", ["config", "--get-regexp", "^url\\..*\\.(insteadof|pushinsteadof)$"], { cwd, encoding: "utf8" });
18566
+ const result = spawnSync10("git", ["config", "--get-regexp", "^url\\..*\\.(insteadof|pushinsteadof)$"], { cwd, encoding: "utf8" });
18496
18567
  if (result.status !== 0 && result.status !== 1)
18497
18568
  return false;
18498
18569
  const prefixes = (result.stdout ?? "").split(/\r?\n/u).map((line) => line.trim().slice(line.trim().indexOf(" ") + 1)).filter(Boolean);
@@ -62780,7 +62851,7 @@ var init_support_matrix = __esm(() => {
62780
62851
  });
62781
62852
 
62782
62853
  // packages/workit-cli/src/admin/host-install.ts
62783
- import { spawnSync as spawnSync10 } from "node:child_process";
62854
+ import { spawnSync as spawnSync11 } from "node:child_process";
62784
62855
  import {
62785
62856
  accessSync,
62786
62857
  constants as constants2,
@@ -63051,7 +63122,7 @@ function runHostCommand(step, context = {}) {
63051
63122
  stderr: `Cannot safely execute batch shim ${step.command}; resolve its Node.js CLI entrypoint and retry`
63052
63123
  };
63053
63124
  }
63054
- const result = spawnSync10(step.command, step.args, {
63125
+ const result = spawnSync11(step.command, step.args, {
63055
63126
  cwd: step.cwd,
63056
63127
  env: (() => {
63057
63128
  const home = context.home ?? context.env?.HOME ?? os6.homedir();
@@ -63256,7 +63327,7 @@ var init_skill_manifests = __esm(() => {
63256
63327
  });
63257
63328
 
63258
63329
  // packages/workit-cli/src/admin/doctor.ts
63259
- import { spawnSync as spawnSync11 } from "node:child_process";
63330
+ import { spawnSync as spawnSync12 } from "node:child_process";
63260
63331
  import {
63261
63332
  existsSync as existsSync13,
63262
63333
  lstatSync as lstatSync4,
@@ -63340,7 +63411,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
63340
63411
  }
63341
63412
  return false;
63342
63413
  }, versionOf = (bin, env, timeout) => {
63343
- const r = spawnSync11(bin, ["--version"], { encoding: "utf8", env, timeout });
63414
+ const r = spawnSync12(bin, ["--version"], { encoding: "utf8", env, timeout });
63344
63415
  if (r.error)
63345
63416
  return null;
63346
63417
  return (r.stdout ?? "").trim();
@@ -63589,7 +63660,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
63589
63660
  `)) {
63590
63661
  return false;
63591
63662
  }
63592
- return spawnSync11(runtime, ["--check", entry], { encoding: "utf8", env }).status === 0;
63663
+ return spawnSync12(runtime, ["--check", entry], { encoding: "utf8", env }).status === 0;
63593
63664
  } catch {
63594
63665
  return false;
63595
63666
  }
@@ -63803,7 +63874,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
63803
63874
  if (!cmd)
63804
63875
  return null;
63805
63876
  try {
63806
- const r = spawnSync11(cmd, ["view", pkgName, "version"], {
63877
+ const r = spawnSync12(cmd, ["view", pkgName, "version"], {
63807
63878
  encoding: "utf8",
63808
63879
  timeout: 20000,
63809
63880
  env: res.env
@@ -64087,6 +64158,16 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
64087
64158
  detail: `malformed config: ${bad.join(", ")}`,
64088
64159
  fix: `Repair the malformed config in ${bad[0]}`
64089
64160
  };
64161
+ }, branchPresetLine = (res, workspace) => {
64162
+ try {
64163
+ const resolved = resolveWorkspacePolicy(readConfigFromDir(res.configDir), workspace, undefined, () => repoBranchPreset(res.cwd));
64164
+ if (resolved.status === "invalid")
64165
+ return [];
64166
+ const { preset, detected } = resolved.branchPolicy;
64167
+ return [`preset: ${detected ? `detected (${preset})` : preset}`];
64168
+ } catch {
64169
+ return [];
64170
+ }
64090
64171
  }, checkWorkspaceMismatch = (res) => {
64091
64172
  const result = readWorkspacesResult(res.configDir);
64092
64173
  const file = result.path;
@@ -64094,7 +64175,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
64094
64175
  return {
64095
64176
  id: "workspace_mismatch",
64096
64177
  status: "pass",
64097
- detail: "no workspaces configured"
64178
+ detail: ["no workspaces configured", ...branchPresetLine(res, null)].join("; ")
64098
64179
  };
64099
64180
  if (result.status === "malformed" || result.status === "invalid")
64100
64181
  return {
@@ -64118,7 +64199,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
64118
64199
  const matched = `current directory matches workspace "${match.name}"`;
64119
64200
  const tracks = releaseTracksReport(res.cwd);
64120
64201
  const lines = describeReleaseTracks(tracks);
64121
- const detail = [matched, ...lines].join("; ");
64202
+ const detail = [matched, ...branchPresetLine(res, match), ...lines].join("; ");
64122
64203
  if (tracks.error)
64123
64204
  return {
64124
64205
  id: "workspace_mismatch",
@@ -64232,7 +64313,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
64232
64313
  }, defaultIdentityProbes = (env) => ({
64233
64314
  origin: (cwd) => {
64234
64315
  try {
64235
- const out = spawnSync11("git", ["remote", "get-url", "origin"], {
64316
+ const out = spawnSync12("git", ["remote", "get-url", "origin"], {
64236
64317
  cwd,
64237
64318
  encoding: "utf8",
64238
64319
  env
@@ -64244,7 +64325,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
64244
64325
  },
64245
64326
  ghLogin: (e) => {
64246
64327
  try {
64247
- const out = spawnSync11("gh", ["api", "user", "--jq", ".login"], { encoding: "utf8", env: e });
64328
+ const out = spawnSync12("gh", ["api", "user", "--jq", ".login"], { encoding: "utf8", env: e });
64248
64329
  const login = (out.stdout ?? "").trim();
64249
64330
  return out.status === 0 && login ? login : null;
64250
64331
  } catch {
@@ -64253,7 +64334,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
64253
64334
  },
64254
64335
  glabLogin: (e) => {
64255
64336
  try {
64256
- const out = spawnSync11("glab", ["api", "user", "--jq", ".username"], {
64337
+ const out = spawnSync12("glab", ["api", "user", "--jq", ".username"], {
64257
64338
  encoding: "utf8",
64258
64339
  env: e
64259
64340
  });
@@ -64265,7 +64346,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
64265
64346
  },
64266
64347
  sshLogin: (host) => {
64267
64348
  try {
64268
- const out = spawnSync11("ssh", ["-o", "BatchMode=yes", "-o", "ConnectTimeout=5", "-T", `git@${host}`], {
64349
+ const out = spawnSync12("ssh", ["-o", "BatchMode=yes", "-o", "ConnectTimeout=5", "-T", `git@${host}`], {
64269
64350
  encoding: "utf8",
64270
64351
  env
64271
64352
  });
@@ -64460,6 +64541,7 @@ var init_doctor = __esm(() => {
64460
64541
  init_runtime_identity();
64461
64542
  init_boundary();
64462
64543
  init_release_tracks();
64544
+ init_branch_policy();
64463
64545
  init_config();
64464
64546
  init_package_root();
64465
64547
  init_registration();
@@ -65473,69 +65555,6 @@ var init_setup = __esm(() => {
65473
65555
  SETUP_OVERRIDES = [...YT_OVERRIDES, ...VCS_OVERRIDES];
65474
65556
  });
65475
65557
 
65476
- // packages/workit-core/src/core/branch-policy.ts
65477
- import { spawnSync as spawnSync12 } from "node:child_process";
65478
- var PREFIXES, detectBranchPolicy = (workspaceRoot) => {
65479
- const branchExists = (name) => {
65480
- const r = spawnSync12("git", ["branch", "--list", name], {
65481
- cwd: workspaceRoot,
65482
- encoding: "utf8"
65483
- });
65484
- return r.status === 0 && (r.stdout ?? "").trim() !== "";
65485
- };
65486
- const develop = branchExists("develop");
65487
- const main = branchExists("main");
65488
- const master = branchExists("master");
65489
- const root = main ? "main" : master ? "master" : null;
65490
- if (develop) {
65491
- return {
65492
- preset: "gitflow",
65493
- developBranch: "develop",
65494
- integration: "merge",
65495
- protected: root ? [root, "develop"] : ["develop"],
65496
- allowed: [...PRESETS.gitflow.allowed],
65497
- prefixes: PREFIXES
65498
- };
65499
- }
65500
- if (root === "main") {
65501
- return {
65502
- preset: "github-flow",
65503
- developBranch: null,
65504
- integration: "pr",
65505
- protected: [root],
65506
- allowed: ["*"],
65507
- prefixes: PREFIXES
65508
- };
65509
- }
65510
- if (root === "master") {
65511
- return {
65512
- preset: "trunk-based",
65513
- developBranch: null,
65514
- integration: "pr",
65515
- protected: [root],
65516
- allowed: ["*"],
65517
- prefixes: PREFIXES
65518
- };
65519
- }
65520
- return {
65521
- preset: "gitflow",
65522
- developBranch: null,
65523
- integration: "merge",
65524
- protected: [],
65525
- allowed: [],
65526
- prefixes: PREFIXES
65527
- };
65528
- };
65529
- var init_branch_policy = __esm(() => {
65530
- init_config();
65531
- PREFIXES = {
65532
- feature: "feature/*",
65533
- bugfix: "bugfix/*",
65534
- release: "release/*",
65535
- hotfix: "hotfix/*"
65536
- };
65537
- });
65538
-
65539
65558
  // packages/workit-core/src/core/workspace-branch-policy.ts
65540
65559
  import { mkdirSync as mkdirSync6, readFileSync as readFileSync15, renameSync as renameSync4, rmSync as rmSync4, writeFileSync as writeFileSync8 } from "node:fs";
65541
65560
  import path31 from "node:path";
@@ -68624,7 +68643,7 @@ function Screen({
68624
68643
  };
68625
68644
  const checkout = resolveBasePath(draft.values);
68626
68645
  const workspace = resolveWorkspaceFromEntries(checkout, draft.values.workspaces);
68627
- const resolved = resolveWorkspacePolicy(config, workspace);
68646
+ const resolved = resolveWorkspacePolicy(config, workspace, undefined, () => repoBranchPreset(checkout));
68628
68647
  if (resolved.status === "invalid")
68629
68648
  return { error: resolved.error };
68630
68649
  return {
@@ -71425,6 +71444,46 @@ var success2 = (data) => ({ ok: true, data }), failure3 = (code, error, unblock)
71425
71444
 
71426
71445
  // packages/workit-core/src/git/ops.ts
71427
71446
  import { spawnSync as spawnSync16 } from "node:child_process";
71447
+ import { existsSync as existsSync18, readFileSync as readFileSync18 } from "node:fs";
71448
+ import path39 from "node:path";
71449
+ function operationInProgress(cwd) {
71450
+ const markers = [
71451
+ ["rebase-merge", "rebase"],
71452
+ ["rebase-apply", "rebase"],
71453
+ ["MERGE_HEAD", "merge"],
71454
+ ["CHERRY_PICK_HEAD", "cherry-pick"],
71455
+ ["REVERT_HEAD", "revert"]
71456
+ ];
71457
+ const run = git5(cwd, ["rev-parse", ...markers.flatMap(([name]) => ["--git-path", name])]);
71458
+ if (!run.ok)
71459
+ return null;
71460
+ const found = run.stdout.split(`
71461
+ `).map((line) => path39.resolve(cwd, line.trim()));
71462
+ const conflicted = () => git5(cwd, ["ls-files", "-u"]).stdout.trim() !== "";
71463
+ for (const [index, [, operation]] of markers.entries()) {
71464
+ const marker = found[index];
71465
+ if (!marker || !existsSync18(marker))
71466
+ continue;
71467
+ if (index === 1 && existsSync18(path39.join(marker, "applying")))
71468
+ return { operation: "am", commitAllowed: false, branch: null };
71469
+ if (index === 0 && existsSync18(path39.join(marker, "amend")) && !conflicted()) {
71470
+ let headName = "";
71471
+ try {
71472
+ headName = readFileSync18(path39.join(marker, "head-name"), "utf8").trim();
71473
+ } catch {}
71474
+ const branch = headName.startsWith("refs/heads/") ? headName.slice("refs/heads/".length) : null;
71475
+ return { operation, commitAllowed: branch !== null, branch };
71476
+ }
71477
+ if ((operation === "revert" || operation === "merge") && !conflicted())
71478
+ return { operation, commitAllowed: true, branch: null };
71479
+ return { operation, commitAllowed: false, branch: null };
71480
+ }
71481
+ return null;
71482
+ }
71483
+ function inProgressFailure(operation, verb) {
71484
+ const command = operation === "am" ? "git am" : `git ${operation}`;
71485
+ return failure3("blocked", `${operation.replace("-", "_")}_in_progress: a ${operation === "am" ? "git am" : operation} is in progress; finish or abort it before workit can ${verb}`, `resolve the conflicts, git add <files>, then ${command} --continue # or ${command} --abort to return to where you started`);
71486
+ }
71428
71487
  function dirtEntries(cwd) {
71429
71488
  const run = git5(cwd, ["status", "--porcelain=v1", "-z", "--untracked-files=all"]);
71430
71489
  if (!run.ok)
@@ -71444,6 +71503,9 @@ function dirtEntries(cwd) {
71444
71503
  function gitBranch(cwd, input) {
71445
71504
  if (!headSha(cwd) && currentBranch(cwd) === null)
71446
71505
  return failure3("invalid_input", "not inside a git repository with a commit");
71506
+ const progress = operationInProgress(cwd);
71507
+ if (progress)
71508
+ return inProgressFailure(progress.operation, "create a branch");
71447
71509
  const explicitBase = input.base?.trim() || null;
71448
71510
  const resolved = vcsConfig("resolve", cwd, {
71449
71511
  track: input.track ?? null,
@@ -71548,53 +71610,156 @@ function lintCommitMessage(cwd, message) {
71548
71610
  correction: `use a ${detected} commit subject`
71549
71611
  };
71550
71612
  }
71613
+ function messageTrailers(cwd, message) {
71614
+ const run = git5(cwd, [...TRAILER_CONFIG, "interpret-trailers", "--parse", "--no-divider"], {
71615
+ input: `${message}
71616
+ `
71617
+ });
71618
+ return run.ok ? run.stdout.split(`
71619
+ `).filter((line) => line.trim()) : [];
71620
+ }
71621
+ function withTrailers(cwd, message, trailers) {
71622
+ if (!trailers.length)
71623
+ return message;
71624
+ const run = git5(cwd, [
71625
+ ...TRAILER_CONFIG,
71626
+ "interpret-trailers",
71627
+ "--no-divider",
71628
+ "--if-exists",
71629
+ "addIfDifferent",
71630
+ ...trailers.flatMap((trailer) => ["--trailer", trailer])
71631
+ ], { input: `${message}
71632
+ ` });
71633
+ return run.ok ? run.stdout.trim() : `${message}
71634
+
71635
+ ${trailers.join(`
71636
+ `)}`;
71637
+ }
71638
+ function branchesContaining(cwd, sha) {
71639
+ const run = git5(cwd, [
71640
+ "for-each-ref",
71641
+ "--format=%(refname)",
71642
+ "--contains",
71643
+ sha,
71644
+ "refs/heads",
71645
+ "refs/remotes"
71646
+ ]);
71647
+ const names = new Set;
71648
+ for (const ref of run.ok ? run.stdout.split(`
71649
+ `) : []) {
71650
+ if (ref.startsWith("refs/heads/"))
71651
+ names.add(ref.slice("refs/heads/".length));
71652
+ else if (ref.startsWith("refs/remotes/")) {
71653
+ const name = ref.split("/").slice(3).join("/");
71654
+ if (name && name !== "HEAD")
71655
+ names.add(name);
71656
+ }
71657
+ }
71658
+ return names;
71659
+ }
71660
+ function sharedBranchContaining(cwd, sha, branch) {
71661
+ const resolved = vcsConfig("resolve", cwd);
71662
+ const defaults = new Set(resolved.ok === false ? [] : [resolved.defaultTargetBranch, resolved.baseBranch].map((value) => String(value ?? "")).filter(Boolean));
71663
+ for (const name of branchesContaining(cwd, sha))
71664
+ if (name !== branch && (defaults.has(name) || isProtectedTarget(cwd, name)))
71665
+ return name;
71666
+ return null;
71667
+ }
71551
71668
  function gitCommit(cwd, input) {
71552
- const message = input.message.trim();
71553
- if (!message)
71554
- return failure3("invalid_input", "a commit message is required (-m <msg>)");
71669
+ const given = input.message?.trim() ?? "";
71670
+ if (!given && !input.amend)
71671
+ return failure3("invalid_input", "a commit message is required (-m <msg> or -F <file>)");
71555
71672
  const paths = input.paths ?? [];
71556
71673
  if (input.all && paths.length)
71557
71674
  return failure3("invalid_input", "pass --all or paths, not both");
71558
- const branch = currentBranch(cwd);
71675
+ const progress = operationInProgress(cwd);
71676
+ if (progress && !progress.commitAllowed)
71677
+ return inProgressFailure(progress.operation, "commit");
71678
+ const branch = progress?.branch ?? currentBranch(cwd);
71559
71679
  if (!branch)
71560
71680
  return failure3("invalid_input", "HEAD is detached; commit on a branch (workit git branch <name>)");
71561
71681
  if (isProtectedTarget(cwd, branch))
71562
71682
  return failure3("blocked", `protected_branch: ${branch} is protected by the workspace branch policy; workit never commits to it`, "workit git branch <feature/name> # then commit there");
71563
- const lint = lintCommitMessage(cwd, message);
71564
- if (!lint.ok)
71565
- return failure3("blocked", lint.error, lint.correction);
71683
+ const previous = input.amend ? headSha(cwd) : null;
71684
+ if (input.amend && !previous)
71685
+ return failure3("invalid_input", `nothing to amend: ${branch} has no commit yet`);
71686
+ if (previous) {
71687
+ const shared = sharedBranchContaining(cwd, previous, branch);
71688
+ if (shared)
71689
+ return failure3("blocked", `amend_shared_commit: ${previous.slice(0, 12)} is already on ${shared}, so it is not ${branch}'s own commit; workit never rewrites it`, "workit git commit -m '<msg>' -- <paths> # add a new commit instead");
71690
+ }
71691
+ let message = given;
71692
+ let carried = [];
71693
+ if (given) {
71694
+ const lint = lintCommitMessage(cwd, given);
71695
+ if (!lint.ok)
71696
+ return failure3("blocked", lint.error, lint.correction);
71697
+ }
71698
+ if (previous) {
71699
+ const kept = git5(cwd, ["log", "-1", "--format=%B", previous]);
71700
+ if (!kept.ok)
71701
+ return failure3("failed", `could not read the message of ${branch}: ${gitError(kept)}`);
71702
+ if (given)
71703
+ carried = messageTrailers(cwd, kept.stdout.trim());
71704
+ else
71705
+ message = kept.stdout.trim();
71706
+ }
71566
71707
  const session = input.actor.session;
71567
71708
  if (session && !SESSION_SAFE.test(session))
71568
71709
  return failure3("invalid_input", "WORKIT_SESSION_ID must be 1-128 characters of [A-Za-z0-9_.:@/+-]");
71569
- const args = ["commit", "--no-edit", "-m", message];
71570
- if (session)
71571
- args.push("--trailer", `Workit-Session: ${session}`);
71710
+ const changeRequired = !input.amend && !input.allowEmpty;
71711
+ const emptyAmend = previous !== null && git5(cwd, ["rev-parse", "--verify", "-q", `${previous}^{tree}`]).stdout.trim() === git5(cwd, ["rev-parse", "--verify", "-q", `${previous}~1^{tree}`]).stdout.trim();
71712
+ const args = ["commit", "--no-edit"];
71713
+ if (input.amend)
71714
+ args.push("--amend");
71715
+ if (input.allowEmpty || emptyAmend)
71716
+ args.push("--allow-empty");
71717
+ args.push("-m", withTrailers(cwd, message, [...carried, ...session ? [`Workit-Session: ${session}`] : []]));
71572
71718
  if (paths.length) {
71573
71719
  if (paths.some((value) => value.startsWith("-") || !value.trim()))
71574
71720
  return failure3("invalid_input", "paths must not be empty or start with -");
71575
- const added = git5(cwd, ["add", "-A", "--", ...paths]);
71576
- if (!added.ok)
71577
- return failure3("failed", `git add failed: ${gitError(added)}`);
71721
+ const tracked = new Set(nulList(git5(cwd, ["ls-files", "-z", "--", ...paths]).stdout));
71722
+ const addable = paths.filter((value) => existsSync18(path39.resolve(cwd, value)) || [...tracked].some((file) => file === value || file.startsWith(`${value}/`)));
71723
+ if (addable.length) {
71724
+ const added = git5(cwd, ["add", "-A", "--", ...addable]);
71725
+ if (!added.ok)
71726
+ return failure3("failed", `git add failed: ${gitError(added)}`);
71727
+ }
71728
+ if (changeRequired && git5(cwd, ["diff", "--cached", "--quiet", "HEAD", "--", ...paths]).ok)
71729
+ return nothingToCommit(`${paths.join(", ")} has no change to commit`);
71578
71730
  args.push("--", ...paths);
71579
71731
  } else if (input.all) {
71580
71732
  const added = git5(cwd, ["add", "-A"]);
71581
71733
  if (!added.ok)
71582
71734
  return failure3("failed", `git add failed: ${gitError(added)}`);
71583
- } else {
71735
+ if (changeRequired && git5(cwd, ["diff", "--cached", "--quiet"]).ok)
71736
+ return nothingToCommit("the working tree is clean");
71737
+ } else if (changeRequired) {
71584
71738
  const staged = git5(cwd, ["diff", "--cached", "--name-only", "-z"]);
71585
71739
  if (staged.ok && !staged.stdout.replaceAll("\x00", "")) {
71586
71740
  const dirt = dirtEntries(cwd);
71587
- return failure3(dirt.length ? "blocked" : "failed", dirt.length ? `nothing_staged: nothing is staged and workit never commits unrelated changes implicitly (${dirt.length} changed: ${dirt.slice(0, 5).map((entry) => entry.slice(3)).join(", ")}${dirt.length > 5 ? ", …" : ""})` : "nothing_to_commit: the working tree is clean", dirt.length ? `workit git commit -m '<msg>' -- <paths> # or --all to commit every change` : undefined);
71741
+ if (!dirt.length)
71742
+ return nothingToCommit("the working tree is clean");
71743
+ return failure3("blocked", `nothing_staged: nothing is staged and workit never commits unrelated changes implicitly (${dirt.length} changed: ${dirt.slice(0, 5).map((entry) => entry.slice(3)).join(", ")}${dirt.length > 5 ? ", …" : ""})`, `workit git commit -m '<msg>' -- <paths> # or --all to commit every change`);
71588
71744
  }
71589
71745
  }
71590
- const committed = git5(cwd, args, { timeoutMs: COMMIT_TIMEOUT_MS, env: input.env });
71746
+ const committed = git5(cwd, args, {
71747
+ timeoutMs: COMMIT_TIMEOUT_MS,
71748
+ env: input.env
71749
+ });
71591
71750
  if (!committed.ok)
71592
- return failure3("failed", `git commit failed: ${gitError(committed, 6)}`);
71751
+ return failure3("failed", `git commit failed (a hook or git refused it):
71752
+ ${gitOutput(committed)}`);
71593
71753
  const sha = headSha(cwd);
71594
71754
  if (!sha)
71595
71755
  return failure3("failed", "the commit did not resolve");
71596
- const files = nulList(git5(cwd, ["diff-tree", "--root", "--no-commit-id", "--name-only", "-r", "-z", sha]).stdout);
71756
+ const merge = git5(cwd, ["rev-parse", "-q", "--verify", `${sha}^2`]).ok;
71757
+ const files = nulList(git5(cwd, merge ? ["diff", "--name-only", "-z", `${sha}^1`, sha] : ["diff-tree", "--root", "--no-commit-id", "--name-only", "-r", "-z", sha]).stdout);
71597
71758
  const leftDirty = dirtEntries(cwd).length;
71759
+ const notes = [];
71760
+ const upstream = `refs/remotes/${pushRemoteName(cwd, branch) ?? "origin"}/${branch}`;
71761
+ if (previous && git5(cwd, ["merge-base", "--is-ancestor", previous, upstream]).ok)
71762
+ notes.push(`the amended commit ${previous.slice(0, 12)} was already pushed (${upstream.slice("refs/remotes/".length)}); publish the rewrite with: workit git push --force-with-lease`);
71598
71763
  const listed = [];
71599
71764
  let size = 0;
71600
71765
  for (const file of files) {
@@ -71614,7 +71779,8 @@ function gitCommit(cwd, input) {
71614
71779
  subject: (message.split(`
71615
71780
  `, 1)[0] ?? "").slice(0, 200),
71616
71781
  files: listed,
71617
- fileCount: files.length
71782
+ fileCount: files.length,
71783
+ ...previous ? { amended: previous } : {}
71618
71784
  });
71619
71785
  return success2({
71620
71786
  sha,
@@ -71623,10 +71789,15 @@ function gitCommit(cwd, input) {
71623
71789
  files,
71624
71790
  leftDirty,
71625
71791
  session,
71792
+ amended: previous,
71793
+ notes,
71626
71794
  recorded: row.ok ? { id: String(row.value.id) } : { error: row.error }
71627
71795
  });
71628
71796
  }
71629
71797
  function pushPreflight(cwd, options = {}) {
71798
+ const progress = operationInProgress(cwd);
71799
+ if (progress)
71800
+ return inProgressFailure(progress.operation, "push");
71630
71801
  const branch = options.branch ?? currentBranch(cwd);
71631
71802
  if (!branch)
71632
71803
  return failure3("invalid_input", "HEAD is detached; push a branch");
@@ -71665,11 +71836,35 @@ function recordedRemoteTip(cwd, plan) {
71665
71836
  return;
71666
71837
  }
71667
71838
  function includesRemoteTip(cwd, branch, tip) {
71668
- if (git5(cwd, ["merge-base", "--is-ancestor", tip, `refs/heads/${branch}`]).ok)
71839
+ const ref = `refs/heads/${branch}`;
71840
+ if (git5(cwd, ["merge-base", "--is-ancestor", tip, ref]).ok)
71669
71841
  return true;
71670
- const reflog = git5(cwd, ["reflog", "show", "--format=%H", `refs/heads/${branch}`, "--"]);
71671
- return reflog.ok && reflog.stdout.split(`
71672
- `).some((line) => line.trim() === tip);
71842
+ const reflog = git5(cwd, ["reflog", "show", "--format=%H", ref, "--"]);
71843
+ const former = [
71844
+ ...new Set((reflog.ok ? reflog.stdout : "").split(`
71845
+ `).map((line) => line.trim()).filter(Boolean))
71846
+ ];
71847
+ if (former.includes(tip))
71848
+ return true;
71849
+ if (!former.length)
71850
+ return false;
71851
+ const outside = git5(cwd, ["rev-list", "-n", "1", "--stdin"], {
71852
+ input: `${tip}
71853
+ ${former.map((sha) => `^${sha}`).join(`
71854
+ `)}
71855
+ `
71856
+ });
71857
+ return outside.ok && !outside.stdout.trim();
71858
+ }
71859
+ function patchEquivalent(cwd, branch, tip) {
71860
+ const ref = `refs/heads/${branch}`;
71861
+ const merges = git5(cwd, ["rev-list", "--count", "--merges", `${ref}..${tip}`]);
71862
+ if (!merges.ok || merges.stdout.trim() !== "0")
71863
+ return false;
71864
+ const cherry = git5(cwd, ["cherry", ref, tip]);
71865
+ const lines = cherry.ok ? cherry.stdout.split(`
71866
+ `).filter(Boolean) : [];
71867
+ return lines.length > 0 && lines.every((line) => line.startsWith("-"));
71673
71868
  }
71674
71869
  function executePush(cwd, plan, input) {
71675
71870
  const before = remoteRefTip(cwd, plan.rawUrl, `refs/heads/${plan.branch}`);
@@ -71704,9 +71899,17 @@ ${run.stdout}`;
71704
71899
  return failure3("unavailable", `git push to ${plan.url} timed out`);
71705
71900
  if (/stale info/iu.test(text))
71706
71901
  return failure3("blocked", `lease_mismatch: ${plan.remote}/${plan.branch} moved while pushing`, `git fetch ${plan.remote} ${plan.branch} && git rebase ${plan.remote}/${plan.branch} # or merge it; then: workit git push`);
71902
+ const rejected = /non-fast-forward|fetch first|\[rejected\]/iu.test(text);
71903
+ const integrated = rejected && before.sha !== null && includesRemoteTip(cwd, plan.branch, before.sha);
71904
+ const equivalent = rejected && !integrated && before.sha !== null && patchEquivalent(cwd, plan.branch, before.sha);
71905
+ if (before.sha !== null && (integrated || equivalent)) {
71906
+ const lease = recordedRemoteTip(cwd, plan) === before.sha ? "workit git push --force-with-lease" : `workit git push --force-with-lease --expect ${before.sha}`;
71907
+ return failure3("failed", integrated ? `non_fast_forward: local ${plan.branch} was rewritten (amend or rebase) after ${plan.remote}/${plan.branch} (${before.sha.slice(0, 12)}) was pushed; that tip is already part of the local history, so do not fetch and rebase` : `non_fast_forward: every commit on ${plan.remote}/${plan.branch} (${before.sha.slice(0, 12)}) has a patch-equivalent commit in local ${plan.branch} (rebased elsewhere); fetching and rebasing would apply them twice`, integrated ? lease : `${lease} --overwrite-unintegrated # after checking ${plan.remote}/${plan.branch} holds nothing else`);
71908
+ }
71707
71909
  if (/non-fast-forward|fetch first|\[rejected\]/iu.test(text))
71708
71910
  return failure3("failed", `non_fast_forward: ${plan.remote}/${plan.branch} has commits this branch does not`, `git fetch ${plan.remote} && git rebase ${plan.remote}/${plan.branch} # or, if the rewrite is intended: workit git push --force-with-lease`);
71709
- return failure3("failed", `git push to ${plan.url} failed: ${gitError(run)}`);
71911
+ return failure3("failed", `git push to ${plan.url} failed:
71912
+ ${gitOutput(run)}`);
71710
71913
  }
71711
71914
  pushed = true;
71712
71915
  }
@@ -71765,7 +71968,11 @@ function deleteRemoteBranch(cwd, target, branch, expectedTip, options = {}) {
71765
71968
  return { ok: true };
71766
71969
  const text = `${run.stderr}
71767
71970
  ${run.stdout}`;
71768
- return /stale info|cannot lock ref|fetch first|non-fast-forward/iu.test(text) ? { ok: false, lease: true, error: "the remote branch tip changed before deletion" } : { ok: false, lease: false, error: gitError(run) };
71971
+ return /stale info|cannot lock ref|fetch first|non-fast-forward/iu.test(text) ? {
71972
+ ok: false,
71973
+ lease: true,
71974
+ error: "the remote branch tip changed before deletion"
71975
+ } : { ok: false, lease: false, error: gitError(run) };
71769
71976
  }
71770
71977
  var COMMIT_TIMEOUT_MS, PUSH_TIMEOUT_MS = 120000, git5 = (cwd, args, options = {}) => {
71771
71978
  const result = spawnSync16("git", args, {
@@ -71776,7 +71983,7 @@ var COMMIT_TIMEOUT_MS, PUSH_TIMEOUT_MS = 120000, git5 = (cwd, args, options = {}
71776
71983
  timeout: options.timeoutMs ?? GIT_TIMEOUTS.local,
71777
71984
  killSignal: "SIGKILL",
71778
71985
  windowsHide: true,
71779
- stdio: ["ignore", "pipe", "pipe"]
71986
+ ...options.input === undefined ? { stdio: ["ignore", "pipe", "pipe"] } : { input: options.input, stdio: ["pipe", "pipe", "pipe"] }
71780
71987
  });
71781
71988
  return {
71782
71989
  ok: result.status === 0,
@@ -71788,7 +71995,22 @@ var COMMIT_TIMEOUT_MS, PUSH_TIMEOUT_MS = 120000, git5 = (cwd, args, options = {}
71788
71995
  const lines = redactText(`${run.stderr}
71789
71996
  ${run.stdout ?? ""}`).split(/\r?\n/u).map((value) => value.trim()).filter((value) => value && !value.startsWith("hint:"));
71790
71997
  return lines.slice(-max).join(" / ").slice(0, 400) || "git failed";
71791
- }, validRefName = (cwd, name) => !name.startsWith("-") && git5(cwd, ["check-ref-format", "--branch", name]).ok, nulList = (text) => text.split("\x00").filter(Boolean), SESSION_SAFE, SHA;
71998
+ }, OUTPUT_LINES = 200, OUTPUT_LINE_CHARS = 1000, gitOutput = (run) => {
71999
+ const lines = redactText(`${run.stderr}
72000
+ ${run.stdout ?? ""}`).split(/\r?\n/u).map((value) => value.trimEnd()).filter((value) => value.trim() && !value.trimStart().startsWith("hint:")).map((value) => value.length > OUTPUT_LINE_CHARS ? `${value.slice(0, OUTPUT_LINE_CHARS)}…` : value);
72001
+ if (!lines.length)
72002
+ return "git failed";
72003
+ if (lines.length <= OUTPUT_LINES)
72004
+ return lines.join(`
72005
+ `);
72006
+ const half = OUTPUT_LINES / 2;
72007
+ return [
72008
+ ...lines.slice(0, half),
72009
+ `… ${lines.length - OUTPUT_LINES} lines omitted …`,
72010
+ ...lines.slice(-half)
72011
+ ].join(`
72012
+ `);
72013
+ }, validRefName = (cwd, name) => !name.startsWith("-") && git5(cwd, ["check-ref-format", "--branch", name]).ok, nulList = (text) => text.split("\x00").filter(Boolean), SESSION_SAFE, TRAILER_CONFIG, nothingToCommit = (what) => failure3("failed", `nothing_to_commit: ${what}`, "workit git commit --allow-empty -m '<msg>' # only to record an empty commit on purpose"), SHA;
71792
72014
  var init_ops = __esm(() => {
71793
72015
  init_branch();
71794
72016
  init_commit_flavors();
@@ -71800,15 +72022,16 @@ var init_ops = __esm(() => {
71800
72022
  init_rev();
71801
72023
  COMMIT_TIMEOUT_MS = 10 * 60000;
71802
72024
  SESSION_SAFE = /^[\w.:@/+-]{1,128}$/u;
72025
+ TRAILER_CONFIG = ["-c", "trailer.separators=:", "-c", "trailer.where=end"];
71803
72026
  SHA = /^[0-9a-f]{40,64}$/u;
71804
72027
  });
71805
72028
 
71806
72029
  // packages/workit-core/src/forge/reruns.ts
71807
72030
  import fs19 from "node:fs";
71808
- import path39 from "node:path";
72031
+ import path40 from "node:path";
71809
72032
  function rerunLogPath(cwd) {
71810
72033
  const common = gitCommonDir(cwd);
71811
- return common ? path39.join(common, "workit", "ci-reruns.jsonl") : null;
72034
+ return common ? path40.join(common, "workit", "ci-reruns.jsonl") : null;
71812
72035
  }
71813
72036
  function rerunCounts(cwd, key) {
71814
72037
  const counts = new Map;
@@ -71843,7 +72066,7 @@ function recordReruns(cwd, rows) {
71843
72066
  if (!file)
71844
72067
  return false;
71845
72068
  try {
71846
- fs19.mkdirSync(path39.dirname(file), { recursive: true });
72069
+ fs19.mkdirSync(path40.dirname(file), { recursive: true });
71847
72070
  fs19.appendFileSync(file, rows.map((row) => `${JSON.stringify(row)}
71848
72071
  `).join(""));
71849
72072
  return true;
@@ -71856,7 +72079,7 @@ function withRerunLock(cwd, fn) {
71856
72079
  if (!file)
71857
72080
  return fn();
71858
72081
  const lock = `${file.slice(0, -".jsonl".length)}.lock`;
71859
- fs19.mkdirSync(path39.dirname(lock), { recursive: true });
72082
+ fs19.mkdirSync(path40.dirname(lock), { recursive: true });
71860
72083
  const until = Date.now() + LOCK_WAIT_MS2;
71861
72084
  for (;; ) {
71862
72085
  try {
@@ -72564,13 +72787,13 @@ var init_pr_ops = __esm(() => {
72564
72787
  // packages/workit-core/src/forge/exec.ts
72565
72788
  import { spawnSync as spawnSync18 } from "node:child_process";
72566
72789
  import fs20 from "node:fs";
72567
- import path40 from "node:path";
72790
+ import path41 from "node:path";
72568
72791
  function findExecutable(bin, env) {
72569
- const dirs = (env.PATH ?? env.Path ?? "").split(path40.delimiter).filter(Boolean);
72792
+ const dirs = (env.PATH ?? env.Path ?? "").split(path41.delimiter).filter(Boolean);
72570
72793
  const exts = process.platform === "win32" ? [".exe"] : [""];
72571
72794
  for (const dir of dirs)
72572
72795
  for (const ext of exts) {
72573
- const candidate = path40.join(dir, `${bin}${ext}`);
72796
+ const candidate = path41.join(dir, `${bin}${ext}`);
72574
72797
  try {
72575
72798
  const stat = fs20.statSync(candidate);
72576
72799
  if (stat.isFile())
@@ -73559,8 +73782,8 @@ var exports_pr = {};
73559
73782
  __export(exports_pr, {
73560
73783
  run: () => run10
73561
73784
  });
73562
- import { readFileSync as readFileSync18 } from "node:fs";
73563
- import path41 from "node:path";
73785
+ import { readFileSync as readFileSync19 } from "node:fs";
73786
+ import path42 from "node:path";
73564
73787
  async function status(argv, io) {
73565
73788
  const flags = parseFlags(argv, { pr: "value", branch: "value", "log-lines": "value" });
73566
73789
  if (typeof flags === "string")
@@ -73631,7 +73854,7 @@ async function create4(argv, io) {
73631
73854
  let body = flags.values.body ?? null;
73632
73855
  if (flags.values["body-file"] !== undefined) {
73633
73856
  try {
73634
- body = readFileSync18(path41.resolve(io.cwd, flags.values["body-file"]), "utf8");
73857
+ body = readFileSync19(path42.resolve(io.cwd, flags.values["body-file"]), "utf8");
73635
73858
  } catch {
73636
73859
  return usage3(io, `cannot read --body-file ${flags.values["body-file"]}`, CREATE_USAGE);
73637
73860
  }
@@ -73976,6 +74199,8 @@ var exports_git = {};
73976
74199
  __export(exports_git, {
73977
74200
  run: () => run12
73978
74201
  });
74202
+ import { readFileSync as readFileSync20 } from "node:fs";
74203
+ import path43 from "node:path";
73979
74204
  async function branch(argv, io) {
73980
74205
  const flags = parseFlags(argv, {
73981
74206
  base: "value",
@@ -74012,7 +74237,15 @@ async function branch(argv, io) {
74012
74237
  ]);
74013
74238
  }
74014
74239
  function parseCommit(argv) {
74015
- const out = { messages: [], all: false, paths: [] };
74240
+ const out = {
74241
+ messages: [],
74242
+ files: [],
74243
+ all: false,
74244
+ amend: false,
74245
+ noEdit: false,
74246
+ allowEmpty: false,
74247
+ paths: []
74248
+ };
74016
74249
  for (let index = 0;index < argv.length; index += 1) {
74017
74250
  const arg = argv[index];
74018
74251
  if (arg === "--") {
@@ -74021,23 +74254,24 @@ function parseCommit(argv) {
74021
74254
  }
74022
74255
  if (arg === "--json")
74023
74256
  continue;
74024
- if (arg === "-a" || arg === "--all") {
74025
- out.all = true;
74026
- continue;
74027
- }
74028
- if (arg === "-m" || arg === "--message") {
74029
- const value = argv[++index];
74030
- if (value === undefined)
74031
- return `${arg} requires a message`;
74032
- out.messages.push(value);
74033
- continue;
74034
- }
74035
- if (arg.startsWith("--message=")) {
74036
- out.messages.push(arg.slice("--message=".length));
74257
+ const flag = COMMIT_SWITCHES[arg];
74258
+ if (flag) {
74259
+ out[flag] = true;
74037
74260
  continue;
74038
74261
  }
74039
- if (arg.startsWith("-m") && arg.length > 2) {
74040
- out.messages.push(arg.slice(2));
74262
+ const valued = [
74263
+ ["-m", "--message", out.messages],
74264
+ ["-F", "--file", out.files]
74265
+ ].find(([shortName, longName]) => arg === shortName || arg === longName || arg.startsWith(`${longName}=`) || arg.startsWith(shortName) && arg.length > 2);
74266
+ if (valued) {
74267
+ const [shortName, longName, into] = valued;
74268
+ if (arg === shortName || arg === longName) {
74269
+ const value = argv[++index];
74270
+ if (value === undefined)
74271
+ return `${arg} requires a ${shortName === "-m" ? "message" : "file"}`;
74272
+ into.push(value);
74273
+ } else
74274
+ into.push(arg.startsWith(`${longName}=`) ? arg.slice(longName.length + 1) : arg.slice(2));
74041
74275
  continue;
74042
74276
  }
74043
74277
  if (arg.startsWith("-"))
@@ -74046,20 +74280,48 @@ function parseCommit(argv) {
74046
74280
  }
74047
74281
  return out;
74048
74282
  }
74283
+ function commitMessage(parsed, io) {
74284
+ if (parsed.messages.length && parsed.files.length)
74285
+ return { error: "pass -m or -F, not both" };
74286
+ if (parsed.files.length > 1)
74287
+ return { error: "-F takes one file" };
74288
+ const [file] = parsed.files;
74289
+ if (file === undefined)
74290
+ return parsed.messages.join(`
74291
+
74292
+ `);
74293
+ try {
74294
+ return file === "-" ? readFileSync20(0, "utf8") : readFileSync20(path43.resolve(io.cwd, file), "utf8");
74295
+ } catch (error) {
74296
+ return {
74297
+ error: `cannot read -F ${file}: ${error instanceof Error ? error.message : String(error)}`
74298
+ };
74299
+ }
74300
+ }
74049
74301
  async function commit(argv, io) {
74050
74302
  const parsed = parseCommit(argv);
74051
74303
  if (typeof parsed === "string")
74052
74304
  return usage3(io, parsed, COMMIT_USAGE);
74053
- if (!parsed.messages.length)
74054
- return usage3(io, "missing -m <msg>", COMMIT_USAGE);
74055
74305
  if (parsed.all && parsed.paths.length)
74056
74306
  return usage3(io, "pass --all or paths, not both", COMMIT_USAGE);
74307
+ if (parsed.noEdit && !parsed.amend)
74308
+ return usage3(io, "--no-edit only applies with --amend", COMMIT_USAGE);
74309
+ const given = parsed.messages.length + parsed.files.length > 0;
74310
+ if (parsed.noEdit && given)
74311
+ return usage3(io, "--no-edit keeps the message; drop -m/-F or --no-edit", COMMIT_USAGE);
74312
+ if (!given && !parsed.noEdit)
74313
+ return usage3(io, parsed.amend ? "--amend needs -m/-F or --no-edit" : "missing -m <msg> or -F <file>", COMMIT_USAGE);
74314
+ const message = given ? commitMessage(parsed, io) : null;
74315
+ if (message !== null && typeof message !== "string")
74316
+ return usage3(io, message.error, COMMIT_USAGE);
74317
+ if (message !== null && !message.trim())
74318
+ return usage3(io, "the commit message is empty", COMMIT_USAGE);
74057
74319
  const result = gitCommit(io.cwd, {
74058
- message: parsed.messages.join(`
74059
-
74060
- `),
74320
+ message,
74061
74321
  all: parsed.all,
74062
74322
  paths: parsed.paths,
74323
+ amend: parsed.amend,
74324
+ allowEmpty: parsed.allowEmpty,
74063
74325
  actor: actorFromEnv(io.env),
74064
74326
  env: io.env
74065
74327
  });
@@ -74068,8 +74330,9 @@ async function commit(argv, io) {
74068
74330
  await ensureImplicitTask(io);
74069
74331
  return emit(io, ok(result.data), (data) => [
74070
74332
  `${data.branch} ${short2(data.sha)} ${data.message.split(`
74071
- `, 1)[0]} (${data.files.length} file${data.files.length === 1 ? "" : "s"})`,
74333
+ `, 1)[0]} (${data.files.length ? `${data.files.length} file${data.files.length === 1 ? "" : "s"}` : "empty commit"})${data.amended ? `, amends ${short2(data.amended)}` : ""}`,
74072
74334
  ...data.leftDirty ? [`${data.leftDirty} change(s) left uncommitted`] : [],
74335
+ ...data.notes.map((note) => `note: ${note}`),
74073
74336
  ..."error" in data.recorded ? [`ledger: not recorded (${data.recorded.error})`] : [],
74074
74337
  ...data.session ? [] : ["note: WORKIT_SESSION_ID is unset; this commit has no session author"]
74075
74338
  ]);
@@ -74103,7 +74366,10 @@ async function push(argv, io) {
74103
74366
  }
74104
74367
  const grant = requireGrant(io.cwd, "push", { forge: !plan.data.local });
74105
74368
  if (!grant.allowed)
74106
- return emit(io, fail("blocked", grant.error, { unblock: grant.unblock, data: { reason: grant.reason } }));
74369
+ return emit(io, fail("blocked", grant.error, {
74370
+ unblock: grant.unblock,
74371
+ data: { reason: grant.reason }
74372
+ }));
74107
74373
  const result = executePush(io.cwd, plan.data, {
74108
74374
  forceWithLease: flags.booleans.has("force-with-lease"),
74109
74375
  overwriteUnintegrated: flags.booleans.has("overwrite-unintegrated"),
@@ -74112,7 +74378,10 @@ async function push(argv, io) {
74112
74378
  actor: actorFromEnv(io.env)
74113
74379
  });
74114
74380
  if (!result.ok)
74115
- return forgeFail(io, result, { branch: plan.data.branch, sha: plan.data.sha });
74381
+ return forgeFail(io, result, {
74382
+ branch: plan.data.branch,
74383
+ sha: plan.data.sha
74384
+ });
74116
74385
  return emit(io, ok(result.data), (data) => [
74117
74386
  data.pushed ? `pushed ${data.branch} ${short2(data.previous)} -> ${short2(data.sha)} to ${data.remote}${data.forced ? " (force-with-lease)" : ""}; remote tip verified` : `${data.remote}/${data.branch} is already at ${short2(data.sha)}; remote tip verified`,
74118
74387
  ...data.upstream ? [`upstream: ${data.remote}/${data.branch}`] : []
@@ -74128,7 +74397,7 @@ async function run12(argv, io) {
74128
74397
  return push(rest, io);
74129
74398
  return usage3(io, sub && !sub.startsWith("-") ? `unknown git subcommand "${sub}"` : "missing git subcommand", USAGE5);
74130
74399
  }
74131
- var BRANCH_USAGE = "workit git branch <name> | --kind feature|bugfix|hotfix --slug <s> [--base <b>] [--track <t>] [--carry] [--json]", COMMIT_USAGE = "workit git commit -m <msg> [--all | [--] <paths…>] [--json]", PUSH_USAGE = "workit git push [--set-upstream] [--force-with-lease [--expect <sha>] [--overwrite-unintegrated]] [--json]", USAGE5 = "workit git branch|commit|push ... (workit help git)", short2 = (sha) => sha ? sha.slice(0, 12) : "(none)";
74400
+ var BRANCH_USAGE = "workit git branch <name> | --kind feature|bugfix|hotfix --slug <s> [--base <b>] [--track <t>] [--carry] [--json]", COMMIT_USAGE = "workit git commit -m|--message <msg>… | -F|--file <file|-> [--amend [--no-edit]] [--allow-empty] [-a|--all | [--] <paths…>] [--json]", PUSH_USAGE = "workit git push [--set-upstream] [--force-with-lease [--expect <sha>] [--overwrite-unintegrated]] [--json]", USAGE5 = "workit git branch|commit|push ... (workit help git)", short2 = (sha) => sha ? sha.slice(0, 12) : "(none)", COMMIT_SWITCHES;
74132
74401
  var init_git2 = __esm(() => {
74133
74402
  init_autonomy();
74134
74403
  init_ops();
@@ -74136,6 +74405,13 @@ var init_git2 = __esm(() => {
74136
74405
  init_implicit_task();
74137
74406
  init_output();
74138
74407
  init_forge_common();
74408
+ COMMIT_SWITCHES = {
74409
+ "-a": "all",
74410
+ "--all": "all",
74411
+ "--amend": "amend",
74412
+ "--no-edit": "noEdit",
74413
+ "--allow-empty": "allowEmpty"
74414
+ };
74139
74415
  });
74140
74416
 
74141
74417
  // packages/workit-core/src/forge/verify.ts
@@ -74462,7 +74738,7 @@ var init_verify_delivery = __esm(() => {
74462
74738
  import { spawnSync as spawnSync20 } from "node:child_process";
74463
74739
  import { createHash as createHash15, randomBytes as randomBytes3 } from "node:crypto";
74464
74740
  import fs22 from "node:fs";
74465
- import path42 from "node:path";
74741
+ import path44 from "node:path";
74466
74742
  function worktreeOf(cwd, branch) {
74467
74743
  const list = gitRun3(cwd, ["worktree", "list", "--porcelain"]);
74468
74744
  if (!list.ok)
@@ -74506,7 +74782,7 @@ function gitRestack(cwd, input) {
74506
74782
  if (!scratch)
74507
74783
  return { ok: false, conflict: false, worktree: cwd, error: "no store root" };
74508
74784
  fs22.mkdirSync(scratch, { recursive: true });
74509
- dir = path42.join(scratch, `${input.branch.replace(/[^A-Za-z0-9._-]+/gu, "_").slice(0, 40)}-${randomBytes3(4).toString("hex")}`);
74785
+ dir = path44.join(scratch, `${input.branch.replace(/[^A-Za-z0-9._-]+/gu, "_").slice(0, 40)}-${randomBytes3(4).toString("hex")}`);
74510
74786
  const added = gitRun3(cwd, ["worktree", "add", "-q", dir, input.branch], GIT_TIMEOUTS.worktree);
74511
74787
  if (!added.ok)
74512
74788
  return { ok: false, conflict: false, worktree: cwd, error: firstLine(added) };
@@ -74547,7 +74823,7 @@ function pruneScratch(cwd) {
74547
74823
  if (!scratch || !fs22.existsSync(scratch))
74548
74824
  return;
74549
74825
  for (const name of fs22.readdirSync(scratch)) {
74550
- const dir = path42.join(scratch, name);
74826
+ const dir = path44.join(scratch, name);
74551
74827
  if (!rebaseInProgress(dir) && !trackedDirt(dir))
74552
74828
  gitRun3(cwd, ["worktree", "remove", "--force", dir]);
74553
74829
  }
@@ -74557,7 +74833,7 @@ function stacksDir(cwd) {
74557
74833
  const root = storeRoot(cwd);
74558
74834
  if (!root.ok)
74559
74835
  return stackFail(root.code, root.error, root.unblock);
74560
- return { ok: true, data: path42.join(root.value.root, "stacks") };
74836
+ return { ok: true, data: path44.join(root.value.root, "stacks") };
74561
74837
  }
74562
74838
  function parseStack(raw) {
74563
74839
  if (!isRecord7(raw) || !Array.isArray(raw.branches))
@@ -74609,7 +74885,7 @@ function readStack(cwd, name) {
74609
74885
  const dir = stacksDir(cwd);
74610
74886
  if (!dir.ok)
74611
74887
  return dir;
74612
- const file = path42.join(dir.data, fileFor(name));
74888
+ const file = path44.join(dir.data, fileFor(name));
74613
74889
  let text;
74614
74890
  try {
74615
74891
  text = fs22.readFileSync(file, "utf8");
@@ -74630,7 +74906,7 @@ function writeStack(cwd, stack, now = new Date) {
74630
74906
  const dir = stacksDir(cwd);
74631
74907
  if (!dir.ok)
74632
74908
  return dir;
74633
- const file = path42.join(dir.data, fileFor(stack.name));
74909
+ const file = path44.join(dir.data, fileFor(stack.name));
74634
74910
  const temp = `${file}.${process.pid}.tmp`;
74635
74911
  try {
74636
74912
  fs22.mkdirSync(dir.data, { recursive: true });
@@ -74658,7 +74934,7 @@ function listStacks(cwd) {
74658
74934
  for (const name of names) {
74659
74935
  let parsed = null;
74660
74936
  try {
74661
- parsed = parseStack(JSON.parse(fs22.readFileSync(path42.join(dir.data, name), "utf8")));
74937
+ parsed = parseStack(JSON.parse(fs22.readFileSync(path44.join(dir.data, name), "utf8")));
74662
74938
  } catch {
74663
74939
  parsed = null;
74664
74940
  }
@@ -74717,7 +74993,7 @@ function takeLock(lock) {
74717
74993
  host: localLockHost(),
74718
74994
  nonce: randomBytes3(8).toString("hex")
74719
74995
  };
74720
- fs22.writeFileSync(path42.join(lock, OWNER), JSON.stringify(owner));
74996
+ fs22.writeFileSync(path44.join(lock, OWNER), JSON.stringify(owner));
74721
74997
  return owner;
74722
74998
  }
74723
74999
  function reapLock(lock, observed) {
@@ -74754,7 +75030,7 @@ async function withStackLock(cwd, name, fn, waitMs = LOCK_WAIT_MS3) {
74754
75030
  return dir;
74755
75031
  if (!validName(name))
74756
75032
  return stackFail("invalid_input", "a stack name must be 1-200 printable characters");
74757
- const lock = path42.join(dir.data, `${fileFor(name)}.lock`);
75033
+ const lock = path44.join(dir.data, `${fileFor(name)}.lock`);
74758
75034
  try {
74759
75035
  fs22.mkdirSync(dir.data, { recursive: true });
74760
75036
  } catch (error) {
@@ -74784,7 +75060,7 @@ async function withStackLock(cwd, name, fn, waitMs = LOCK_WAIT_MS3) {
74784
75060
  const touch = () => {
74785
75061
  try {
74786
75062
  const now = new Date;
74787
- fs22.utimesSync(path42.join(lock, OWNER), now, now);
75063
+ fs22.utimesSync(path44.join(lock, OWNER), now, now);
74788
75064
  } catch {}
74789
75065
  };
74790
75066
  const beat = setInterval(touch, HEARTBEAT_MS);
@@ -75624,7 +75900,7 @@ var STACK_VERSION = 1, stackFail = (code, error, unblock, data) => ({
75624
75900
  return !status.ok || status.stdout.trim() !== "";
75625
75901
  }, scratchDir = (cwd) => {
75626
75902
  const dir = stacksDir(cwd);
75627
- return dir.ok ? path42.join(dir.data, "worktrees") : null;
75903
+ return dir.ok ? path44.join(dir.data, "worktrees") : null;
75628
75904
  }, firstLine = (run) => (`${run.stderr}
75629
75905
  ${run.stdout}`.split(`
75630
75906
  `).find((line) => line.trim()) ?? "git failed").trim().slice(0, 300), stackFileName = (name) => `${name.replace(/[^A-Za-z0-9._-]+/gu, "_").replace(/^[._]+/u, "").slice(0, 48).toLowerCase()}-${createHash15("sha256").update(name).digest("hex").slice(0, 12)}.json`, fileFor, validName = (name) => {
@@ -75639,12 +75915,12 @@ ${run.stdout}`.split(`
75639
75915
  }, isRecord7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), str3 = (value) => typeof value === "string" && value ? value : null, LOCK_WAIT_MS3 = 2000, HEARTBEAT_MS = 20000, HEARTBEAT_STALE_MS, OWNERLESS_STALE_MS = 30000, OWNER = "owner", readOwner = (lock) => {
75640
75916
  let raw = null;
75641
75917
  try {
75642
- raw = fs22.readFileSync(path42.join(lock, OWNER), "utf8");
75918
+ raw = fs22.readFileSync(path44.join(lock, OWNER), "utf8");
75643
75919
  } catch {
75644
75920
  raw = null;
75645
75921
  }
75646
75922
  let age = null;
75647
- for (const target of [path42.join(lock, OWNER), lock])
75923
+ for (const target of [path44.join(lock, OWNER), lock])
75648
75924
  try {
75649
75925
  age = Date.now() - fs22.statSync(target).mtimeMs;
75650
75926
  break;
@@ -75894,7 +76170,7 @@ var init_stack2 = __esm(() => {
75894
76170
  import { spawnSync as spawnSync21 } from "node:child_process";
75895
76171
  import { createHash as createHash16 } from "node:crypto";
75896
76172
  import fs23 from "node:fs";
75897
- import path43 from "node:path";
76173
+ import path45 from "node:path";
75898
76174
  function expandBraces(pattern, limit = 64) {
75899
76175
  for (let index = 0;index < pattern.length; index += 1) {
75900
76176
  if (pattern[index] === "\\") {
@@ -76128,7 +76404,7 @@ function parsePlan(raw, defaults, name) {
76128
76404
  id,
76129
76405
  branch,
76130
76406
  base: optional("base") ?? "",
76131
- worktree: path43.resolve(defaults.root, optional("worktree") ?? path43.join(defaults.worktreeRoot, id)),
76407
+ worktree: path45.resolve(defaults.root, optional("worktree") ?? path45.join(defaults.worktreeRoot, id)),
76132
76408
  tier,
76133
76409
  dependsOn: [...new Set(dependsOn ?? [])],
76134
76410
  scope,
@@ -76357,11 +76633,11 @@ function defaultTrunk(cwd) {
76357
76633
  }
76358
76634
  function mainCheckout(cwd) {
76359
76635
  const common = gitCommonDir(cwd);
76360
- return common && path43.basename(common) === ".git" ? path43.dirname(common) : path43.resolve(cwd);
76636
+ return common && path45.basename(common) === ".git" ? path45.dirname(common) : path45.resolve(cwd);
76361
76637
  }
76362
76638
  function worktreeRoot(cwd) {
76363
76639
  const main = mainCheckout(cwd);
76364
- return path43.join(path43.dirname(main), `${path43.basename(main)}-wt`);
76640
+ return path45.join(path45.dirname(main), `${path45.basename(main)}-wt`);
76365
76641
  }
76366
76642
  function sliceHash(slice) {
76367
76643
  const {
@@ -76379,7 +76655,7 @@ function fanoutsDir(cwd) {
76379
76655
  const root = storeRoot(cwd);
76380
76656
  if (!root.ok)
76381
76657
  return fanoutFail(root.code, root.error, root.unblock);
76382
- return { ok: true, data: path43.join(root.value.root, "fanouts") };
76658
+ return { ok: true, data: path45.join(root.value.root, "fanouts") };
76383
76659
  }
76384
76660
  function parseStored(raw) {
76385
76661
  if (!isRecord8(raw) || typeof raw.name !== "string" || typeof raw.trunk !== "string")
@@ -76445,7 +76721,7 @@ function readFanout(cwd, name) {
76445
76721
  const dir = fanoutsDir(cwd);
76446
76722
  if (!dir.ok)
76447
76723
  return dir;
76448
- const file = path43.join(dir.data, stackFileName(name));
76724
+ const file = path45.join(dir.data, stackFileName(name));
76449
76725
  let parsed = null;
76450
76726
  try {
76451
76727
  parsed = parseStored(JSON.parse(fs23.readFileSync(file, "utf8")));
@@ -76470,7 +76746,7 @@ function listFanouts(cwd) {
76470
76746
  for (const entry of names) {
76471
76747
  let parsed = null;
76472
76748
  try {
76473
- parsed = parseStored(JSON.parse(fs23.readFileSync(path43.join(dir.data, entry), "utf8")));
76749
+ parsed = parseStored(JSON.parse(fs23.readFileSync(path45.join(dir.data, entry), "utf8")));
76474
76750
  } catch {
76475
76751
  parsed = null;
76476
76752
  }
@@ -76500,7 +76776,7 @@ function writeFanout(cwd, plan) {
76500
76776
  const dir = fanoutsDir(cwd);
76501
76777
  if (!dir.ok)
76502
76778
  return dir;
76503
- const file = path43.join(dir.data, stackFileName(plan.name));
76779
+ const file = path45.join(dir.data, stackFileName(plan.name));
76504
76780
  const temp = `${file}.${process.pid}.tmp`;
76505
76781
  try {
76506
76782
  fs23.mkdirSync(dir.data, { recursive: true });
@@ -76704,7 +76980,7 @@ var init_fanout = __esm(() => {
76704
76980
  // packages/workit-core/src/fanout-worktree.ts
76705
76981
  import { randomBytes as randomBytes4 } from "node:crypto";
76706
76982
  import fs24 from "node:fs";
76707
- import path44 from "node:path";
76983
+ import path46 from "node:path";
76708
76984
  function rowSample(lines) {
76709
76985
  const out = [];
76710
76986
  let bytes = 0;
@@ -76764,7 +77040,7 @@ function adminDir(worktree) {
76764
77040
  const file = dir.stdout.trim();
76765
77041
  let token = null;
76766
77042
  try {
76767
- token = fs24.readFileSync(path44.join(file, TOKEN_FILE), "utf8").trim() || null;
77043
+ token = fs24.readFileSync(path46.join(file, TOKEN_FILE), "utf8").trim() || null;
76768
77044
  } catch {
76769
77045
  token = null;
76770
77046
  }
@@ -76772,7 +77048,7 @@ function adminDir(worktree) {
76772
77048
  const stat = worktreeDeps.stat(file);
76773
77049
  return {
76774
77050
  path: file,
76775
- id: path44.basename(file),
77051
+ id: path46.basename(file),
76776
77052
  ino: stat.ino.toString(),
76777
77053
  token,
76778
77054
  birthMs: stat.birthtimeNs > 0n ? Number(stat.birthtimeMs) : null,
@@ -76788,7 +77064,7 @@ function markAdminDir(worktree) {
76788
77064
  return null;
76789
77065
  const token = randomBytes4(16).toString("hex");
76790
77066
  try {
76791
- fs24.writeFileSync(path44.join(admin.path, TOKEN_FILE), `${token}
77067
+ fs24.writeFileSync(path46.join(admin.path, TOKEN_FILE), `${token}
76792
77068
  `);
76793
77069
  } catch {
76794
77070
  return null;
@@ -76803,7 +77079,7 @@ function excludeScratch(cwd) {
76803
77079
  const common = gitCommonDir(cwd);
76804
77080
  if (!common)
76805
77081
  return;
76806
- const file = path44.join(common, "info", "exclude");
77082
+ const file = path46.join(common, "info", "exclude");
76807
77083
  let text = "";
76808
77084
  try {
76809
77085
  text = fs24.readFileSync(file, "utf8");
@@ -76812,7 +77088,7 @@ function excludeScratch(cwd) {
76812
77088
  }
76813
77089
  if (text.split(/\r?\n/u).includes(EXCLUDE_LINE))
76814
77090
  return;
76815
- fs24.mkdirSync(path44.dirname(file), { recursive: true });
77091
+ fs24.mkdirSync(path46.dirname(file), { recursive: true });
76816
77092
  fs24.appendFileSync(file, `${text && !text.endsWith(`
76817
77093
  `) ? `
76818
77094
  ` : ""}# workit fanout: per-worker scratch dirs
@@ -76820,7 +77096,7 @@ ${EXCLUDE_LINE}
76820
77096
  `);
76821
77097
  }
76822
77098
  function ensureScratch(worktree) {
76823
- const scratch = path44.join(worktree, SCRATCH_DIR);
77099
+ const scratch = path46.join(worktree, SCRATCH_DIR);
76824
77100
  fs24.mkdirSync(scratch, { recursive: true });
76825
77101
  return scratch;
76826
77102
  }
@@ -77038,7 +77314,7 @@ function unpushedCount(cwd, branch) {
77038
77314
  return count.ok ? Number(count.stdout.trim()) : null;
77039
77315
  }
77040
77316
  function removeScratch(worktree) {
77041
- const scratch = path44.join(worktree, SCRATCH_DIR);
77317
+ const scratch = path46.join(worktree, SCRATCH_DIR);
77042
77318
  let stat;
77043
77319
  try {
77044
77320
  stat = fs24.lstatSync(scratch);
@@ -77054,16 +77330,16 @@ var SLOW, SCRATCH_DIR = ".workit-scratch", EXCLUDE_LINE, DIRTY_ROW_BYTES = 2000,
77054
77330
  try {
77055
77331
  return fs24.realpathSync.native(target);
77056
77332
  } catch {
77057
- return path44.resolve(target);
77333
+ return path46.resolve(target);
77058
77334
  }
77059
77335
  }, samePath = (a, b) => {
77060
77336
  const left = canonical2(a);
77061
77337
  const right = canonical2(b);
77062
77338
  return process.platform === "win32" || process.platform === "darwin" ? left.toLowerCase() === right.toLowerCase() : left === right;
77063
77339
  }, inside2 = (child, parent) => {
77064
- const relative = path44.relative(canonical2(parent), canonical2(child));
77065
- return relative === "" || !relative.startsWith("..") && !path44.isAbsolute(relative);
77066
- }, slicePath = (cwd, slice) => path44.isAbsolute(slice.worktree) ? slice.worktree : path44.resolve(mainCheckout(cwd), slice.worktree), CLOCK_SLACK_MS = 2000, TOKEN_FILE = "workit-fanout-token", worktreeDeps, worktreeFailed = (stderr) => fanoutFail("failed", `git worktree add failed: ${stderr.trim().split(`
77340
+ const relative = path46.relative(canonical2(parent), canonical2(child));
77341
+ return relative === "" || !relative.startsWith("..") && !path46.isAbsolute(relative);
77342
+ }, slicePath = (cwd, slice) => path46.isAbsolute(slice.worktree) ? slice.worktree : path46.resolve(mainCheckout(cwd), slice.worktree), CLOCK_SLACK_MS = 2000, TOKEN_FILE = "workit-fanout-token", worktreeDeps, worktreeFailed = (stderr) => fanoutFail("failed", `git worktree add failed: ${stderr.trim().split(`
77067
77343
  `)[0] || "unknown error"}`);
77068
77344
  var init_fanout_worktree = __esm(() => {
77069
77345
  init_ops();
@@ -77839,7 +78115,7 @@ __export(exports_fanout, {
77839
78115
  run: () => run15
77840
78116
  });
77841
78117
  import fs25 from "node:fs";
77842
- import path45 from "node:path";
78118
+ import path47 from "node:path";
77843
78119
  function forgeFor(io, offline) {
77844
78120
  if (offline)
77845
78121
  return { forge: null, error: "--offline" };
@@ -77900,7 +78176,7 @@ function plan2(argv, io) {
77900
78176
  return usage3(io, "pass --trunk or --track, not both (--track picks the trunk)", PLAN_USAGE2);
77901
78177
  if (flags.positionals.length !== 1)
77902
78178
  return usage3(io, flags.positionals.length ? `unexpected argument ${flags.positionals[1]}` : "missing <plan.json>", PLAN_USAGE2);
77903
- const file = path45.resolve(io.cwd, flags.positionals[0]);
78179
+ const file = path47.resolve(io.cwd, flags.positionals[0]);
77904
78180
  let raw;
77905
78181
  try {
77906
78182
  raw = JSON.parse(fs25.readFileSync(file, "utf8"));
@@ -93332,7 +93608,7 @@ var init_ast = __esm(() => {
93332
93608
 
93333
93609
  // packages/workit-cli/src/test-audit/rules.ts
93334
93610
  import { builtinModules } from "node:module";
93335
- import path46 from "node:path";
93611
+ import path48 from "node:path";
93336
93612
  function testCallee(call) {
93337
93613
  let callee = call.callee;
93338
93614
  if (callee.type === "CallExpression")
@@ -93825,12 +94101,12 @@ function checkConstantsOnly(ctx, emit) {
93825
94101
  }
93826
94102
  function checkOverMocking(ctx, emit) {
93827
94103
  const { program } = ctx.parsed;
93828
- const unit = path46.basename(ctx.file).replace(/\.(?:[cm]?[jt]sx?)$/, "").replace(/\.(?:test|spec)$/, "");
94104
+ const unit = path48.basename(ctx.file).replace(/\.(?:[cm]?[jt]sx?)$/, "").replace(/\.(?:test|spec)$/, "");
93829
94105
  const mocked = find2(program, (node) => node.type === "CallExpression" && MODULE_MOCK.test(calleeName(node.callee) ?? "") && node.arguments[0]?.type === "StringLiteral");
93830
94106
  const internal = mocked.filter((call) => /^(?:\.|@\/|~\/)/.test(call.arguments[0].value));
93831
94107
  for (const call of mocked) {
93832
94108
  const spec = call.arguments[0].value;
93833
- const base = path46.basename(spec.replace(/\/index(?:\.[cm]?[jt]sx?)?$/, "")).replace(/\.(?:[cm]?[jt]sx?)$/, "");
94109
+ const base = path48.basename(spec.replace(/\/index(?:\.[cm]?[jt]sx?)?$/, "")).replace(/\.(?:[cm]?[jt]sx?)$/, "");
93834
94110
  if (base === unit)
93835
94111
  emit({
93836
94112
  rule: "over-mocking",
@@ -94219,8 +94495,8 @@ var init_rules = __esm(() => {
94219
94495
 
94220
94496
  // packages/workit-cli/src/test-audit/audit.ts
94221
94497
  import { spawnSync as spawnSync23 } from "node:child_process";
94222
- import { existsSync as existsSync18, readdirSync as readdirSync8, readFileSync as readFileSync19, statSync as statSync7 } from "node:fs";
94223
- import path47 from "node:path";
94498
+ import { existsSync as existsSync19, readdirSync as readdirSync8, readFileSync as readFileSync21, statSync as statSync7 } from "node:fs";
94499
+ import path49 from "node:path";
94224
94500
  function git6(cwd, args) {
94225
94501
  const run = spawnSync23("git", ["-c", "core.quotepath=off", ...args], {
94226
94502
  cwd,
@@ -94233,7 +94509,7 @@ function walkTests(dir, out) {
94233
94509
  for (const entry of readdirSync8(dir, { withFileTypes: true })) {
94234
94510
  if (entry.name.startsWith(".") && entry.name !== "." && entry.isDirectory())
94235
94511
  continue;
94236
- const full = path47.join(dir, entry.name);
94512
+ const full = path49.join(dir, entry.name);
94237
94513
  if (entry.isDirectory()) {
94238
94514
  if (!SKIP_DIRS.has(entry.name))
94239
94515
  walkTests(full, out);
@@ -94245,8 +94521,8 @@ function expandPaths(cwd, inputs) {
94245
94521
  const files = [];
94246
94522
  const missing = [];
94247
94523
  for (const input of inputs.length > 0 ? inputs : ["."]) {
94248
- const full = path47.resolve(cwd, input);
94249
- if (!existsSync18(full))
94524
+ const full = path49.resolve(cwd, input);
94525
+ if (!existsSync19(full))
94250
94526
  missing.push(input);
94251
94527
  else if (statSync7(full).isDirectory())
94252
94528
  walkTests(full, files);
@@ -94282,11 +94558,11 @@ function changedLines(cwd, base) {
94282
94558
  for (let offset = 0;offset < count; offset += 1)
94283
94559
  lines.add(start + offset);
94284
94560
  }
94285
- changed.set(path47.join(root, file), lines);
94561
+ changed.set(path49.join(root, file), lines);
94286
94562
  }
94287
94563
  const untracked = git6(root, ["ls-files", "--others", "--exclude-standard", "-z"]);
94288
94564
  for (const file of untracked.stdout.split("\x00").filter(Boolean))
94289
- changed.set(path47.join(root, file), "all");
94565
+ changed.set(path49.join(root, file), "all");
94290
94566
  return changed;
94291
94567
  }
94292
94568
  function auditFiles(files, display) {
@@ -94299,7 +94575,7 @@ function auditFiles(files, display) {
94299
94575
  const shown = display(file);
94300
94576
  let parsed;
94301
94577
  try {
94302
- parsed = parseSource(readFileSync19(file, "utf8"), file);
94578
+ parsed = parseSource(readFileSync21(file, "utf8"), file);
94303
94579
  } catch (error) {
94304
94580
  parseErrors.push({
94305
94581
  file: shown,
@@ -94348,7 +94624,7 @@ function auditFiles(files, display) {
94348
94624
  durationMs: Math.round(performance.now() - started)
94349
94625
  };
94350
94626
  }
94351
- var TEST_FILE, CODE_FILE, SKIP_DIRS, toPosix = (file) => file.split(path47.sep).join("/"), SEVERITY_ORDER, ruleIds;
94627
+ var TEST_FILE, CODE_FILE, SKIP_DIRS, toPosix = (file) => file.split(path49.sep).join("/"), SEVERITY_ORDER, ruleIds;
94352
94628
  var init_audit = __esm(() => {
94353
94629
  init_ast();
94354
94630
  init_rules();
@@ -94362,12 +94638,12 @@ var init_audit = __esm(() => {
94362
94638
  // packages/workit-cli/src/test-audit/mutate.ts
94363
94639
  import { spawn as spawn2 } from "node:child_process";
94364
94640
  import {
94365
- existsSync as existsSync19,
94641
+ existsSync as existsSync20,
94366
94642
  lstatSync as lstatSync7,
94367
94643
  mkdirSync as mkdirSync9,
94368
94644
  mkdtempSync as mkdtempSync4,
94369
94645
  readdirSync as readdirSync9,
94370
- readFileSync as readFileSync20,
94646
+ readFileSync as readFileSync22,
94371
94647
  readlinkSync as readlinkSync3,
94372
94648
  realpathSync as realpathSync9,
94373
94649
  renameSync as renameSync5,
@@ -94378,7 +94654,7 @@ import {
94378
94654
  copyFileSync as copyFileSync6
94379
94655
  } from "node:fs";
94380
94656
  import os14 from "node:os";
94381
- import path48 from "node:path";
94657
+ import path50 from "node:path";
94382
94658
  function mutantsFor(source, file, lines) {
94383
94659
  const parsed = parseSource(source, file);
94384
94660
  const out = [];
@@ -94451,7 +94727,7 @@ function relatedTests(source, testFiles) {
94451
94727
  for (const match of text.matchAll(SPECIFIER)) {
94452
94728
  const spec = match[1];
94453
94729
  if (spec.startsWith(".")) {
94454
- if (stripExt(path48.posix.join(path48.posix.dirname(file), spec)) === target)
94730
+ if (stripExt(path50.posix.join(path50.posix.dirname(file), spec)) === target)
94455
94731
  return true;
94456
94732
  } else if (spec.includes("/") && stripExt(spec).split("/").slice(-2).join("/") === tail)
94457
94733
  return true;
@@ -94471,12 +94747,12 @@ function removeStaleCopies(tmp = os14.tmpdir()) {
94471
94747
  for (const name of names) {
94472
94748
  if (!name.startsWith(TEMP_PREFIX))
94473
94749
  continue;
94474
- const dir = path48.join(tmp, name);
94750
+ const dir = path50.join(tmp, name);
94475
94751
  try {
94476
94752
  const stat = lstatSync7(dir);
94477
94753
  if (!stat.isDirectory() || uid !== undefined && stat.uid !== uid)
94478
94754
  continue;
94479
- const pid = Number(readFileSync20(path48.join(dir, PID_FILE), "utf8"));
94755
+ const pid = Number(readFileSync22(path50.join(dir, PID_FILE), "utf8"));
94480
94756
  if (Number.isInteger(pid) && pid > 0 && alive(pid))
94481
94757
  continue;
94482
94758
  } catch (error) {
@@ -94493,8 +94769,8 @@ function removeStaleCopies(tmp = os14.tmpdir()) {
94493
94769
  function linkNodeModules(origin, target, repo, copy) {
94494
94770
  mkdirSync9(target, { recursive: true });
94495
94771
  for (const entry of readdirSync9(origin, { withFileTypes: true })) {
94496
- const from = path48.join(origin, entry.name);
94497
- const to = path48.join(target, entry.name);
94772
+ const from = path50.join(origin, entry.name);
94773
+ const to = path50.join(target, entry.name);
94498
94774
  if (entry.name.startsWith("@") && entry.isDirectory()) {
94499
94775
  linkNodeModules(from, to, repo, copy);
94500
94776
  continue;
@@ -94510,9 +94786,9 @@ function linkNodeModules(origin, target, repo, copy) {
94510
94786
  } catch {
94511
94787
  continue;
94512
94788
  }
94513
- const relative = path48.relative(repo, real);
94514
- if (!relative.startsWith("..") && !path48.isAbsolute(relative) && !relative.split(path48.sep).includes("node_modules")) {
94515
- symlinkSync(path48.join(copy, relative), to, "junction");
94789
+ const relative = path50.relative(repo, real);
94790
+ if (!relative.startsWith("..") && !path50.isAbsolute(relative) && !relative.split(path50.sep).includes("node_modules")) {
94791
+ symlinkSync(path50.join(copy, relative), to, "junction");
94516
94792
  continue;
94517
94793
  }
94518
94794
  }
@@ -94528,7 +94804,7 @@ function copySymlink(repo, tree, from, to) {
94528
94804
  }
94529
94805
  const realRepo = realpathSync9(repo);
94530
94806
  if (real && inside3(realRepo, real)) {
94531
- symlinkSync(path48.relative(path48.dirname(to), path48.join(tree, path48.relative(realRepo, real))), to);
94807
+ symlinkSync(path50.relative(path50.dirname(to), path50.join(tree, path50.relative(realRepo, real))), to);
94532
94808
  return;
94533
94809
  }
94534
94810
  if (real && statSync8(real).isFile())
@@ -94537,10 +94813,10 @@ function copySymlink(repo, tree, from, to) {
94537
94813
  symlinkSync(readlinkSync3(from), to);
94538
94814
  }
94539
94815
  function writeInside(copy, file, content) {
94540
- const target = path48.join(copy, file);
94816
+ const target = path50.join(copy, file);
94541
94817
  try {
94542
94818
  const stat = lstatSync7(target);
94543
- if (!stat.isFile() || !inside3(realpathSync9(copy), realpathSync9(path48.dirname(target))))
94819
+ if (!stat.isFile() || !inside3(realpathSync9(copy), realpathSync9(path50.dirname(target))))
94544
94820
  return false;
94545
94821
  } catch {
94546
94822
  return false;
@@ -94551,9 +94827,9 @@ function writeInside(copy, file, content) {
94551
94827
  return true;
94552
94828
  }
94553
94829
  function copyWorkingTree(repo) {
94554
- const dir = mkdtempSync4(path48.join(os14.tmpdir(), TEMP_PREFIX));
94555
- writeFileSync10(path48.join(dir, PID_FILE), String(process.pid));
94556
- const tree = path48.join(dir, "tree");
94830
+ const dir = mkdtempSync4(path50.join(os14.tmpdir(), TEMP_PREFIX));
94831
+ writeFileSync10(path50.join(dir, PID_FILE), String(process.pid));
94832
+ const tree = path50.join(dir, "tree");
94557
94833
  const head = git6(repo, ["rev-parse", "--verify", "--quiet", "HEAD"]).stdout.trim();
94558
94834
  if (head && git6(dir, ["clone", "-q", "--shared", "--no-checkout", repo, tree]).ok) {
94559
94835
  const branch = git6(repo, ["symbolic-ref", "--quiet", "--short", "HEAD"]).stdout.trim();
@@ -94570,29 +94846,29 @@ function copyWorkingTree(repo) {
94570
94846
  for (const relative of listed) {
94571
94847
  if (!relative)
94572
94848
  continue;
94573
- const from = path48.join(repo, relative);
94849
+ const from = path50.join(repo, relative);
94574
94850
  let stat;
94575
94851
  try {
94576
94852
  stat = lstatSync7(from);
94577
94853
  } catch {
94578
94854
  continue;
94579
94855
  }
94580
- const to = path48.join(tree, relative);
94581
- mkdirSync9(path48.dirname(to), { recursive: true });
94856
+ const to = path50.join(tree, relative);
94857
+ mkdirSync9(path50.dirname(to), { recursive: true });
94582
94858
  if (stat.isSymbolicLink())
94583
94859
  copySymlink(repo, tree, from, to);
94584
94860
  else if (stat.isFile())
94585
94861
  copyFileSync6(from, to);
94586
- let parent = path48.dirname(relative);
94862
+ let parent = path50.dirname(relative);
94587
94863
  while (parent !== "." && !dirs.has(parent)) {
94588
94864
  dirs.add(parent);
94589
- parent = path48.dirname(parent);
94865
+ parent = path50.dirname(parent);
94590
94866
  }
94591
94867
  }
94592
94868
  for (const parent of dirs) {
94593
- const modules = path48.join(repo, parent, "node_modules");
94594
- if (existsSync19(modules) && !existsSync19(path48.join(tree, parent, "node_modules")))
94595
- linkNodeModules(modules, path48.join(tree, parent, "node_modules"), repo, tree);
94869
+ const modules = path50.join(repo, parent, "node_modules");
94870
+ if (existsSync20(modules) && !existsSync20(path50.join(tree, parent, "node_modules")))
94871
+ linkNodeModules(modules, path50.join(tree, parent, "node_modules"), repo, tree);
94596
94872
  }
94597
94873
  return { dir, tree };
94598
94874
  }
@@ -94631,20 +94907,20 @@ async function runMutation(options) {
94631
94907
  const tracked = git6(repo, ["ls-files", "-co", "--exclude-standard", "-z"]).stdout.split("\x00").filter(Boolean);
94632
94908
  const testFiles = tracked.filter((file) => TEST_FILE.test(file)).map((file) => {
94633
94909
  try {
94634
- return { file, text: readFileSync20(path48.join(repo, file), "utf8") };
94910
+ return { file, text: readFileSync22(path50.join(repo, file), "utf8") };
94635
94911
  } catch {
94636
94912
  return { file, text: "" };
94637
94913
  }
94638
94914
  });
94639
94915
  const candidates = [];
94640
94916
  for (const [absolute, lines] of [...options.changed].toSorted(([a], [b]) => a.localeCompare(b))) {
94641
- const relative = toPosix(path48.relative(repo, absolute));
94917
+ const relative = toPosix(path50.relative(repo, absolute));
94642
94918
  if (!CODE_FILE.test(relative) || TEST_FILE.test(relative) || relative.endsWith(".d.ts"))
94643
94919
  continue;
94644
- if (NOT_SOURCE.test(relative) || !existsSync19(absolute))
94920
+ if (NOT_SOURCE.test(relative) || !existsSync20(absolute))
94645
94921
  continue;
94646
94922
  try {
94647
- candidates.push(...mutantsFor(readFileSync20(absolute, "utf8"), relative, lines));
94923
+ candidates.push(...mutantsFor(readFileSync22(absolute, "utf8"), relative, lines));
94648
94924
  } catch {}
94649
94925
  }
94650
94926
  const mutants = candidates.map((mutant, index) => ({ ...mutant, id: index + 1 }));
@@ -94652,9 +94928,9 @@ async function runMutation(options) {
94652
94928
  for (const mutant of mutants)
94653
94929
  if (!related.has(mutant.file))
94654
94930
  related.set(mutant.file, relatedTests(mutant.file, testFiles));
94655
- const cwdRelative = path48.relative(repo, options.cwd);
94931
+ const cwdRelative = path50.relative(repo, options.cwd);
94656
94932
  const asArg = (file) => {
94657
- const relative = toPosix(path48.relative(cwdRelative, file));
94933
+ const relative = toPosix(path50.relative(cwdRelative, file));
94658
94934
  return shellQuote(relative.startsWith(".") ? relative : `./${relative}`);
94659
94935
  };
94660
94936
  const commandFor = (tests) => options.command.replaceAll("{files}", () => tests.map(asArg).join(" "));
@@ -94689,7 +94965,7 @@ async function runMutation(options) {
94689
94965
  return finish2(options, 0, results, counts, started);
94690
94966
  removeStaleCopies();
94691
94967
  const { dir: copyDir, tree: copy } = copyWorkingTree(repo);
94692
- const copyCwd = path48.join(copy, cwdRelative);
94968
+ const copyCwd = path50.join(copy, cwdRelative);
94693
94969
  let current = null;
94694
94970
  const restore = () => {
94695
94971
  if (current)
@@ -94724,7 +95000,7 @@ async function runMutation(options) {
94724
95000
  record({ ...mutant, status: "skipped", durationMs: 0, tests, reason: "budget" });
94725
95001
  continue;
94726
95002
  }
94727
- const original = readFileSync20(path48.join(copy, mutant.file), "utf8");
95003
+ const original = readFileSync22(path50.join(copy, mutant.file), "utf8");
94728
95004
  const mutated = original.slice(0, mutant.start) + mutant.replacement + original.slice(mutant.end);
94729
95005
  current = { file: mutant.file, original };
94730
95006
  if (!writeInside(copy, mutant.file, mutated)) {
@@ -94781,8 +95057,8 @@ var NOT_SOURCE, FLIPS, isStringy = (node) => node.type === "StringLiteral" || no
94781
95057
  return error.code === "EPERM";
94782
95058
  }
94783
95059
  }, inside3 = (root, file) => {
94784
- const relative = path48.relative(root, file);
94785
- return relative === "" || !relative.startsWith("..") && !path48.isAbsolute(relative);
95060
+ const relative = path50.relative(root, file);
95061
+ return relative === "" || !relative.startsWith("..") && !path50.isAbsolute(relative);
94786
95062
  }, active, killTree = (child) => {
94787
95063
  try {
94788
95064
  if (child.pid && process.platform !== "win32")
@@ -94830,8 +95106,8 @@ var exports_test_audit = {};
94830
95106
  __export(exports_test_audit, {
94831
95107
  run: () => run17
94832
95108
  });
94833
- import { existsSync as existsSync20 } from "node:fs";
94834
- import path49 from "node:path";
95109
+ import { existsSync as existsSync21 } from "node:fs";
95110
+ import path51 from "node:path";
94835
95111
  function parse7(argv) {
94836
95112
  const options = {
94837
95113
  paths: [],
@@ -94935,8 +95211,8 @@ async function run17(argv, io) {
94935
95211
  return emit(io, fail("invalid_input", `${options}
94936
95212
  usage: ${USAGE10}`));
94937
95213
  const top = git6(io.cwd, ["rev-parse", "--show-toplevel"]);
94938
- const repo = top.ok ? path49.resolve(top.stdout.trim()) : null;
94939
- const display = (file) => toPosix(path49.relative(io.cwd, file) || file);
95214
+ const repo = top.ok ? path51.resolve(top.stdout.trim()) : null;
95215
+ const display = (file) => toPosix(path51.relative(io.cwd, file) || file);
94940
95216
  let base = null;
94941
95217
  let changed = null;
94942
95218
  if (options.diff || options.mutate) {
@@ -94953,7 +95229,7 @@ usage: ${USAGE10}`));
94953
95229
  }
94954
95230
  let files;
94955
95231
  if (options.diff && changed)
94956
- files = [...changed.keys()].filter((file) => TEST_FILE.test(toPosix(file)) && existsSync20(file)).toSorted();
95232
+ files = [...changed.keys()].filter((file) => TEST_FILE.test(toPosix(file)) && existsSync21(file)).toSorted();
94957
95233
  else {
94958
95234
  const expanded = expandPaths(io.cwd, options.paths);
94959
95235
  if (expanded.missing.length > 0)
@@ -95178,8 +95454,8 @@ var exports_youtrack = {};
95178
95454
  __export(exports_youtrack, {
95179
95455
  run: () => run19
95180
95456
  });
95181
- import { readFileSync as readFileSync21 } from "node:fs";
95182
- import path50 from "node:path";
95457
+ import { readFileSync as readFileSync23 } from "node:fs";
95458
+ import path52 from "node:path";
95183
95459
  async function run19(argv, io) {
95184
95460
  const [sub, ...rest] = argv;
95185
95461
  if (sub !== "note" && sub !== "time" && sub !== "meeting")
@@ -95225,7 +95501,7 @@ async function run19(argv, io) {
95225
95501
  let markdown = flags.values.markdown ?? "";
95226
95502
  if (flags.values.file)
95227
95503
  try {
95228
- markdown = readFileSync21(path50.resolve(io.cwd, flags.values.file), "utf8");
95504
+ markdown = readFileSync23(path52.resolve(io.cwd, flags.values.file), "utf8");
95229
95505
  } catch (error) {
95230
95506
  return usage3(io, `cannot read ${flags.values.file}: ${String(error)}`, USAGE12);
95231
95507
  }
@@ -95293,8 +95569,8 @@ var exports_changelog = {};
95293
95569
  __export(exports_changelog, {
95294
95570
  run: () => run20
95295
95571
  });
95296
- import { readFileSync as readFileSync22 } from "node:fs";
95297
- import path51 from "node:path";
95572
+ import { readFileSync as readFileSync24 } from "node:fs";
95573
+ import path53 from "node:path";
95298
95574
  async function run20(argv, io) {
95299
95575
  const [sub, ...rest] = argv;
95300
95576
  if (sub !== "apply")
@@ -95340,7 +95616,7 @@ async function run20(argv, io) {
95340
95616
  ]);
95341
95617
  }
95342
95618
  var USAGE13 = "workit changelog apply (--entries <JSON|@file|-> | --normalize-only) [--path CHANGELOG.md] [--preview]", readEntries = (raw, cwd) => {
95343
- const text = raw === "-" ? readFileSync22(0, "utf8") : raw.startsWith("@") ? readFileSync22(path51.resolve(cwd, raw.slice(1)), "utf8") : raw;
95619
+ const text = raw === "-" ? readFileSync24(0, "utf8") : raw.startsWith("@") ? readFileSync24(path53.resolve(cwd, raw.slice(1)), "utf8") : raw;
95344
95620
  return JSON.parse(text);
95345
95621
  };
95346
95622
  var init_changelog2 = __esm(() => {
@@ -95436,7 +95712,7 @@ var init_handoff = __esm(() => {
95436
95712
 
95437
95713
  // packages/workit-cli/src/main.ts
95438
95714
  init_package();
95439
- import path52 from "node:path";
95715
+ import path54 from "node:path";
95440
95716
 
95441
95717
  // packages/workit-cli/src/help.ts
95442
95718
  var HELP_FLAGS = new Set(["--help", "-h"]);
@@ -95764,8 +96040,8 @@ var VERBS = [
95764
96040
  },
95765
96041
  {
95766
96042
  name: "commit",
95767
- usage: "workit git commit -m|--message <msg>… [-a|--all | [--] <paths…>]",
95768
- summary: "Commit with a linted Conventional Commit message and the session trailer"
96043
+ usage: "workit git commit -m|--message <msg>… | -F|--file <file|-> [--amend [--no-edit]] [--allow-empty] [-a|--all | [--] <paths…>]",
96044
+ summary: "Commit (or --amend) with a linted Conventional Commit message and the session trailer"
95769
96045
  },
95770
96046
  {
95771
96047
  name: "push",
@@ -95776,6 +96052,7 @@ var VERBS = [
95776
96052
  examples: [
95777
96053
  "workit git branch --kind feature --slug help-everywhere",
95778
96054
  'workit git commit -m "fix(cli): answer help flags first" --all',
96055
+ "workit git commit --amend --no-edit -- src/forgotten.ts",
95779
96056
  "workit git push --set-upstream"
95780
96057
  ],
95781
96058
  load: () => Promise.resolve().then(() => (init_git2(), exports_git))
@@ -96056,7 +96333,7 @@ var defaultIo = (json) => ({
96056
96333
  });
96057
96334
  async function main(argv, overrides = {}, options = {}) {
96058
96335
  const parsed = parseGlobals(argv);
96059
- const cwd = path52.resolve(overrides.cwd ?? process.cwd(), parsed.cwd ?? ".");
96336
+ const cwd = path54.resolve(overrides.cwd ?? process.cwd(), parsed.cwd ?? ".");
96060
96337
  const io = { ...defaultIo(parsed.json), ...overrides, json: parsed.json, cwd };
96061
96338
  if (parsed.error)
96062
96339
  return emit(io, fail("invalid_input", parsed.error));