@brainervirus/workit-cursor 8.13.0 → 8.13.1

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.
@@ -8561,242 +8561,666 @@ function pushRemoteName(cwd, branch) {
8561
8561
  }
8562
8562
 
8563
8563
  // packages/workit-core/src/ledger.ts
8564
- import { spawnSync as spawnSync7 } from "node:child_process";
8565
- import { createHash as createHash7, randomBytes } from "node:crypto";
8566
- import fs5 from "node:fs";
8564
+ import { spawnSync as spawnSync8 } from "node:child_process";
8565
+ import { createHash as createHash7, randomBytes as randomBytes2 } from "node:crypto";
8566
+ import fs6 from "node:fs";
8567
8567
  import path8 from "node:path";
8568
8568
 
8569
- // packages/workit-core/src/core/vcs-config.ts
8570
- import fs2 from "node:fs";
8571
- import path4 from "node:path";
8572
- import { spawnSync as spawnSync5 } from "node:child_process";
8569
+ // packages/workit-core/src/core/store-lock.ts
8570
+ import { spawnSync as spawnSync3 } from "node:child_process";
8571
+ import fs3 from "node:fs";
8572
+ import { hostname } from "node:os";
8573
8573
 
8574
- // packages/workit-core/src/core/config.ts
8575
- import {
8576
- copyFileSync,
8577
- cpSync,
8578
- existsSync,
8579
- mkdirSync,
8580
- readFileSync,
8581
- readdirSync,
8582
- writeFileSync
8583
- } from "node:fs";
8584
- import os2 from "node:os";
8574
+ // packages/workit-core/src/store/paths.ts
8575
+ import { spawnSync as spawnSync2 } from "node:child_process";
8576
+ import { createHash as createHash3 } from "node:crypto";
8577
+ import fs2 from "node:fs";
8585
8578
  import path2 from "node:path";
