@brainervirus/workit-cursor 8.12.3 → 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) {
@@ -8842,6 +9266,15 @@ var repoBranchPreset = (workspaceRoot) => {
8842
9266
  const detected = detectBranchPolicy(workspaceRoot);
8843
9267
  return detected.developBranch || detected.allowed.length ? detected.preset : null;
8844
9268
  };
9269
+ var releaseTagPrefixOf = (workspaceRoot, policyPrefix) => {
9270
+ if (typeof policyPrefix === "string")
9271
+ return policyPrefix.trim();
9272
+ const r = spawnSync4("git", ["config", "--get", "gitflow.prefix.versiontag"], {
9273
+ cwd: workspaceRoot,
9274
+ encoding: "utf8"
9275
+ });
9276
+ return r.status === 0 ? (r.stdout ?? "").trim() : "v";
9277
+ };
8845
9278
 
8846
9279
  // packages/workit-core/src/core/workspaces.ts
8847
9280
  var nonBlank = string2().refine((value) => value.trim().length > 0, "must not be blank");
@@ -8901,7 +9334,7 @@ var releaseTrackSchema = object({
8901
9334
  context.addIssue({ code: "custom", path: [field], message: "entries must be unique" });
8902
9335
  }
8903
9336
  }
8904
- 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(".."))) {
8905
9338
  context.addIssue({
8906
9339
  code: "custom",
8907
9340
  path: ["versionSource", "path"],
@@ -8962,7 +9395,7 @@ var workspacesFileSchema = object({ workspaces: array(workspaceConfigSchema).opt
8962
9395
  names.add(workspace.name);
8963
9396
  }
8964
9397
  });
8965
- var workspacesRevision = (contents) => createHash3("sha256").update(contents ?? "<missing>").digest("hex");
9398
+ var workspacesRevision = (contents) => createHash4("sha256").update(contents ?? "<missing>").digest("hex");
8966
9399
  var validateWorkspacesDocument = (parsed, file) => {
8967
9400
  if (!isConfigObject(parsed)) {
8968
9401
  return { status: "malformed", path: file, entries: [], error: `${file} is not a JSON object` };
@@ -9000,7 +9433,7 @@ var validateWorkspacesDocument = (parsed, file) => {
9000
9433
  };
9001
9434
  };
9002
9435
  var readWorkspacesResult = (dir = configDir()) => {
9003
- const file = path3.join(dir, "workspaces.json");
9436
+ const file = path4.join(dir, "workspaces.json");
9004
9437
  let raw;
9005
9438
  try {
9006
9439
  raw = readFileSync2(file, "utf8");
@@ -9099,7 +9532,7 @@ var mainCheckoutOf = (cwd) => {
9099
9532
  for (const name of ["GIT_DIR", "GIT_WORK_TREE", "GIT_COMMON_DIR", "GIT_INDEX_FILE"])
9100
9533
  delete env[name];
9101
9534
  const git = (args) => {
9102
- const run = spawnSync3("git", args, {
9535
+ const run = spawnSync5("git", args, {
9103
9536
  cwd,
9104
9537
  env,
9105
9538
  encoding: "utf8",
@@ -9111,9 +9544,9 @@ var mainCheckoutOf = (cwd) => {
9111
9544
  let main = null;
9112
9545
  const dirs = git(["rev-parse", "--path-format=absolute", "--git-dir", "--git-common-dir"]);
9113
9546
  const [gitDir, common] = (dirs ?? "").trim().split(/\r?\n/u);
9114
- if (gitDir && common && path3.resolve(gitDir) !== path3.resolve(common)) {
9547
+ if (gitDir && common && path4.resolve(gitDir) !== path4.resolve(common)) {
9115
9548
  const listed = /^worktree (.+)$/mu.exec(git(["worktree", "list", "--porcelain"]) ?? "")?.[1];
9116
- main = listed?.trim() || (path3.basename(common) === ".git" ? path3.dirname(common) : common);
9549
+ main = listed?.trim() || (path4.basename(common) === ".git" ? path4.dirname(common) : common);
9117
9550
  }
9118
9551
  mainCheckouts.set(cwd, main);
9119
9552
  return main;
@@ -9204,7 +9637,7 @@ var runtimeWorkspaceIndexSchema = object({
9204
9637
  workspaces: array(object({ name: nonBlank, glob: nonBlank }).passthrough()).optional()
9205
9638
  }).passthrough();
9206
9639
  var matchRuntimeWorkspace = (cwd, dir = configDir()) => {
9207
- const file = path3.join(dir, "workspaces.json");
9640
+ const file = path4.join(dir, "workspaces.json");
9208
9641
  let raw;
9209
9642
  try {
9210
9643
  raw = readFileSync2(file, "utf8");
@@ -9297,8 +9730,8 @@ function workspaceDefaultTarget(cwd, globalDefault) {
9297
9730
  }
9298
9731
 
9299
9732
  // packages/workit-core/src/core/release-tracks.ts
9300
- import { createHash as createHash4 } from "node:crypto";
9301
- 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";
9302
9735
  class ReleaseTrackError extends Error {
9303
9736
  }
9304
9737
  var KNOWN_FIELDS = new Set([
@@ -9564,603 +9997,323 @@ function resolveReleaseTrack(input) {
9564
9997
  return fallback(`no track branch could be compared with ${branch} (fetch the track branches)`);
9565
9998
  const ancestry = new Map;
9566
9999
  const descends = (older, newer) => {
9567
- if (older === newer)
9568
- return false;
9569
- const key = `${older}>${newer}`;
9570
- if (!ancestry.has(key))
9571
- ancestry.set(key, input.probe.isAncestor(older, newer));
9572
- return ancestry.get(key);
9573
- };
9574
- const winners = forks.filter((fork) => forks.every((other) => other.track === fork.track || descends(other.sha, fork.sha)));
9575
- const winningTracks = [...new Set(winners.map((fork) => fork.track))];
9576
- if (winningTracks.length === 1 && forks.some((fork) => fork.track !== winningTracks[0])) {
9577
- const win = winners[0];
9578
- return done(win.track, "ancestry", `${branch} forked from ${win.ref} at ${win.sha.slice(0, 12)}, after every other line's fork point`);
9579
- }
9580
- if (winningTracks.length === 1)
9581
- return fallback(`only ${label(winningTracks[0])} could be compared with ${branch}`);
9582
- const newest = forks.filter((fork) => !forks.some((other) => descends(fork.sha, other.sha)));
9583
- 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 ")}`);
9584
- }
9585
- var git2 = (cwd, args) => {
9586
- const run = spawnSync4("git", args, {
9587
- cwd,
9588
- encoding: "utf8",
9589
- timeout: 20000,
9590
- windowsHide: true,
9591
- stdio: ["ignore", "pipe", "ignore"]
9592
- });
9593
- return run.status === 0 ? (run.stdout ?? "").trim() : null;
9594
- };
9595
- var recordedBaseKey = (branch) => `branch.${branch}.workitBase`;
9596
- var escapeRegExp = (text) => text.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
9597
- var reflog = (cwd, ref) => (git2(cwd, ["log", "-g", "--date=unix", "--format=%H%x09%gd%x09%gs", ref, "--"]) ?? "").split(`
9598
- `).map((line) => {
9599
- const [sha = "", selector = "", ...subject] = line.split("\t");
9600
- return {
9601
- sha,
9602
- time: Number(/@\{(\d+)\}$/u.exec(selector)?.[1] ?? Number.NaN),
9603
- subject: subject.join("\t")
9604
- };
9605
- }).filter((entry) => entry.sha && Number.isFinite(entry.time));
9606
- function gitTrackProbe(cwd) {
9607
- let remote;
9608
- let remotes;
9609
- const tip = (branch) => {
9610
- remote ??= pushRemoteName(cwd) ?? "origin";
9611
- return resolveRef(cwd, `refs/remotes/${remote}/${branch}`) ?? resolveRef(cwd, `refs/heads/${branch}`);
9612
- };
9613
- return {
9614
- remotes: () => remotes ??= remoteNames(cwd),
9615
- recordedBase: (branch) => git2(cwd, ["config", "--get", recordedBaseKey(branch)]) || null,
9616
- createdFrom: (branch) => {
9617
- const log = git2(cwd, ["reflog", "show", "--format=%gs", `refs/heads/${branch}`, "--"]);
9618
- const oldest = log?.split(`
9619
- `).findLast(Boolean) ?? "";
9620
- const match = /^branch: Created from (\S+)$/u.exec(oldest);
9621
- return match && match[1] !== "HEAD" ? match[1] : null;
9622
- },
9623
- checkedOutFrom: (branch) => {
9624
- const own = reflog(cwd, `refs/heads/${branch}`);
9625
- const created = own.at(-1);
9626
- if (!created || created.subject !== "branch: Created from HEAD")
9627
- return null;
9628
- const pattern = new RegExp(`^checkout: moving from (\\S+) to ${escapeRegExp(branch)}$`, "u");
9629
- const entry = reflog(cwd, "HEAD").findLast((item) => item.time >= created.time && item.sha === created.sha && pattern.test(item.subject));
9630
- return entry ? pattern.exec(entry.subject)?.[1] ?? null : null;
9631
- },
9632
- mergeBase: (branch, trackBranch) => {
9633
- const base = tip(trackBranch);
9634
- return base && resolveRef(cwd, `refs/heads/${branch}`) ? mergeBase(cwd, base, `refs/heads/${branch}`) : null;
9635
- },
9636
- isAncestor: (ancestor, descendant) => git2(cwd, ["merge-base", "--is-ancestor", ancestor, descendant]) !== null
9637
- };
9638
- }
9639
- function workspaceReleaseTracks(cwd) {
9640
- const raw = resolveRuntimeReleaseTracks(cwd);
9641
- return { workspace: raw?.workspace ?? null, ...readReleaseTracks(raw?.releaseTracks) };
9642
- }
9643
- var memo2 = new Map;
9644
- var MEMO_LIMIT = 64;
9645
- function resolveTrackFor(cwd, options) {
9646
- const read = workspaceReleaseTracks(cwd);
9647
- if (read.error)
9648
- return { status: "invalid", error: read.error, issues: read.issues };
9649
- if (read.tracks.length === 0 && !options.track?.trim())
9650
- return { status: "none", issues: read.issues };
9651
- const branch = options.branch === undefined ? currentBranch(cwd) : options.branch;
9652
- const envRequested = process.env.WORKFLOW_RELEASE_TRACK?.trim() || null;
9653
- const input = {
9654
- tracks: read.tracks,
9655
- requested: options.track ?? null,
9656
- envRequested,
9657
- branch,
9658
- defaultBranch: options.defaultBranch,
9659
- issues: read.issues
9660
- };
9661
- let key = null;
9662
- if (!options.probe) {
9663
- const refs = git2(cwd, [
9664
- "for-each-ref",
9665
- "--format=%(refname) %(objectname)",
9666
- "refs/heads",
9667
- "refs/remotes"
9668
- ]) ?? "";
9669
- const head = git2(cwd, ["reflog", "show", "-n", "1", "--format=%H %gs", "HEAD", "--"]) ?? "";
9670
- const own = branch ? [
9671
- git2(cwd, [
9672
- "log",
9673
- "-g",
9674
- "-n",
9675
- "1",
9676
- "--date=unix",
9677
- "--format=%H %gd %gs",
9678
- `refs/heads/${branch}`,
9679
- "--"
9680
- ]) ?? "",
9681
- git2(cwd, ["config", "--get", recordedBaseKey(branch)]) ?? ""
9682
- ] : [];
9683
- key = createHash4("sha256").update(JSON.stringify({ cwd, input, refs, head, own })).digest("hex");
9684
- const hit = memo2.get(key);
9685
- if (hit)
9686
- return hit;
9687
- }
9688
- const resolution = resolveReleaseTrack({ ...input, probe: options.probe ?? gitTrackProbe(cwd) });
9689
- if (key) {
9690
- if (memo2.size >= MEMO_LIMIT)
9691
- memo2.delete(memo2.keys().next().value);
9692
- memo2.set(key, resolution);
9693
- }
9694
- return resolution;
9695
- }
9696
-
9697
- // packages/workit-core/src/core/vcs-config.ts
9698
- var workspacesPath = () => path4.join(configDir(), "workspaces.json");
9699
- var vcsCwd = (cwd) => cwd ?? process.env.WORKFLOW_WORKSPACE_ROOT ?? process.cwd();
9700
- var remoteProvider = (cwd) => {
9701
- const r = spawnSync5("git", ["remote", "get-url", "origin"], { cwd, encoding: "utf8" });
9702
- if (r.status !== 0)
9703
- return null;
9704
- const url = (r.stdout ?? "").trim();
9705
- if (/github\.com[:/]/.test(url))
9706
- return "github";
9707
- if (/gitlab\.com[:/]/.test(url))
9708
- return "gitlab";
9709
- return null;
9710
- };
9711
- var readVcsConfig = () => {
9712
- const file = vcsConfigPath();
9713
- let raw;
9714
- try {
9715
- raw = fs2.readFileSync(file, "utf8");
9716
- } catch {
9717
- return { status: "missing", path: file, config: {} };
9718
- }
9719
- let parsed;
9720
- try {
9721
- parsed = JSON.parse(raw);
9722
- } catch {
9723
- return { status: "malformed", path: file, config: {}, error: `${file} is not valid JSON` };
9724
- }
9725
- if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
9726
- return { status: "valid", path: file, config: parsed };
9727
- }
9728
- return { status: "malformed", path: file, config: {}, error: `${file} is not a JSON object` };
9729
- };
9730
- var trackSummary = (resolution) => resolution.status === "resolved" ? {
9731
- name: resolution.track?.name ?? null,
9732
- source: resolution.source,
9733
- detail: resolution.detail,
9734
- warnings: resolution.warnings,
9735
- track: resolution.track,
9736
- tracks: resolution.tracks,
9737
- blocking: resolution.blocking
9738
- } : null;
9739
- function vcsConfig(mode, cwd, options = {}) {
9740
- const ws = resolveRuntimeWorkspaceVcs(vcsCwd(cwd));
9741
- const wsVcs = ws?.vcs ?? {};
9742
- const wsYt = ws?.youtrack ?? {};
9743
- const wsIssues = ws?.issues ?? {};
9744
- const { status: cfgStatus, path: cfgPath, config: cfg, error: cfgError } = readVcsConfig();
9745
- const root = vcsCwd(cwd);
9746
- const determinateRoot = cwd !== undefined || process.env.WORKFLOW_WORKSPACE_ROOT !== undefined;
9747
- const rawProvider = wsVcs.provider ?? (determinateRoot ? remoteProvider(root) : null) ?? cfg.provider ?? null;
9748
- const provider = rawProvider === null || rawProvider === undefined ? null : String(rawProvider).toLowerCase() || null;
9749
- const workspaceDefault = workspaceDefaultTarget(root, cfg.defaultTargetBranch);
9750
- const linkIssues = typeof wsYt.link_issues === "boolean" ? wsYt.link_issues : null;
9751
- const youtrackBaseUrl = typeof wsYt.baseUrl === "string" ? wsYt.baseUrl : null;
9752
- let issuesProvider = null;
9753
- let linkOnPr = null;
9754
- if (provider === "github" && typeof wsIssues.provider === "string" && wsIssues.provider.toLowerCase() === "github") {
9755
- issuesProvider = "github";
9756
- linkOnPr = typeof wsIssues.link_on_pr === "boolean" ? wsIssues.link_on_pr : null;
9757
- }
9758
- if (mode === "resolve") {
9759
- if (cfgStatus === "malformed")
9760
- return { ok: false, error: cfgError, configPath: cfgPath };
9761
- const trackResolution = resolveTrackFor(root, {
9762
- track: options.track,
9763
- ...options.branch !== undefined ? { branch: options.branch } : {},
9764
- defaultBranch: workspaceDefault
9765
- });
9766
- if (trackResolution.status === "invalid")
9767
- return { ok: false, error: trackResolution.error, configPath: workspacesPath() };
9768
- const releaseTrack = trackSummary(trackResolution);
9769
- const defaultTarget = releaseTrack?.track?.pullRequestTarget ?? workspaceDefault;
9770
- const baseBranch = releaseTrack?.track?.baseBranch ?? defaultTarget;
9771
- return {
9772
- ok: true,
9773
- workspace_name: ws?.name ?? null,
9774
- provider,
9775
- defaultTargetBranch: defaultTarget,
9776
- workspaceDefaultTargetBranch: workspaceDefault,
9777
- baseBranch,
9778
- releaseTrack,
9779
- link_issues: linkIssues,
9780
- youtrack_base_url: youtrackBaseUrl,
9781
- issues_provider: issuesProvider,
9782
- link_on_pr: linkOnPr
9783
- };
9784
- }
9785
- if (cfgStatus === "malformed") {
9786
- return { ok: false, error: cfgError, configPath: cfgPath };
9787
- }
9788
- if (cfgStatus === "missing" && provider === null)
9789
- return { ok: false, error: `vcs.json is missing: ${cfgPath}`, configPath: cfgPath };
9790
- if (provider === null) {
9791
- return {
9792
- ok: false,
9793
- error: `no vcs provider configured (set vcs.provider in ${cfgPath}, match a workspace entry, or run inside a checkout with a recognized origin remote)`,
9794
- configPath: cfgPath
9795
- };
9796
- }
9797
- const prov = cfg[provider] ?? {};
9798
- const out = {
9799
- ok: true,
9800
- configPath: path4.resolve(cfgPath),
9801
- provider,
9802
- defaultTargetBranch: workspaceDefault,
9803
- pr: cfg.pr ?? {},
9804
- workspace_name: ws?.name ?? null,
9805
- link_issues: linkIssues,
9806
- youtrack_base_url: youtrackBaseUrl,
9807
- issues_provider: issuesProvider,
9808
- 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);
9809
10006
  };
9810
- if (provider === "gitlab") {
9811
- out.gitlab = {
9812
- host: prov.host ?? "gitlab.com",
9813
- apiUrl: prov.apiUrl ?? "https://gitlab.com/api/v4"
9814
- };
9815
- } else if (provider === "github") {
9816
- 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`);
9817
10012
  }
9818
- 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 ")}`);
9819
10017
  }
9820
-
9821
- // packages/workit-core/src/host-session.ts
9822
- import { createHash as createHash6 } from "node:crypto";
9823
- import fs4 from "node:fs";
9824
- import path7 from "node:path";
9825
-
9826
- // packages/workit-core/src/core/logger.ts
9827
- import os3 from "node:os";
9828
- import path5 from "node:path";
9829
- var SENSITIVE_WORDS = new Set([
9830
- "token",
9831
- "secret",
9832
- "password",
9833
- "passwd",
9834
- "authorization",
9835
- "credential",
9836
- "credentials",
9837
- "cookie",
9838
- "api",
9839
- "apikey",
9840
- "apitoken",
9841
- "accesstoken",
9842
- "clientsecret",
9843
- "refreshtoken",
9844
- "key",
9845
- "bearer",
9846
- "prompt",
9847
- "message",
9848
- "messages",
9849
- "content",
9850
- "body",
9851
- "args",
9852
- "argument",
9853
- "result",
9854
- "results",
9855
- "output",
9856
- "issue",
9857
- "description",
9858
- "summary",
9859
- "payload",
9860
- "script",
9861
- "command",
9862
- "text",
9863
- "query"
9864
- ]);
9865
- var STACK_WORDS = new Set(["stack", "stacktrace", "trace"]);
9866
- var escapeRegExp2 = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
9867
- var resolveStateDir = () => {
9868
- const override = process.env.WORKFLOW_TOOLKIT_STATE;
9869
- if (override)
9870
- return override;
9871
- const home = os3.homedir();
9872
- if (process.env.XDG_STATE_HOME)
9873
- return path5.join(process.env.XDG_STATE_HOME, "workit");
9874
- switch (os3.platform()) {
9875
- case "darwin":
9876
- return path5.join(home, "Library", "Application Support", "workit");
9877
- case "win32":
9878
- return path5.join(process.env.LOCALAPPDATA ?? path5.join(home, "AppData", "Local"), "workit");
9879
- default:
9880
- return path5.join(process.env.HOME ?? home, ".local", "state", "workit");
9881
- }
9882
- };
9883
- var homePattern = new RegExp(`${escapeRegExp2(os3.homedir())}(?=[/\\\\]|$)|\\$HOME(?=[/\\\\]|$)`, "g");
9884
-
9885
- // packages/workit-core/src/store/paths.ts
9886
- import { spawnSync as spawnSync6 } from "node:child_process";
9887
- import { createHash as createHash5 } from "node:crypto";
9888
- import fs3 from "node:fs";
9889
- import path6 from "node:path";
9890
- var NOT_A_REPO = /not a git repository \(or any (of the parent directories|parent up to)/u;
9891
- var gitRun = (cwd, args) => {
10018
+ var git2 = (cwd, args) => {
9892
10019
  const run = spawnSync6("git", args, {
9893
10020
  cwd,
9894
10021
  encoding: "utf8",
9895
10022
  timeout: 20000,
9896
- killSignal: "SIGKILL",
9897
10023
  windowsHide: true,
9898
- env: {
9899
- ...process.env,
9900
- GIT_TERMINAL_PROMPT: "0",
9901
- GIT_OPTIONAL_LOCKS: "0",
9902
- LC_ALL: "C",
9903
- LANGUAGE: "C"
9904
- }
10024
+ stdio: ["ignore", "pipe", "ignore"]
9905
10025
  });
9906
- 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");
9907
10033
  return {
9908
- ok: run.status === 0,
9909
- stdout: run.stdout ?? "",
9910
- stderr: error ? String(error.message) : run.stderr ?? "",
9911
- missing: error?.code === "ENOENT"
10034
+ sha,
10035
+ time: Number(/@\{(\d+)\}$/u.exec(selector)?.[1] ?? Number.NaN),
10036
+ subject: subject.join("\t")
9912
10037
  };
9913
- };
9914
- var shortHash = (value) => createHash5("sha256").update(value).digest("hex").slice(0, 12);
9915
- var real = (file) => {
9916
- try {
9917
- return fs3.realpathSync.native(file);
9918
- } catch {
9919
- return file;
9920
- }
9921
- };
9922
- var DISCOVERY_ENV = [
9923
- "GIT_DIR",
9924
- "GIT_COMMON_DIR",
9925
- "GIT_WORK_TREE",
9926
- "GIT_CEILING_DIRECTORIES",
9927
- "GIT_DISCOVERY_ACROSS_FILESYSTEM"
9928
- ];
9929
- var isGitDir = (dir) => {
9930
- try {
9931
- return fs3.statSync(path6.join(dir, "HEAD")).isFile() && fs3.statSync(path6.join(dir, "objects")).isDirectory() && fs3.statSync(path6.join(dir, "refs")).isDirectory();
9932
- } catch {
9933
- return false;
9934
- }
9935
- };
9936
- var reftableRefs = (common) => {
9937
- try {
9938
- return /^\s*refstorage\s*=\s*reftable\s*$/imu.test(fs3.readFileSync(path6.join(common, "config"), "utf8"));
9939
- } catch {
9940
- return false;
9941
- }
9942
- };
9943
- function fastGit(root) {
9944
- if (DISCOVERY_ENV.some((name) => process.env[name]))
9945
- return null;
9946
- let dir = real(root);
9947
- let device = null;
9948
- for (;; ) {
9949
- let stat;
9950
- try {
9951
- stat = fs3.statSync(dir);
9952
- } catch {
9953
- return null;
9954
- }
9955
- if (device !== null && stat.dev !== device)
9956
- return { kind: "none" };
9957
- device = stat.dev;
9958
- const dotGit = path6.join(dir, ".git");
9959
- let entry = null;
9960
- try {
9961
- entry = fs3.statSync(dotGit);
9962
- } catch {}
9963
- let gitDir = null;
9964
- let top = null;
9965
- if (entry) {
9966
- top = dir;
9967
- if (entry.isDirectory())
9968
- gitDir = dotGit;
9969
- else if (entry.isFile()) {
9970
- let text = "";
9971
- try {
9972
- text = fs3.readFileSync(dotGit, "utf8");
9973
- } catch {
9974
- return null;
9975
- }
9976
- const match = /^gitdir: (.+)$/mu.exec(text);
9977
- if (!match)
9978
- return null;
9979
- gitDir = path6.resolve(dir, match[1].trim());
9980
- } else
9981
- return null;
9982
- if (!fs3.existsSync(path6.join(gitDir, "HEAD")))
9983
- return null;
9984
- } else if (isGitDir(dir))
9985
- gitDir = dir;
9986
- if (gitDir) {
9987
- let common = gitDir;
9988
- try {
9989
- common = path6.resolve(gitDir, fs3.readFileSync(path6.join(gitDir, "commondir"), "utf8").trim());
9990
- } catch {}
9991
- 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")
9992
10060
  return null;
9993
- return {
9994
- kind: "repo",
9995
- common: real(common),
9996
- gitDir: real(gitDir),
9997
- top: top === null ? null : real(top),
9998
- reftable: reftableRefs(common)
9999
- };
10000
- }
10001
- const parent = path6.dirname(dir);
10002
- if (parent === dir)
10003
- return { kind: "none" };
10004
- 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;
10005
10120
  }
10006
- }
10007
- function resolveStore(root) {
10008
- root = path6.resolve(root);
10009
- let directory = false;
10010
- try {
10011
- directory = fs3.statSync(root).isDirectory();
10012
- } catch {}
10013
- if (!directory)
10014
- return { dir: path6.join(root, ".workit"), shared: false, top: null };
10015
- const fast = fastGit(root);
10016
- if (fast?.kind === "none")
10017
- return { dir: path6.join(root, ".workit"), shared: false, top: null };
10018
- if (fast?.kind === "repo")
10019
- return {
10020
- dir: path6.join(fast.common, "workit"),
10021
- shared: true,
10022
- top: fast.top,
10023
- git: { gitDir: fast.gitDir, top: fast.top, reftable: fast.reftable }
10024
- };
10025
- const run = gitRun(root, ["rev-parse", "--path-format=absolute", "--git-common-dir"]);
10026
- const common = run.stdout.trim();
10027
- if (run.ok && common) {
10028
- const top = gitRun(root, ["rev-parse", "--path-format=absolute", "--show-toplevel"]);
10029
- return {
10030
- dir: path6.join(real(path6.resolve(root, common)), "workit"),
10031
- shared: true,
10032
- top: top.ok && top.stdout.trim() ? real(top.stdout.trim()) : null
10033
- };
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);
10034
10126
  }
10035
- if (run.missing || !run.ok && NOT_A_REPO.test(run.stderr))
10036
- return { dir: path6.join(root, ".workit"), shared: false, top: null };
10037
- return new Error(`cannot locate the workit store: git rev-parse failed (${run.stderr.trim().split(`
10038
- `)[0] || "no output"}); fix the repository (git status should work here)`);
10127
+ return resolution;
10039
10128
  }
10040
- var rebasingBranch = (gitDir) => {
10041
- for (const state of ["rebase-merge", "rebase-apply"]) {
10042
- try {
10043
- const ref = fs3.readFileSync(path6.join(gitDir, state, "head-name"), "utf8").trim();
10044
- const match = /^refs\/heads\/(.+)$/u.exec(ref);
10045
- if (match)
10046
- return match[1];
10047
- } catch {}
10048
- }
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";
10049
10142
  return null;
10050
10143
  };
10051
- function resolveTaskKey(root, location) {
10052
- if (!location.shared)
10053
- return { key: `dir-${shortHash(root)}`, kind: "dir", branch: null };
10054
- const branchKey = (name) => ({ key: name, kind: "branch", branch: name });
10055
- if (location.git && !location.git.reftable) {
10056
- let head = "";
10057
- try {
10058
- head = fs3.readFileSync(path6.join(location.git.gitDir, "HEAD"), "utf8").trim();
10059
- } catch {}
10060
- const ref = /^ref: refs\/heads\/(.+)$/u.exec(head);
10061
- if (ref && ref[1] !== ".invalid")
10062
- return branchKey(ref[1]);
10063
- if (/^[0-9a-f]{40,64}$/u.test(head)) {
10064
- const rebasing = rebasingBranch(location.git.gitDir);
10065
- if (rebasing)
10066
- return branchKey(rebasing);
10067
- return {
10068
- key: `detached-${shortHash(location.git.top ?? location.git.gitDir)}`,
10069
- kind: "detached",
10070
- branch: null
10071
- };
10072
- }
10073
- }
10074
- const branch = gitRun(root, ["symbolic-ref", "-q", "--short", "HEAD"]);
10075
- const name = branch.ok ? branch.stdout.trim() : "";
10076
- if (name && name !== ".invalid")
10077
- return branchKey(name);
10078
- const gitDir = gitRun(root, ["rev-parse", "--path-format=absolute", "--git-dir"]);
10079
- if (gitDir.ok && gitDir.stdout.trim()) {
10080
- const rebasing = rebasingBranch(gitDir.stdout.trim());
10081
- if (rebasing)
10082
- return branchKey(rebasing);
10083
- }
10084
- const top = gitRun(root, ["rev-parse", "--path-format=absolute", "--show-toplevel"]);
10085
- const worktree = top.ok && top.stdout.trim() ? top.stdout.trim() : root;
10086
- return { key: `detached-${shortHash(real(worktree))}`, kind: "detached", branch: null };
10087
- }
10088
- function checkoutRootOf(root, location) {
10089
- const resolved = path6.resolve(root);
10090
- let canonical = resolved;
10144
+ var readVcsConfig = () => {
10145
+ const file = vcsConfigPath();
10146
+ let raw;
10091
10147
  try {
10092
- canonical = fs3.realpathSync(resolved);
10148
+ raw = fs4.readFileSync(file, "utf8");
10093
10149
  } catch {
10094
- return resolved;
10150
+ return { status: "missing", path: file, config: {} };
10095
10151
  }
10096
- const found = location ?? resolveStore(canonical);
10097
- if (found instanceof Error || !found.shared || !found.top)
10098
- return canonical;
10099
- if (real(canonical) === found.top)
10100
- return canonical;
10152
+ let parsed;
10101
10153
  try {
10102
- return fs3.realpathSync(found.top);
10154
+ parsed = JSON.parse(raw);
10103
10155
  } catch {
10104
- return canonical;
10156
+ return { status: "malformed", path: file, config: {}, error: `${file} is not valid JSON` };
10105
10157
  }
10106
- }
10107
- function branchRenames(location) {
10108
- if (!location.shared)
10109
- return [];
10110
- const files = [];
10111
- const walk = (dir) => {
10112
- let entries;
10113
- try {
10114
- entries = fs3.readdirSync(dir, { withFileTypes: true });
10115
- } catch {
10116
- return;
10117
- }
10118
- for (const entry of entries)
10119
- if (entry.isDirectory())
10120
- walk(path6.join(dir, entry.name));
10121
- else
10122
- 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
10123
10242
  };
10124
- walk(path6.join(path6.dirname(location.dir), "logs", "refs", "heads"));
10125
- if (location.git)
10126
- files.push(path6.join(location.git.gitDir, "logs", "HEAD"));
10127
- const seen = new Set;
10128
- const renames = [];
10129
- for (const file of files) {
10130
- let text = "";
10131
- try {
10132
- text = fs3.readFileSync(file, "utf8");
10133
- } catch {
10134
- continue;
10135
- }
10136
- for (const match of text.matchAll(/ (\d+) [+-]\d{4}\tBranch: renamed refs\/heads\/(\S+) to refs\/heads\/(\S+)/gu)) {
10137
- const rename = { from: match[2], to: match[3], at: Number(match[1]) };
10138
- const id = `${rename.at}\x00${rename.from}\x00${rename.to}`;
10139
- if (seen.has(id))
10140
- continue;
10141
- seen.add(id);
10142
- renames.push(rename);
10143
- }
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");
10144
10314
  }
10145
- return renames.toSorted((left, right) => left.at - right.at);
10146
- }
10147
- function followRenames(key, boundAt, renames) {
10148
- let current = key;
10149
- let since = Math.floor(Date.parse(boundAt) / 1000);
10150
- for (const rename of renames)
10151
- if (rename.from === current && rename.at >= since) {
10152
- current = rename.to;
10153
- since = rename.at;
10154
- }
10155
- return current;
10156
- }
10157
- var checkoutSlug = (root) => {
10158
- let canonical = real(root);
10159
- if (process.platform === "win32")
10160
- canonical = canonical.toLowerCase();
10161
- const base = path6.basename(canonical).replace(/[^A-Za-z0-9._-]/g, "_").slice(0, 40) || "root";
10162
- return `${base}-${shortHash(canonical)}`;
10163
10315
  };
10316
+ var homePattern = new RegExp(`${escapeRegExp2(os3.homedir())}(?=[/\\\\]|$)|\\$HOME(?=[/\\\\]|$)`, "g");
10164
10317
 
10165
10318
  // packages/workit-core/src/host-session.ts
10166
10319
  var HOST_SESSION_VARS = [
@@ -10191,9 +10344,10 @@ function hostSessionFromEnv(env) {
10191
10344
  return inner ? { host: inner.host, session: inner.session } : { host: declared, session: null };
10192
10345
  }
10193
10346
  var ISSUED_PER_LEAD = 64;
10194
- var ISSUED_SESSIONS_MARKER = "workit-issued-sessions/2";
10347
+ var ISSUED_SESSIONS_MARKER = "workit-issued-sessions/3";
10195
10348
  var hash = (value) => createHash6("sha256").update(value).digest("hex").slice(0, 32);
10196
10349
  var userIssuedRoot = () => path7.join(resolveStateDir(), "issued");
10350
+ var userTokenRoot = () => path7.join(resolveStateDir(), "issued-tokens");
10197
10351
  function migrateRepoIssued(cwd) {
10198
10352
  if (cwd === undefined)
10199
10353
  return;
@@ -10203,7 +10357,7 @@ function migrateRepoIssued(cwd) {
10203
10357
  if (location instanceof Error)
10204
10358
  return;
10205
10359
  old = path7.join(location.dir, "hooks", "issued");
10206
- if (!fs4.existsSync(old))
10360
+ if (!fs5.existsSync(old))
10207
10361
  return;
10208
10362
  } catch {
10209
10363
  return;
@@ -10211,13 +10365,18 @@ function migrateRepoIssued(cwd) {
10211
10365
  for (const lead of safeList(old)) {
10212
10366
  try {
10213
10367
  const from = path7.join(old, lead);
10214
- if (!fs4.statSync(from).isDirectory())
10368
+ if (!fs5.statSync(from).isDirectory())
10215
10369
  continue;
10216
10370
  const to = path7.join(userIssuedRoot(), lead);
10217
- fs4.mkdirSync(to, { recursive: true, mode: 448 });
10218
- for (const id of fs4.readdirSync(from)) {
10219
- fs4.copyFileSync(path7.join(from, id), path7.join(to, id));
10220
- 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 });
10221
10380
  }
10222
10381
  removeIfEmpty(from);
10223
10382
  } catch {}
@@ -10226,31 +10385,81 @@ function migrateRepoIssued(cwd) {
10226
10385
  }
10227
10386
  var safeList = (dir) => {
10228
10387
  try {
10229
- return fs4.readdirSync(dir);
10388
+ return fs5.readdirSync(dir);
10230
10389
  } catch {
10231
10390
  return [];
10232
10391
  }
10233
10392
  };
10234
10393
  var removeIfEmpty = (dir) => {
10235
10394
  try {
10236
- fs4.rmdirSync(dir);
10395
+ fs5.rmdirSync(dir);
10237
10396
  } catch {}
10238
10397
  };
10398
+ var tokenHash = (token) => createHash6("sha256").update(token).digest("hex");
10239
10399
  function recordIssuedSession(cwd, lead, issued) {
10400
+ const token = randomBytes(18).toString("base64url");
10240
10401
  try {
10241
10402
  migrateRepoIssued(cwd);
10242
- const dir = path7.join(userIssuedRoot(), hash(lead));
10243
- fs4.mkdirSync(dir, { recursive: true, mode: 448 });
10244
10403
  const own = hash(issued);
10245
- fs4.writeFileSync(path7.join(dir, own), ISSUED_SESSIONS_MARKER);
10246
- 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);
10247
10421
  if (entries.length <= ISSUED_PER_LEAD)
10248
10422
  return;
10249
- 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);
10250
- for (const { name } of byAge.slice(0, entries.length - ISSUED_PER_LEAD))
10251
- 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
+ }
10252
10428
  } catch {}
10253
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
+ }
10254
10463
  var OWN_CHILD_SHELL = new Set(["opencode", "pi"]);
10255
10464
 
10256
10465
  // packages/workit-core/src/ledger.ts
@@ -10267,7 +10476,7 @@ var err = (code, error, unblock) => ({
10267
10476
  ...unblock ? { unblock } : {}
10268
10477
  });
10269
10478
  var gitRun2 = (cwd, args, timeoutMs = GIT_TIMEOUTS.local) => {
10270
- const run = spawnSync7("git", args, {
10479
+ const run = spawnSync8("git", args, {
10271
10480
  cwd,
10272
10481
  encoding: "utf8",
10273
10482
  timeout: timeoutMs,
@@ -10379,7 +10588,7 @@ function ledgerPath(cwd) {
10379
10588
  const root = storeRoot(cwd);
10380
10589
  return root.ok ? { ok: true, value: path8.join(root.value.root, "ledger", "ledger.jsonl") } : root;
10381
10590
  }
10382
- var newId2 = () => `${Date.now().toString(36)}-${randomBytes(6).toString("hex")}`;
10591
+ var newId2 = () => `${Date.now().toString(36)}-${randomBytes2(6).toString("hex")}`;
10383
10592
  var NETWORK_FS_TYPES = new Set([
10384
10593
  26985,
10385
10594
  20859,
@@ -10397,7 +10606,7 @@ var onNetworkFs = (dir) => {
10397
10606
  if (process.platform !== "linux")
10398
10607
  return false;
10399
10608
  try {
10400
- return isNetworkFsType(fs5.statfsSync(dir).type);
10609
+ return isNetworkFsType(fs6.statfsSync(dir).type);
10401
10610
  } catch {
10402
10611
  return false;
10403
10612
  }
@@ -10407,7 +10616,7 @@ var LOCK_STALE_MS = 30000;
10407
10616
  var LOCK_OWNER = "owner";
10408
10617
  var lockToken = (lock) => {
10409
10618
  try {
10410
- return fs5.readFileSync(path8.join(lock, LOCK_OWNER), "utf8");
10619
+ return fs6.readFileSync(path8.join(lock, LOCK_OWNER), "utf8");
10411
10620
  } catch {
10412
10621
  return null;
10413
10622
  }
@@ -10415,7 +10624,7 @@ var lockToken = (lock) => {
10415
10624
  var lockAgeMs = (lock) => {
10416
10625
  for (const target of [path8.join(lock, LOCK_OWNER), lock]) {
10417
10626
  try {
10418
- return Date.now() - fs5.statSync(target).mtimeMs;
10627
+ return Date.now() - fs6.statSync(target).mtimeMs;
10419
10628
  } catch {}
10420
10629
  }
10421
10630
  return null;
@@ -10423,55 +10632,55 @@ var lockAgeMs = (lock) => {
10423
10632
  var ledgerLock = {
10424
10633
  acquire(lock) {
10425
10634
  try {
10426
- fs5.mkdirSync(lock);
10635
+ fs6.mkdirSync(lock);
10427
10636
  } catch (error) {
10428
10637
  if (error.code === "EEXIST")
10429
10638
  return null;
10430
10639
  throw error;
10431
10640
  }
10432
- const token = `${process.pid}-${randomBytes(8).toString("hex")}`;
10433
- 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);
10434
10643
  return token;
10435
10644
  },
10436
10645
  release(lock, token) {
10437
10646
  if (lockToken(lock) !== token)
10438
10647
  return false;
10439
- const tomb = `${lock}.release-${randomBytes(6).toString("hex")}`;
10648
+ const tomb = `${lock}.release-${randomBytes2(6).toString("hex")}`;
10440
10649
  try {
10441
- fs5.renameSync(lock, tomb);
10650
+ fs6.renameSync(lock, tomb);
10442
10651
  } catch {
10443
10652
  return false;
10444
10653
  }
10445
10654
  if (lockToken(tomb) !== token) {
10446
10655
  try {
10447
- fs5.renameSync(tomb, lock);
10656
+ fs6.renameSync(tomb, lock);
10448
10657
  } catch {}
10449
10658
  return false;
10450
10659
  }
10451
- fs5.rmSync(tomb, { recursive: true, force: true });
10660
+ fs6.rmSync(tomb, { recursive: true, force: true });
10452
10661
  return true;
10453
10662
  },
10454
10663
  token: lockToken,
10455
10664
  reap(lock, observed) {
10456
- const tomb = `${lock}.reap-${randomBytes(6).toString("hex")}`;
10665
+ const tomb = `${lock}.reap-${randomBytes2(6).toString("hex")}`;
10457
10666
  try {
10458
- fs5.renameSync(lock, tomb);
10667
+ fs6.renameSync(lock, tomb);
10459
10668
  } catch {
10460
10669
  return false;
10461
10670
  }
10462
10671
  if (lockToken(tomb) === observed) {
10463
- fs5.rmSync(tomb, { recursive: true, force: true });
10672
+ fs6.rmSync(tomb, { recursive: true, force: true });
10464
10673
  return true;
10465
10674
  }
10466
10675
  try {
10467
- fs5.renameSync(tomb, lock);
10676
+ fs6.renameSync(tomb, lock);
10468
10677
  } catch {
10469
- fs5.rmSync(tomb, { recursive: true, force: true });
10678
+ fs6.rmSync(tomb, { recursive: true, force: true });
10470
10679
  }
10471
10680
  return false;
10472
10681
  }
10473
10682
  };
10474
- function withLedgerLock(file, fn, waitMs = LOCK_WAIT_MS) {
10683
+ function withLedgerLock(file, fn, waitMs = hostAwareLockTimeout(LOCK_WAIT_MS)) {
10475
10684
  const lock = `${file}.lock`;
10476
10685
  const deadline = Date.now() + waitMs;
10477
10686
  let token = null;
@@ -10500,7 +10709,7 @@ var TAIL_BYTES = 64 * 1024;
10500
10709
  var lastLine = (fd, size) => {
10501
10710
  for (let span = Math.min(size, TAIL_BYTES);span > 0; span = Math.min(size, span * 2)) {
10502
10711
  const buffer = Buffer.alloc(span);
10503
- fs5.readSync(fd, buffer, 0, span, size - span);
10712
+ fs6.readSync(fd, buffer, 0, span, size - span);
10504
10713
  const end = buffer.lastIndexOf(10);
10505
10714
  if (end < 0) {
10506
10715
  if (span === size)
@@ -10529,15 +10738,23 @@ var lastLine = (fd, size) => {
10529
10738
  }
10530
10739
  return null;
10531
10740
  };
10741
+ var tighten = (fd) => {
10742
+ try {
10743
+ if ((fs6.fstatSync(fd).mode & 63) !== 0)
10744
+ fs6.fchmodSync(fd, 384);
10745
+ } catch {}
10746
+ };
10532
10747
  function appendLine(file, build, options) {
10533
10748
  const write = () => {
10534
- const fd = fs5.openSync(file, "a+");
10749
+ const fd = fs6.openSync(file, "a+", 384);
10535
10750
  try {
10536
- const size = fs5.fstatSync(fd).size;
10751
+ if (process.platform !== "win32")
10752
+ tighten(fd);
10753
+ const size = fs6.fstatSync(fd).size;
10537
10754
  let fence = "";
10538
10755
  if (size > 0) {
10539
10756
  const last = Buffer.alloc(1);
10540
- fs5.readSync(fd, last, 0, 1, size - 1);
10757
+ fs6.readSync(fd, last, 0, 1, size - 1);
10541
10758
  if (last[0] !== 10)
10542
10759
  fence = `
10543
10760
  `;
@@ -10546,16 +10763,16 @@ function appendLine(file, build, options) {
10546
10763
  if (!line.ok)
10547
10764
  return line;
10548
10765
  const bytes = Buffer.from(fence + line.value);
10549
- const written = fs5.writeSync(fd, bytes);
10766
+ const written = fs6.writeSync(fd, bytes);
10550
10767
  if (written !== bytes.length)
10551
10768
  return err("unavailable", `short write to ${file}: ${written} of ${bytes.length} bytes; the row is torn and will be skipped`);
10552
10769
  return { ok: true, value: undefined };
10553
10770
  } finally {
10554
- fs5.closeSync(fd);
10771
+ fs6.closeSync(fd);
10555
10772
  }
10556
10773
  };
10557
10774
  try {
10558
- fs5.mkdirSync(path8.dirname(file), { recursive: true });
10775
+ fs6.mkdirSync(path8.dirname(file), { recursive: true });
10559
10776
  if (!options.forceLock && !onNetworkFs(path8.dirname(file)))
10560
10777
  return write();
10561
10778
  const locked = withLedgerLock(file, write);
@@ -10627,8 +10844,10 @@ function readLedger(cwd, options = {}) {
10627
10844
  if (!file.ok)
10628
10845
  return file;
10629
10846
  let text = "";
10847
+ let stat = null;
10630
10848
  try {
10631
- text = fs5.readFileSync(file.value, "utf8");
10849
+ stat = fs6.statSync(file.value);
10850
+ text = fs6.readFileSync(file.value, "utf8");
10632
10851
  } catch (error) {
10633
10852
  if (error.code === "ENOENT")
10634
10853
  return {
@@ -10647,6 +10866,7 @@ function readLedger(cwd, options = {}) {
10647
10866
  const integrity = { chained: 0, legacy: 0, unverified: [] };
10648
10867
  const earlier = new Set([null]);
10649
10868
  let chainStarted = false;
10869
+ const ids = new Set;
10650
10870
  for (const raw of text.split(`
10651
10871
  `)) {
10652
10872
  const trimmed = raw.trim();
@@ -10680,6 +10900,10 @@ function readLedger(cwd, options = {}) {
10680
10900
  reason = "unsigned";
10681
10901
  else
10682
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);
10683
10907
  if (reason)
10684
10908
  rowIntegrity = "unverified";
10685
10909
  const pr = typeof parsed.pr === "number" && Number.isInteger(parsed.pr) ? parsed.pr : undefined;
@@ -10703,7 +10927,8 @@ function readLedger(cwd, options = {}) {
10703
10927
  seq: rows.length + 1,
10704
10928
  superseded: false,
10705
10929
  supersedeIgnored: false,
10706
- ...rowIntegrity ? { integrity: rowIntegrity } : {}
10930
+ ...rowIntegrity ? { integrity: rowIntegrity } : {},
10931
+ ...reason ? { integrityReason: reason } : {}
10707
10932
  };
10708
10933
  rows.push(row);
10709
10934
  if (!options.integrity)
@@ -10731,18 +10956,48 @@ function readLedger(cwd, options = {}) {
10731
10956
  const byId = new Map;
10732
10957
  for (const row of rows) {
10733
10958
  const target = row.supersedes ? byId.get(row.supersedes) : undefined;
10734
- if (target && supersedeAllowed(row, target, renameAwareBranch(scopeOf, row.branch)) === null)
10959
+ if (target && row.integrity !== "unverified" && supersedeAllowed(row, target, renameAwareBranch(scopeOf, row.branch)) === null)
10735
10960
  target.superseded = true;
10736
10961
  else if (row.supersedes)
10737
10962
  row.supersedeIgnored = true;
10738
10963
  if (!byId.has(row.id))
10739
10964
  byId.set(row.id, row);
10740
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
+ });
10741
10972
  return {
10742
10973
  ok: true,
10743
10974
  value: { path: file.value, rows, skipped, integrity: options.integrity ? integrity : null }
10744
10975
  };
10745
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)}`;
10746
11001
  function verdictBasis(verdict, current) {
10747
11002
  if (verdict.head && verdict.head === current.head)
10748
11003
  return "fresh";
@@ -10750,7 +11005,7 @@ function verdictBasis(verdict, current) {
10750
11005
  return "carried";
10751
11006
  return "stale";
10752
11007
  }
10753
- 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);
10754
11009
  function trailerSessions(cwd, base, head) {
10755
11010
  if (!base || !head || !safeRef(base) || !safeRef(head))
10756
11011
  return [];
@@ -10831,6 +11086,56 @@ var authoredBy = (authors, session) => {
10831
11086
  return true;
10832
11087
  return false;
10833
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));
10834
11139
  var independenceReasons = (row, authors) => {
10835
11140
  const reasons = [];
10836
11141
  if (!row.actor.session)
@@ -10841,15 +11146,17 @@ var independenceReasons = (row, authors) => {
10841
11146
  reasons.push("self");
10842
11147
  return reasons;
10843
11148
  };
10844
- function effectiveOf(rows, authors, sameCode, isCurrent) {
11149
+ function effectiveOf(rows, reasonsOf, sameCode, isCurrent) {
10845
11150
  let effective = null;
10846
11151
  for (const row of rows) {
10847
11152
  if (!effective) {
10848
11153
  effective = row;
10849
11154
  continue;
10850
11155
  }
10851
- const rowIndependent = independenceReasons(row, authors).length === 0;
10852
- 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;
10853
11160
  if (effIndependent && !rowIndependent && isCurrent(effective))
10854
11161
  continue;
10855
11162
  if (effIndependent && FAILING.has(String(effective.result)) && row.actor.session === effective.actor.session && sameCode(effective, row))
@@ -10863,7 +11170,7 @@ function restackCarries(rows, branch, from, to) {
10863
11170
  return false;
10864
11171
  const edges = new Map;
10865
11172
  for (const row of rows)
10866
- 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)
10867
11174
  edges.set(row.fromHead, [...edges.get(row.fromHead) ?? [], row.head]);
10868
11175
  const seen = new Set([from]);
10869
11176
  const queue = [from];
@@ -10879,7 +11186,8 @@ function restackCarries(rows, branch, from, to) {
10879
11186
  }
10880
11187
  return false;
10881
11188
  }
10882
- function checkVerdicts(cwd, branch, rows) {
11189
+ function checkVerdicts(cwd, branch, ledgerRows) {
11190
+ const rows = withIntegrity(cwd, ledgerRows);
10883
11191
  const head = branchHead(cwd, branch);
10884
11192
  const fallbackBase = defaultBase(cwd);
10885
11193
  const scope = branchScope(cwd, branch);
@@ -10909,21 +11217,47 @@ function checkVerdicts(cwd, branch, rows) {
10909
11217
  return verdictBasis(failed, { head: row.head, ...keys }) !== "stale" || restackCarries(rows, branch, failed.head, row.head);
10910
11218
  };
10911
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);
10912
11246
  const byKind = new Map;
10913
11247
  for (const row of rows)
10914
- if (isLiveVerdict(row) && scope(row)) {
11248
+ if (isLiveVerdict(row, retired) && scope(row)) {
10915
11249
  const kind = typeof row.kind === "string" ? row.kind : "review";
10916
11250
  byKind.set(kind, [...byKind.get(kind) ?? [], row]);
10917
11251
  }
10918
11252
  const verdicts = [];
10919
11253
  const warnings = [];
10920
11254
  for (const [kind, list] of byKind) {
10921
- const row = effectiveOf(list, authors, sameCode, (candidate) => basisOf(candidate) !== "stale");
11255
+ const row = effectiveOf(list, reasonsOf, sameCode, (candidate) => basisOf(candidate) !== "stale");
10922
11256
  if (!row)
10923
11257
  continue;
10924
11258
  const basis = basisOf(row);
10925
11259
  const current = basis !== "stale";
10926
- const independence = independenceReasons(row, authors);
11260
+ const independence = reasonsOf(row);
10927
11261
  const reasons = [
10928
11262
  ...current ? [] : ["stale"],
10929
11263
  ...PASSING.has(String(row.result)) ? [] : ["not_passing"],
@@ -10940,23 +11274,31 @@ function checkVerdicts(cwd, branch, rows) {
10940
11274
  });
10941
11275
  if (reasons.length === 0) {
10942
11276
  for (const later of list)
10943
- 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")
10944
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`);
10945
11279
  }
10946
11280
  }
10947
11281
  for (const row of rows)
10948
11282
  if (row.integrity === "unverified" && scope(row))
10949
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
+ }
10950
11292
  verdicts.sort((a, b) => a.verdict.seq - b.verdict.seq);
10951
11293
  const newestCurrent = verdicts.findLast((entry) => entry.current) ?? verdicts.at(-1) ?? null;
10952
11294
  const failing = verdicts.find((entry) => entry.current && entry.independent && FAILING.has(String(entry.verdict.result)));
10953
11295
  const newestAccepted = verdicts.findLast((entry) => entry.accepted && STRONG_RESULTS.has(String(entry.verdict.result))) ?? null;
10954
- 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: [] } : {
10955
11297
  accepted: false,
10956
11298
  verdict: failing?.verdict ?? null,
10957
11299
  reasons: failing ? ["failing_verdict"] : !newestCurrent ? ["no_verdict"] : newestCurrent.reasons.length ? newestCurrent.reasons : ["type_check_only"]
10958
11300
  };
10959
- 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)));
10960
11302
  return {
10961
11303
  branch,
10962
11304
  head,
@@ -11922,12 +12264,12 @@ function sameFileIdentity(left, right, platform = process.platform) {
11922
12264
  }
11923
12265
 
11924
12266
  // node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.js
11925
- import { randomBytes as randomBytes2 } from "node:crypto";
12267
+ import { randomBytes as randomBytes3 } from "node:crypto";
11926
12268
  import fsSync from "node:fs";
11927
12269
  import path11 from "node:path";
11928
12270
 
11929
12271
  // node_modules/@openclaw/fs-safe/dist/bounded-read.js
11930
- import fs6 from "node:fs";
12272
+ import fs7 from "node:fs";
11931
12273
 
11932
12274
  // node_modules/@openclaw/fs-safe/dist/byte-budget.js
11933
12275
  function normalizeMaxBytes(value, options = {}) {
@@ -11964,7 +12306,7 @@ function readFileDescriptorBoundedSync(fd, maxBytes) {
11964
12306
  let total = 0;
11965
12307
  while (true) {
11966
12308
  const length = nextReadLength(total, maxBytes, scratch.length);
11967
- const bytesRead = fs6.readSync(fd, scratch, 0, length, null);
12309
+ const bytesRead = fs7.readSync(fd, scratch, 0, length, null);
11968
12310
  if (bytesRead === 0) {
11969
12311
  return Buffer.concat(chunks, total);
11970
12312
  }
@@ -11973,9 +12315,9 @@ function readFileDescriptorBoundedSync(fd, maxBytes) {
11973
12315
  }
11974
12316
 
11975
12317
  // node_modules/@openclaw/fs-safe/dist/read-open-flags.js
11976
- import fs7 from "node:fs";
12318
+ import fs8 from "node:fs";
11977
12319
  function resolveReadOpenFlags(options) {
11978
- const constants = options?.constants ?? fs7.constants;
12320
+ const constants = options?.constants ?? fs8.constants;
11979
12321
  const noFollow = process.platform !== "win32" && options?.followSymlinks !== true && typeof constants.O_NOFOLLOW === "number" ? constants.O_NOFOLLOW : 0;
11980
12322
  const nonBlocking = process.platform !== "win32" && typeof constants.O_NONBLOCK === "number" ? constants.O_NONBLOCK : 0;
11981
12323
  return constants.O_RDONLY | noFollow | nonBlocking;
@@ -11992,7 +12334,7 @@ function parseSidecarLockSnapshot(snapshot, parser) {
11992
12334
  }
11993
12335
  function createSidecarLockOwnershipToken() {
11994
12336
  let token = SIDECAR_LOCK_OWNERSHIP_TOKEN_PREFIX;
11995
- for (const byte of randomBytes2(SIDECAR_LOCK_OWNERSHIP_TOKEN_BYTES)) {
12337
+ for (const byte of randomBytes3(SIDECAR_LOCK_OWNERSHIP_TOKEN_BYTES)) {
11996
12338
  for (let bit = 7;bit >= 0; bit -= 1) {
11997
12339
  token += byte & 1 << bit ? "\t" : " ";
11998
12340
  }
@@ -12166,7 +12508,7 @@ function getFsSafeLockConfig() {
12166
12508
  }
12167
12509
 
12168
12510
  // node_modules/@openclaw/fs-safe/dist/file-lock-sync.js
12169
- import fs8 from "node:fs";
12511
+ import fs9 from "node:fs";
12170
12512
  import path12 from "node:path";
12171
12513
 
12172
12514
  // node_modules/@openclaw/fs-safe/dist/timing.js
@@ -12197,7 +12539,7 @@ function releaseAllSyncHeldLocks() {
12197
12539
  }
12198
12540
  try {
12199
12541
  if (held.fd !== undefined)
12200
- fs8.closeSync(held.fd);
12542
+ fs9.closeSync(held.fd);
12201
12543
  } catch {}
12202
12544
  try {
12203
12545
  removeSidecarLockIfUnchangedSync(held.lockPath, held.snapshot);
@@ -12232,7 +12574,7 @@ function releaseSyncHeldLock(held) {
12232
12574
  if (held.fd !== undefined) {
12233
12575
  const fd = held.fd;
12234
12576
  held.fd = undefined;
12235
- fs8.closeSync(fd);
12577
+ fs9.closeSync(fd);
12236
12578
  }
12237
12579
  removeSidecarLockIfUnchangedSync(held.lockPath, held.snapshot);
12238
12580
  heldLocks.delete(held.normalizedTargetPath);
@@ -12255,11 +12597,11 @@ function createSyncHeldLockHandle(held) {
12255
12597
  };
12256
12598
  }
12257
12599
  function canonicalLockParentSync(parent) {
12258
- return process.platform === "win32" ? fs8.realpathSync.native(parent) : fs8.realpathSync(parent);
12600
+ return process.platform === "win32" ? fs9.realpathSync.native(parent) : fs9.realpathSync(parent);
12259
12601
  }
12260
12602
  function normalizeTargetPath(targetPath) {
12261
12603
  const resolved = path12.resolve(targetPath);
12262
- fs8.mkdirSync(path12.dirname(resolved), { recursive: true });
12604
+ fs9.mkdirSync(path12.dirname(resolved), { recursive: true });
12263
12605
  try {
12264
12606
  return path12.join(canonicalLockParentSync(path12.dirname(resolved)), path12.basename(resolved));
12265
12607
  } catch {
@@ -12287,7 +12629,7 @@ function defaultShouldReclaim(snapshot, staleMs, nowMs) {
12287
12629
  }
12288
12630
  function reclaimGuardExists(reclaimGuardPath) {
12289
12631
  try {
12290
- fs8.lstatSync(reclaimGuardPath);
12632
+ fs9.lstatSync(reclaimGuardPath);
12291
12633
  return true;
12292
12634
  } catch (error) {
12293
12635
  if (error.code === "ENOENT")
@@ -12351,19 +12693,19 @@ function acquireFileLockSync(targetPath, options) {
12351
12693
  const { raw, ownershipToken } = serializeSidecarLockPayload(payload);
12352
12694
  let lockFileCreateDenied = false;
12353
12695
  try {
12354
- 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;
12355
12697
  try {
12356
- 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);
12357
12699
  } catch (error) {
12358
12700
  lockFileCreateDenied = isTransientLockFileDenial(error, lockPath);
12359
12701
  throw error;
12360
12702
  }
12361
- fs8.writeFileSync(fd, raw, "utf8");
12362
- fs8.fsyncSync(fd);
12703
+ fs9.writeFileSync(fd, raw, "utf8");
12704
+ fs9.fsyncSync(fd);
12363
12705
  const snapshot = {
12364
12706
  raw,
12365
12707
  payload,
12366
- stat: fs8.fstatSync(fd),
12708
+ stat: fs9.fstatSync(fd),
12367
12709
  ownershipToken
12368
12710
  };
12369
12711
  const createdHeld = {
@@ -12398,8 +12740,8 @@ function acquireFileLockSync(targetPath, options) {
12398
12740
  return returnedHandle;
12399
12741
  } catch (error) {
12400
12742
  if (fd !== undefined) {
12401
- const failed = { payload: null, stat: fs8.fstatSync(fd) };
12402
- fs8.closeSync(fd);
12743
+ const failed = { payload: null, stat: fs9.fstatSync(fd) };
12744
+ fs9.closeSync(fd);
12403
12745
  fd = undefined;
12404
12746
  removeSidecarLockIfUnchangedSync(lockPath, failed);
12405
12747
  }
@@ -12448,7 +12790,7 @@ function acquireFileLockSync(targetPath, options) {
12448
12790
  })) {
12449
12791
  let ownsReclaimGuard = false;
12450
12792
  try {
12451
- fs8.mkdirSync(reclaimGuardPath);
12793
+ fs9.mkdirSync(reclaimGuardPath);
12452
12794
  ownsReclaimGuard = true;
12453
12795
  if (removeSidecarLockIfUnchangedSync(lockPath, snapshot))
12454
12796
  continue;
@@ -12461,7 +12803,7 @@ function acquireFileLockSync(targetPath, options) {
12461
12803
  } finally {
12462
12804
  if (ownsReclaimGuard) {
12463
12805
  try {
12464
- fs8.rmdirSync(reclaimGuardPath);
12806
+ fs9.rmdirSync(reclaimGuardPath);
12465
12807
  } catch {}
12466
12808
  }
12467
12809
  }
@@ -12490,146 +12832,6 @@ var packageRoot = () => {
12490
12832
  }
12491
12833
  };
12492
12834
 
12493
- // packages/workit-core/src/core/store-lock.ts
12494
- import { spawnSync as spawnSync8 } from "node:child_process";
12495
- import fs9 from "node:fs";
12496
- import { hostname } from "node:os";
12497
- var metadataLockSchema = object({
12498
- pid: number2().int().nonnegative().safe(),
12499
- processStart: string2().nullable(),
12500
- host: string2().min(1),
12501
- nonce: string2().min(1),
12502
- externalAction: literal(true).optional()
12503
- }).strict();
12504
- var FOREIGN_LOCK_TTL_MS = 10 * 60000;
12505
- var UNREADABLE_LOCK_TTL_MS = 30000;
12506
- var RECLAIM_GUARD_TTL_MS = 30000;
12507
- var defaultLockTimeoutMs = 250;
12508
- var defaultLockTimeout = () => defaultLockTimeoutMs;
12509
- var parseMetadataLock = (raw) => {
12510
- let value;
12511
- try {
12512
- value = JSON.parse(raw);
12513
- } catch {
12514
- throw Object.assign(new Error("metadata lock is invalid"), { code: "metadata_lock_invalid" });
12515
- }
12516
- const parsed = metadataLockSchema.safeParse(value);
12517
- if (!parsed.success)
12518
- throw Object.assign(new Error("metadata lock is invalid"), { code: "metadata_lock_invalid" });
12519
- return parsed.data;
12520
- };
12521
- var parseMetadataLockOrNull = (raw) => {
12522
- try {
12523
- return parseMetadataLock(raw);
12524
- } catch {
12525
- return null;
12526
- }
12527
- };
12528
- var parseProcStatStart = (stat) => {
12529
- const close = stat.lastIndexOf(")");
12530
- if (close < 0)
12531
- return null;
12532
- return stat.slice(close + 1).trim().split(/\s+/)[19] ?? null;
12533
- };
12534
- var processStartOf = (pid) => {
12535
- if (process.platform === "linux") {
12536
- try {
12537
- return parseProcStatStart(fs9.readFileSync(`/proc/${pid}/stat`, "utf8"));
12538
- } catch {
12539
- return null;
12540
- }
12541
- }
12542
- if (process.platform === "darwin" || process.platform === "freebsd") {
12543
- try {
12544
- const run = spawnSync8("ps", ["-o", "lstart=", "-p", String(pid)], {
12545
- encoding: "utf8",
12546
- timeout: 1000
12547
- });
12548
- const value = run.status === 0 ? run.stdout.trim() : "";
12549
- return value || null;
12550
- } catch {
12551
- return null;
12552
- }
12553
- }
12554
- return null;
12555
- };
12556
- var readTrimmed = (file) => {
12557
- try {
12558
- return fs9.readFileSync(file, "utf8").trim() || null;
12559
- } catch {
12560
- return null;
12561
- }
12562
- };
12563
- var cachedLockHost = null;
12564
- var localLockHost = () => {
12565
- if (cachedLockHost !== null)
12566
- return cachedLockHost;
12567
- let pidns = null;
12568
- try {
12569
- pidns = /\[(\d+)\]/.exec(fs9.readlinkSync("/proc/self/ns/pid"))?.[1] ?? null;
12570
- } catch {}
12571
- const boot = readTrimmed("/proc/sys/kernel/random/boot_id");
12572
- cachedLockHost = pidns || boot ? `${hostname()}#${pidns ?? "?"}:${boot ?? "?"}` : hostname();
12573
- return cachedLockHost;
12574
- };
12575
- var pidAlive = (pid) => {
12576
- if (!Number.isSafeInteger(pid) || pid <= 0)
12577
- return false;
12578
- try {
12579
- process.kill(pid, 0);
12580
- return true;
12581
- } catch (error) {
12582
- return error.code === "EPERM";
12583
- }
12584
- };
12585
- var TASK_STORE_LOCK = { reclaimUnverifiedAfterTtl: true };
12586
- var classifyLockOwner = (payload, ageMs, localHost = localLockHost(), options = {}) => {
12587
- const lock = metadataLockSchema.safeParse(payload);
12588
- if (!lock.success)
12589
- return ageMs !== null && ageMs > UNREADABLE_LOCK_TTL_MS ? { state: "stale", reason: "unreadable lock left behind" } : { state: "unknown", reason: "lock is being written" };
12590
- const { pid, processStart, host } = lock.data;
12591
- const [lockName, lockSpace] = host.split("#");
12592
- const [localName, localSpace] = localHost.split("#");
12593
- if (lockSpace && localSpace && lockName === localName) {
12594
- const [lockNs, lockBoot] = lockSpace.split(":");
12595
- const [localNs, localBoot] = localSpace.split(":");
12596
- if (lockNs === localNs && lockNs !== "?" && lockBoot !== "?" && lockBoot !== localBoot)
12597
- return { state: "stale", reason: "lock was taken before this machine rebooted" };
12598
- }
12599
- if (host !== localHost)
12600
- 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` };
12601
- if (!pidAlive(pid))
12602
- return { state: "stale", reason: `pid ${pid} is not running` };
12603
- const currentStart = processStartOf(pid);
12604
- if (processStart !== null && currentStart !== null && processStart !== currentStart)
12605
- return { state: "stale", reason: `pid ${pid} now belongs to a different process` };
12606
- if (options.reclaimUnverifiedAfterTtl === true && (processStart === null || currentStart === null) && ageMs !== null && ageMs > FOREIGN_LOCK_TTL_MS)
12607
- return {
12608
- state: "stale",
12609
- reason: `pid ${pid} is running but the lock is older than its TTL and the pid's start time cannot be checked (reused pid)`
12610
- };
12611
- return { state: "live", reason: `held by running pid ${pid}` };
12612
- };
12613
- var ageOf = (file, nowMs) => {
12614
- try {
12615
- return nowMs - fs9.lstatSync(file).mtimeMs;
12616
- } catch {
12617
- return null;
12618
- }
12619
- };
12620
- var clearAbandonedReclaimGuard = (lockPath, nowMs = Date.now()) => {
12621
- const guard = `${lockPath}.reclaim`;
12622
- const age = ageOf(guard, nowMs);
12623
- if (age === null || age <= RECLAIM_GUARD_TTL_MS)
12624
- return false;
12625
- try {
12626
- fs9.rmdirSync(guard);
12627
- return true;
12628
- } catch {
12629
- return false;
12630
- }
12631
- };
12632
-
12633
12835
  // packages/workit-core/src/store/event-log.ts
12634
12836
  import { randomUUID as randomUUID2 } from "node:crypto";
12635
12837
  import fs10 from "node:fs";
@@ -15705,11 +15907,11 @@ what and whether, the CLI does how and records what it observed.
15705
15907
  Native host allow/ask/deny, sandbox and org rules stay authoritative. Never
15706
15908
  switch paths to evade a denial, never claim enforcement a host cannot provide,
15707
15909
  and never fabricate an approval or verdict. A question answer is not
15708
- host permission, and imported decisions or handoffs never grant authority.
15910
+ host permission, and imported decisions or handoffs grant no authority.
15709
15911
 
15710
15912
  Autonomy contract:
15711
15913
  - Continue to the requested endpoint (commit, push, PR, green CI, verified,
15712
- merged) without asking "continue?", running checks and repairs on the way.
15914
+ merged) without asking "continue?", running checks and repairs.
15713
15915
  Stop only for a new consequential choice, a host denial, a conflicting edit
15714
15916
  or an unresolved blocker; report open gaps and uncertain workers.
15715
15917
  - No endpoint named: stop at a local commit on a policy-compliant branch and
@@ -15719,7 +15921,8 @@ Autonomy contract:
15719
15921
  never merging; \`merged\` also lands it with \`workit pr merge\`.
15720
15922
  Merge and release need a workspace grant only the user raises from their
15721
15923
  own terminal (a blocked verb names how); never edit workspaces.json or route
15722
- around a grant_required.
15924
+ around grant_required. Release: \`workit release start <v>\`, commits,
15925
+ \`finish\`; \`publish\` if asked.
15723
15926
  - Ask only for a product or preference choice, or for authority you lack, with
15724
15927
  your recommended answer. Facts are yours: read, run or prototype.
15725
15928
  - Label claims measured (run and seen this session), inferred, or guess.
@@ -15734,22 +15937,22 @@ Autonomy contract:
15734
15937
  reconcile every requested item and observe it (\`workit verify-delivery\`).
15735
15938
 
15736
15939
  CLI first: if a step has one right answer, use the \`workit\` verb instead of
15737
- hand-running git, gh or glab (\`workit git|pr|ci|check\`; \`workit help\` lists
15738
- the rest; lacking \`workit\`, \`npx -y @brainervirus/workit-cli\`).
15940
+ hand-running git, gh or glab (\`workit git|pr|ci|check|help\`;
15941
+ else \`npx -y @brainervirus/workit-cli\`).
15739
15942
  Add Co-Authored-By via \`workit git commit --trailer "K: v" -- <paths>\`, never
15740
15943
  an amend.
15741
15944
  The task families (task, policy, evidence, finding, decision, worker, state)
15742
15945
  are optional continuity for tracked work; each branch has one implicit task,
15743
- and a solo edit needs no task. A decision
15744
- records a user choice; it never authorizes an effect. Judge work once with
15946
+ and a solo edit needs no task. A decision records a user choice; it never
15947
+ authorizes an effect. Judge work once with
15745
15948
  \`workit policy assess --judge risk= behavior= product-choice= plan=\`.
15746
- Across repositories, bind each item to its checkout, branch and endpoint,
15747
- resolve competing targets before a mutation, and reconcile an uncertain
15748
- external effect before retrying it.
15949
+ Across repositories, bind items to checkout, branch and endpoint, resolve
15950
+ competing targets before a mutation, and reconcile an uncertain
15951
+ external effect before a retry.
15749
15952
 
15750
15953
  Mid-task: never silently drop or resume an objective (workit-continue).
15751
15954
 
15752
- Skills: load the skill, never act from memory of it (slash aliases /wk-<name>).
15955
+ Skills: load the skill, never act from memory (aliases /wk-<name>).
15753
15956
  - brainstorm, plan, spec, grill, should we: workit-shape
15754
15957
  - implement, build, add a feature: workit-implement
15755
15958
  - review, blast radius: workit-review
@@ -19485,7 +19688,7 @@ var refusal = (action, branch, dir) => {
19485
19688
  return `workit: raw ${quote(action.via === "api" ? `${action.forge} api` : action.forge === "gh" ? "gh pr merge" : "glab mr merge")}${action.via === "api" ? " merge" : ""} bypasses the merge grant and the verdict gate. Run ${quote("workit pr merge")} instead (it checks both; ${quote("workit help pr")}).`;
19486
19689
  const plainPush = action.kind === "push" && !action.blindForce && !action.deletes;
19487
19690
  if (dir && branch && plainPush && releaseBranch(dir, branch))
19488
- return `workit: raw ${quote("git push")} onto ${branch}, a protected release branch, bypasses workit's protected-branch check. To finish a release or hotfix, push it with ${quote(`workit git push --release ${branch} [--release v<version>]`)}: it pushes ${branch} (fast-forward only) and the release tag, verifies the remote and records it, under the ${quote("release")} grant (if refused, ask the user to run ${quote("workit grant set <workspace> release=true")}). Feature work goes through ${quote("workit git push")} and ${quote("workit pr merge")}.`;
19691
+ return `workit: raw ${quote("git push")} onto ${branch}, a protected release branch, bypasses workit's protected-branch check. To finish a release or hotfix, push it with ${quote(`workit git push --release ${branch} [--release ${releaseTagPrefixOf(dir)}<version>]`)}: it pushes ${branch} (fast-forward only) and the release tag, verifies the remote and records it, under the ${quote("release")} grant (if refused, ask the user to run ${quote("workit grant set <workspace> release=true")}). Feature work goes through ${quote("workit git push")} and ${quote("workit pr merge")}.`;
19489
19692
  return `workit: raw ${quote("git push")}${action.kind === "push" && action.blindForce ? " --force" : ""} onto ${branch}, a protected branch or the default target, bypasses workit's protected-branch check. Push your feature branch with ${quote("workit git push")} (${quote("workit git push --force-with-lease")} to rewrite it) and land it with ${quote("workit pr merge")}.`;
19490
19693
  };
19491
19694
  var refFacts = (repo, remote) => {
@@ -19572,15 +19775,16 @@ var guardedTarget = (dir, target) => {
19572
19775
  return null;
19573
19776
  };
19574
19777
  var isTag = (facts, name) => facts.tags.has(name) && !facts.heads.has(name);
19575
- var tagRefusal = (action, facts) => {
19778
+ var tagRefusal = (action, facts, dir) => {
19576
19779
  const tags = [
19577
19780
  ...action.tags ?? [],
19578
19781
  ...action.targets.filter((name) => name !== null && isTag(facts, name))
19579
19782
  ];
19580
19783
  if (!tags.length && !action.allTags)
19581
19784
  return null;
19582
- const named = tags.length ? tags.join(" --release ") : "v<version>";
19583
- return `workit: raw ${quote(`git push${action.allTags ? " --tags" : ""}`)} of ${tags.length ? `tag ${tags.join(", ")}` : "tags"} bypasses the release grant. Push a release tag with ${quote(`workit git push --release ${named}`)} (v<version> tags whose commit the production branch already has; a tag never moves), under the ${quote("release")} grant (if refused, ask the user to run ${quote("workit grant set <workspace> release=true")}).`;
19785
+ const shape = `${releaseTagPrefixOf(dir)}<version>`;
19786
+ const named = tags.length ? tags.join(" --release ") : shape;
19787
+ return `workit: raw ${quote(`git push${action.allTags ? " --tags" : ""}`)} of ${tags.length ? `tag ${tags.join(", ")}` : "tags"} bypasses the release grant. Push a release tag with ${quote(`workit git push --release ${named}`)} (${shape} tags whose commit the production branch already has; a tag never moves), under the ${quote("release")} grant (if refused, ask the user to run ${quote("workit grant set <workspace> release=true")}).`;
19584
19788
  };
19585
19789
  var remoteHeads = (repo, remote) => {
19586
19790
  const listed = spawnSync11("git", ["ls-remote", "--heads", remote], {
@@ -19646,7 +19850,7 @@ var pushRefusal = (action, repo, config) => {
19646
19850
  if (guarded)
19647
19851
  return refusal(action, guarded, repo.dir);
19648
19852
  }
19649
- return tagRefusal(pushed, facts) ?? (action.deletes ? null : offPolicyPush(pushed, repo, facts, remote));
19853
+ return tagRefusal(pushed, facts, repo.dir) ?? (action.deletes ? null : offPolicyPush(pushed, repo, facts, remote));
19650
19854
  };
19651
19855
  var actionsIn = (cwd, command, dialect, cache = new Map) => {
19652
19856
  const out = [];
@@ -20302,10 +20506,10 @@ function stopObligation(input, descriptor) {
20302
20506
  const branch = currentBranch(input.cwd);
20303
20507
  if (!branch || guardedBranch(input.cwd, branch))
20304
20508
  return null;
20305
- const ledger = readLedger(input.cwd);
20509
+ const ledger = readLedger(input.cwd, { integrity: true });
20306
20510
  if (!ledger.ok)
20307
20511
  return null;
20308
- const all = ledger.value.rows;
20512
+ const all = ledger.value.rows.filter((row) => row.integrity !== "unverified");
20309
20513
  const rows = all.filter((row) => row.branch === branch);
20310
20514
  const mine = rows.filter((row) => sessionOf(row) === session);
20311
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"));
@@ -20681,9 +20885,9 @@ ${subagentStartText({ ...input, cwd: "" }, descriptor, event)}`;
20681
20885
  if (role !== "judge" || !event.agentId)
20682
20886
  return readOnly;
20683
20887
  const own = subagentSession(session, event.agentId);
20684
- if (session)
20685
- recordIssuedSession(input.cwd || process.cwd(), session, own);
20686
- 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." : ""}`;
20687
20891
  };
20688
20892
  function handleHook(input, deps) {
20689
20893
  const { descriptor } = deps;