8586
-
8587
- // packages/workit-core/src/core/boundary.ts
8588
- var EVENT = {
8589
- initialization: "initialization",
8590
- provenance: "provenance",
8591
- assets: "assets",
8592
- configurationSource: "configuration_source",
8593
- hooks: "hooks",
8594
- mcpConnection: "mcp_connection",
8595
- migration: "migration",
8596
- installSteps: "install_steps",
8597
- uncaughtFailure: "uncaught_failure",
8598
- toolsFailed: "tools_failed",
8599
- doctor: "doctor"
8600
- };
8601
- var errorDetail = (err) => {
8602
- if (err instanceof Error) {
8603
- return { error_name: err.name, error: err.message };
8604
- }
8605
- return { error_name: "unknown", error: String(err) };
8606
- };
8607
-
8608
- // packages/workit-core/src/core/config.ts
8609
- var diagnosticLogger;
8610
- var PRESETS = {
8611
- gitflow: {
8612
- allowed: ["feature/*", "bugfix/*", "hotfix/*", "release/*"],
8613
- protected: ["main", "develop", "master", "prod", "production"]
8614
- },
8615
- "github-flow": { allowed: ["*"], protected: ["main"] },
8616
- "trunk-based": { allowed: ["*"], protected: ["main"] },
8617
- custom: { allowed: [], protected: [] }
8618
- };
8619
- var mergePreset = (preset, input = {}, current = {
8620
- branchPolicy: { preset, allowed: [], protected: [] }
8621
- }) => {
8622
- const defs = PRESETS[preset];
8579
+ var NOT_A_REPO = /not a git repository \(or any (of the parent directories|parent up to)/u;
8580
+ var gitRun = (cwd, args) => {
8581
+ const run = spawnSync2("git", args, {
8582
+ cwd,
8583
+ encoding: "utf8",
8584
+ timeout: 20000,
8585
+ killSignal: "SIGKILL",
8586
+ windowsHide: true,
8587
+ env: {
8588
+ ...process.env,
8589
+ GIT_TERMINAL_PROMPT: "0",
8590
+ GIT_OPTIONAL_LOCKS: "0",
8591
+ LC_ALL: "C",
8592
+ LANGUAGE: "C"
8593
+ }
8594
+ });
8595
+ const error = run.error;
8623
8596
  return {
8624
- preset,
8625
- allowed: preset === "custom" ? input.allowed ?? current.branchPolicy.allowed : [...defs.allowed],
8626
- protected: preset === "custom" ? input.protectedNames ?? current.branchPolicy.protected : [...defs.protected]
8597
+ ok: run.status === 0,
8598
+ stdout: run.stdout ?? "",
8599
+ stderr: error ? String(error.message) : run.stderr ?? "",
8600
+ missing: error?.code === "ENOENT"
8627
8601
  };
8628
8602
  };
8629
- var resolveConfigDir = () => process.env.WORKFLOW_TOOLKIT_CONFIG ?? process.env.WORKFLOW_TOOLKIT_CONFIG_DIR ?? path2.join(process.env.XDG_CONFIG_HOME || path2.join(os2.homedir(), ".config"), "workit");
8630
- var migratedDir = null;
8631
- var migrationFailed = false;
8632
- var ensureConfigDir = (dir = resolveConfigDir()) => {
8633
- if (migratedDir === dir)
8634
- return dir;
8635
- if (process.env.WORKFLOW_TOOLKIT_CONFIG || process.env.WORKFLOW_TOOLKIT_CONFIG_DIR) {
8636
- migratedDir = dir;
8637
- return dir;
8638
- }
8639
- const legacy = path2.join(process.env.XDG_CONFIG_HOME || path2.join(os2.homedir(), ".config"), "workflow-toolkit");
8640
- if (!existsSync(legacy)) {
8641
- migratedDir = dir;
8642
- return dir;
8643
- }
8644
- if (!migrationFailed && existsSync(dir)) {
8645
- migratedDir = dir;
8646
- return dir;
8647
- }
8648
- migrationFailed = false;
8649
- mkdirSync(dir, { recursive: true });
8650
- diagnosticLogger?.info(EVENT.migration, { from: legacy, to: dir });
8651
- for (const entry of readdirSync(legacy, { withFileTypes: true })) {
8652
- const src = path2.join(legacy, entry.name);
8653
- const dest = path2.join(dir, entry.name);
8654
- if (existsSync(dest))
8655
- continue;
8656
- try {
8657
- if (entry.isDirectory())
8658
- cpSync(src, dest, { recursive: true });
8659
- else if (entry.isFile())
8660
- copyFileSync(src, dest);
8661
- } catch (err) {
8662
- migrationFailed = true;
8663
- diagnosticLogger?.warn(EVENT.migration, { from: src, ok: false, ...errorDetail(err) });
8664
- console.warn(`[workit] config migration: a file could not be copied to ${dir}; it will be retried on the next run`);
8665
- }
8666
- }
8667
- if (!migrationFailed)
8668
- migratedDir = dir;
8669
- return dir;
8670
- };
8671
- var configDir = () => ensureConfigDir();
8672
- var LOCALE_RE = /^[a-z]{2,3}(-(?:[A-Z]{2}|[0-9]{3}))?$/;
8673
- var DEFAULTS = {
8674
- locale: "en",
8675
- localeOptions: ["en", "es-CL", "es-MX", "es-AR", "pt-BR"],
8676
- branchPolicy: {
8677
- preset: "gitflow",
8678
- allowed: [...PRESETS.gitflow.allowed],
8679
- protected: [...PRESETS.gitflow.protected]
8680
- },
8681
- commitPolicy: { preset: "conventional" }
8682
- };
8683
- var unchosenBranchPolicy = new WeakSet([DEFAULTS]);
8684
- var readSafe = (p) => {
8603
+ var shortHash = (value) => createHash3("sha256").update(value).digest("hex").slice(0, 12);
8604
+ var real = (file) => {
8685
8605
  try {
8686
- return readFileSync(p, "utf8");
8606
+ return fs2.realpathSync.native(file);
8687
8607
  } catch {
8688
- return null;
8608
+ return file;
8689
8609
  }
8690
8610
  };
8691
- var isConfigObject = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
8692
- var parseConfigResult = (raw, file) => {
8693
- if (raw === null)
8694
- return { status: "missing", path: file };
8695
- let parsed;
8611
+ var DISCOVERY_ENV = [
8612
+ "GIT_DIR",
8613
+ "GIT_COMMON_DIR",
8614
+ "GIT_WORK_TREE",
8615
+ "GIT_CEILING_DIRECTORIES",
8616
+ "GIT_DISCOVERY_ACROSS_FILESYSTEM"
8617
+ ];
8618
+ var isGitDir = (dir) => {
8696
8619
  try {
8697
- parsed = JSON.parse(raw);
8620
+ return fs2.statSync(path2.join(dir, "HEAD")).isFile() && fs2.statSync(path2.join(dir, "objects")).isDirectory() && fs2.statSync(path2.join(dir, "refs")).isDirectory();
8698
8621
  } catch {
8699
- return { status: "malformed", path: file, error: `${file} is not valid JSON` };
8700
- }
8701
- if (!isConfigObject(parsed)) {
8702
- return { status: "malformed", path: file, error: `${file} is not a JSON object` };
8622
+ return false;
8703
8623
  }
8704
- const input = parsed;
8705
- const locale = LOCALE_RE.test(String(input.locale ?? "")) ? input.locale : DEFAULTS.locale;
8706
- const preset = Object.hasOwn(PRESETS, input.branchPolicy?.preset) ? input.branchPolicy?.preset : "gitflow";
8707
- const commitPreset = COMMIT_PRESETS.includes(String(input.commitPolicy?.preset ?? "")) ? input.commitPolicy?.preset : "conventional";
8708
- const config = {
8709
- locale,
8710
- localeOptions: Array.isArray(input.localeOptions) ? input.localeOptions : DEFAULTS.localeOptions,
8711
- branchPolicy: mergePreset(preset, {
8712
- allowed: Array.isArray(input.branchPolicy?.allowed) ? input.branchPolicy.allowed : undefined,
8713
- protectedNames: Array.isArray(input.branchPolicy?.protected) ? input.branchPolicy.protected : undefined
8714
- }, DEFAULTS),
8715
- commitPolicy: {
8716
- preset: commitPreset,
8717
- ...typeof input.commitPolicy?.pattern === "string" ? { pattern: input.commitPolicy.pattern } : {}
8718
- }
8719
- };
8720
- if (!Object.hasOwn(PRESETS, input.branchPolicy?.preset))
8721
- unchosenBranchPolicy.add(config);
8722
- return { status: "valid", path: file, config };
8723
- };
8724
- var readConfigTyped = (dir) => {
8725
- const file = path2.join(dir ?? configDir(), "config.json");
8726
- return parseConfigResult(readSafe(file), file);
8727
- };
8728
- var readConfig = () => {
8729
- const result = readConfigTyped();
8730
- if (result.status === "malformed")
8731
- throw new Error(result.error);
8732
- return result.config ?? DEFAULTS;
8733
- };
8734
- var vcsConfigPath = () => process.env.WORKFLOW_VCS_CONFIG ?? path2.join(configDir(), "vcs.json");
8735
- var branchGlobPattern = (glob) => new RegExp(`^${glob.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*")}$`, "i");
8736
- var resolveBranchPolicy = (config, workspace, repoPreset) => {
8737
- const wp = workspace?.branchPolicy ?? {};
8738
- const workspacePreset = Object.hasOwn(PRESETS, wp.preset);
8739
- const detected = !workspacePreset && unchosenBranchPolicy.has(config) ? repoPreset?.() ?? null : null;
8740
- const preset = workspacePreset ? wp.preset : detected ?? config.branchPolicy?.preset ?? "gitflow";
8741
- const merged = mergePreset(preset, {
8742
- allowed: wp.allowed,
8743
- protectedNames: wp.protected
8744
- }, config);
8745
- const allowed = merged.allowed.map(branchGlobPattern);
8746
- return {
8747
- preset,
8748
- allowed,
8749
- protected: new Set([...merged.protected, ...detected ? PRESETS.gitflow.protected : []].map((p) => p.toLowerCase())),
8750
- integration: wp.integration === "merge" ? "merge" : "pr",
8751
- defaultTargetBranch: preset === "github-flow" ? "main" : preset === "trunk-based" ? "master" : "develop",
8752
- detected: detected !== null
8753
- };
8754
8624
  };
8755
- var COMMIT_PRESETS = [
8756
- "conventional",
8757
- "gitmoji",
8758
- "ticket-prefix",
8759
- "freeform",
8760
- "custom",
8761
- "auto"
8762
- ];
8763
- var resolveCommitPolicy = (config, workspace) => {
8764
- const wp = workspace?.commitPolicy ?? {};
8765
- if (COMMIT_PRESETS.includes(String(wp.preset ?? ""))) {
8766
- return {
8767
- preset: wp.preset,
8768
- ...typeof wp.pattern === "string" ? { pattern: wp.pattern } : {}
8769
- };
8625
+ var reftableRefs = (common) => {
8626
+ try {
8627
+ return /^\s*refstorage\s*=\s*reftable\s*$/imu.test(fs2.readFileSync(path2.join(common, "config"), "utf8"));
8628
+ } catch {
8629
+ return false;
8770
8630
  }
8771
- return config.commitPolicy ?? { preset: "conventional" };
8772
- };
8773
-
8774
- // packages/workit-core/src/core/workspaces.ts
8775
- import { spawnSync as spawnSync3 } from "node:child_process";
8776
- import { createHash as createHash3 } from "node:crypto";
8777
- import { readFileSync as readFileSync2, realpathSync } from "node:fs";
8778
- import path3 from "node:path";
8779
-
8780
- // packages/workit-core/src/core/branch-policy.ts
8781
- import { spawnSync as spawnSync2 } from "node:child_process";
8782
- var PREFIXES = {
8783
- feature: "feature/*",
8784
- bugfix: "bugfix/*",
8785
- release: "release/*",
8786
- hotfix: "hotfix/*"
8787
- };
8788
- var branchNames = (workspaceRoot) => {
8789
- const r = spawnSync2("git", ["for-each-ref", "--format=%(refname)", "refs/heads", "refs/remotes/origin"], { cwd: workspaceRoot, encoding: "utf8" });
8790
- if (r.status !== 0)
8791
- return new Set;
8792
- return new Set((r.stdout ?? "").split(`
8793
- `).filter(Boolean).map((ref) => ref.startsWith("refs/heads/") ? ref.slice("refs/heads/".length) : ref.slice("refs/remotes/origin/".length)));
8794
8631
  };
8795
- var productionBranchOf = (names) => names.has("main") ? "main" : names.has("master") ? "master" : "main";
8796
- var detectBranchPolicy = (workspaceRoot) => {
8797
- const names = branchNames(workspaceRoot);
8798
- const develop = names.has("develop");
8799
- const main = names.has("main");
8632
+ function fastGit(root) {
8633
+ if (DISCOVERY_ENV.some((name) => process.env[name]))
8634
+ return null;
8635
+ let dir = real(root);
8636
+ let device = null;
8637
+ for (;; ) {
8638
+ let stat;
8639
+ try {
8640
+ stat = fs2.statSync(dir);
8641
+ } catch {
8642
+ return null;
8643
+ }
8644
+ if (device !== null && stat.dev !== device)
8645
+ return { kind: "none" };
8646
+ device = stat.dev;
8647
+ const dotGit = path2.join(dir, ".git");
8648
+ let entry = null;
8649
+ try {
8650
+ entry = fs2.statSync(dotGit);
8651
+ } catch {}
8652
+ let gitDir = null;
8653
+ let top = null;
8654
+ if (entry) {
8655
+ top = dir;
8656
+ if (entry.isDirectory())
8657
+ gitDir = dotGit;
8658
+ else if (entry.isFile()) {
8659
+ let text = "";
8660
+ try {
8661
+ text = fs2.readFileSync(dotGit, "utf8");
8662
+ } catch {
8663
+ return null;
8664
+ }
8665
+ const match = /^gitdir: (.+)$/mu.exec(text);
8666
+ if (!match)
8667
+ return null;
8668
+ gitDir = path2.resolve(dir, match[1].trim());
8669
+ } else
8670
+ return null;
8671
+ if (!fs2.existsSync(path2.join(gitDir, "HEAD")))
8672
+ return null;
8673
+ } else if (isGitDir(dir))
8674
+ gitDir = dir;
8675
+ if (gitDir) {
8676
+ let common = gitDir;
8677
+ try {
8678
+ common = path2.resolve(gitDir, fs2.readFileSync(path2.join(gitDir, "commondir"), "utf8").trim());
8679
+ } catch {}
8680
+ if (!fs2.existsSync(path2.join(common, "objects")))
8681
+ return null;
8682
+ return {
8683
+ kind: "repo",
8684
+ common: real(common),
8685
+ gitDir: real(gitDir),
8686
+ top: top === null ? null : real(top),
8687
+ reftable: reftableRefs(common)
8688
+ };
8689
+ }
8690
+ const parent = path2.dirname(dir);
8691
+ if (parent === dir)
8692
+ return { kind: "none" };
8693
+ dir = parent;
8694
+ }
8695
+ }
8696
+ function resolveStore(root) {
8697
+ root = path2.resolve(root);
8698
+ let directory = false;
8699
+ try {
8700
+ directory = fs2.statSync(root).isDirectory();
8701
+ } catch {}
8702
+ if (!directory)
8703
+ return { dir: path2.join(root, ".workit"), shared: false, top: null };
8704
+ const fast = fastGit(root);
8705
+ if (fast?.kind === "none")
8706
+ return { dir: path2.join(root, ".workit"), shared: false, top: null };
8707
+ if (fast?.kind === "repo")
8708
+ return {
8709
+ dir: path2.join(fast.common, "workit"),
8710
+ shared: true,
8711
+ top: fast.top,
8712
+ git: { gitDir: fast.gitDir, top: fast.top, reftable: fast.reftable }
8713
+ };
8714
+ const run = gitRun(root, ["rev-parse", "--path-format=absolute", "--git-common-dir"]);
8715
+ const common = run.stdout.trim();
8716
+ if (run.ok && common) {
8717
+ const top = gitRun(root, ["rev-parse", "--path-format=absolute", "--show-toplevel"]);
8718
+ return {
8719
+ dir: path2.join(real(path2.resolve(root, common)), "workit"),
8720
+ shared: true,
8721
+ top: top.ok && top.stdout.trim() ? real(top.stdout.trim()) : null
8722
+ };
8723
+ }
8724
+ if (run.missing || !run.ok && NOT_A_REPO.test(run.stderr))
8725
+ return { dir: path2.join(root, ".workit"), shared: false, top: null };
8726
+ return new Error(`cannot locate the workit store: git rev-parse failed (${run.stderr.trim().split(`
8727
+ `)[0] || "no output"}); fix the repository (git status should work here)`);
8728
+ }
8729
+ var rebasingBranch = (gitDir) => {
8730
+ for (const state of ["rebase-merge", "rebase-apply"]) {
8731
+ try {
8732
+ const ref = fs2.readFileSync(path2.join(gitDir, state, "head-name"), "utf8").trim();
8733
+ const match = /^refs\/heads\/(.+)$/u.exec(ref);
8734
+ if (match)
8735
+ return match[1];
8736
+ } catch {}
8737
+ }
8738
+ return null;
8739
+ };
8740
+ function resolveTaskKey(root, location) {
8741
+ if (!location.shared)
8742
+ return { key: `dir-${shortHash(root)}`, kind: "dir", branch: null };
8743
+ const branchKey = (name) => ({ key: name, kind: "branch", branch: name });
8744
+ if (location.git && !location.git.reftable) {
8745
+ let head = "";
8746
+ try {
8747
+ head = fs2.readFileSync(path2.join(location.git.gitDir, "HEAD"), "utf8").trim();
8748
+ } catch {}
8749
+ const ref = /^ref: refs\/heads\/(.+)$/u.exec(head);
8750
+ if (ref && ref[1] !== ".invalid")
8751
+ return branchKey(ref[1]);
8752
+ if (/^[0-9a-f]{40,64}$/u.test(head)) {
8753
+ const rebasing = rebasingBranch(location.git.gitDir);
8754
+ if (rebasing)
8755
+ return branchKey(rebasing);
8756
+ return {
8757
+ key: `detached-${shortHash(location.git.top ?? location.git.gitDir)}`,
8758
+ kind: "detached",
8759
+ branch: null
8760
+ };
8761
+ }
8762
+ }
8763
+ const branch = gitRun(root, ["symbolic-ref", "-q", "--short", "HEAD"]);
8764
+ const name = branch.ok ? branch.stdout.trim() : "";
8765
+ if (name && name !== ".invalid")
8766
+ return branchKey(name);
8767
+ const gitDir = gitRun(root, ["rev-parse", "--path-format=absolute", "--git-dir"]);
8768
+ if (gitDir.ok && gitDir.stdout.trim()) {
8769
+ const rebasing = rebasingBranch(gitDir.stdout.trim());
8770
+ if (rebasing)
8771
+ return branchKey(rebasing);
8772
+ }
8773
+ const top = gitRun(root, ["rev-parse", "--path-format=absolute", "--show-toplevel"]);
8774
+ const worktree = top.ok && top.stdout.trim() ? top.stdout.trim() : root;
8775
+ return { key: `detached-${shortHash(real(worktree))}`, kind: "detached", branch: null };
8776
+ }
8777
+ function checkoutRootOf(root, location) {
8778
+ const resolved = path2.resolve(root);
8779
+ let canonical = resolved;
8780
+ try {
8781
+ canonical = fs2.realpathSync(resolved);
8782
+ } catch {
8783
+ return resolved;
8784
+ }
8785
+ const found = location ?? resolveStore(canonical);
8786
+ if (found instanceof Error || !found.shared || !found.top)
8787
+ return canonical;
8788
+ if (real(canonical) === found.top)
8789
+ return canonical;
8790
+ try {
8791
+ return fs2.realpathSync(found.top);
8792
+ } catch {
8793
+ return canonical;
8794
+ }
8795
+ }
8796
+ function branchRenames(location) {
8797
+ if (!location.shared)
8798
+ return [];
8799
+ const files = [];
8800
+ const walk = (dir) => {
8801
+ let entries;
8802
+ try {
8803
+ entries = fs2.readdirSync(dir, { withFileTypes: true });
8804
+ } catch {
8805
+ return;
8806
+ }
8807
+ for (const entry of entries)
8808
+ if (entry.isDirectory())
8809
+ walk(path2.join(dir, entry.name));
8810
+ else
8811
+ files.push(path2.join(dir, entry.name));
8812
+ };
8813
+ walk(path2.join(path2.dirname(location.dir), "logs", "refs", "heads"));
8814
+ if (location.git)
8815
+ files.push(path2.join(location.git.gitDir, "logs", "HEAD"));
8816
+ const seen = new Set;
8817
+ const renames = [];
8818
+ for (const file of files) {
8819
+ let text = "";
8820
+ try {
8821
+ text = fs2.readFileSync(file, "utf8");
8822
+ } catch {
8823
+ continue;
8824
+ }
8825
+ for (const match of text.matchAll(/ (\d+) [+-]\d{4}\tBranch: renamed refs\/heads\/(\S+) to refs\/heads\/(\S+)/gu)) {
8826
+ const rename = { from: match[2], to: match[3], at: Number(match[1]) };
8827
+ const id = `${rename.at}\x00${rename.from}\x00${rename.to}`;
8828
+ if (seen.has(id))
8829
+ continue;
8830
+ seen.add(id);
8831
+ renames.push(rename);
8832
+ }
8833
+ }
8834
+ return renames.toSorted((left, right) => left.at - right.at);
8835
+ }
8836
+ function followRenames(key, boundAt, renames) {
8837
+ let current = key;
8838
+ let since = Math.floor(Date.parse(boundAt) / 1000);
8839
+ for (const rename of renames)
8840
+ if (rename.from === current && rename.at >= since) {
8841
+ current = rename.to;
8842
+ since = rename.at;
8843
+ }
8844
+ return current;
8845
+ }
8846
+ var checkoutSlug = (root) => {
8847
+ let canonical = real(root);
8848
+ if (process.platform === "win32")
8849
+ canonical = canonical.toLowerCase();
8850
+ const base = path2.basename(canonical).replace(/[^A-Za-z0-9._-]/g, "_").slice(0, 40) || "root";
8851
+ return `${base}-${shortHash(canonical)}`;
8852
+ };
8853
+
8854
+ // packages/workit-core/src/core/store-lock.ts
8855
+ var metadataLockSchema = object({
8856
+ pid: number2().int().nonnegative().safe(),
8857
+ processStart: string2().nullable(),
8858
+ host: string2().min(1),
8859
+ nonce: string2().min(1),
8860
+ externalAction: literal(true).optional()
8861
+ }).strict();
8862
+ var FOREIGN_LOCK_TTL_MS = 10 * 60000;
8863
+ var UNREADABLE_LOCK_TTL_MS = 30000;
8864
+ var RECLAIM_GUARD_TTL_MS = 30000;
8865
+ var IN_PROCESS_LOCK_TIMEOUT_MS = 250;
8866
+ var defaultLockTimeoutMs = IN_PROCESS_LOCK_TIMEOUT_MS;
8867
+ var defaultLockTimeout = () => defaultLockTimeoutMs;
8868
+ var hostAwareLockTimeout = (cliMs) => defaultLockTimeoutMs > IN_PROCESS_LOCK_TIMEOUT_MS ? cliMs : IN_PROCESS_LOCK_TIMEOUT_MS;
8869
+ var parseMetadataLock = (raw) => {
8870
+ let value;
8871
+ try {
8872
+ value = JSON.parse(raw);
8873
+ } catch {
8874
+ throw Object.assign(new Error("metadata lock is invalid"), { code: "metadata_lock_invalid" });
8875
+ }
8876
+ const parsed = metadataLockSchema.safeParse(value);
8877
+ if (!parsed.success)
8878
+ throw Object.assign(new Error("metadata lock is invalid"), { code: "metadata_lock_invalid" });
8879
+ return parsed.data;
8880
+ };
8881
+ var parseMetadataLockOrNull = (raw) => {
8882
+ try {
8883
+ return parseMetadataLock(raw);
8884
+ } catch {
8885
+ return null;
8886
+ }
8887
+ };
8888
+ var parseProcStatStart = (stat) => {
8889
+ const close = stat.lastIndexOf(")");
8890
+ if (close < 0)
8891
+ return null;
8892
+ return stat.slice(close + 1).trim().split(/\s+/)[19] ?? null;
8893
+ };
8894
+ var processStartOf = (pid) => {
8895
+ if (process.platform === "linux") {
8896
+ try {
8897
+ return parseProcStatStart(fs3.readFileSync(`/proc/${pid}/stat`, "utf8"));
8898
+ } catch {
8899
+ return null;
8900
+ }
8901
+ }
8902
+ if (process.platform === "darwin" || process.platform === "freebsd") {
8903
+ try {
8904
+ const run = spawnSync3("ps", ["-o", "lstart=", "-p", String(pid)], {
8905
+ encoding: "utf8",
8906
+ timeout: 1000
8907
+ });
8908
+ const value = run.status === 0 ? run.stdout.trim() : "";
8909
+ return value || null;
8910
+ } catch {
8911
+ return null;
8912
+ }
8913
+ }
8914
+ return null;
8915
+ };
8916
+ var readTrimmed = (file) => {
8917
+ try {
8918
+ return fs3.readFileSync(file, "utf8").trim() || null;
8919
+ } catch {
8920
+ return null;
8921
+ }
8922
+ };
8923
+ var cachedLockHost = null;
8924
+ var localLockHost = () => {
8925
+ if (cachedLockHost !== null)
8926
+ return cachedLockHost;
8927
+ let pidns = null;
8928
+ try {
8929
+ pidns = /\[(\d+)\]/.exec(fs3.readlinkSync("/proc/self/ns/pid"))?.[1] ?? null;
8930
+ } catch {}
8931
+ const boot = readTrimmed("/proc/sys/kernel/random/boot_id");
8932
+ cachedLockHost = pidns || boot ? `${hostname()}#${pidns ?? "?"}:${boot ?? "?"}` : hostname();
8933
+ return cachedLockHost;
8934
+ };
8935
+ var pidAlive = (pid) => {
8936
+ if (!Number.isSafeInteger(pid) || pid <= 0)
8937
+ return false;
8938
+ try {
8939
+ process.kill(pid, 0);
8940
+ return true;
8941
+ } catch (error) {
8942
+ return error.code === "EPERM";
8943
+ }
8944
+ };
8945
+ var TASK_STORE_LOCK = { reclaimUnverifiedAfterTtl: true };
8946
+ var classifyLockOwner = (payload, ageMs, localHost = localLockHost(), options = {}) => {
8947
+ const lock = metadataLockSchema.safeParse(payload);
8948
+ if (!lock.success)
8949
+ return ageMs !== null && ageMs > UNREADABLE_LOCK_TTL_MS ? { state: "stale", reason: "unreadable lock left behind" } : { state: "unknown", reason: "lock is being written" };
8950
+ const { pid, processStart, host } = lock.data;
8951
+ const [lockName, lockSpace] = host.split("#");
8952
+ const [localName, localSpace] = localHost.split("#");
8953
+ if (lockSpace && localSpace && lockName === localName) {
8954
+ const [lockNs, lockBoot] = lockSpace.split(":");
8955
+ const [localNs, localBoot] = localSpace.split(":");
8956
+ if (lockNs === localNs && lockNs !== "?" && lockBoot !== "?" && lockBoot !== localBoot)
8957
+ return { state: "stale", reason: "lock was taken before this machine rebooted" };
8958
+ }
8959
+ if (host !== localHost)
8960
+ return ageMs !== null && ageMs > FOREIGN_LOCK_TTL_MS ? { state: "stale", reason: `lock from ${host} is older than its TTL` } : { state: "unknown", reason: `lock is held from ${host}; its pid cannot be checked here` };
8961
+ if (!pidAlive(pid))
8962
+ return { state: "stale", reason: `pid ${pid} is not running` };
8963
+ const currentStart = processStartOf(pid);
8964
+ if (processStart !== null && currentStart !== null && processStart !== currentStart)
8965
+ return { state: "stale", reason: `pid ${pid} now belongs to a different process` };
8966
+ if (options.reclaimUnverifiedAfterTtl === true && (processStart === null || currentStart === null) && ageMs !== null && ageMs > FOREIGN_LOCK_TTL_MS)
8967
+ return {
8968
+ state: "stale",
8969
+ reason: `pid ${pid} is running but the lock is older than its TTL and the pid's start time cannot be checked (reused pid)`
8970
+ };
8971
+ return { state: "live", reason: `held by running pid ${pid}` };
8972
+ };
8973
+ var ageOf = (file, nowMs) => {
8974
+ try {
8975
+ return nowMs - fs3.lstatSync(file).mtimeMs;
8976
+ } catch {
8977
+ return null;
8978
+ }
8979
+ };
8980
+ var clearAbandonedReclaimGuard = (lockPath, nowMs = Date.now()) => {
8981
+ const guard = `${lockPath}.reclaim`;
8982
+ const age = ageOf(guard, nowMs);
8983
+ if (age === null || age <= RECLAIM_GUARD_TTL_MS)
8984
+ return false;
8985
+ try {
8986
+ fs3.rmdirSync(guard);
8987
+ return true;
8988
+ } catch {
8989
+ return false;
8990
+ }
8991
+ };
8992
+
8993
+ // packages/workit-core/src/core/vcs-config.ts
8994
+ import fs4 from "node:fs";
8995
+ import path5 from "node:path";
8996
+ import { spawnSync as spawnSync7 } from "node:child_process";
8997
+
8998
+ // packages/workit-core/src/core/config.ts
8999
+ import {
9000
+ copyFileSync,
9001
+ cpSync,
9002
+ existsSync,
9003
+ mkdirSync,
9004
+ readFileSync,
9005
+ readdirSync,
9006
+ writeFileSync
9007
+ } from "node:fs";
9008
+ import os2 from "node:os";
9009
+ import path3 from "node:path";
9010
+
9011
+ // packages/workit-core/src/core/boundary.ts
9012
+ var EVENT = {
9013
+ initialization: "initialization",
9014
+ provenance: "provenance",
9015
+ assets: "assets",
9016
+ configurationSource: "configuration_source",
9017
+ hooks: "hooks",
9018
+ mcpConnection: "mcp_connection",
9019
+ migration: "migration",
9020
+ installSteps: "install_steps",
9021
+ uncaughtFailure: "uncaught_failure",
9022
+ toolsFailed: "tools_failed",
9023
+ doctor: "doctor"
9024
+ };
9025
+ var errorDetail = (err) => {
9026
+ if (err instanceof Error) {
9027
+ return { error_name: err.name, error: err.message };
9028
+ }
9029
+ return { error_name: "unknown", error: String(err) };
9030
+ };
9031
+
9032
+ // packages/workit-core/src/core/config.ts
9033
+ var diagnosticLogger;
9034
+ var PRESETS = {
9035
+ gitflow: {
9036
+ allowed: ["feature/*", "bugfix/*", "hotfix/*", "release/*"],
9037
+ protected: ["main", "develop", "master", "prod", "production"]
9038
+ },
9039
+ "github-flow": { allowed: ["*"], protected: ["main"] },
9040
+ "trunk-based": { allowed: ["*"], protected: ["main"] },
9041
+ custom: { allowed: [], protected: [] }
9042
+ };
9043
+ var mergePreset = (preset, input = {}, current = {
9044
+ branchPolicy: { preset, allowed: [], protected: [] }
9045
+ }) => {
9046
+ const defs = PRESETS[preset];
9047
+ return {
9048
+ preset,
9049
+ allowed: preset === "custom" ? input.allowed ?? current.branchPolicy.allowed : [...defs.allowed],
9050
+ protected: preset === "custom" ? input.protectedNames ?? current.branchPolicy.protected : [...defs.protected]
9051
+ };
9052
+ };
9053
+ var resolveConfigDir = () => process.env.WORKFLOW_TOOLKIT_CONFIG ?? process.env.WORKFLOW_TOOLKIT_CONFIG_DIR ?? path3.join(process.env.XDG_CONFIG_HOME || path3.join(os2.homedir(), ".config"), "workit");
9054
+ var migratedDir = null;
9055
+ var migrationFailed = false;
9056
+ var ensureConfigDir = (dir = resolveConfigDir()) => {
9057
+ if (migratedDir === dir)
9058
+ return dir;
9059
+ if (process.env.WORKFLOW_TOOLKIT_CONFIG || process.env.WORKFLOW_TOOLKIT_CONFIG_DIR) {
9060
+ migratedDir = dir;
9061
+ return dir;
9062
+ }
9063
+ const legacy = path3.join(process.env.XDG_CONFIG_HOME || path3.join(os2.homedir(), ".config"), "workflow-toolkit");
9064
+ if (!existsSync(legacy)) {
9065
+ migratedDir = dir;
9066
+ return dir;
9067
+ }
9068
+ if (!migrationFailed && existsSync(dir)) {
9069
+ migratedDir = dir;
9070
+ return dir;
9071
+ }
9072
+ migrationFailed = false;
9073
+ mkdirSync(dir, { recursive: true });
9074
+ diagnosticLogger?.info(EVENT.migration, { from: legacy, to: dir });
9075
+ for (const entry of readdirSync(legacy, { withFileTypes: true })) {
9076
+ const src = path3.join(legacy, entry.name);
9077
+ const dest = path3.join(dir, entry.name);
9078
+ if (existsSync(dest))
9079
+ continue;
9080
+ try {
9081
+ if (entry.isDirectory())
9082
+ cpSync(src, dest, { recursive: true });
9083
+ else if (entry.isFile())
9084
+ copyFileSync(src, dest);
9085
+ } catch (err) {
9086
+ migrationFailed = true;
9087
+ diagnosticLogger?.warn(EVENT.migration, { from: src, ok: false, ...errorDetail(err) });
9088
+ console.warn(`[workit] config migration: a file could not be copied to ${dir}; it will be retried on the next run`);
9089
+ }
9090
+ }
9091
+ if (!migrationFailed)
9092
+ migratedDir = dir;
9093
+ return dir;
9094
+ };
9095
+ var configDir = () => ensureConfigDir();
9096
+ var LOCALE_RE = /^[a-z]{2,3}(-(?:[A-Z]{2}|[0-9]{3}))?$/;
9097
+ var DEFAULTS = {
9098
+ locale: "en",
9099
+ localeOptions: ["en", "es-CL", "es-MX", "es-AR", "pt-BR"],
9100
+ branchPolicy: {
9101
+ preset: "gitflow",
9102
+ allowed: [...PRESETS.gitflow.allowed],
9103
+ protected: [...PRESETS.gitflow.protected]
9104
+ },
9105
+ commitPolicy: { preset: "conventional" }
9106
+ };
9107
+ var unchosenBranchPolicy = new WeakSet([DEFAULTS]);
9108
+ var readSafe = (p) => {
9109
+ try {
9110
+ return readFileSync(p, "utf8");
9111
+ } catch {
9112
+ return null;
9113
+ }
9114
+ };
9115
+ var isConfigObject = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
9116
+ var parseConfigResult = (raw, file) => {
9117
+ if (raw === null)
9118
+ return { status: "missing", path: file };
9119
+ let parsed;
9120
+ try {
9121
+ parsed = JSON.parse(raw);
9122
+ } catch {
9123
+ return { status: "malformed", path: file, error: `${file} is not valid JSON` };
9124
+ }
9125
+ if (!isConfigObject(parsed)) {
9126
+ return { status: "malformed", path: file, error: `${file} is not a JSON object` };
9127
+ }
9128
+ const input = parsed;
9129
+ const locale = LOCALE_RE.test(String(input.locale ?? "")) ? input.locale : DEFAULTS.locale;
9130
+ const preset = Object.hasOwn(PRESETS, input.branchPolicy?.preset) ? input.branchPolicy?.preset : "gitflow";
9131
+ const commitPreset = COMMIT_PRESETS.includes(String(input.commitPolicy?.preset ?? "")) ? input.commitPolicy?.preset : "conventional";
9132
+ const config = {
9133
+ locale,
9134
+ localeOptions: Array.isArray(input.localeOptions) ? input.localeOptions : DEFAULTS.localeOptions,
9135
+ branchPolicy: mergePreset(preset, {
9136
+ allowed: Array.isArray(input.branchPolicy?.allowed) ? input.branchPolicy.allowed : undefined,
9137
+ protectedNames: Array.isArray(input.branchPolicy?.protected) ? input.branchPolicy.protected : undefined
9138
+ }, DEFAULTS),
9139
+ commitPolicy: {
9140
+ preset: commitPreset,
9141
+ ...typeof input.commitPolicy?.pattern === "string" ? { pattern: input.commitPolicy.pattern } : {}
9142
+ }
9143
+ };
9144
+ if (!Object.hasOwn(PRESETS, input.branchPolicy?.preset))
9145
+ unchosenBranchPolicy.add(config);
9146
+ return { status: "valid", path: file, config };
9147
+ };
9148
+ var readConfigTyped = (dir) => {
9149
+ const file = path3.join(dir ?? configDir(), "config.json");
9150
+ return parseConfigResult(readSafe(file), file);
9151
+ };
9152
+ var readConfig = () => {
9153
+ const result = readConfigTyped();
9154
+ if (result.status === "malformed")
9155
+ throw new Error(result.error);
9156
+ return result.config ?? DEFAULTS;
9157
+ };
9158
+ var vcsConfigPath = () => process.env.WORKFLOW_VCS_CONFIG ?? path3.join(configDir(), "vcs.json");
9159
+ var branchGlobPattern = (glob) => new RegExp(`^${glob.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*")}$`, "i");
9160
+ var resolveBranchPolicy = (config, workspace, repoPreset) => {
9161
+ const wp = workspace?.branchPolicy ?? {};
9162
+ const workspacePreset = Object.hasOwn(PRESETS, wp.preset);
9163
+ const detected = !workspacePreset && unchosenBranchPolicy.has(config) ? repoPreset?.() ?? null : null;
9164
+ const preset = workspacePreset ? wp.preset : detected ?? config.branchPolicy?.preset ?? "gitflow";
9165
+ const merged = mergePreset(preset, {
9166
+ allowed: wp.allowed,
9167
+ protectedNames: wp.protected
9168
+ }, config);
9169
+ const allowed = merged.allowed.map(branchGlobPattern);
9170
+ return {
9171
+ preset,
9172
+ allowed,
9173
+ protected: new Set([...merged.protected, ...detected ? PRESETS.gitflow.protected : []].map((p) => p.toLowerCase())),
9174
+ integration: wp.integration === "merge" ? "merge" : "pr",
9175
+ defaultTargetBranch: preset === "github-flow" ? "main" : preset === "trunk-based" ? "master" : "develop",
9176
+ detected: detected !== null
9177
+ };
9178
+ };
9179
+ var COMMIT_PRESETS = [
9180
+ "conventional",
9181
+ "gitmoji",
9182
+ "ticket-prefix",
9183
+ "freeform",
9184
+ "custom",
9185
+ "auto"
9186
+ ];
9187
+ var resolveCommitPolicy = (config, workspace) => {
9188
+ const wp = workspace?.commitPolicy ?? {};
9189
+ if (COMMIT_PRESETS.includes(String(wp.preset ?? ""))) {
9190
+ return {
9191
+ preset: wp.preset,
9192
+ ...typeof wp.pattern === "string" ? { pattern: wp.pattern } : {}
9193
+ };
9194
+ }
9195
+ return config.commitPolicy ?? { preset: "conventional" };
9196
+ };
9197
+
9198
+ // packages/workit-core/src/core/workspaces.ts
9199
+ import { spawnSync as spawnSync5 } from "node:child_process";
9200
+ import { createHash as createHash4 } from "node:crypto";
9201
+ import { readFileSync as readFileSync2, realpathSync } from "node:fs";
9202
+ import path4 from "node:path";
9203
+
9204
+ // packages/workit-core/src/core/branch-policy.ts
9205
+ import { spawnSync as spawnSync4 } from "node:child_process";
9206
+ var PREFIXES = {
9207
+ feature: "feature/*",
9208
+ bugfix: "bugfix/*",
9209
+ release: "release/*",
9210
+ hotfix: "hotfix/*"
9211
+ };
9212
+ var branchNames = (workspaceRoot) => {
9213
+ const r = spawnSync4("git", ["for-each-ref", "--format=%(refname)", "refs/heads", "refs/remotes/origin"], { cwd: workspaceRoot, encoding: "utf8" });
9214
+ if (r.status !== 0)
9215
+ return new Set;
9216
+ return new Set((r.stdout ?? "").split(`
9217
+ `).filter(Boolean).map((ref) => ref.startsWith("refs/heads/") ? ref.slice("refs/heads/".length) : ref.slice("refs/remotes/origin/".length)));
9218
+ };
9219
+ var productionBranchOf = (names) => names.has("main") ? "main" : names.has("master") ? "master" : "main";
9220
+ var detectBranchPolicy = (workspaceRoot) => {
9221
+ const names = branchNames(workspaceRoot);
9222
+ const develop = names.has("develop");
9223
+ const main = names.has("main");
8800
9224
  const master = names.has("master");
8801
9225
  const root = main ? "main" : master ? "master" : null;
8802
9226
  if (develop) {
@@ -8845,7 +9269,7 @@ var repoBranchPreset = (workspaceRoot) => {
8845
9269
  var releaseTagPrefixOf = (workspaceRoot, policyPrefix) => {
8846
9270
  if (typeof policyPrefix === "string")
8847
9271
  return policyPrefix.trim();
8848
- const r = spawnSync2("git", ["config", "--get", "gitflow.prefix.versiontag"], {
9272
+ const r = spawnSync4("git", ["config", "--get", "gitflow.prefix.versiontag"], {
8849
9273
  cwd: workspaceRoot,
8850
9274
  encoding: "utf8"
8851
9275
  });
@@ -8910,7 +9334,7 @@ var releaseTrackSchema = object({
8910
9334
  context.addIssue({ code: "custom", path: [field], message: "entries must be unique" });
8911
9335
  }
8912
9336
  }
8913
- if (track.versionSource?.kind === "package-json" && (path3.posix.isAbsolute(track.versionSource.path.replaceAll("\\", "/")) || /^[A-Za-z]:\//u.test(track.versionSource.path.replaceAll("\\", "/")) || track.versionSource.path.replaceAll("\\", "/").split("/").includes(".."))) {
9337
+ if (track.versionSource?.kind === "package-json" && (path4.posix.isAbsolute(track.versionSource.path.replaceAll("\\", "/")) || /^[A-Za-z]:\//u.test(track.versionSource.path.replaceAll("\\", "/")) || track.versionSource.path.replaceAll("\\", "/").split("/").includes(".."))) {
8914
9338
  context.addIssue({
8915
9339
  code: "custom",
8916
9340
  path: ["versionSource", "path"],
@@ -8971,7 +9395,7 @@ var workspacesFileSchema = object({ workspaces: array(workspaceConfigSchema).opt
8971
9395
  names.add(workspace.name);
8972
9396
  }
8973
9397
  });
8974
- var workspacesRevision = (contents) => createHash3("sha256").update(contents ?? "<missing>").digest("hex");
9398
+ var workspacesRevision = (contents) => createHash4("sha256").update(contents ?? "<missing>").digest("hex");
8975
9399
  var validateWorkspacesDocument = (parsed, file) => {
8976
9400
  if (!isConfigObject(parsed)) {
8977
9401
  return { status: "malformed", path: file, entries: [], error: `${file} is not a JSON object` };
@@ -9009,7 +9433,7 @@ var validateWorkspacesDocument = (parsed, file) => {
9009
9433
  };
9010
9434
  };
9011
9435
  var readWorkspacesResult = (dir = configDir()) => {
9012
- const file = path3.join(dir, "workspaces.json");
9436
+ const file = path4.join(dir, "workspaces.json");
9013
9437
  let raw;
9014
9438
  try {
9015
9439
  raw = readFileSync2(file, "utf8");
@@ -9108,7 +9532,7 @@ var mainCheckoutOf = (cwd) => {
9108
9532
  for (const name of ["GIT_DIR", "GIT_WORK_TREE", "GIT_COMMON_DIR", "GIT_INDEX_FILE"])
9109
9533
  delete env[name];
9110
9534
  const git = (args) => {
9111
- const run = spawnSync3("git", args, {
9535
+ const run = spawnSync5("git", args, {
9112
9536
  cwd,
9113
9537
  env,
9114
9538
  encoding: "utf8",
@@ -9120,9 +9544,9 @@ var mainCheckoutOf = (cwd) => {
9120
9544
  let main = null;
9121
9545
  const dirs = git(["rev-parse", "--path-format=absolute", "--git-dir", "--git-common-dir"]);
9122
9546
  const [gitDir, common] = (dirs ?? "").trim().split(/\r?\n/u);
9123
- if (gitDir && common && path3.resolve(gitDir) !== path3.resolve(common)) {
9547
+ if (gitDir && common && path4.resolve(gitDir) !== path4.resolve(common)) {
9124
9548
  const listed = /^worktree (.+)$/mu.exec(git(["worktree", "list", "--porcelain"]) ?? "")?.[1];
9125
- main = listed?.trim() || (path3.basename(common) === ".git" ? path3.dirname(common) : common);
9549
+ main = listed?.trim() || (path4.basename(common) === ".git" ? path4.dirname(common) : common);
9126
9550
  }
9127
9551
  mainCheckouts.set(cwd, main);
9128
9552
  return main;
@@ -9213,7 +9637,7 @@ var runtimeWorkspaceIndexSchema = object({
9213
9637
  workspaces: array(object({ name: nonBlank, glob: nonBlank }).passthrough()).optional()
9214
9638
  }).passthrough();
9215
9639
  var matchRuntimeWorkspace = (cwd, dir = configDir()) => {
9216
- const file = path3.join(dir, "workspaces.json");
9640
+ const file = path4.join(dir, "workspaces.json");
9217
9641
  let raw;
9218
9642
  try {
9219
9643
  raw = readFileSync2(file, "utf8");
@@ -9306,8 +9730,8 @@ function workspaceDefaultTarget(cwd, globalDefault) {
9306
9730
  }
9307
9731
 
9308
9732
  // packages/workit-core/src/core/release-tracks.ts
9309
- import { createHash as createHash4 } from "node:crypto";
9310
- import { spawnSync as spawnSync4 } from "node:child_process";
9733
+ import { createHash as createHash5 } from "node:crypto";
9734
+ import { spawnSync as spawnSync6 } from "node:child_process";
9311
9735
  class ReleaseTrackError extends Error {
9312
9736
  }
9313
9737
  var KNOWN_FIELDS = new Set([
@@ -9573,603 +9997,323 @@ function resolveReleaseTrack(input) {
9573
9997
  return fallback(`no track branch could be compared with ${branch} (fetch the track branches)`);
9574
9998
  const ancestry = new Map;
9575
9999
  const descends = (older, newer) => {
9576
- if (older === newer)
9577
- return false;
9578
- const key = `${older}>${newer}`;
9579
- if (!ancestry.has(key))
9580
- ancestry.set(key, input.probe.isAncestor(older, newer));
9581
- return ancestry.get(key);
9582
- };
9583
- const winners = forks.filter((fork) => forks.every((other) => other.track === fork.track || descends(other.sha, fork.sha)));
9584
- const winningTracks = [...new Set(winners.map((fork) => fork.track))];
9585
- if (winningTracks.length === 1 && forks.some((fork) => fork.track !== winningTracks[0])) {
9586
- const win = winners[0];
9587
- return done(win.track, "ancestry", `${branch} forked from ${win.ref} at ${win.sha.slice(0, 12)}, after every other line's fork point`);
9588
- }
9589
- if (winningTracks.length === 1)
9590
- return fallback(`only ${label(winningTracks[0])} could be compared with ${branch}`);
9591
- const newest = forks.filter((fork) => !forks.some((other) => descends(fork.sha, other.sha)));
9592
- return fallback(`${branch} has the same or unrelated fork points with ${[...new Set(newest.map((fork) => `${fork.ref} (${fork.track.name === IMPLICIT ? "workspace default" : fork.track.name})`))].join(" and ")}`);
9593
- }
9594
- var git2 = (cwd, args) => {
9595
- const run = spawnSync4("git", args, {
9596
- cwd,
9597
- encoding: "utf8",
9598
- timeout: 20000,
9599
- windowsHide: true,
9600
- stdio: ["ignore", "pipe", "ignore"]
9601
- });
9602
- return run.status === 0 ? (run.stdout ?? "").trim() : null;
9603
- };
9604
- var recordedBaseKey = (branch) => `branch.${branch}.workitBase`;
9605
- var escapeRegExp = (text) => text.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
9606
- var reflog = (cwd, ref) => (git2(cwd, ["log", "-g", "--date=unix", "--format=%H%x09%gd%x09%gs", ref, "--"]) ?? "").split(`
9607
- `).map((line) => {
9608
- const [sha = "", selector = "", ...subject] = line.split("\t");
9609
- return {
9610
- sha,
9611
- time: Number(/@\{(\d+)\}$/u.exec(selector)?.[1] ?? Number.NaN),
9612
- subject: subject.join("\t")
9613
- };
9614
- }).filter((entry) => entry.sha && Number.isFinite(entry.time));
9615
- function gitTrackProbe(cwd) {
9616
- let remote;
9617
- let remotes;
9618
- const tip = (branch) => {
9619
- remote ??= pushRemoteName(cwd) ?? "origin";
9620
- return resolveRef(cwd, `refs/remotes/${remote}/${branch}`) ?? resolveRef(cwd, `refs/heads/${branch}`);
9621
- };
9622
- return {
9623
- remotes: () => remotes ??= remoteNames(cwd),
9624
- recordedBase: (branch) => git2(cwd, ["config", "--get", recordedBaseKey(branch)]) || null,
9625
- createdFrom: (branch) => {
9626
- const log = git2(cwd, ["reflog", "show", "--format=%gs", `refs/heads/${branch}`, "--"]);
9627
- const oldest = log?.split(`
9628
- `).findLast(Boolean) ?? "";
9629
- const match = /^branch: Created from (\S+)$/u.exec(oldest);
9630
- return match && match[1] !== "HEAD" ? match[1] : null;
9631
- },
9632
- checkedOutFrom: (branch) => {
9633
- const own = reflog(cwd, `refs/heads/${branch}`);
9634
- const created = own.at(-1);
9635
- if (!created || created.subject !== "branch: Created from HEAD")
9636
- return null;
9637
- const pattern = new RegExp(`^checkout: moving from (\\S+) to ${escapeRegExp(branch)}$`, "u");
9638
- const entry = reflog(cwd, "HEAD").findLast((item) => item.time >= created.time && item.sha === created.sha && pattern.test(item.subject));
9639
- return entry ? pattern.exec(entry.subject)?.[1] ?? null : null;
9640
- },
9641
- mergeBase: (branch, trackBranch) => {
9642
- const base = tip(trackBranch);
9643
- return base && resolveRef(cwd, `refs/heads/${branch}`) ? mergeBase(cwd, base, `refs/heads/${branch}`) : null;
9644
- },
9645
- isAncestor: (ancestor, descendant) => git2(cwd, ["merge-base", "--is-ancestor", ancestor, descendant]) !== null
9646
- };
9647
- }
9648
- function workspaceReleaseTracks(cwd) {
9649
- const raw = resolveRuntimeReleaseTracks(cwd);
9650
- return { workspace: raw?.workspace ?? null, ...readReleaseTracks(raw?.releaseTracks) };
9651
- }
9652
- var memo2 = new Map;
9653
- var MEMO_LIMIT = 64;
9654
- function resolveTrackFor(cwd, options) {
9655
- const read = workspaceReleaseTracks(cwd);
9656
- if (read.error)
9657
- return { status: "invalid", error: read.error, issues: read.issues };
9658
- if (read.tracks.length === 0 && !options.track?.trim())
9659
- return { status: "none", issues: read.issues };
9660
- const branch = options.branch === undefined ? currentBranch(cwd) : options.branch;
9661
- const envRequested = process.env.WORKFLOW_RELEASE_TRACK?.trim() || null;
9662
- const input = {
9663
- tracks: read.tracks,
9664
- requested: options.track ?? null,
9665
- envRequested,
9666
- branch,
9667
- defaultBranch: options.defaultBranch,
9668
- issues: read.issues
9669
- };
9670
- let key = null;
9671
- if (!options.probe) {
9672
- const refs = git2(cwd, [
9673
- "for-each-ref",
9674
- "--format=%(refname) %(objectname)",
9675
- "refs/heads",
9676
- "refs/remotes"
9677
- ]) ?? "";
9678
- const head = git2(cwd, ["reflog", "show", "-n", "1", "--format=%H %gs", "HEAD", "--"]) ?? "";
9679
- const own = branch ? [
9680
- git2(cwd, [
9681
- "log",
9682
- "-g",
9683
- "-n",
9684
- "1",
9685
- "--date=unix",
9686
- "--format=%H %gd %gs",
9687
- `refs/heads/${branch}`,
9688
- "--"
9689
- ]) ?? "",
9690
- git2(cwd, ["config", "--get", recordedBaseKey(branch)]) ?? ""
9691
- ] : [];
9692
- key = createHash4("sha256").update(JSON.stringify({ cwd, input, refs, head, own })).digest("hex");
9693
- const hit = memo2.get(key);
9694
- if (hit)
9695
- return hit;
9696
- }
9697
- const resolution = resolveReleaseTrack({ ...input, probe: options.probe ?? gitTrackProbe(cwd) });
9698
- if (key) {
9699
- if (memo2.size >= MEMO_LIMIT)
9700
- memo2.delete(memo2.keys().next().value);
9701
- memo2.set(key, resolution);
9702
- }
9703
- return resolution;
9704
- }
9705
-
9706
- // packages/workit-core/src/core/vcs-config.ts
9707
- var workspacesPath = () => path4.join(configDir(), "workspaces.json");
9708
- var vcsCwd = (cwd) => cwd ?? process.env.WORKFLOW_WORKSPACE_ROOT ?? process.cwd();
9709
- var remoteProvider = (cwd) => {
9710
- const r = spawnSync5("git", ["remote", "get-url", "origin"], { cwd, encoding: "utf8" });
9711
- if (r.status !== 0)
9712
- return null;
9713
- const url = (r.stdout ?? "").trim();
9714
- if (/github\.com[:/]/.test(url))
9715
- return "github";
9716
- if (/gitlab\.com[:/]/.test(url))
9717
- return "gitlab";
9718
- return null;
9719
- };
9720
- var readVcsConfig = () => {
9721
- const file = vcsConfigPath();
9722
- let raw;
9723
- try {
9724
- raw = fs2.readFileSync(file, "utf8");
9725
- } catch {
9726
- return { status: "missing", path: file, config: {} };
9727
- }
9728
- let parsed;
9729
- try {
9730
- parsed = JSON.parse(raw);
9731
- } catch {
9732
- return { status: "malformed", path: file, config: {}, error: `${file} is not valid JSON` };
9733
- }
9734
- if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
9735
- return { status: "valid", path: file, config: parsed };
9736
- }
9737
- return { status: "malformed", path: file, config: {}, error: `${file} is not a JSON object` };
9738
- };
9739
- var trackSummary = (resolution) => resolution.status === "resolved" ? {
9740
- name: resolution.track?.name ?? null,
9741
- source: resolution.source,
9742
- detail: resolution.detail,
9743
- warnings: resolution.warnings,
9744
- track: resolution.track,
9745
- tracks: resolution.tracks,
9746
- blocking: resolution.blocking
9747
- } : null;
9748
- function vcsConfig(mode, cwd, options = {}) {
9749
- const ws = resolveRuntimeWorkspaceVcs(vcsCwd(cwd));
9750
- const wsVcs = ws?.vcs ?? {};
9751
- const wsYt = ws?.youtrack ?? {};
9752
- const wsIssues = ws?.issues ?? {};
9753
- const { status: cfgStatus, path: cfgPath, config: cfg, error: cfgError } = readVcsConfig();
9754
- const root = vcsCwd(cwd);
9755
- const determinateRoot = cwd !== undefined || process.env.WORKFLOW_WORKSPACE_ROOT !== undefined;
9756
- const rawProvider = wsVcs.provider ?? (determinateRoot ? remoteProvider(root) : null) ?? cfg.provider ?? null;
9757
- const provider = rawProvider === null || rawProvider === undefined ? null : String(rawProvider).toLowerCase() || null;
9758
- const workspaceDefault = workspaceDefaultTarget(root, cfg.defaultTargetBranch);
9759
- const linkIssues = typeof wsYt.link_issues === "boolean" ? wsYt.link_issues : null;
9760
- const youtrackBaseUrl = typeof wsYt.baseUrl === "string" ? wsYt.baseUrl : null;
9761
- let issuesProvider = null;
9762
- let linkOnPr = null;
9763
- if (provider === "github" && typeof wsIssues.provider === "string" && wsIssues.provider.toLowerCase() === "github") {
9764
- issuesProvider = "github";
9765
- linkOnPr = typeof wsIssues.link_on_pr === "boolean" ? wsIssues.link_on_pr : null;
9766
- }
9767
- if (mode === "resolve") {
9768
- if (cfgStatus === "malformed")
9769
- return { ok: false, error: cfgError, configPath: cfgPath };
9770
- const trackResolution = resolveTrackFor(root, {
9771
- track: options.track,
9772
- ...options.branch !== undefined ? { branch: options.branch } : {},
9773
- defaultBranch: workspaceDefault
9774
- });
9775
- if (trackResolution.status === "invalid")
9776
- return { ok: false, error: trackResolution.error, configPath: workspacesPath() };
9777
- const releaseTrack = trackSummary(trackResolution);
9778
- const defaultTarget = releaseTrack?.track?.pullRequestTarget ?? workspaceDefault;
9779
- const baseBranch = releaseTrack?.track?.baseBranch ?? defaultTarget;
9780
- return {
9781
- ok: true,
9782
- workspace_name: ws?.name ?? null,
9783
- provider,
9784
- defaultTargetBranch: defaultTarget,
9785
- workspaceDefaultTargetBranch: workspaceDefault,
9786
- baseBranch,
9787
- releaseTrack,
9788
- link_issues: linkIssues,
9789
- youtrack_base_url: youtrackBaseUrl,
9790
- issues_provider: issuesProvider,
9791
- link_on_pr: linkOnPr
9792
- };
9793
- }
9794
- if (cfgStatus === "malformed") {
9795
- return { ok: false, error: cfgError, configPath: cfgPath };
9796
- }
9797
- if (cfgStatus === "missing" && provider === null)
9798
- return { ok: false, error: `vcs.json is missing: ${cfgPath}`, configPath: cfgPath };
9799
- if (provider === null) {
9800
- return {
9801
- ok: false,
9802
- error: `no vcs provider configured (set vcs.provider in ${cfgPath}, match a workspace entry, or run inside a checkout with a recognized origin remote)`,
9803
- configPath: cfgPath
9804
- };
9805
- }
9806
- const prov = cfg[provider] ?? {};
9807
- const out = {
9808
- ok: true,
9809
- configPath: path4.resolve(cfgPath),
9810
- provider,
9811
- defaultTargetBranch: workspaceDefault,
9812
- pr: cfg.pr ?? {},
9813
- workspace_name: ws?.name ?? null,
9814
- link_issues: linkIssues,
9815
- youtrack_base_url: youtrackBaseUrl,
9816
- issues_provider: issuesProvider,
9817
- link_on_pr: linkOnPr
10000
+ if (older === newer)
10001
+ return false;
10002
+ const key = `${older}>${newer}`;
10003
+ if (!ancestry.has(key))
10004
+ ancestry.set(key, input.probe.isAncestor(older, newer));
10005
+ return ancestry.get(key);
9818
10006
  };
9819
- if (provider === "gitlab") {
9820
- out.gitlab = {
9821
- host: prov.host ?? "gitlab.com",
9822
- apiUrl: prov.apiUrl ?? "https://gitlab.com/api/v4"
9823
- };
9824
- } else if (provider === "github") {
9825
- out.github = { host: prov.host ?? "github.com" };
10007
+ const winners = forks.filter((fork) => forks.every((other) => other.track === fork.track || descends(other.sha, fork.sha)));
10008
+ const winningTracks = [...new Set(winners.map((fork) => fork.track))];
10009
+ if (winningTracks.length === 1 && forks.some((fork) => fork.track !== winningTracks[0])) {
10010
+ const win = winners[0];
10011
+ return done(win.track, "ancestry", `${branch} forked from ${win.ref} at ${win.sha.slice(0, 12)}, after every other line's fork point`);
9826
10012
  }
9827
- return out;
10013
+ if (winningTracks.length === 1)
10014
+ return fallback(`only ${label(winningTracks[0])} could be compared with ${branch}`);
10015
+ const newest = forks.filter((fork) => !forks.some((other) => descends(fork.sha, other.sha)));
10016
+ return fallback(`${branch} has the same or unrelated fork points with ${[...new Set(newest.map((fork) => `${fork.ref} (${fork.track.name === IMPLICIT ? "workspace default" : fork.track.name})`))].join(" and ")}`);
9828
10017
  }
9829
-
9830
- // packages/workit-core/src/host-session.ts
9831
- import { createHash as createHash6 } from "node:crypto";
9832
- import fs4 from "node:fs";
9833
- import path7 from "node:path";
9834
-
9835
- // packages/workit-core/src/core/logger.ts
9836
- import os3 from "node:os";
9837
- import path5 from "node:path";
9838
- var SENSITIVE_WORDS = new Set([
9839
- "token",
9840
- "secret",
9841
- "password",
9842
- "passwd",
9843
- "authorization",
9844
- "credential",
9845
- "credentials",
9846
- "cookie",
9847
- "api",
9848
- "apikey",
9849
- "apitoken",
9850
- "accesstoken",
9851
- "clientsecret",
9852
- "refreshtoken",
9853
- "key",
9854
- "bearer",
9855
- "prompt",
9856
- "message",
9857
- "messages",
9858
- "content",
9859
- "body",
9860
- "args",
9861
- "argument",
9862
- "result",
9863
- "results",
9864
- "output",
9865
- "issue",
9866
- "description",
9867
- "summary",
9868
- "payload",
9869
- "script",
9870
- "command",
9871
- "text",
9872
- "query"
9873
- ]);
9874
- var STACK_WORDS = new Set(["stack", "stacktrace", "trace"]);
9875
- var escapeRegExp2 = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
9876
- var resolveStateDir = () => {
9877
- const override = process.env.WORKFLOW_TOOLKIT_STATE;
9878
- if (override)
9879
- return override;
9880
- const home = os3.homedir();
9881
- if (process.env.XDG_STATE_HOME)
9882
- return path5.join(process.env.XDG_STATE_HOME, "workit");
9883
- switch (os3.platform()) {
9884
- case "darwin":
9885
- return path5.join(home, "Library", "Application Support", "workit");
9886
- case "win32":
9887
- return path5.join(process.env.LOCALAPPDATA ?? path5.join(home, "AppData", "Local"), "workit");
9888
- default:
9889
- return path5.join(process.env.HOME ?? home, ".local", "state", "workit");
9890
- }
9891
- };
9892
- var homePattern = new RegExp(`${escapeRegExp2(os3.homedir())}(?=[/\\\\]|$)|\\$HOME(?=[/\\\\]|$)`, "g");
9893
-
9894
- // packages/workit-core/src/store/paths.ts
9895
- import { spawnSync as spawnSync6 } from "node:child_process";
9896
- import { createHash as createHash5 } from "node:crypto";
9897
- import fs3 from "node:fs";
9898
- import path6 from "node:path";
9899
- var NOT_A_REPO = /not a git repository \(or any (of the parent directories|parent up to)/u;
9900
- var gitRun = (cwd, args) => {
10018
+ var git2 = (cwd, args) => {
9901
10019
  const run = spawnSync6("git", args, {
9902
10020
  cwd,
9903
10021
  encoding: "utf8",
9904
10022
  timeout: 20000,
9905
- killSignal: "SIGKILL",
9906
10023
  windowsHide: true,
9907
- env: {
9908
- ...process.env,
9909
- GIT_TERMINAL_PROMPT: "0",
9910
- GIT_OPTIONAL_LOCKS: "0",
9911
- LC_ALL: "C",
9912
- LANGUAGE: "C"
9913
- }
10024
+ stdio: ["ignore", "pipe", "ignore"]
9914
10025
  });
9915
- const error = run.error;
10026
+ return run.status === 0 ? (run.stdout ?? "").trim() : null;
10027
+ };
10028
+ var recordedBaseKey = (branch) => `branch.${branch}.workitBase`;
10029
+ var escapeRegExp = (text) => text.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
10030
+ var reflog = (cwd, ref) => (git2(cwd, ["log", "-g", "--date=unix", "--format=%H%x09%gd%x09%gs", ref, "--"]) ?? "").split(`
10031
+ `).map((line) => {
10032
+ const [sha = "", selector = "", ...subject] = line.split("\t");
9916
10033
  return {
9917
- ok: run.status === 0,
9918
- stdout: run.stdout ?? "",
9919
- stderr: error ? String(error.message) : run.stderr ?? "",
9920
- missing: error?.code === "ENOENT"
10034
+ sha,
10035
+ time: Number(/@\{(\d+)\}$/u.exec(selector)?.[1] ?? Number.NaN),
10036
+ subject: subject.join("\t")
9921
10037
  };
9922
- };
9923
- var shortHash = (value) => createHash5("sha256").update(value).digest("hex").slice(0, 12);
9924
- var real = (file) => {
9925
- try {
9926
- return fs3.realpathSync.native(file);
9927
- } catch {
9928
- return file;
9929
- }
9930
- };
9931
- var DISCOVERY_ENV = [
9932
- "GIT_DIR",
9933
- "GIT_COMMON_DIR",
9934
- "GIT_WORK_TREE",
9935
- "GIT_CEILING_DIRECTORIES",
9936
- "GIT_DISCOVERY_ACROSS_FILESYSTEM"
9937
- ];
9938
- var isGitDir = (dir) => {
9939
- try {
9940
- return fs3.statSync(path6.join(dir, "HEAD")).isFile() && fs3.statSync(path6.join(dir, "objects")).isDirectory() && fs3.statSync(path6.join(dir, "refs")).isDirectory();
9941
- } catch {
9942
- return false;
9943
- }
9944
- };
9945
- var reftableRefs = (common) => {
9946
- try {
9947
- return /^\s*refstorage\s*=\s*reftable\s*$/imu.test(fs3.readFileSync(path6.join(common, "config"), "utf8"));
9948
- } catch {
9949
- return false;
9950
- }
9951
- };
9952
- function fastGit(root) {
9953
- if (DISCOVERY_ENV.some((name) => process.env[name]))
9954
- return null;
9955
- let dir = real(root);
9956
- let device = null;
9957
- for (;; ) {
9958
- let stat;
9959
- try {
9960
- stat = fs3.statSync(dir);
9961
- } catch {
9962
- return null;
9963
- }
9964
- if (device !== null && stat.dev !== device)
9965
- return { kind: "none" };
9966
- device = stat.dev;
9967
- const dotGit = path6.join(dir, ".git");
9968
- let entry = null;
9969
- try {
9970
- entry = fs3.statSync(dotGit);
9971
- } catch {}
9972
- let gitDir = null;
9973
- let top = null;
9974
- if (entry) {
9975
- top = dir;
9976
- if (entry.isDirectory())
9977
- gitDir = dotGit;
9978
- else if (entry.isFile()) {
9979
- let text = "";
9980
- try {
9981
- text = fs3.readFileSync(dotGit, "utf8");
9982
- } catch {
9983
- return null;
9984
- }
9985
- const match = /^gitdir: (.+)$/mu.exec(text);
9986
- if (!match)
9987
- return null;
9988
- gitDir = path6.resolve(dir, match[1].trim());
9989
- } else
9990
- return null;
9991
- if (!fs3.existsSync(path6.join(gitDir, "HEAD")))
9992
- return null;
9993
- } else if (isGitDir(dir))
9994
- gitDir = dir;
9995
- if (gitDir) {
9996
- let common = gitDir;
9997
- try {
9998
- common = path6.resolve(gitDir, fs3.readFileSync(path6.join(gitDir, "commondir"), "utf8").trim());
9999
- } catch {}
10000
- if (!fs3.existsSync(path6.join(common, "objects")))
10038
+ }).filter((entry) => entry.sha && Number.isFinite(entry.time));
10039
+ function gitTrackProbe(cwd) {
10040
+ let remote;
10041
+ let remotes;
10042
+ const tip = (branch) => {
10043
+ remote ??= pushRemoteName(cwd) ?? "origin";
10044
+ return resolveRef(cwd, `refs/remotes/${remote}/${branch}`) ?? resolveRef(cwd, `refs/heads/${branch}`);
10045
+ };
10046
+ return {
10047
+ remotes: () => remotes ??= remoteNames(cwd),
10048
+ recordedBase: (branch) => git2(cwd, ["config", "--get", recordedBaseKey(branch)]) || null,
10049
+ createdFrom: (branch) => {
10050
+ const log = git2(cwd, ["reflog", "show", "--format=%gs", `refs/heads/${branch}`, "--"]);
10051
+ const oldest = log?.split(`
10052
+ `).findLast(Boolean) ?? "";
10053
+ const match = /^branch: Created from (\S+)$/u.exec(oldest);
10054
+ return match && match[1] !== "HEAD" ? match[1] : null;
10055
+ },
10056
+ checkedOutFrom: (branch) => {
10057
+ const own = reflog(cwd, `refs/heads/${branch}`);
10058
+ const created = own.at(-1);
10059
+ if (!created || created.subject !== "branch: Created from HEAD")
10001
10060
  return null;
10002
- return {
10003
- kind: "repo",
10004
- common: real(common),
10005
- gitDir: real(gitDir),
10006
- top: top === null ? null : real(top),
10007
- reftable: reftableRefs(common)
10008
- };
10009
- }
10010
- const parent = path6.dirname(dir);
10011
- if (parent === dir)
10012
- return { kind: "none" };
10013
- dir = parent;
10061
+ const pattern = new RegExp(`^checkout: moving from (\\S+) to ${escapeRegExp(branch)}$`, "u");
10062
+ const entry = reflog(cwd, "HEAD").findLast((item) => item.time >= created.time && item.sha === created.sha && pattern.test(item.subject));
10063
+ return entry ? pattern.exec(entry.subject)?.[1] ?? null : null;
10064
+ },
10065
+ mergeBase: (branch, trackBranch) => {
10066
+ const base = tip(trackBranch);
10067
+ return base && resolveRef(cwd, `refs/heads/${branch}`) ? mergeBase(cwd, base, `refs/heads/${branch}`) : null;
10068
+ },
10069
+ isAncestor: (ancestor, descendant) => git2(cwd, ["merge-base", "--is-ancestor", ancestor, descendant]) !== null
10070
+ };
10071
+ }
10072
+ function workspaceReleaseTracks(cwd) {
10073
+ const raw = resolveRuntimeReleaseTracks(cwd);
10074
+ return { workspace: raw?.workspace ?? null, ...readReleaseTracks(raw?.releaseTracks) };
10075
+ }
10076
+ var memo2 = new Map;
10077
+ var MEMO_LIMIT = 64;
10078
+ function resolveTrackFor(cwd, options) {
10079
+ const read = workspaceReleaseTracks(cwd);
10080
+ if (read.error)
10081
+ return { status: "invalid", error: read.error, issues: read.issues };
10082
+ if (read.tracks.length === 0 && !options.track?.trim())
10083
+ return { status: "none", issues: read.issues };
10084
+ const branch = options.branch === undefined ? currentBranch(cwd) : options.branch;
10085
+ const envRequested = process.env.WORKFLOW_RELEASE_TRACK?.trim() || null;
10086
+ const input = {
10087
+ tracks: read.tracks,
10088
+ requested: options.track ?? null,
10089
+ envRequested,
10090
+ branch,
10091
+ defaultBranch: options.defaultBranch,
10092
+ issues: read.issues
10093
+ };
10094
+ let key = null;
10095
+ if (!options.probe) {
10096
+ const refs = git2(cwd, [
10097
+ "for-each-ref",
10098
+ "--format=%(refname) %(objectname)",
10099
+ "refs/heads",
10100
+ "refs/remotes"
10101
+ ]) ?? "";
10102
+ const head = git2(cwd, ["reflog", "show", "-n", "1", "--format=%H %gs", "HEAD", "--"]) ?? "";
10103
+ const own = branch ? [
10104
+ git2(cwd, [
10105
+ "log",
10106
+ "-g",
10107
+ "-n",
10108
+ "1",
10109
+ "--date=unix",
10110
+ "--format=%H %gd %gs",
10111
+ `refs/heads/${branch}`,
10112
+ "--"
10113
+ ]) ?? "",
10114
+ git2(cwd, ["config", "--get", recordedBaseKey(branch)]) ?? ""
10115
+ ] : [];
10116
+ key = createHash5("sha256").update(JSON.stringify({ cwd, input, refs, head, own })).digest("hex");
10117
+ const hit = memo2.get(key);
10118
+ if (hit)
10119
+ return hit;
10014
10120
  }
10015
- }
10016
- function resolveStore(root) {
10017
- root = path6.resolve(root);
10018
- let directory = false;
10019
- try {
10020
- directory = fs3.statSync(root).isDirectory();
10021
- } catch {}
10022
- if (!directory)
10023
- return { dir: path6.join(root, ".workit"), shared: false, top: null };
10024
- const fast = fastGit(root);
10025
- if (fast?.kind === "none")
10026
- return { dir: path6.join(root, ".workit"), shared: false, top: null };
10027
- if (fast?.kind === "repo")
10028
- return {
10029
- dir: path6.join(fast.common, "workit"),
10030
- shared: true,
10031
- top: fast.top,
10032
- git: { gitDir: fast.gitDir, top: fast.top, reftable: fast.reftable }
10033
- };
10034
- const run = gitRun(root, ["rev-parse", "--path-format=absolute", "--git-common-dir"]);
10035
- const common = run.stdout.trim();
10036
- if (run.ok && common) {
10037
- const top = gitRun(root, ["rev-parse", "--path-format=absolute", "--show-toplevel"]);
10038
- return {
10039
- dir: path6.join(real(path6.resolve(root, common)), "workit"),
10040
- shared: true,
10041
- top: top.ok && top.stdout.trim() ? real(top.stdout.trim()) : null
10042
- };
10121
+ const resolution = resolveReleaseTrack({ ...input, probe: options.probe ?? gitTrackProbe(cwd) });
10122
+ if (key) {
10123
+ if (memo2.size >= MEMO_LIMIT)
10124
+ memo2.delete(memo2.keys().next().value);
10125
+ memo2.set(key, resolution);
10043
10126
  }
10044
- if (run.missing || !run.ok && NOT_A_REPO.test(run.stderr))
10045
- return { dir: path6.join(root, ".workit"), shared: false, top: null };
10046
- return new Error(`cannot locate the workit store: git rev-parse failed (${run.stderr.trim().split(`
10047
- `)[0] || "no output"}); fix the repository (git status should work here)`);
10127
+ return resolution;
10048
10128
  }
10049
- var rebasingBranch = (gitDir) => {
10050
- for (const state of ["rebase-merge", "rebase-apply"]) {
10051
- try {
10052
- const ref = fs3.readFileSync(path6.join(gitDir, state, "head-name"), "utf8").trim();
10053
- const match = /^refs\/heads\/(.+)$/u.exec(ref);
10054
- if (match)
10055
- return match[1];
10056
- } catch {}
10057
- }
10129
+
10130
+ // packages/workit-core/src/core/vcs-config.ts
10131
+ var workspacesPath = () => path5.join(configDir(), "workspaces.json");
10132
+ var vcsCwd = (cwd) => cwd ?? process.env.WORKFLOW_WORKSPACE_ROOT ?? process.cwd();
10133
+ var remoteProvider = (cwd) => {
10134
+ const r = spawnSync7("git", ["remote", "get-url", "origin"], { cwd, encoding: "utf8" });
10135
+ if (r.status !== 0)
10136
+ return null;
10137
+ const url = (r.stdout ?? "").trim();
10138
+ if (/github\.com[:/]/.test(url))
10139
+ return "github";
10140
+ if (/gitlab\.com[:/]/.test(url))
10141
+ return "gitlab";
10058
10142
  return null;
10059
10143
  };
10060
- function resolveTaskKey(root, location) {
10061
- if (!location.shared)
10062
- return { key: `dir-${shortHash(root)}`, kind: "dir", branch: null };
10063
- const branchKey = (name) => ({ key: name, kind: "branch", branch: name });
10064
- if (location.git && !location.git.reftable) {
10065
- let head = "";
10066
- try {
10067
- head = fs3.readFileSync(path6.join(location.git.gitDir, "HEAD"), "utf8").trim();
10068
- } catch {}
10069
- const ref = /^ref: refs\/heads\/(.+)$/u.exec(head);
10070
- if (ref && ref[1] !== ".invalid")
10071
- return branchKey(ref[1]);
10072
- if (/^[0-9a-f]{40,64}$/u.test(head)) {
10073
- const rebasing = rebasingBranch(location.git.gitDir);
10074
- if (rebasing)
10075
- return branchKey(rebasing);
10076
- return {
10077
- key: `detached-${shortHash(location.git.top ?? location.git.gitDir)}`,
10078
- kind: "detached",
10079
- branch: null
10080
- };
10081
- }
10082
- }
10083
- const branch = gitRun(root, ["symbolic-ref", "-q", "--short", "HEAD"]);
10084
- const name = branch.ok ? branch.stdout.trim() : "";
10085
- if (name && name !== ".invalid")
10086
- return branchKey(name);
10087
- const gitDir = gitRun(root, ["rev-parse", "--path-format=absolute", "--git-dir"]);
10088
- if (gitDir.ok && gitDir.stdout.trim()) {
10089
- const rebasing = rebasingBranch(gitDir.stdout.trim());
10090
- if (rebasing)
10091
- return branchKey(rebasing);
10092
- }
10093
- const top = gitRun(root, ["rev-parse", "--path-format=absolute", "--show-toplevel"]);
10094
- const worktree = top.ok && top.stdout.trim() ? top.stdout.trim() : root;
10095
- return { key: `detached-${shortHash(real(worktree))}`, kind: "detached", branch: null };
10096
- }
10097
- function checkoutRootOf(root, location) {
10098
- const resolved = path6.resolve(root);
10099
- let canonical = resolved;
10144
+ var readVcsConfig = () => {
10145
+ const file = vcsConfigPath();
10146
+ let raw;
10100
10147
  try {
10101
- canonical = fs3.realpathSync(resolved);
10148
+ raw = fs4.readFileSync(file, "utf8");
10102
10149
  } catch {
10103
- return resolved;
10150
+ return { status: "missing", path: file, config: {} };
10104
10151
  }
10105
- const found = location ?? resolveStore(canonical);
10106
- if (found instanceof Error || !found.shared || !found.top)
10107
- return canonical;
10108
- if (real(canonical) === found.top)
10109
- return canonical;
10152
+ let parsed;
10110
10153
  try {
10111
- return fs3.realpathSync(found.top);
10154
+ parsed = JSON.parse(raw);
10112
10155
  } catch {
10113
- return canonical;
10156
+ return { status: "malformed", path: file, config: {}, error: `${file} is not valid JSON` };
10114
10157
  }
10115
- }
10116
- function branchRenames(location) {
10117
- if (!location.shared)
10118
- return [];
10119
- const files = [];
10120
- const walk = (dir) => {
10121
- let entries;
10122
- try {
10123
- entries = fs3.readdirSync(dir, { withFileTypes: true });
10124
- } catch {
10125
- return;
10126
- }
10127
- for (const entry of entries)
10128
- if (entry.isDirectory())
10129
- walk(path6.join(dir, entry.name));
10130
- else
10131
- files.push(path6.join(dir, entry.name));
10158
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
10159
+ return { status: "valid", path: file, config: parsed };
10160
+ }
10161
+ return { status: "malformed", path: file, config: {}, error: `${file} is not a JSON object` };
10162
+ };
10163
+ var trackSummary = (resolution) => resolution.status === "resolved" ? {
10164
+ name: resolution.track?.name ?? null,
10165
+ source: resolution.source,
10166
+ detail: resolution.detail,
10167
+ warnings: resolution.warnings,
10168
+ track: resolution.track,
10169
+ tracks: resolution.tracks,
10170
+ blocking: resolution.blocking
10171
+ } : null;
10172
+ function vcsConfig(mode, cwd, options = {}) {
10173
+ const ws = resolveRuntimeWorkspaceVcs(vcsCwd(cwd));
10174
+ const wsVcs = ws?.vcs ?? {};
10175
+ const wsYt = ws?.youtrack ?? {};
10176
+ const wsIssues = ws?.issues ?? {};
10177
+ const { status: cfgStatus, path: cfgPath, config: cfg, error: cfgError } = readVcsConfig();
10178
+ const root = vcsCwd(cwd);
10179
+ const determinateRoot = cwd !== undefined || process.env.WORKFLOW_WORKSPACE_ROOT !== undefined;
10180
+ const rawProvider = wsVcs.provider ?? (determinateRoot ? remoteProvider(root) : null) ?? cfg.provider ?? null;
10181
+ const provider = rawProvider === null || rawProvider === undefined ? null : String(rawProvider).toLowerCase() || null;
10182
+ const workspaceDefault = workspaceDefaultTarget(root, cfg.defaultTargetBranch);
10183
+ const linkIssues = typeof wsYt.link_issues === "boolean" ? wsYt.link_issues : null;
10184
+ const youtrackBaseUrl = typeof wsYt.baseUrl === "string" ? wsYt.baseUrl : null;
10185
+ let issuesProvider = null;
10186
+ let linkOnPr = null;
10187
+ if (provider === "github" && typeof wsIssues.provider === "string" && wsIssues.provider.toLowerCase() === "github") {
10188
+ issuesProvider = "github";
10189
+ linkOnPr = typeof wsIssues.link_on_pr === "boolean" ? wsIssues.link_on_pr : null;
10190
+ }
10191
+ if (mode === "resolve") {
10192
+ if (cfgStatus === "malformed")
10193
+ return { ok: false, error: cfgError, configPath: cfgPath };
10194
+ const trackResolution = resolveTrackFor(root, {
10195
+ track: options.track,
10196
+ ...options.branch !== undefined ? { branch: options.branch } : {},
10197
+ defaultBranch: workspaceDefault
10198
+ });
10199
+ if (trackResolution.status === "invalid")
10200
+ return { ok: false, error: trackResolution.error, configPath: workspacesPath() };
10201
+ const releaseTrack = trackSummary(trackResolution);
10202
+ const defaultTarget = releaseTrack?.track?.pullRequestTarget ?? workspaceDefault;
10203
+ const baseBranch = releaseTrack?.track?.baseBranch ?? defaultTarget;
10204
+ return {
10205
+ ok: true,
10206
+ workspace_name: ws?.name ?? null,
10207
+ provider,
10208
+ defaultTargetBranch: defaultTarget,
10209
+ workspaceDefaultTargetBranch: workspaceDefault,
10210
+ baseBranch,
10211
+ releaseTrack,
10212
+ link_issues: linkIssues,
10213
+ youtrack_base_url: youtrackBaseUrl,
10214
+ issues_provider: issuesProvider,
10215
+ link_on_pr: linkOnPr
10216
+ };
10217
+ }
10218
+ if (cfgStatus === "malformed") {
10219
+ return { ok: false, error: cfgError, configPath: cfgPath };
10220
+ }
10221
+ if (cfgStatus === "missing" && provider === null)
10222
+ return { ok: false, error: `vcs.json is missing: ${cfgPath}`, configPath: cfgPath };
10223
+ if (provider === null) {
10224
+ return {
10225
+ ok: false,
10226
+ error: `no vcs provider configured (set vcs.provider in ${cfgPath}, match a workspace entry, or run inside a checkout with a recognized origin remote)`,
10227
+ configPath: cfgPath
10228
+ };
10229
+ }
10230
+ const prov = cfg[provider] ?? {};
10231
+ const out = {
10232
+ ok: true,
10233
+ configPath: path5.resolve(cfgPath),
10234
+ provider,
10235
+ defaultTargetBranch: workspaceDefault,
10236
+ pr: cfg.pr ?? {},
10237
+ workspace_name: ws?.name ?? null,
10238
+ link_issues: linkIssues,
10239
+ youtrack_base_url: youtrackBaseUrl,
10240
+ issues_provider: issuesProvider,
10241
+ link_on_pr: linkOnPr
10132
10242
  };
10133
- walk(path6.join(path6.dirname(location.dir), "logs", "refs", "heads"));
10134
- if (location.git)
10135
- files.push(path6.join(location.git.gitDir, "logs", "HEAD"));
10136
- const seen = new Set;
10137
- const renames = [];
10138
- for (const file of files) {
10139
- let text = "";
10140
- try {
10141
- text = fs3.readFileSync(file, "utf8");
10142
- } catch {
10143
- continue;
10144
- }
10145
- for (const match of text.matchAll(/ (\d+) [+-]\d{4}\tBranch: renamed refs\/heads\/(\S+) to refs\/heads\/(\S+)/gu)) {
10146
- const rename = { from: match[2], to: match[3], at: Number(match[1]) };
10147
- const id = `${rename.at}\x00${rename.from}\x00${rename.to}`;
10148
- if (seen.has(id))
10149
- continue;
10150
- seen.add(id);
10151
- renames.push(rename);
10152
- }
10243
+ if (provider === "gitlab") {
10244
+ out.gitlab = {
10245
+ host: prov.host ?? "gitlab.com",
10246
+ apiUrl: prov.apiUrl ?? "https://gitlab.com/api/v4"
10247
+ };
10248
+ } else if (provider === "github") {
10249
+ out.github = { host: prov.host ?? "github.com" };
10250
+ }
10251
+ return out;
10252
+ }
10253
+
10254
+ // packages/workit-core/src/host-session.ts
10255
+ import { createHash as createHash6, randomBytes, timingSafeEqual } from "node:crypto";
10256
+ import fs5 from "node:fs";
10257
+ import path7 from "node:path";
10258
+
10259
+ // packages/workit-core/src/core/logger.ts
10260
+ import os3 from "node:os";
10261
+ import path6 from "node:path";
10262
+ var SENSITIVE_WORDS = new Set([
10263
+ "token",
10264
+ "secret",
10265
+ "password",
10266
+ "passwd",
10267
+ "authorization",
10268
+ "credential",
10269
+ "credentials",
10270
+ "cookie",
10271
+ "api",
10272
+ "apikey",
10273
+ "apitoken",
10274
+ "accesstoken",
10275
+ "clientsecret",
10276
+ "refreshtoken",
10277
+ "key",
10278
+ "bearer",
10279
+ "prompt",
10280
+ "message",
10281
+ "messages",
10282
+ "content",
10283
+ "body",
10284
+ "args",
10285
+ "argument",
10286
+ "result",
10287
+ "results",
10288
+ "output",
10289
+ "issue",
10290
+ "description",
10291
+ "summary",
10292
+ "payload",
10293
+ "script",
10294
+ "command",
10295
+ "text",
10296
+ "query"
10297
+ ]);
10298
+ var STACK_WORDS = new Set(["stack", "stacktrace", "trace"]);
10299
+ var escapeRegExp2 = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
10300
+ var resolveStateDir = () => {
10301
+ const override = process.env.WORKFLOW_TOOLKIT_STATE;
10302
+ if (override)
10303
+ return override;
10304
+ const home = os3.homedir();
10305
+ if (process.env.XDG_STATE_HOME)
10306
+ return path6.join(process.env.XDG_STATE_HOME, "workit");
10307
+ switch (os3.platform()) {
10308
+ case "darwin":
10309
+ return path6.join(home, "Library", "Application Support", "workit");
10310
+ case "win32":
10311
+ return path6.join(process.env.LOCALAPPDATA ?? path6.join(home, "AppData", "Local"), "workit");
10312
+ default:
10313
+ return path6.join(process.env.HOME ?? home, ".local", "state", "workit");
10153
10314
  }
10154
- return renames.toSorted((left, right) => left.at - right.at);
10155
- }
10156
- function followRenames(key, boundAt, renames) {
10157
- let current = key;
10158
- let since = Math.floor(Date.parse(boundAt) / 1000);
10159
- for (const rename of renames)
10160
- if (rename.from === current && rename.at >= since) {
10161
- current = rename.to;
10162
- since = rename.at;
10163
- }
10164
- return current;
10165
- }
10166
- var checkoutSlug = (root) => {
10167
- let canonical = real(root);
10168
- if (process.platform === "win32")
10169
- canonical = canonical.toLowerCase();
10170
- const base = path6.basename(canonical).replace(/[^A-Za-z0-9._-]/g, "_").slice(0, 40) || "root";
10171
- return `${base}-${shortHash(canonical)}`;
10172
10315
  };
10316
+ var homePattern = new RegExp(`${escapeRegExp2(os3.homedir())}(?=[/\\\\]|$)|\\$HOME(?=[/\\\\]|$)`, "g");
10173
10317
 
10174
10318
  // packages/workit-core/src/host-session.ts
10175
10319
  var HOST_SESSION_VARS = [
@@ -10200,9 +10344,10 @@ function hostSessionFromEnv(env) {
10200
10344
  return inner ? { host: inner.host, session: inner.session } : { host: declared, session: null };
10201
10345
  }
10202
10346
  var ISSUED_PER_LEAD = 64;
10203
- var ISSUED_SESSIONS_MARKER = "workit-issued-sessions/2";
10347
+ var ISSUED_SESSIONS_MARKER = "workit-issued-sessions/3";
10204
10348
  var hash = (value) => createHash6("sha256").update(value).digest("hex").slice(0, 32);
10205
10349
  var userIssuedRoot = () => path7.join(resolveStateDir(), "issued");
10350
+ var userTokenRoot = () => path7.join(resolveStateDir(), "issued-tokens");
10206
10351
  function migrateRepoIssued(cwd) {
10207
10352
  if (cwd === undefined)
10208
10353
  return;
@@ -10212,7 +10357,7 @@ function migrateRepoIssued(cwd) {
10212
10357
  if (location instanceof Error)
10213
10358
  return;
10214
10359
  old = path7.join(location.dir, "hooks", "issued");
10215
- if (!fs4.existsSync(old))
10360
+ if (!fs5.existsSync(old))
10216
10361
  return;
10217
10362
  } catch {
10218
10363
  return;
@@ -10220,13 +10365,18 @@ function migrateRepoIssued(cwd) {
10220
10365
  for (const lead of safeList(old)) {
10221
10366
  try {
10222
10367
  const from = path7.join(old, lead);
10223
- if (!fs4.statSync(from).isDirectory())
10368
+ if (!fs5.statSync(from).isDirectory())
10224
10369
  continue;
10225
10370
  const to = path7.join(userIssuedRoot(), lead);
10226
- fs4.mkdirSync(to, { recursive: true, mode: 448 });
10227
- for (const id of fs4.readdirSync(from)) {
10228
- fs4.copyFileSync(path7.join(from, id), path7.join(to, id));
10229
- fs4.rmSync(path7.join(from, id), { force: true });
10371
+ fs5.mkdirSync(to, { recursive: true, mode: 448 });
10372
+ for (const id of fs5.readdirSync(from)) {
10373
+ try {
10374
+ fs5.copyFileSync(path7.join(from, id), path7.join(to, id), fs5.constants.COPYFILE_EXCL);
10375
+ } catch (error) {
10376
+ if (error.code !== "EEXIST")
10377
+ throw error;
10378
+ }
10379
+ fs5.rmSync(path7.join(from, id), { force: true });
10230
10380
  }
10231
10381
  removeIfEmpty(from);
10232
10382
  } catch {}
@@ -10235,31 +10385,81 @@ function migrateRepoIssued(cwd) {
10235
10385
  }
10236
10386
  var safeList = (dir) => {
10237
10387
  try {
10238
- return fs4.readdirSync(dir);
10388
+ return fs5.readdirSync(dir);
10239
10389
  } catch {
10240
10390
  return [];
10241
10391
  }
10242
10392
  };
10243
10393
  var removeIfEmpty = (dir) => {
10244
10394
  try {
10245
- fs4.rmdirSync(dir);
10395
+ fs5.rmdirSync(dir);
10246
10396
  } catch {}
10247
10397
  };
10398
+ var tokenHash = (token) => createHash6("sha256").update(token).digest("hex");
10248
10399
  function recordIssuedSession(cwd, lead, issued) {
10400
+ const token = randomBytes(18).toString("base64url");
10249
10401
  try {
10250
10402
  migrateRepoIssued(cwd);
10251
- const dir = path7.join(userIssuedRoot(), hash(lead));
10252
- fs4.mkdirSync(dir, { recursive: true, mode: 448 });
10253
10403
  const own = hash(issued);
10254
- fs4.writeFileSync(path7.join(dir, own), ISSUED_SESSIONS_MARKER);
10255
- const entries = fs4.readdirSync(dir);
10404
+ const tokens = path7.join(userTokenRoot(), hash(lead));
10405
+ fs5.mkdirSync(tokens, { recursive: true, mode: 448 });
10406
+ fs5.writeFileSync(path7.join(tokens, own), `${tokenHash(token)}
10407
+ `, { mode: 384, flag: "wx" });
10408
+ const dir = path7.join(userIssuedRoot(), hash(lead));
10409
+ fs5.mkdirSync(dir, { recursive: true, mode: 448 });
10410
+ fs5.writeFileSync(path7.join(dir, own), `${ISSUED_SESSIONS_MARKER}
10411
+ `, { mode: 384 });
10412
+ pruneIssued(dir, own, tokens);
10413
+ return token;
10414
+ } catch {
10415
+ return null;
10416
+ }
10417
+ }
10418
+ function pruneIssued(dir, own, tokens) {
10419
+ try {
10420
+ const entries = fs5.readdirSync(dir);
10256
10421
  if (entries.length <= ISSUED_PER_LEAD)
10257
10422
  return;
10258
- const byAge = entries.filter((name) => name !== own).map((name) => ({ name, at: fs4.statSync(path7.join(dir, name)).mtimeMs })).toSorted((a, b) => a.at - b.at);
10259
- for (const { name } of byAge.slice(0, entries.length - ISSUED_PER_LEAD))
10260
- fs4.rmSync(path7.join(dir, name), { force: true });
10423
+ const byAge = entries.filter((name) => name !== own).map((name) => ({ name, at: fs5.statSync(path7.join(dir, name)).mtimeMs })).toSorted((a, b) => a.at - b.at);
10424
+ for (const { name } of byAge.slice(0, entries.length - ISSUED_PER_LEAD)) {
10425
+ fs5.rmSync(path7.join(dir, name), { force: true });
10426
+ fs5.rmSync(path7.join(tokens, name), { force: true });
10427
+ }
10261
10428
  } catch {}
10262
10429
  }
10430
+ var issuedRecord = (cwd, claimed) => {
10431
+ const cut = claimed.lastIndexOf(":");
10432
+ if (cut <= 0)
10433
+ return null;
10434
+ migrateRepoIssued(cwd);
10435
+ try {
10436
+ return fs5.readFileSync(path7.join(userIssuedRoot(), hash(claimed.slice(0, cut)), hash(claimed)), "utf8");
10437
+ } catch {
10438
+ return null;
10439
+ }
10440
+ };
10441
+ var TOKEN_HASH = /^([0-9a-f]{64})$/mu;
10442
+ var issuedToken = (claimed) => {
10443
+ const cut = claimed.lastIndexOf(":");
10444
+ if (cut <= 0)
10445
+ return { stored: null, leadBound: false };
10446
+ const dir = path7.join(userTokenRoot(), hash(claimed.slice(0, cut)));
10447
+ try {
10448
+ const text = fs5.readFileSync(path7.join(dir, hash(claimed)), "utf8");
10449
+ return { stored: TOKEN_HASH.exec(text)?.[1] ?? null, leadBound: true };
10450
+ } catch {
10451
+ return { stored: null, leadBound: safeList(dir).length > 0 };
10452
+ }
10453
+ };
10454
+ function issuedBinding(cwd, claimed) {
10455
+ const record = issuedRecord(cwd, claimed);
10456
+ const { stored, leadBound } = issuedToken(claimed);
10457
+ if (stored)
10458
+ return "token";
10459
+ if (record === null)
10460
+ return null;
10461
+ return leadBound ? "token" : "legacy";
10462
+ }
10263
10463
  var OWN_CHILD_SHELL = new Set(["opencode", "pi"]);
10264
10464
 
10265
10465
  // packages/workit-core/src/ledger.ts
@@ -10276,7 +10476,7 @@ var err = (code, error, unblock) => ({
10276
10476
  ...unblock ? { unblock } : {}
10277
10477
  });
10278
10478
  var gitRun2 = (cwd, args, timeoutMs = GIT_TIMEOUTS.local) => {
10279
- const run = spawnSync7("git", args, {
10479
+ const run = spawnSync8("git", args, {
10280
10480
  cwd,
10281
10481
  encoding: "utf8",
10282
10482
  timeout: timeoutMs,
@@ -10388,7 +10588,7 @@ function ledgerPath(cwd) {
10388
10588
  const root = storeRoot(cwd);
10389
10589
  return root.ok ? { ok: true, value: path8.join(root.value.root, "ledger", "ledger.jsonl") } : root;
10390
10590
  }
10391
- var newId2 = () => `${Date.now().toString(36)}-${randomBytes(6).toString("hex")}`;
10591
+ var newId2 = () => `${Date.now().toString(36)}-${randomBytes2(6).toString("hex")}`;
10392
10592
  var NETWORK_FS_TYPES = new Set([
10393
10593
  26985,
10394
10594
  20859,
@@ -10406,7 +10606,7 @@ var onNetworkFs = (dir) => {
10406
10606
  if (process.platform !== "linux")
10407
10607
  return false;
10408
10608
  try {
10409
- return isNetworkFsType(fs5.statfsSync(dir).type);
10609
+ return isNetworkFsType(fs6.statfsSync(dir).type);
10410
10610
  } catch {
10411
10611
  return false;
10412
10612
  }
@@ -10416,7 +10616,7 @@ var LOCK_STALE_MS = 30000;
10416
10616
  var LOCK_OWNER = "owner";
10417
10617
  var lockToken = (lock) => {
10418
10618
  try {
10419
- return fs5.readFileSync(path8.join(lock, LOCK_OWNER), "utf8");
10619
+ return fs6.readFileSync(path8.join(lock, LOCK_OWNER), "utf8");
10420
10620
  } catch {
10421
10621
  return null;
10422
10622
  }
@@ -10424,7 +10624,7 @@ var lockToken = (lock) => {
10424
10624
  var lockAgeMs = (lock) => {
10425
10625
  for (const target of [path8.join(lock, LOCK_OWNER), lock]) {
10426
10626
  try {
10427
- return Date.now() - fs5.statSync(target).mtimeMs;
10627
+ return Date.now() - fs6.statSync(target).mtimeMs;
10428
10628
  } catch {}
10429
10629
  }
10430
10630
  return null;
@@ -10432,55 +10632,55 @@ var lockAgeMs = (lock) => {
10432
10632
  var ledgerLock = {
10433
10633
  acquire(lock) {
10434
10634
  try {
10435
- fs5.mkdirSync(lock);
10635
+ fs6.mkdirSync(lock);
10436
10636
  } catch (error) {
10437
10637
  if (error.code === "EEXIST")
10438
10638
  return null;
10439
10639
  throw error;
10440
10640
  }
10441
- const token = `${process.pid}-${randomBytes(8).toString("hex")}`;
10442
- fs5.writeFileSync(path8.join(lock, LOCK_OWNER), token);
10641
+ const token = `${process.pid}-${randomBytes2(8).toString("hex")}`;
10642
+ fs6.writeFileSync(path8.join(lock, LOCK_OWNER), token);
10443
10643
  return token;
10444
10644
  },
10445
10645
  release(lock, token) {
10446
10646
  if (lockToken(lock) !== token)
10447
10647
  return false;
10448
- const tomb = `${lock}.release-${randomBytes(6).toString("hex")}`;
10648
+ const tomb = `${lock}.release-${randomBytes2(6).toString("hex")}`;
10449
10649
  try {
10450
- fs5.renameSync(lock, tomb);
10650
+ fs6.renameSync(lock, tomb);
10451
10651
  } catch {
10452
10652
  return false;
10453
10653
  }
10454
10654
  if (lockToken(tomb) !== token) {
10455
10655
  try {
10456
- fs5.renameSync(tomb, lock);
10656
+ fs6.renameSync(tomb, lock);
10457
10657
  } catch {}
10458
10658
  return false;
10459
10659
  }
10460
- fs5.rmSync(tomb, { recursive: true, force: true });
10660
+ fs6.rmSync(tomb, { recursive: true, force: true });
10461
10661
  return true;
10462
10662
  },
10463
10663
  token: lockToken,
10464
10664
  reap(lock, observed) {
10465
- const tomb = `${lock}.reap-${randomBytes(6).toString("hex")}`;
10665
+ const tomb = `${lock}.reap-${randomBytes2(6).toString("hex")}`;
10466
10666
  try {
10467
- fs5.renameSync(lock, tomb);
10667
+ fs6.renameSync(lock, tomb);
10468
10668
  } catch {
10469
10669
  return false;
10470
10670
  }
10471
10671
  if (lockToken(tomb) === observed) {
10472
- fs5.rmSync(tomb, { recursive: true, force: true });
10672
+ fs6.rmSync(tomb, { recursive: true, force: true });
10473
10673
  return true;
10474
10674
  }
10475
10675
  try {
10476
- fs5.renameSync(tomb, lock);
10676
+ fs6.renameSync(tomb, lock);
10477
10677
  } catch {
10478
- fs5.rmSync(tomb, { recursive: true, force: true });
10678
+ fs6.rmSync(tomb, { recursive: true, force: true });
10479
10679
  }
10480
10680
  return false;
10481
10681
  }
10482
10682
  };
10483
- function withLedgerLock(file, fn, waitMs = LOCK_WAIT_MS) {
10683
+ function withLedgerLock(file, fn, waitMs = hostAwareLockTimeout(LOCK_WAIT_MS)) {
10484
10684
  const lock = `${file}.lock`;
10485
10685
  const deadline = Date.now() + waitMs;
10486
10686
  let token = null;
@@ -10509,7 +10709,7 @@ var TAIL_BYTES = 64 * 1024;
10509
10709
  var lastLine = (fd, size) => {
10510
10710
  for (let span = Math.min(size, TAIL_BYTES);span > 0; span = Math.min(size, span * 2)) {
10511
10711
  const buffer = Buffer.alloc(span);
10512
- fs5.readSync(fd, buffer, 0, span, size - span);
10712
+ fs6.readSync(fd, buffer, 0, span, size - span);
10513
10713
  const end = buffer.lastIndexOf(10);
10514
10714
  if (end < 0) {
10515
10715
  if (span === size)
@@ -10538,15 +10738,23 @@ var lastLine = (fd, size) => {
10538
10738
  }
10539
10739
  return null;
10540
10740
  };
10741
+ var tighten = (fd) => {
10742
+ try {
10743
+ if ((fs6.fstatSync(fd).mode & 63) !== 0)
10744
+ fs6.fchmodSync(fd, 384);
10745
+ } catch {}
10746
+ };
10541
10747
  function appendLine(file, build, options) {
10542
10748
  const write = () => {
10543
- const fd = fs5.openSync(file, "a+");
10749
+ const fd = fs6.openSync(file, "a+", 384);
10544
10750
  try {
10545
- const size = fs5.fstatSync(fd).size;
10751
+ if (process.platform !== "win32")
10752
+ tighten(fd);
10753
+ const size = fs6.fstatSync(fd).size;
10546
10754
  let fence = "";
10547
10755
  if (size > 0) {
10548
10756
  const last = Buffer.alloc(1);
10549
- fs5.readSync(fd, last, 0, 1, size - 1);
10757
+ fs6.readSync(fd, last, 0, 1, size - 1);
10550
10758
  if (last[0] !== 10)
10551
10759
  fence = `
10552
10760
  `;
@@ -10555,16 +10763,16 @@ function appendLine(file, build, options) {
10555
10763
  if (!line.ok)
10556
10764
  return line;
10557
10765
  const bytes = Buffer.from(fence + line.value);
10558
- const written = fs5.writeSync(fd, bytes);
10766
+ const written = fs6.writeSync(fd, bytes);
10559
10767
  if (written !== bytes.length)
10560
10768
  return err("unavailable", `short write to ${file}: ${written} of ${bytes.length} bytes; the row is torn and will be skipped`);
10561
10769
  return { ok: true, value: undefined };
10562
10770
  } finally {
10563
- fs5.closeSync(fd);
10771
+ fs6.closeSync(fd);
10564
10772
  }
10565
10773
  };
10566
10774
  try {
10567
- fs5.mkdirSync(path8.dirname(file), { recursive: true });
10775
+ fs6.mkdirSync(path8.dirname(file), { recursive: true });
10568
10776
  if (!options.forceLock && !onNetworkFs(path8.dirname(file)))
10569
10777
  return write();
10570
10778
  const locked = withLedgerLock(file, write);
@@ -10636,8 +10844,10 @@ function readLedger(cwd, options = {}) {
10636
10844
  if (!file.ok)
10637
10845
  return file;
10638
10846
  let text = "";
10847
+ let stat = null;
10639
10848
  try {
10640
- text = fs5.readFileSync(file.value, "utf8");
10849
+ stat = fs6.statSync(file.value);
10850
+ text = fs6.readFileSync(file.value, "utf8");
10641
10851
  } catch (error) {
10642
10852
  if (error.code === "ENOENT")
10643
10853
  return {
@@ -10656,6 +10866,7 @@ function readLedger(cwd, options = {}) {
10656
10866
  const integrity = { chained: 0, legacy: 0, unverified: [] };
10657
10867
  const earlier = new Set([null]);
10658
10868
  let chainStarted = false;
10869
+ const ids = new Set;
10659
10870
  for (const raw of text.split(`
10660
10871
  `)) {
10661
10872
  const trimmed = raw.trim();
@@ -10689,6 +10900,10 @@ function readLedger(cwd, options = {}) {
10689
10900
  reason = "unsigned";
10690
10901
  else
10691
10902
  rowIntegrity = "legacy";
10903
+ if (options.integrity && !reason && ids.has(parsed.id))
10904
+ reason = "duplicate_id";
10905
+ if (options.integrity)
10906
+ ids.add(parsed.id);
10692
10907
  if (reason)
10693
10908
  rowIntegrity = "unverified";
10694
10909
  const pr = typeof parsed.pr === "number" && Number.isInteger(parsed.pr) ? parsed.pr : undefined;
@@ -10712,7 +10927,8 @@ function readLedger(cwd, options = {}) {
10712
10927
  seq: rows.length + 1,
10713
10928
  superseded: false,
10714
10929
  supersedeIgnored: false,
10715
- ...rowIntegrity ? { integrity: rowIntegrity } : {}
10930
+ ...rowIntegrity ? { integrity: rowIntegrity } : {},
10931
+ ...reason ? { integrityReason: reason } : {}
10716
10932
  };
10717
10933
  rows.push(row);
10718
10934
  if (!options.integrity)
@@ -10740,18 +10956,48 @@ function readLedger(cwd, options = {}) {
10740
10956
  const byId = new Map;
10741
10957
  for (const row of rows) {
10742
10958
  const target = row.supersedes ? byId.get(row.supersedes) : undefined;
10743
- if (target && supersedeAllowed(row, target, renameAwareBranch(scopeOf, row.branch)) === null)
10959
+ if (target && row.integrity !== "unverified" && supersedeAllowed(row, target, renameAwareBranch(scopeOf, row.branch)) === null)
10744
10960
  target.superseded = true;
10745
10961
  else if (row.supersedes)
10746
10962
  row.supersedeIgnored = true;
10747
10963
  if (!byId.has(row.id))
10748
10964
  byId.set(row.id, row);
10749
10965
  }
10966
+ if (options.integrity && stat && Buffer.byteLength(text) === stat.size)
10967
+ tamperCache.set(file.value, {
10968
+ size: stat.size,
10969
+ stamp: fileStamp(stat),
10970
+ breaks: integrity.unverified.filter((row) => TAMPER_REASONS.has(row.reason))
10971
+ });
10750
10972
  return {
10751
10973
  ok: true,
10752
10974
  value: { path: file.value, rows, skipped, integrity: options.integrity ? integrity : null }
10753
10975
  };
10754
10976
  }
10977
+ var TAMPER_REASONS = new Set(["hash_mismatch", "broken_link"]);
10978
+ var tamperCache = new Map;
10979
+ var fileStamp = (stat) => `${stat.ino}:${stat.mtimeMs}:${stat.ctimeMs}`;
10980
+ function ledgerTampering(cwd) {
10981
+ const file = ledgerPath(cwd);
10982
+ if (!file.ok)
10983
+ return [];
10984
+ try {
10985
+ const stat = fs6.statSync(file.value);
10986
+ const cached = tamperCache.get(file.value);
10987
+ if (cached && cached.size === stat.size && cached.stamp === fileStamp(stat))
10988
+ return cached.breaks;
10989
+ } catch {
10990
+ return [];
10991
+ }
10992
+ const read = readLedger(cwd, { integrity: true });
10993
+ return read.ok ? (read.value.integrity?.unverified ?? []).filter((row) => TAMPER_REASONS.has(row.reason)) : [];
10994
+ }
10995
+ function tamperedUnblock(cwd) {
10996
+ const file = ledgerPath(cwd);
10997
+ const where = file.ok ? file.value : "the ledger file";
10998
+ return `workit ledger verify-integrity # lists the edited or missing rows; if the edit is understood, move ${where} aside (mv ${where} ${where}.tampered-<date>) and record the verdicts again`;
10999
+ }
11000
+ var tamperedWarning = (cwd, breaks) => `the ledger was edited, or a row was removed or moved (${breaks.slice(0, 3).map((row) => `${row.reason} at row ${row.seq}`).join(", ")}${breaks.length > 3 ? ", …" : ""}): no verdict is accepted until it is repaired. ${tamperedUnblock(cwd)}`;
10755
11001
  function verdictBasis(verdict, current) {
10756
11002
  if (verdict.head && verdict.head === current.head)
10757
11003
  return "fresh";
@@ -10759,7 +11005,7 @@ function verdictBasis(verdict, current) {
10759
11005
  return "carried";
10760
11006
  return "stale";
10761
11007
  }
10762
- var isLiveVerdict = (row) => row.type === "verdict" && typeof row.result === "string" && !row.superseded;
11008
+ var isLiveVerdict = (row, retired) => row.type === "verdict" && typeof row.result === "string" && !retired.has(row.id);
10763
11009
  function trailerSessions(cwd, base, head) {
10764
11010
  if (!base || !head || !safeRef(base) || !safeRef(head))
10765
11011
  return [];
@@ -10840,6 +11086,56 @@ var authoredBy = (authors, session) => {
10840
11086
  return true;
10841
11087
  return false;
10842
11088
  };
11089
+ var TRUST_REASONS = new Set([
11090
+ "unsigned",
11091
+ "hash_mismatch",
11092
+ "broken_link",
11093
+ "duplicate_id",
11094
+ "unissued_session",
11095
+ "unbound_session"
11096
+ ]);
11097
+ var SHELL_VOUCHED_HOSTS = new Set([
11098
+ "cli",
11099
+ "opencode",
11100
+ "pi",
11101
+ "codex_cli",
11102
+ "codex_desktop"
11103
+ ]);
11104
+ function sessionTrust(cwd, row) {
11105
+ const session = row.actor.session;
11106
+ if (!session || row.self === true)
11107
+ return null;
11108
+ const source = typeof row.sessionSource === "string" ? row.sessionSource : "env";
11109
+ const host = row.actor.host;
11110
+ const issuedOnly = source === "flag" ? host !== "cli" : source === "env" && session.includes(":") && !SHELL_VOUCHED_HOSTS.has(host);
11111
+ if (!issuedOnly)
11112
+ return null;
11113
+ const binding = issuedBinding(cwd, session);
11114
+ if (binding === null)
11115
+ return "unissued_session";
11116
+ return binding === "token" && row.sessionBound !== true ? "unbound_session" : null;
11117
+ }
11118
+ function trustReasons(cwd, row) {
11119
+ if (row.integrity === "unverified")
11120
+ return [row.integrityReason ?? "unsigned"];
11121
+ if (row.integrity !== "verified")
11122
+ return ["unsigned"];
11123
+ const session = sessionTrust(cwd, row);
11124
+ return session ? [session] : [];
11125
+ }
11126
+ function withIntegrity(cwd, rows) {
11127
+ if (rows.every((row) => row.integrity !== undefined))
11128
+ return rows;
11129
+ const read = readLedger(cwd, { integrity: true });
11130
+ const bySeq = new Map(read.ok ? read.value.rows.map((row) => [row.seq, row]) : []);
11131
+ return rows.map((row) => {
11132
+ if (row.integrity !== undefined)
11133
+ return row;
11134
+ const fresh = bySeq.get(row.seq);
11135
+ return fresh && fresh.id === row.id && fresh.at === row.at ? fresh : row;
11136
+ });
11137
+ }
11138
+ var untrusted = (reasons) => reasons.some((reason) => TRUST_REASONS.has(reason));
10843
11139
  var independenceReasons = (row, authors) => {
10844
11140
  const reasons = [];
10845
11141
  if (!row.actor.session)
@@ -10850,15 +11146,17 @@ var independenceReasons = (row, authors) => {
10850
11146
  reasons.push("self");
10851
11147
  return reasons;
10852
11148
  };
10853
- function effectiveOf(rows, authors, sameCode, isCurrent) {
11149
+ function effectiveOf(rows, reasonsOf, sameCode, isCurrent) {
10854
11150
  let effective = null;
10855
11151
  for (const row of rows) {
10856
11152
  if (!effective) {
10857
11153
  effective = row;
10858
11154
  continue;
10859
11155
  }
10860
- const rowIndependent = independenceReasons(row, authors).length === 0;
10861
- const effIndependent = independenceReasons(effective, authors).length === 0;
11156
+ if (untrusted(reasonsOf(row)) && !untrusted(reasonsOf(effective)))
11157
+ continue;
11158
+ const rowIndependent = reasonsOf(row).length === 0;
11159
+ const effIndependent = reasonsOf(effective).length === 0;
10862
11160
  if (effIndependent && !rowIndependent && isCurrent(effective))
10863
11161
  continue;
10864
11162
  if (effIndependent && FAILING.has(String(effective.result)) && row.actor.session === effective.actor.session && sameCode(effective, row))
@@ -10872,7 +11170,7 @@ function restackCarries(rows, branch, from, to) {
10872
11170
  return false;
10873
11171
  const edges = new Map;
10874
11172
  for (const row of rows)
10875
- if (row.type === "stack.restacked" && row.observer === "workit_cli" && row.branch === branch && row.patchEqual === true && typeof row.fromHead === "string" && row.head)
11173
+ if (row.type === "stack.restacked" && row.observer === "workit_cli" && row.integrity === "verified" && row.branch === branch && row.patchEqual === true && typeof row.fromHead === "string" && row.head)
10876
11174
  edges.set(row.fromHead, [...edges.get(row.fromHead) ?? [], row.head]);
10877
11175
  const seen = new Set([from]);
10878
11176
  const queue = [from];
@@ -10888,7 +11186,8 @@ function restackCarries(rows, branch, from, to) {
10888
11186
  }
10889
11187
  return false;
10890
11188
  }
10891
- function checkVerdicts(cwd, branch, rows) {
11189
+ function checkVerdicts(cwd, branch, ledgerRows) {
11190
+ const rows = withIntegrity(cwd, ledgerRows);
10892
11191
  const head = branchHead(cwd, branch);
10893
11192
  const fallbackBase = defaultBase(cwd);
10894
11193
  const scope = branchScope(cwd, branch);
@@ -10918,21 +11217,47 @@ function checkVerdicts(cwd, branch, rows) {
10918
11217
  return verdictBasis(failed, { head: row.head, ...keys }) !== "stale" || restackCarries(rows, branch, failed.head, row.head);
10919
11218
  };
10920
11219
  const authors = authorSessions(cwd, rows, branch, { base: fallbackBase, head }, scope);
11220
+ const failureAuthorCache = new Map;
11221
+ const failureAuthors = (row) => {
11222
+ let set = failureAuthorCache.get(row.seq);
11223
+ if (!set) {
11224
+ set = authorSessions(cwd, rows.filter((other) => other.integrity === "verified" && other.seq < row.seq), branch, { base: null, head: null }, scope);
11225
+ failureAuthorCache.set(row.seq, set);
11226
+ }
11227
+ return set;
11228
+ };
11229
+ const reasonCache = new Map;
11230
+ const reasonsOf = (row) => {
11231
+ let reasons = reasonCache.get(row);
11232
+ if (!reasons) {
11233
+ const failing = FAILING.has(String(row.result));
11234
+ reasons = [
11235
+ ...trustReasons(cwd, row),
11236
+ ...independenceReasons(row, failing ? failureAuthors(row) : authors)
11237
+ ];
11238
+ reasonCache.set(row, reasons);
11239
+ }
11240
+ return reasons;
11241
+ };
11242
+ const retired = new Set;
11243
+ for (const row of rows)
11244
+ if (row.type === "verdict" && row.supersedes && !row.supersedeIgnored && trustReasons(cwd, row).length === 0)
11245
+ retired.add(row.supersedes);
10921
11246
  const byKind = new Map;
10922
11247
  for (const row of rows)
10923
- if (isLiveVerdict(row) && scope(row)) {
11248
+ if (isLiveVerdict(row, retired) && scope(row)) {
10924
11249
  const kind = typeof row.kind === "string" ? row.kind : "review";
10925
11250
  byKind.set(kind, [...byKind.get(kind) ?? [], row]);
10926
11251
  }
10927
11252
  const verdicts = [];
10928
11253
  const warnings = [];
10929
11254
  for (const [kind, list] of byKind) {
10930
- const row = effectiveOf(list, authors, sameCode, (candidate) => basisOf(candidate) !== "stale");
11255
+ const row = effectiveOf(list, reasonsOf, sameCode, (candidate) => basisOf(candidate) !== "stale");
10931
11256
  if (!row)
10932
11257
  continue;
10933
11258
  const basis = basisOf(row);
10934
11259
  const current = basis !== "stale";
10935
- const independence = independenceReasons(row, authors);
11260
+ const independence = reasonsOf(row);
10936
11261
  const reasons = [
10937
11262
  ...current ? [] : ["stale"],
10938
11263
  ...PASSING.has(String(row.result)) ? [] : ["not_passing"],
@@ -10949,23 +11274,31 @@ function checkVerdicts(cwd, branch, rows) {
10949
11274
  });
10950
11275
  if (reasons.length === 0) {
10951
11276
  for (const later of list)
10952
- if (later.seq > row.seq && independenceReasons(later, authors).length > 0 && FAILING.has(String(later.result)) && basisOf(later) !== "stale")
11277
+ if (later.seq > row.seq && reasonsOf(later).length > 0 && !untrusted(reasonsOf(later)) && FAILING.has(String(later.result)) && basisOf(later) !== "stale")
10953
11278
  warnings.push(`the author's own (self) ${String(later.result)} ${kind} verdict ${later.id} by ${later.actor.session ?? "no session"} on the current code is newer than the accepted pass ${row.id}: read it before merging`);
10954
11279
  }
10955
11280
  }
10956
11281
  for (const row of rows)
10957
11282
  if (row.integrity === "unverified" && scope(row))
10958
11283
  warnings.push(`ledger row ${row.id} (${row.type}) on ${row.branch ?? branch} is unverified: the workit CLI did not write it or it changed afterwards (workit ledger verify-integrity)`);
11284
+ const tampered = ledgerTampering(cwd);
11285
+ if (tampered.length) {
11286
+ warnings.push(tamperedWarning(cwd, tampered));
11287
+ for (const entry of verdicts) {
11288
+ entry.accepted = false;
11289
+ entry.reasons = [...entry.reasons, "ledger_tampered"];
11290
+ }
11291
+ }
10959
11292
  verdicts.sort((a, b) => a.verdict.seq - b.verdict.seq);
10960
11293
  const newestCurrent = verdicts.findLast((entry) => entry.current) ?? verdicts.at(-1) ?? null;
10961
11294
  const failing = verdicts.find((entry) => entry.current && entry.independent && FAILING.has(String(entry.verdict.result)));
10962
11295
  const newestAccepted = verdicts.findLast((entry) => entry.accepted && STRONG_RESULTS.has(String(entry.verdict.result))) ?? null;
10963
- const accepted = newestAccepted && !failing ? { accepted: true, verdict: newestAccepted.verdict, reasons: [] } : {
11296
+ const accepted = tampered.length ? { accepted: false, verdict: null, reasons: ["ledger_tampered"] } : newestAccepted && !failing ? { accepted: true, verdict: newestAccepted.verdict, reasons: [] } : {
10964
11297
  accepted: false,
10965
11298
  verdict: failing?.verdict ?? null,
10966
11299
  reasons: failing ? ["failing_verdict"] : !newestCurrent ? ["no_verdict"] : newestCurrent.reasons.length ? newestCurrent.reasons : ["type_check_only"]
10967
11300
  };
10968
- const selfEntry = failing ? undefined : verdicts.findLast((entry) => entry.current && !entry.independent && STRONG_RESULTS.has(String(entry.verdict.result)));
11301
+ const selfEntry = failing ? undefined : verdicts.findLast((entry) => entry.current && !entry.independent && !untrusted(entry.reasons) && STRONG_RESULTS.has(String(entry.verdict.result)));
10969
11302
  return {
10970
11303
  branch,
10971
11304
  head,
@@ -11931,12 +12264,12 @@ function sameFileIdentity(left, right, platform = process.platform) {
11931
12264
  }
11932
12265
 
11933
12266
  // node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.js
11934
- import { randomBytes as randomBytes2 } from "node:crypto";
12267
+ import { randomBytes as randomBytes3 } from "node:crypto";
11935
12268
  import fsSync from "node:fs";
11936
12269
  import path11 from "node:path";
11937
12270
 
11938
12271
  // node_modules/@openclaw/fs-safe/dist/bounded-read.js
11939
- import fs6 from "node:fs";
12272
+ import fs7 from "node:fs";
11940
12273
 
11941
12274
  // node_modules/@openclaw/fs-safe/dist/byte-budget.js
11942
12275
  function normalizeMaxBytes(value, options = {}) {
@@ -11973,7 +12306,7 @@ function readFileDescriptorBoundedSync(fd, maxBytes) {
11973
12306
  let total = 0;
11974
12307
  while (true) {
11975
12308
  const length = nextReadLength(total, maxBytes, scratch.length);
11976
- const bytesRead = fs6.readSync(fd, scratch, 0, length, null);
12309
+ const bytesRead = fs7.readSync(fd, scratch, 0, length, null);
11977
12310
  if (bytesRead === 0) {
11978
12311
  return Buffer.concat(chunks, total);
11979
12312
  }
@@ -11982,9 +12315,9 @@ function readFileDescriptorBoundedSync(fd, maxBytes) {
11982
12315
  }
11983
12316
 
11984
12317
  // node_modules/@openclaw/fs-safe/dist/read-open-flags.js
11985
- import fs7 from "node:fs";
12318
+ import fs8 from "node:fs";
11986
12319
  function resolveReadOpenFlags(options) {
11987
- const constants = options?.constants ?? fs7.constants;
12320
+ const constants = options?.constants ?? fs8.constants;
11988
12321
  const noFollow = process.platform !== "win32" && options?.followSymlinks !== true && typeof constants.O_NOFOLLOW === "number" ? constants.O_NOFOLLOW : 0;
11989
12322
  const nonBlocking = process.platform !== "win32" && typeof constants.O_NONBLOCK === "number" ? constants.O_NONBLOCK : 0;
11990
12323
  return constants.O_RDONLY | noFollow | nonBlocking;
@@ -12001,7 +12334,7 @@ function parseSidecarLockSnapshot(snapshot, parser) {
12001
12334
  }
12002
12335
  function createSidecarLockOwnershipToken() {
12003
12336
  let token = SIDECAR_LOCK_OWNERSHIP_TOKEN_PREFIX;
12004
- for (const byte of randomBytes2(SIDECAR_LOCK_OWNERSHIP_TOKEN_BYTES)) {
12337
+ for (const byte of randomBytes3(SIDECAR_LOCK_OWNERSHIP_TOKEN_BYTES)) {
12005
12338
  for (let bit = 7;bit >= 0; bit -= 1) {
12006
12339
  token += byte & 1 << bit ? "\t" : " ";
12007
12340
  }
@@ -12175,7 +12508,7 @@ function getFsSafeLockConfig() {
12175
12508
  }
12176
12509
 
12177
12510
  // node_modules/@openclaw/fs-safe/dist/file-lock-sync.js
12178
- import fs8 from "node:fs";
12511
+ import fs9 from "node:fs";
12179
12512
  import path12 from "node:path";
12180
12513
 
12181
12514
  // node_modules/@openclaw/fs-safe/dist/timing.js
@@ -12206,7 +12539,7 @@ function releaseAllSyncHeldLocks() {
12206
12539
  }
12207
12540
  try {
12208
12541
  if (held.fd !== undefined)
12209
- fs8.closeSync(held.fd);
12542
+ fs9.closeSync(held.fd);
12210
12543
  } catch {}
12211
12544
  try {
12212
12545
  removeSidecarLockIfUnchangedSync(held.lockPath, held.snapshot);
@@ -12241,7 +12574,7 @@ function releaseSyncHeldLock(held) {
12241
12574
  if (held.fd !== undefined) {
12242
12575
  const fd = held.fd;
12243
12576
  held.fd = undefined;
12244
- fs8.closeSync(fd);
12577
+ fs9.closeSync(fd);
12245
12578
  }
12246
12579
  removeSidecarLockIfUnchangedSync(held.lockPath, held.snapshot);
12247
12580
  heldLocks.delete(held.normalizedTargetPath);
@@ -12264,11 +12597,11 @@ function createSyncHeldLockHandle(held) {
12264
12597
  };
12265
12598
  }
12266
12599
  function canonicalLockParentSync(parent) {
12267
- return process.platform === "win32" ? fs8.realpathSync.native(parent) : fs8.realpathSync(parent);
12600
+ return process.platform === "win32" ? fs9.realpathSync.native(parent) : fs9.realpathSync(parent);
12268
12601
  }
12269
12602
  function normalizeTargetPath(targetPath) {
12270
12603
  const resolved = path12.resolve(targetPath);
12271
- fs8.mkdirSync(path12.dirname(resolved), { recursive: true });
12604
+ fs9.mkdirSync(path12.dirname(resolved), { recursive: true });
12272
12605
  try {
12273
12606
  return path12.join(canonicalLockParentSync(path12.dirname(resolved)), path12.basename(resolved));
12274
12607
  } catch {
@@ -12296,7 +12629,7 @@ function defaultShouldReclaim(snapshot, staleMs, nowMs) {
12296
12629
  }
12297
12630
  function reclaimGuardExists(reclaimGuardPath) {
12298
12631
  try {
12299
- fs8.lstatSync(reclaimGuardPath);
12632
+ fs9.lstatSync(reclaimGuardPath);
12300
12633
  return true;
12301
12634
  } catch (error) {
12302
12635
  if (error.code === "ENOENT")
@@ -12360,19 +12693,19 @@ function acquireFileLockSync(targetPath, options) {
12360
12693
  const { raw, ownershipToken } = serializeSidecarLockPayload(payload);
12361
12694
  let lockFileCreateDenied = false;
12362
12695
  try {
12363
- const noFollow = process.platform !== "win32" && typeof fs8.constants.O_NOFOLLOW === "number" ? fs8.constants.O_NOFOLLOW : 0;
12696
+ const noFollow = process.platform !== "win32" && typeof fs9.constants.O_NOFOLLOW === "number" ? fs9.constants.O_NOFOLLOW : 0;
12364
12697
  try {
12365
- fd = fs8.openSync(lockPath, fs8.constants.O_WRONLY | fs8.constants.O_CREAT | fs8.constants.O_EXCL | noFollow, 384);
12698
+ fd = fs9.openSync(lockPath, fs9.constants.O_WRONLY | fs9.constants.O_CREAT | fs9.constants.O_EXCL | noFollow, 384);
12366
12699
  } catch (error) {
12367
12700
  lockFileCreateDenied = isTransientLockFileDenial(error, lockPath);
12368
12701
  throw error;
12369
12702
  }
12370
- fs8.writeFileSync(fd, raw, "utf8");
12371
- fs8.fsyncSync(fd);
12703
+ fs9.writeFileSync(fd, raw, "utf8");
12704
+ fs9.fsyncSync(fd);
12372
12705
  const snapshot = {
12373
12706
  raw,
12374
12707
  payload,
12375
- stat: fs8.fstatSync(fd),
12708
+ stat: fs9.fstatSync(fd),
12376
12709
  ownershipToken
12377
12710
  };
12378
12711
  const createdHeld = {
@@ -12407,8 +12740,8 @@ function acquireFileLockSync(targetPath, options) {
12407
12740
  return returnedHandle;
12408
12741
  } catch (error) {
12409
12742
  if (fd !== undefined) {
12410
- const failed = { payload: null, stat: fs8.fstatSync(fd) };
12411
- fs8.closeSync(fd);
12743
+ const failed = { payload: null, stat: fs9.fstatSync(fd) };
12744
+ fs9.closeSync(fd);
12412
12745
  fd = undefined;
12413
12746
  removeSidecarLockIfUnchangedSync(lockPath, failed);
12414
12747
  }
@@ -12457,7 +12790,7 @@ function acquireFileLockSync(targetPath, options) {
12457
12790
  })) {
12458
12791
  let ownsReclaimGuard = false;
12459
12792
  try {
12460
- fs8.mkdirSync(reclaimGuardPath);
12793
+ fs9.mkdirSync(reclaimGuardPath);
12461
12794
  ownsReclaimGuard = true;
12462
12795
  if (removeSidecarLockIfUnchangedSync(lockPath, snapshot))
12463
12796
  continue;
@@ -12470,7 +12803,7 @@ function acquireFileLockSync(targetPath, options) {
12470
12803
  } finally {
12471
12804
  if (ownsReclaimGuard) {
12472
12805
  try {
12473
- fs8.rmdirSync(reclaimGuardPath);
12806
+ fs9.rmdirSync(reclaimGuardPath);
12474
12807
  } catch {}
12475
12808
  }
12476
12809
  }
@@ -12499,146 +12832,6 @@ var packageRoot = () => {
12499
12832
  }
12500
12833
  };
12501
12834
 
12502
- // packages/workit-core/src/core/store-lock.ts
12503
- import { spawnSync as spawnSync8 } from "node:child_process";
12504
- import fs9 from "node:fs";
12505
- import { hostname } from "node:os";
12506
- var metadataLockSchema = object({
12507
- pid: number2().int().nonnegative().safe(),
12508
- processStart: string2().nullable(),
12509
- host: string2().min(1),
12510
- nonce: string2().min(1),
12511
- externalAction: literal(true).optional()
12512
- }).strict();
12513
- var FOREIGN_LOCK_TTL_MS = 10 * 60000;
12514
- var UNREADABLE_LOCK_TTL_MS = 30000;
12515
- var RECLAIM_GUARD_TTL_MS = 30000;
12516
- var defaultLockTimeoutMs = 250;
12517
- var defaultLockTimeout = () => defaultLockTimeoutMs;
12518
- var parseMetadataLock = (raw) => {
12519
- let value;
12520
- try {
12521
- value = JSON.parse(raw);
12522
- } catch {
12523
- throw Object.assign(new Error("metadata lock is invalid"), { code: "metadata_lock_invalid" });
12524
- }
12525
- const parsed = metadataLockSchema.safeParse(value);
12526
- if (!parsed.success)
12527
- throw Object.assign(new Error("metadata lock is invalid"), { code: "metadata_lock_invalid" });
12528
- return parsed.data;
12529
- };
12530
- var parseMetadataLockOrNull = (raw) => {
12531
- try {
12532
- return parseMetadataLock(raw);
12533
- } catch {
12534
- return null;
12535
- }
12536
- };
12537
- var parseProcStatStart = (stat) => {
12538
- const close = stat.lastIndexOf(")");
12539
- if (close < 0)
12540
- return null;
12541
- return stat.slice(close + 1).trim().split(/\s+/)[19] ?? null;
12542
- };
12543
- var processStartOf = (pid) => {
12544
- if (process.platform === "linux") {
12545
- try {
12546
- return parseProcStatStart(fs9.readFileSync(`/proc/${pid}/stat`, "utf8"));
12547
- } catch {
12548
- return null;
12549
- }
12550
- }
12551
- if (process.platform === "darwin" || process.platform === "freebsd") {
12552
- try {
12553
- const run = spawnSync8("ps", ["-o", "lstart=", "-p", String(pid)], {
12554
- encoding: "utf8",
12555
- timeout: 1000
12556
- });
12557
- const value = run.status === 0 ? run.stdout.trim() : "";
12558
- return value || null;
12559
- } catch {
12560
- return null;
12561
- }
12562
- }
12563
- return null;
12564
- };
12565
- var readTrimmed = (file) => {
12566
- try {
12567
- return fs9.readFileSync(file, "utf8").trim() || null;
12568
- } catch {
12569
- return null;
12570
- }
12571
- };
12572
- var cachedLockHost = null;
12573
- var localLockHost = () => {
12574
- if (cachedLockHost !== null)
12575
- return cachedLockHost;
12576
- let pidns = null;
12577
- try {
12578
- pidns = /\[(\d+)\]/.exec(fs9.readlinkSync("/proc/self/ns/pid"))?.[1] ?? null;
12579
- } catch {}
12580
- const boot = readTrimmed("/proc/sys/kernel/random/boot_id");
12581
- cachedLockHost = pidns || boot ? `${hostname()}#${pidns ?? "?"}:${boot ?? "?"}` : hostname();
12582
- return cachedLockHost;
12583
- };
12584
- var pidAlive = (pid) => {
12585
- if (!Number.isSafeInteger(pid) || pid <= 0)
12586
- return false;
12587
- try {
12588
- process.kill(pid, 0);
12589
- return true;
12590
- } catch (error) {
12591
- return error.code === "EPERM";
12592
- }
12593
- };
12594
- var TASK_STORE_LOCK = { reclaimUnverifiedAfterTtl: true };
12595
- var classifyLockOwner = (payload, ageMs, localHost = localLockHost(), options = {}) => {
12596
- const lock = metadataLockSchema.safeParse(payload);
12597
- if (!lock.success)
12598
- return ageMs !== null && ageMs > UNREADABLE_LOCK_TTL_MS ? { state: "stale", reason: "unreadable lock left behind" } : { state: "unknown", reason: "lock is being written" };
12599
- const { pid, processStart, host } = lock.data;
12600
- const [lockName, lockSpace] = host.split("#");
12601
- const [localName, localSpace] = localHost.split("#");
12602
- if (lockSpace && localSpace && lockName === localName) {
12603
- const [lockNs, lockBoot] = lockSpace.split(":");
12604
- const [localNs, localBoot] = localSpace.split(":");
12605
- if (lockNs === localNs && lockNs !== "?" && lockBoot !== "?" && lockBoot !== localBoot)
12606
- return { state: "stale", reason: "lock was taken before this machine rebooted" };
12607
- }
12608
- if (host !== localHost)
12609
- return ageMs !== null && ageMs > FOREIGN_LOCK_TTL_MS ? { state: "stale", reason: `lock from ${host} is older than its TTL` } : { state: "unknown", reason: `lock is held from ${host}; its pid cannot be checked here` };
12610
- if (!pidAlive(pid))
12611
- return { state: "stale", reason: `pid ${pid} is not running` };
12612
- const currentStart = processStartOf(pid);
12613
- if (processStart !== null && currentStart !== null && processStart !== currentStart)
12614
- return { state: "stale", reason: `pid ${pid} now belongs to a different process` };
12615
- if (options.reclaimUnverifiedAfterTtl === true && (processStart === null || currentStart === null) && ageMs !== null && ageMs > FOREIGN_LOCK_TTL_MS)
12616
- return {
12617
- state: "stale",
12618
- reason: `pid ${pid} is running but the lock is older than its TTL and the pid's start time cannot be checked (reused pid)`
12619
- };
12620
- return { state: "live", reason: `held by running pid ${pid}` };
12621
- };
12622
- var ageOf = (file, nowMs) => {
12623
- try {
12624
- return nowMs - fs9.lstatSync(file).mtimeMs;
12625
- } catch {
12626
- return null;
12627
- }
12628
- };
12629
- var clearAbandonedReclaimGuard = (lockPath, nowMs = Date.now()) => {
12630
- const guard = `${lockPath}.reclaim`;
12631
- const age = ageOf(guard, nowMs);
12632
- if (age === null || age <= RECLAIM_GUARD_TTL_MS)
12633
- return false;
12634
- try {
12635
- fs9.rmdirSync(guard);
12636
- return true;
12637
- } catch {
12638
- return false;
12639
- }
12640
- };
12641
-
12642
12835
  // packages/workit-core/src/store/event-log.ts
12643
12836
  import { randomUUID as randomUUID2 } from "node:crypto";
12644
12837
  import fs10 from "node:fs";
@@ -20313,10 +20506,10 @@ function stopObligation(input, descriptor) {
20313
20506
  const branch = currentBranch(input.cwd);
20314
20507
  if (!branch || guardedBranch(input.cwd, branch))
20315
20508
  return null;
20316
- const ledger = readLedger(input.cwd);
20509
+ const ledger = readLedger(input.cwd, { integrity: true });
20317
20510
  if (!ledger.ok)
20318
20511
  return null;
20319
- const all = ledger.value.rows;
20512
+ const all = ledger.value.rows.filter((row) => row.integrity !== "unverified");
20320
20513
  const rows = all.filter((row) => row.branch === branch);
20321
20514
  const mine = rows.filter((row) => sessionOf(row) === session);
20322
20515
  const commits = new Set(mine.filter((row) => row.type === "commit.recorded").map((row) => typeof row.sha === "string" ? row.sha : row.head).filter((sha) => typeof sha === "string"));
@@ -20692,9 +20885,9 @@ ${subagentStartText({ ...input, cwd: "" }, descriptor, event)}`;
20692
20885
  if (role !== "judge" || !event.agentId)
20693
20886
  return readOnly;
20694
20887
  const own = subagentSession(session, event.agentId);
20695
- if (session)
20696
- recordIssuedSession(input.cwd || process.cwd(), session, own);
20697
- return `${readOnly} Its own Workit session is ${own}: record verdicts with \`workit ledger verdict <result> --session ${own} ...\` so the ledger tells it apart from the author.`;
20888
+ const token = session ? recordIssuedSession(input.cwd || process.cwd(), session, own) : null;
20889
+ const flags = token ? `--session ${own} --token ${token}` : `--session ${own}`;
20890
+ return `${readOnly} Its own Workit session is ${own}: record verdicts with \`workit ledger verdict <result> ${flags} ...\` so the ledger tells it apart from the author.${token ? " The token is this subagent's alone: never write it into a report, a file or a commit. --token needs a workit CLI at least as new as this plugin; if the CLI answers that --token is an unknown option, update the workit CLI (workit upgrade --cli) and record again." : ""}`;
20698
20891
  };
20699
20892
  function handleHook(input, deps) {
20700
20893
  const { descriptor } = deps;