@brainervirus/workit-cursor 8.13.0 → 8.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8561,242 +8561,666 @@ function pushRemoteName(cwd, branch) {
8561
8561
  }
8562
8562
 
8563
8563
  // packages/workit-core/src/ledger.ts
8564
- import { spawnSync as spawnSync7 } from "node:child_process";
8565
- import { createHash as createHash7, randomBytes } from "node:crypto";
8566
- import fs5 from "node:fs";
8564
+ import { spawnSync as spawnSync8 } from "node:child_process";
8565
+ import { createHash as createHash7, randomBytes as randomBytes2 } from "node:crypto";
8566
+ import fs6 from "node:fs";
8567
8567
  import path8 from "node:path";
8568
8568
 
8569
- // packages/workit-core/src/core/vcs-config.ts
8570
- import fs2 from "node:fs";
8571
- import path4 from "node:path";
8572
- import { spawnSync as spawnSync5 } from "node:child_process";
8569
+ // packages/workit-core/src/core/store-lock.ts
8570
+ import { spawnSync as spawnSync3 } from "node:child_process";
8571
+ import fs3 from "node:fs";
8572
+ import { hostname } from "node:os";
8573
8573
 
8574
- // packages/workit-core/src/core/config.ts
8575
- import {
8576
- copyFileSync,
8577
- cpSync,
8578
- existsSync,
8579
- mkdirSync,
8580
- readFileSync,
8581
- readdirSync,
8582
- writeFileSync
8583
- } from "node:fs";
8584
- import os2 from "node:os";
8574
+ // packages/workit-core/src/store/paths.ts
8575
+ import { spawnSync as spawnSync2 } from "node:child_process";
8576
+ import { createHash as createHash3 } from "node:crypto";
8577
+ import fs2 from "node:fs";
8585
8578
  import path2 from "node:path";
8586
-
8587
- // packages/workit-core/src/core/boundary.ts
8588
- var EVENT = {
8589
- initialization: "initialization",
8590
- provenance: "provenance",
8591
- assets: "assets",
8592
- configurationSource: "configuration_source",
8593
- hooks: "hooks",
8594
- mcpConnection: "mcp_connection",
8595
- migration: "migration",
8596
- installSteps: "install_steps",
8597
- uncaughtFailure: "uncaught_failure",
8598
- toolsFailed: "tools_failed",
8599
- doctor: "doctor"
8600
- };
8601
- var errorDetail = (err) => {
8602
- if (err instanceof Error) {
8603
- return { error_name: err.name, error: err.message };
8604
- }
8605
- return { error_name: "unknown", error: String(err) };
8606
- };
8607
-
8608
- // packages/workit-core/src/core/config.ts
8609
- var diagnosticLogger;
8610
- var PRESETS = {
8611
- gitflow: {
8612
- allowed: ["feature/*", "bugfix/*", "hotfix/*", "release/*"],
8613
- protected: ["main", "develop", "master", "prod", "production"]
8614
- },
8615
- "github-flow": { allowed: ["*"], protected: ["main"] },
8616
- "trunk-based": { allowed: ["*"], protected: ["main"] },
8617
- custom: { allowed: [], protected: [] }
8618
- };
8619
- var mergePreset = (preset, input = {}, current = {
8620
- branchPolicy: { preset, allowed: [], protected: [] }
8621
- }) => {
8622
- const defs = PRESETS[preset];
8579
+ var NOT_A_REPO = /not a git repository \(or any (of the parent directories|parent up to)/u;
8580
+ var gitRun = (cwd, args) => {
8581
+ const run = spawnSync2("git", args, {
8582
+ cwd,
8583
+ encoding: "utf8",
8584
+ timeout: 20000,
8585
+ killSignal: "SIGKILL",
8586
+ windowsHide: true,
8587
+ env: {
8588
+ ...process.env,
8589
+ GIT_TERMINAL_PROMPT: "0",
8590
+ GIT_OPTIONAL_LOCKS: "0",
8591
+ LC_ALL: "C",
8592
+ LANGUAGE: "C"
8593
+ }
8594
+ });
8595
+ const error = run.error;
8623
8596
  return {
8624
- preset,
8625
- allowed: preset === "custom" ? input.allowed ?? current.branchPolicy.allowed : [...defs.allowed],
8626
- protected: preset === "custom" ? input.protectedNames ?? current.branchPolicy.protected : [...defs.protected]
8597
+ ok: run.status === 0,
8598
+ stdout: run.stdout ?? "",
8599
+ stderr: error ? String(error.message) : run.stderr ?? "",
8600
+ missing: error?.code === "ENOENT"
8627
8601
  };
8628
8602
  };
8629
- var resolveConfigDir = () => process.env.WORKFLOW_TOOLKIT_CONFIG ?? process.env.WORKFLOW_TOOLKIT_CONFIG_DIR ?? path2.join(process.env.XDG_CONFIG_HOME || path2.join(os2.homedir(), ".config"), "workit");
8630
- var migratedDir = null;
8631
- var migrationFailed = false;
8632
- var ensureConfigDir = (dir = resolveConfigDir()) => {
8633
- if (migratedDir === dir)
8634
- return dir;
8635
- if (process.env.WORKFLOW_TOOLKIT_CONFIG || process.env.WORKFLOW_TOOLKIT_CONFIG_DIR) {
8636
- migratedDir = dir;
8637
- return dir;
8638
- }
8639
- const legacy = path2.join(process.env.XDG_CONFIG_HOME || path2.join(os2.homedir(), ".config"), "workflow-toolkit");
8640
- if (!existsSync(legacy)) {
8641
- migratedDir = dir;
8642
- return dir;
8643
- }
8644
- if (!migrationFailed && existsSync(dir)) {
8645
- migratedDir = dir;
8646
- return dir;
8647
- }
8648
- migrationFailed = false;
8649
- mkdirSync(dir, { recursive: true });
8650
- diagnosticLogger?.info(EVENT.migration, { from: legacy, to: dir });
8651
- for (const entry of readdirSync(legacy, { withFileTypes: true })) {
8652
- const src = path2.join(legacy, entry.name);
8653
- const dest = path2.join(dir, entry.name);
8654
- if (existsSync(dest))
8655
- continue;
8656
- try {
8657
- if (entry.isDirectory())
8658
- cpSync(src, dest, { recursive: true });
8659
- else if (entry.isFile())
8660
- copyFileSync(src, dest);
8661
- } catch (err) {
8662
- migrationFailed = true;
8663
- diagnosticLogger?.warn(EVENT.migration, { from: src, ok: false, ...errorDetail(err) });
8664
- console.warn(`[workit] config migration: a file could not be copied to ${dir}; it will be retried on the next run`);
8665
- }
8666
- }
8667
- if (!migrationFailed)
8668
- migratedDir = dir;
8669
- return dir;
8670
- };
8671
- var configDir = () => ensureConfigDir();
8672
- var LOCALE_RE = /^[a-z]{2,3}(-(?:[A-Z]{2}|[0-9]{3}))?$/;
8673
- var DEFAULTS = {
8674
- locale: "en",
8675
- localeOptions: ["en", "es-CL", "es-MX", "es-AR", "pt-BR"],
8676
- branchPolicy: {
8677
- preset: "gitflow",
8678
- allowed: [...PRESETS.gitflow.allowed],
8679
- protected: [...PRESETS.gitflow.protected]
8680
- },
8681
- commitPolicy: { preset: "conventional" }
8682
- };
8683
- var unchosenBranchPolicy = new WeakSet([DEFAULTS]);
8684
- var readSafe = (p) => {
8603
+ var shortHash = (value) => createHash3("sha256").update(value).digest("hex").slice(0, 12);
8604
+ var real = (file) => {
8685
8605
  try {
8686
- return readFileSync(p, "utf8");
8606
+ return fs2.realpathSync.native(file);
8687
8607
  } catch {
8688
- return null;
8608
+ return file;
8689
8609
  }
8690
8610
  };
8691
- var isConfigObject = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
8692
- var parseConfigResult = (raw, file) => {
8693
- if (raw === null)
8694
- return { status: "missing", path: file };
8695
- let parsed;
8611
+ var DISCOVERY_ENV = [
8612
+ "GIT_DIR",
8613
+ "GIT_COMMON_DIR",
8614
+ "GIT_WORK_TREE",
8615
+ "GIT_CEILING_DIRECTORIES",
8616
+ "GIT_DISCOVERY_ACROSS_FILESYSTEM"
8617
+ ];
8618
+ var isGitDir = (dir) => {
8696
8619
  try {
8697
- parsed = JSON.parse(raw);
8620
+ return fs2.statSync(path2.join(dir, "HEAD")).isFile() && fs2.statSync(path2.join(dir, "objects")).isDirectory() && fs2.statSync(path2.join(dir, "refs")).isDirectory();
8698
8621
  } catch {
8699
- return { status: "malformed", path: file, error: `${file} is not valid JSON` };
8700
- }
8701
- if (!isConfigObject(parsed)) {
8702
- return { status: "malformed", path: file, error: `${file} is not a JSON object` };
8622
+ return false;
8703
8623
  }
8704
- const input = parsed;
8705
- const locale = LOCALE_RE.test(String(input.locale ?? "")) ? input.locale : DEFAULTS.locale;
8706
- const preset = Object.hasOwn(PRESETS, input.branchPolicy?.preset) ? input.branchPolicy?.preset : "gitflow";
8707
- const commitPreset = COMMIT_PRESETS.includes(String(input.commitPolicy?.preset ?? "")) ? input.commitPolicy?.preset : "conventional";
8708
- const config = {
8709
- locale,
8710
- localeOptions: Array.isArray(input.localeOptions) ? input.localeOptions : DEFAULTS.localeOptions,
8711
- branchPolicy: mergePreset(preset, {
8712
- allowed: Array.isArray(input.branchPolicy?.allowed) ? input.branchPolicy.allowed : undefined,
8713
- protectedNames: Array.isArray(input.branchPolicy?.protected) ? input.branchPolicy.protected : undefined
8714
- }, DEFAULTS),
8715
- commitPolicy: {
8716
- preset: commitPreset,
8717
- ...typeof input.commitPolicy?.pattern === "string" ? { pattern: input.commitPolicy.pattern } : {}
8718
- }
8719
- };
8720
- if (!Object.hasOwn(PRESETS, input.branchPolicy?.preset))
8721
- unchosenBranchPolicy.add(config);
8722
- return { status: "valid", path: file, config };
8723
- };
8724
- var readConfigTyped = (dir) => {
8725
- const file = path2.join(dir ?? configDir(), "config.json");
8726
- return parseConfigResult(readSafe(file), file);
8727
- };
8728
- var readConfig = () => {
8729
- const result = readConfigTyped();
8730
- if (result.status === "malformed")
8731
- throw new Error(result.error);
8732
- return result.config ?? DEFAULTS;
8733
- };
8734
- var vcsConfigPath = () => process.env.WORKFLOW_VCS_CONFIG ?? path2.join(configDir(), "vcs.json");
8735
- var branchGlobPattern = (glob) => new RegExp(`^${glob.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*")}$`, "i");
8736
- var resolveBranchPolicy = (config, workspace, repoPreset) => {
8737
- const wp = workspace?.branchPolicy ?? {};
8738
- const workspacePreset = Object.hasOwn(PRESETS, wp.preset);
8739
- const detected = !workspacePreset && unchosenBranchPolicy.has(config) ? repoPreset?.() ?? null : null;
8740
- const preset = workspacePreset ? wp.preset : detected ?? config.branchPolicy?.preset ?? "gitflow";
8741
- const merged = mergePreset(preset, {
8742
- allowed: wp.allowed,
8743
- protectedNames: wp.protected
8744
- }, config);
8745
- const allowed = merged.allowed.map(branchGlobPattern);
8746
- return {
8747
- preset,
8748
- allowed,
8749
- protected: new Set([...merged.protected, ...detected ? PRESETS.gitflow.protected : []].map((p) => p.toLowerCase())),
8750
- integration: wp.integration === "merge" ? "merge" : "pr",
8751
- defaultTargetBranch: preset === "github-flow" ? "main" : preset === "trunk-based" ? "master" : "develop",
8752
- detected: detected !== null
8753
- };
8754
8624
  };
8755
- var COMMIT_PRESETS = [
8756
- "conventional",
8757
- "gitmoji",
8758
- "ticket-prefix",
8759
- "freeform",
8760
- "custom",
8761
- "auto"
8762
- ];
8763
- var resolveCommitPolicy = (config, workspace) => {
8764
- const wp = workspace?.commitPolicy ?? {};
8765
- if (COMMIT_PRESETS.includes(String(wp.preset ?? ""))) {
8766
- return {
8767
- preset: wp.preset,
8768
- ...typeof wp.pattern === "string" ? { pattern: wp.pattern } : {}
8769
- };
8625
+ var reftableRefs = (common) => {
8626
+ try {
8627
+ return /^\s*refstorage\s*=\s*reftable\s*$/imu.test(fs2.readFileSync(path2.join(common, "config"), "utf8"));
8628
+ } catch {
8629
+ return false;
8770
8630
  }
8771
- return config.commitPolicy ?? { preset: "conventional" };
8772
- };
8773
-
8774
- // packages/workit-core/src/core/workspaces.ts
8775
- import { spawnSync as spawnSync3 } from "node:child_process";
8776
- import { createHash as createHash3 } from "node:crypto";
8777
- import { readFileSync as readFileSync2, realpathSync } from "node:fs";
8778
- import path3 from "node:path";
8779
-
8780
- // packages/workit-core/src/core/branch-policy.ts
8781
- import { spawnSync as spawnSync2 } from "node:child_process";
8782
- var PREFIXES = {
8783
- feature: "feature/*",
8784
- bugfix: "bugfix/*",
8785
- release: "release/*",
8786
- hotfix: "hotfix/*"
8787
- };
8788
- var branchNames = (workspaceRoot) => {
8789
- const r = spawnSync2("git", ["for-each-ref", "--format=%(refname)", "refs/heads", "refs/remotes/origin"], { cwd: workspaceRoot, encoding: "utf8" });
8790
- if (r.status !== 0)
8791
- return new Set;
8792
- return new Set((r.stdout ?? "").split(`
8793
- `).filter(Boolean).map((ref) => ref.startsWith("refs/heads/") ? ref.slice("refs/heads/".length) : ref.slice("refs/remotes/origin/".length)));
8794
8631
  };
8795
- var productionBranchOf = (names) => names.has("main") ? "main" : names.has("master") ? "master" : "main";
8796
- var detectBranchPolicy = (workspaceRoot) => {
8797
- const names = branchNames(workspaceRoot);
8798
- const develop = names.has("develop");
8799
- const main = names.has("main");
8632
+ function fastGit(root) {
8633
+ if (DISCOVERY_ENV.some((name) => process.env[name]))
8634
+ return null;
8635
+ let dir = real(root);
8636
+ let device = null;
8637
+ for (;; ) {
8638
+ let stat;
8639
+ try {
8640
+ stat = fs2.statSync(dir);
8641
+ } catch {
8642
+ return null;
8643
+ }
8644
+ if (device !== null && stat.dev !== device)
8645
+ return { kind: "none" };
8646
+ device = stat.dev;
8647
+ const dotGit = path2.join(dir, ".git");
8648
+ let entry = null;
8649
+ try {
8650
+ entry = fs2.statSync(dotGit);
8651
+ } catch {}
8652
+ let gitDir = null;
8653
+ let top = null;
8654
+ if (entry) {
8655
+ top = dir;
8656
+ if (entry.isDirectory())
8657
+ gitDir = dotGit;
8658
+ else if (entry.isFile()) {
8659
+ let text = "";
8660
+ try {
8661
+ text = fs2.readFileSync(dotGit, "utf8");
8662
+ } catch {
8663
+ return null;
8664
+ }
8665
+ const match = /^gitdir: (.+)$/mu.exec(text);
8666
+ if (!match)
8667
+ return null;
8668
+ gitDir = path2.resolve(dir, match[1].trim());
8669
+ } else
8670
+ return null;
8671
+ if (!fs2.existsSync(path2.join(gitDir, "HEAD")))
8672
+ return null;
8673
+ } else if (isGitDir(dir))
8674
+ gitDir = dir;
8675
+ if (gitDir) {
8676
+ let common = gitDir;
8677
+ try {
8678
+ common = path2.resolve(gitDir, fs2.readFileSync(path2.join(gitDir, "commondir"), "utf8").trim());
8679
+ } catch {}
8680
+ if (!fs2.existsSync(path2.join(common, "objects")))
8681
+ return null;
8682
+ return {
8683
+ kind: "repo",
8684
+ common: real(common),
8685
+ gitDir: real(gitDir),
8686
+ top: top === null ? null : real(top),
8687
+ reftable: reftableRefs(common)
8688
+ };
8689
+ }
8690
+ const parent = path2.dirname(dir);
8691
+ if (parent === dir)
8692
+ return { kind: "none" };
8693
+ dir = parent;
8694
+ }
8695
+ }
8696
+ function resolveStore(root) {
8697
+ root = path2.resolve(root);
8698
+ let directory = false;
8699
+ try {
8700
+ directory = fs2.statSync(root).isDirectory();
8701
+ } catch {}
8702
+ if (!directory)
8703
+ return { dir: path2.join(root, ".workit"), shared: false, top: null };
8704
+ const fast = fastGit(root);
8705
+ if (fast?.kind === "none")
8706
+ return { dir: path2.join(root, ".workit"), shared: false, top: null };
8707
+ if (fast?.kind === "repo")
8708
+ return {
8709
+ dir: path2.join(fast.common, "workit"),
8710
+ shared: true,
8711
+ top: fast.top,
8712
+ git: { gitDir: fast.gitDir, top: fast.top, reftable: fast.reftable }
8713
+ };
8714
+ const run = gitRun(root, ["rev-parse", "--path-format=absolute", "--git-common-dir"]);
8715
+ const common = run.stdout.trim();
8716
+ if (run.ok && common) {
8717
+ const top = gitRun(root, ["rev-parse", "--path-format=absolute", "--show-toplevel"]);
8718
+ return {
8719
+ dir: path2.join(real(path2.resolve(root, common)), "workit"),
8720
+ shared: true,
8721
+ top: top.ok && top.stdout.trim() ? real(top.stdout.trim()) : null
8722
+ };
8723
+ }
8724
+ if (run.missing || !run.ok && NOT_A_REPO.test(run.stderr))
8725
+ return { dir: path2.join(root, ".workit"), shared: false, top: null };
8726
+ return new Error(`cannot locate the workit store: git rev-parse failed (${run.stderr.trim().split(`
8727
+ `)[0] || "no output"}); fix the repository (git status should work here)`);
8728
+ }
8729
+ var rebasingBranch = (gitDir) => {
8730
+ for (const state of ["rebase-merge", "rebase-apply"]) {
8731
+ try {
8732
+ const ref = fs2.readFileSync(path2.join(gitDir, state, "head-name"), "utf8").trim();
8733
+ const match = /^refs\/heads\/(.+)$/u.exec(ref);
8734
+ if (match)
8735
+ return match[1];
8736
+ } catch {}
8737
+ }
8738
+ return null;
8739
+ };
8740
+ function resolveTaskKey(root, location) {
8741
+ if (!location.shared)
8742
+ return { key: `dir-${shortHash(root)}`, kind: "dir", branch: null };
8743
+ const branchKey = (name) => ({ key: name, kind: "branch", branch: name });
8744
+ if (location.git && !location.git.reftable) {
8745
+ let head = "";
8746
+ try {
8747
+ head = fs2.readFileSync(path2.join(location.git.gitDir, "HEAD"), "utf8").trim();
8748
+ } catch {}
8749
+ const ref = /^ref: refs\/heads\/(.+)$/u.exec(head);
8750
+ if (ref && ref[1] !== ".invalid")
8751
+ return branchKey(ref[1]);
8752
+ if (/^[0-9a-f]{40,64}$/u.test(head)) {
8753
+ const rebasing = rebasingBranch(location.git.gitDir);
8754
+ if (rebasing)
8755
+ return branchKey(rebasing);
8756
+ return {
8757
+ key: `detached-${shortHash(location.git.top ?? location.git.gitDir)}`,
8758
+ kind: "detached",
8759
+ branch: null
8760
+ };
8761
+ }
8762
+ }
8763
+ const branch = gitRun(root, ["symbolic-ref", "-q", "--short", "HEAD"]);
8764
+ const name = branch.ok ? branch.stdout.trim() : "";
8765
+ if (name && name !== ".invalid")
8766
+ return branchKey(name);
8767
+ const gitDir = gitRun(root, ["rev-parse", "--path-format=absolute", "--git-dir"]);
8768
+ if (gitDir.ok && gitDir.stdout.trim()) {
8769
+ const rebasing = rebasingBranch(gitDir.stdout.trim());
8770
+ if (rebasing)
8771
+ return branchKey(rebasing);
8772
+ }
8773
+ const top = gitRun(root, ["rev-parse", "--path-format=absolute", "--show-toplevel"]);
8774
+ const worktree = top.ok && top.stdout.trim() ? top.stdout.trim() : root;
8775
+ return { key: `detached-${shortHash(real(worktree))}`, kind: "detached", branch: null };
8776
+ }
8777
+ function checkoutRootOf(root, location) {
8778
+ const resolved = path2.resolve(root);
8779
+ let canonical = resolved;
8780
+ try {
8781
+ canonical = fs2.realpathSync(resolved);
8782
+ } catch {
8783
+ return resolved;
8784
+ }
8785
+ const found = location ?? resolveStore(canonical);
8786
+ if (found instanceof Error || !found.shared || !found.top)
8787
+ return canonical;
8788
+ if (real(canonical) === found.top)
8789
+ return canonical;
8790
+ try {
8791
+ return fs2.realpathSync(found.top);
8792
+ } catch {
8793
+ return canonical;
8794
+ }
8795
+ }
8796
+ function branchRenames(location) {
8797
+ if (!location.shared)
8798
+ return [];
8799
+ const files = [];
8800
+ const walk = (dir) => {
8801
+ let entries;
8802
+ try {
8803
+ entries = fs2.readdirSync(dir, { withFileTypes: true });
8804
+ } catch {
8805
+ return;
8806
+ }
8807
+ for (const entry of entries)
8808
+ if (entry.isDirectory())
8809
+ walk(path2.join(dir, entry.name));
8810
+ else
8811
+ files.push(path2.join(dir, entry.name));
8812
+ };
8813
+ walk(path2.join(path2.dirname(location.dir), "logs", "refs", "heads"));
8814
+ if (location.git)
8815
+ files.push(path2.join(location.git.gitDir, "logs", "HEAD"));
8816
+ const seen = new Set;
8817
+ const renames = [];
8818
+ for (const file of files) {
8819
+ let text = "";
8820
+ try {
8821
+ text = fs2.readFileSync(file, "utf8");
8822
+ } catch {
8823
+ continue;
8824
+ }
8825
+ for (const match of text.matchAll(/ (\d+) [+-]\d{4}\tBranch: renamed refs\/heads\/(\S+) to refs\/heads\/(\S+)/gu)) {
8826
+ const rename = { from: match[2], to: match[3], at: Number(match[1]) };
8827
+ const id = `${rename.at}\x00${rename.from}\x00${rename.to}`;
8828
+ if (seen.has(id))
8829
+ continue;
8830
+ seen.add(id);
8831
+ renames.push(rename);
8832
+ }
8833
+ }
8834
+ return renames.toSorted((left, right) => left.at - right.at);
8835
+ }
8836
+ function followRenames(key, boundAt, renames) {
8837
+ let current = key;
8838
+ let since = Math.floor(Date.parse(boundAt) / 1000);
8839
+ for (const rename of renames)
8840
+ if (rename.from === current && rename.at >= since) {
8841
+ current = rename.to;
8842
+ since = rename.at;
8843
+ }
8844
+ return current;
8845
+ }
8846
+ var checkoutSlug = (root) => {
8847
+ let canonical = real(root);
8848
+ if (process.platform === "win32")
8849
+ canonical = canonical.toLowerCase();
8850
+ const base = path2.basename(canonical).replace(/[^A-Za-z0-9._-]/g, "_").slice(0, 40) || "root";
8851
+ return `${base}-${shortHash(canonical)}`;
8852
+ };
8853
+
8854
+ // packages/workit-core/src/core/store-lock.ts
8855
+ var metadataLockSchema = object({
8856
+ pid: number2().int().nonnegative().safe(),
8857
+ processStart: string2().nullable(),
8858
+ host: string2().min(1),
8859
+ nonce: string2().min(1),
8860
+ externalAction: literal(true).optional()
8861
+ }).strict();
8862
+ var FOREIGN_LOCK_TTL_MS = 10 * 60000;
8863
+ var UNREADABLE_LOCK_TTL_MS = 30000;
8864
+ var RECLAIM_GUARD_TTL_MS = 30000;
8865
+ var IN_PROCESS_LOCK_TIMEOUT_MS = 250;
8866
+ var defaultLockTimeoutMs = IN_PROCESS_LOCK_TIMEOUT_MS;
8867
+ var defaultLockTimeout = () => defaultLockTimeoutMs;
8868
+ var hostAwareLockTimeout = (cliMs) => defaultLockTimeoutMs > IN_PROCESS_LOCK_TIMEOUT_MS ? cliMs : IN_PROCESS_LOCK_TIMEOUT_MS;
8869
+ var parseMetadataLock = (raw) => {
8870
+ let value;
8871
+ try {
8872
+ value = JSON.parse(raw);
8873
+ } catch {
8874
+ throw Object.assign(new Error("metadata lock is invalid"), { code: "metadata_lock_invalid" });
8875
+ }
8876
+ const parsed = metadataLockSchema.safeParse(value);
8877
+ if (!parsed.success)
8878
+ throw Object.assign(new Error("metadata lock is invalid"), { code: "metadata_lock_invalid" });
8879
+ return parsed.data;
8880
+ };
8881
+ var parseMetadataLockOrNull = (raw) => {
8882
+ try {
8883
+ return parseMetadataLock(raw);
8884
+ } catch {
8885
+ return null;
8886
+ }
8887
+ };
8888
+ var parseProcStatStart = (stat) => {
8889
+ const close = stat.lastIndexOf(")");
8890
+ if (close < 0)
8891
+ return null;
8892
+ return stat.slice(close + 1).trim().split(/\s+/)[19] ?? null;
8893
+ };
8894
+ var processStartOf = (pid) => {
8895
+ if (process.platform === "linux") {
8896
+ try {
8897
+ return parseProcStatStart(fs3.readFileSync(`/proc/${pid}/stat`, "utf8"));
8898
+ } catch {
8899
+ return null;
8900
+ }
8901
+ }
8902
+ if (process.platform === "darwin" || process.platform === "freebsd") {
8903
+ try {
8904
+ const run = spawnSync3("ps", ["-o", "lstart=", "-p", String(pid)], {
8905
+ encoding: "utf8",
8906
+ timeout: 1000
8907
+ });
8908
+ const value = run.status === 0 ? run.stdout.trim() : "";
8909
+ return value || null;
8910
+ } catch {
8911
+ return null;
8912
+ }
8913
+ }
8914
+ return null;
8915
+ };
8916
+ var readTrimmed = (file) => {
8917
+ try {
8918
+ return fs3.readFileSync(file, "utf8").trim() || null;
8919
+ } catch {
8920
+ return null;
8921
+ }
8922
+ };
8923
+ var cachedLockHost = null;
8924
+ var localLockHost = () => {
8925
+ if (cachedLockHost !== null)
8926
+ return cachedLockHost;
8927
+ let pidns = null;
8928
+ try {
8929
+ pidns = /\[(\d+)\]/.exec(fs3.readlinkSync("/proc/self/ns/pid"))?.[1] ?? null;
8930
+ } catch {}
8931
+ const boot = readTrimmed("/proc/sys/kernel/random/boot_id");
8932
+ cachedLockHost = pidns || boot ? `${hostname()}#${pidns ?? "?"}:${boot ?? "?"}` : hostname();
8933
+ return cachedLockHost;
8934
+ };
8935
+ var pidAlive = (pid) => {
8936
+ if (!Number.isSafeInteger(pid) || pid <= 0)
8937
+ return false;
8938
+ try {
8939
+ process.kill(pid, 0);
8940
+ return true;
8941
+ } catch (error) {
8942
+ return error.code === "EPERM";
8943
+ }
8944
+ };
8945
+ var TASK_STORE_LOCK = { reclaimUnverifiedAfterTtl: true };
8946
+ var classifyLockOwner = (payload, ageMs, localHost = localLockHost(), options = {}) => {
8947
+ const lock = metadataLockSchema.safeParse(payload);
8948
+ if (!lock.success)
8949
+ return ageMs !== null && ageMs > UNREADABLE_LOCK_TTL_MS ? { state: "stale", reason: "unreadable lock left behind" } : { state: "unknown", reason: "lock is being written" };
8950
+ const { pid, processStart, host } = lock.data;
8951
+ const [lockName, lockSpace] = host.split("#");
8952
+ const [localName, localSpace] = localHost.split("#");
8953
+ if (lockSpace && localSpace && lockName === localName) {
8954
+ const [lockNs, lockBoot] = lockSpace.split(":");
8955
+ const [localNs, localBoot] = localSpace.split(":");
8956
+ if (lockNs === localNs && lockNs !== "?" && lockBoot !== "?" && lockBoot !== localBoot)
8957
+ return { state: "stale", reason: "lock was taken before this machine rebooted" };
8958
+ }
8959
+ if (host !== localHost)
8960
+ return ageMs !== null && ageMs > FOREIGN_LOCK_TTL_MS ? { state: "stale", reason: `lock from ${host} is older than its TTL` } : { state: "unknown", reason: `lock is held from ${host}; its pid cannot be checked here` };
8961
+ if (!pidAlive(pid))
8962
+ return { state: "stale", reason: `pid ${pid} is not running` };
8963
+ const currentStart = processStartOf(pid);
8964
+ if (processStart !== null && currentStart !== null && processStart !== currentStart)
8965
+ return { state: "stale", reason: `pid ${pid} now belongs to a different process` };
8966
+ if (options.reclaimUnverifiedAfterTtl === true && (processStart === null || currentStart === null) && ageMs !== null && ageMs > FOREIGN_LOCK_TTL_MS)
8967
+ return {
8968
+ state: "stale",
8969
+ reason: `pid ${pid} is running but the lock is older than its TTL and the pid's start time cannot be checked (reused pid)`
8970
+ };
8971
+ return { state: "live", reason: `held by running pid ${pid}` };
8972
+ };
8973
+ var ageOf = (file, nowMs) => {
8974
+ try {
8975
+ return nowMs - fs3.lstatSync(file).mtimeMs;
8976
+ } catch {
8977
+ return null;
8978
+ }
8979
+ };
8980
+ var clearAbandonedReclaimGuard = (lockPath, nowMs = Date.now()) => {
8981
+ const guard = `${lockPath}.reclaim`;
8982
+ const age = ageOf(guard, nowMs);
8983
+ if (age === null || age <= RECLAIM_GUARD_TTL_MS)
8984
+ return false;
8985
+ try {
8986
+ fs3.rmdirSync(guard);
8987
+ return true;
8988
+ } catch {
8989
+ return false;
8990
+ }
8991
+ };
8992
+
8993
+ // packages/workit-core/src/core/vcs-config.ts
8994
+ import fs4 from "node:fs";
8995
+ import path5 from "node:path";
8996
+ import { spawnSync as spawnSync7 } from "node:child_process";
8997
+
8998
+ // packages/workit-core/src/core/config.ts
8999
+ import {
9000
+ copyFileSync,
9001
+ cpSync,
9002
+ existsSync,
9003
+ mkdirSync,
9004
+ readFileSync,
9005
+ readdirSync,
9006
+ writeFileSync
9007
+ } from "node:fs";
9008
+ import os2 from "node:os";
9009
+ import path3 from "node:path";
9010
+
9011
+ // packages/workit-core/src/core/boundary.ts
9012
+ var EVENT = {
9013
+ initialization: "initialization",
9014
+ provenance: "provenance",
9015
+ assets: "assets",
9016
+ configurationSource: "configuration_source",
9017
+ hooks: "hooks",
9018
+ mcpConnection: "mcp_connection",
9019
+ migration: "migration",
9020
+ installSteps: "install_steps",
9021
+ uncaughtFailure: "uncaught_failure",
9022
+ toolsFailed: "tools_failed",
9023
+ doctor: "doctor"
9024
+ };
9025
+ var errorDetail = (err) => {
9026
+ if (err instanceof Error) {
9027
+ return { error_name: err.name, error: err.message };
9028
+ }
9029
+ return { error_name: "unknown", error: String(err) };
9030
+ };
9031
+
9032
+ // packages/workit-core/src/core/config.ts
9033
+ var diagnosticLogger;
9034
+ var PRESETS = {
9035
+ gitflow: {
9036
+ allowed: ["feature/*", "bugfix/*", "hotfix/*", "release/*"],
9037
+ protected: ["main", "develop", "master", "prod", "production"]
9038
+ },
9039
+ "github-flow": { allowed: ["*"], protected: ["main"] },
9040
+ "trunk-based": { allowed: ["*"], protected: ["main"] },
9041
+ custom: { allowed: [], protected: [] }
9042
+ };
9043
+ var mergePreset = (preset, input = {}, current = {
9044
+ branchPolicy: { preset, allowed: [], protected: [] }
9045
+ }) => {
9046
+ const defs = PRESETS[preset];
9047
+ return {
9048
+ preset,
9049
+ allowed: preset === "custom" ? input.allowed ?? current.branchPolicy.allowed : [...defs.allowed],
9050
+ protected: preset === "custom" ? input.protectedNames ?? current.branchPolicy.protected : [...defs.protected]
9051
+ };
9052
+ };
9053
+ var resolveConfigDir = () => process.env.WORKFLOW_TOOLKIT_CONFIG ?? process.env.WORKFLOW_TOOLKIT_CONFIG_DIR ?? path3.join(process.env.XDG_CONFIG_HOME || path3.join(os2.homedir(), ".config"), "workit");
9054
+ var migratedDir = null;
9055
+ var migrationFailed = false;
9056
+ var ensureConfigDir = (dir = resolveConfigDir()) => {
9057
+ if (migratedDir === dir)
9058
+ return dir;
9059
+ if (process.env.WORKFLOW_TOOLKIT_CONFIG || process.env.WORKFLOW_TOOLKIT_CONFIG_DIR) {
9060
+ migratedDir = dir;
9061
+ return dir;
9062
+ }
9063
+ const legacy = path3.join(process.env.XDG_CONFIG_HOME || path3.join(os2.homedir(), ".config"), "workflow-toolkit");
9064
+ if (!existsSync(legacy)) {
9065
+ migratedDir = dir;
9066
+ return dir;
9067
+ }
9068
+ if (!migrationFailed && existsSync(dir)) {
9069
+ migratedDir = dir;
9070
+ return dir;
9071
+ }
9072
+ migrationFailed = false;
9073
+ mkdirSync(dir, { recursive: true });
9074
+ diagnosticLogger?.info(EVENT.migration, { from: legacy, to: dir });
9075
+ for (const entry of readdirSync(legacy, { withFileTypes: true })) {
9076
+ const src = path3.join(legacy, entry.name);
9077
+ const dest = path3.join(dir, entry.name);
9078
+ if (existsSync(dest))
9079
+ continue;
9080
+ try {
9081
+ if (entry.isDirectory())
9082
+ cpSync(src, dest, { recursive: true });
9083
+ else if (entry.isFile())
9084
+ copyFileSync(src, dest);
9085
+ } catch (err) {
9086
+ migrationFailed = true;
9087
+ diagnosticLogger?.warn(EVENT.migration, { from: src, ok: false, ...errorDetail(err) });
9088
+ console.warn(`[workit] config migration: a file could not be copied to ${dir}; it will be retried on the next run`);
9089
+ }
9090
+ }
9091
+ if (!migrationFailed)
9092
+ migratedDir = dir;
9093
+ return dir;
9094
+ };
9095
+ var configDir = () => ensureConfigDir();
9096
+ var LOCALE_RE = /^[a-z]{2,3}(-(?:[A-Z]{2}|[0-9]{3}))?$/;
9097
+ var DEFAULTS = {
9098
+ locale: "en",
9099
+ localeOptions: ["en", "es-CL", "es-MX", "es-AR", "pt-BR"],
9100
+ branchPolicy: {
9101
+ preset: "gitflow",
9102
+ allowed: [...PRESETS.gitflow.allowed],
9103
+ protected: [...PRESETS.gitflow.protected]
9104
+ },
9105
+ commitPolicy: { preset: "conventional" }
9106
+ };
9107
+ var unchosenBranchPolicy = new WeakSet([DEFAULTS]);
9108
+ var readSafe = (p) => {
9109
+ try {
9110
+ return readFileSync(p, "utf8");
9111
+ } catch {
9112
+ return null;
9113
+ }
9114
+ };
9115
+ var isConfigObject = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
9116
+ var parseConfigResult = (raw, file) => {
9117
+ if (raw === null)
9118
+ return { status: "missing", path: file };
9119
+ let parsed;
9120
+ try {
9121
+ parsed = JSON.parse(raw);
9122
+ } catch {
9123
+ return { status: "malformed", path: file, error: `${file} is not valid JSON` };
9124
+ }
9125
+ if (!isConfigObject(parsed)) {
9126
+ return { status: "malformed", path: file, error: `${file} is not a JSON object` };
9127
+ }
9128
+ const input = parsed;
9129
+ const locale = LOCALE_RE.test(String(input.locale ?? "")) ? input.locale : DEFAULTS.locale;
9130
+ const preset = Object.hasOwn(PRESETS, input.branchPolicy?.preset) ? input.branchPolicy?.preset : "gitflow";
9131
+ const commitPreset = COMMIT_PRESETS.includes(String(input.commitPolicy?.preset ?? "")) ? input.commitPolicy?.preset : "conventional";
9132
+ const config = {
9133
+ locale,
9134
+ localeOptions: Array.isArray(input.localeOptions) ? input.localeOptions : DEFAULTS.localeOptions,
9135
+ branchPolicy: mergePreset(preset, {
9136
+ allowed: Array.isArray(input.branchPolicy?.allowed) ? input.branchPolicy.allowed : undefined,
9137
+ protectedNames: Array.isArray(input.branchPolicy?.protected) ? input.branchPolicy.protected : undefined
9138
+ }, DEFAULTS),
9139
+ commitPolicy: {
9140
+ preset: commitPreset,
9141
+ ...typeof input.commitPolicy?.pattern === "string" ? { pattern: input.commitPolicy.pattern } : {}
9142
+ }
9143
+ };
9144
+ if (!Object.hasOwn(PRESETS, input.branchPolicy?.preset))
9145
+ unchosenBranchPolicy.add(config);
9146
+ return { status: "valid", path: file, config };
9147
+ };
9148
+ var readConfigTyped = (dir) => {
9149
+ const file = path3.join(dir ?? configDir(), "config.json");
9150
+ return parseConfigResult(readSafe(file), file);
9151
+ };
9152
+ var readConfig = () => {
9153
+ const result = readConfigTyped();
9154
+ if (result.status === "malformed")
9155
+ throw new Error(result.error);
9156
+ return result.config ?? DEFAULTS;
9157
+ };
9158
+ var vcsConfigPath = () => process.env.WORKFLOW_VCS_CONFIG ?? path3.join(configDir(), "vcs.json");
9159
+ var branchGlobPattern = (glob) => new RegExp(`^${glob.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*")}$`, "i");
9160
+ var resolveBranchPolicy = (config, workspace, repoPreset) => {
9161
+ const wp = workspace?.branchPolicy ?? {};
9162
+ const workspacePreset = Object.hasOwn(PRESETS, wp.preset);
9163
+ const detected = !workspacePreset && unchosenBranchPolicy.has(config) ? repoPreset?.() ?? null : null;
9164
+ const preset = workspacePreset ? wp.preset : detected ?? config.branchPolicy?.preset ?? "gitflow";
9165
+ const merged = mergePreset(preset, {
9166
+ allowed: wp.allowed,
9167
+ protectedNames: wp.protected
9168
+ }, config);
9169
+ const allowed = merged.allowed.map(branchGlobPattern);
9170
+ return {
9171
+ preset,
9172
+ allowed,
9173
+ protected: new Set([...merged.protected, ...detected ? PRESETS.gitflow.protected : []].map((p) => p.toLowerCase())),
9174
+ integration: wp.integration === "merge" ? "merge" : "pr",
9175
+ defaultTargetBranch: preset === "github-flow" ? "main" : preset === "trunk-based" ? "master" : "develop",
9176
+ detected: detected !== null
9177
+ };
9178
+ };
9179
+ var COMMIT_PRESETS = [
9180
+ "conventional",
9181
+ "gitmoji",
9182
+ "ticket-prefix",
9183
+ "freeform",
9184
+ "custom",
9185
+ "auto"
9186
+ ];
9187
+ var resolveCommitPolicy = (config, workspace) => {
9188
+ const wp = workspace?.commitPolicy ?? {};
9189
+ if (COMMIT_PRESETS.includes(String(wp.preset ?? ""))) {
9190
+ return {
9191
+ preset: wp.preset,
9192
+ ...typeof wp.pattern === "string" ? { pattern: wp.pattern } : {}
9193
+ };
9194
+ }
9195
+ return config.commitPolicy ?? { preset: "conventional" };
9196
+ };
9197
+
9198
+ // packages/workit-core/src/core/workspaces.ts
9199
+ import { spawnSync as spawnSync5 } from "node:child_process";
9200
+ import { createHash as createHash4 } from "node:crypto";
9201
+ import { readFileSync as readFileSync2, realpathSync } from "node:fs";
9202
+ import path4 from "node:path";
9203
+
9204
+ // packages/workit-core/src/core/branch-policy.ts
9205
+ import { spawnSync as spawnSync4 } from "node:child_process";
9206
+ var PREFIXES = {
9207
+ feature: "feature/*",
9208
+ bugfix: "bugfix/*",
9209
+ release: "release/*",
9210
+ hotfix: "hotfix/*"
9211
+ };
9212
+ var branchNames = (workspaceRoot) => {
9213
+ const r = spawnSync4("git", ["for-each-ref", "--format=%(refname)", "refs/heads", "refs/remotes/origin"], { cwd: workspaceRoot, encoding: "utf8" });
9214
+ if (r.status !== 0)
9215
+ return new Set;
9216
+ return new Set((r.stdout ?? "").split(`
9217
+ `).filter(Boolean).map((ref) => ref.startsWith("refs/heads/") ? ref.slice("refs/heads/".length) : ref.slice("refs/remotes/origin/".length)));
9218
+ };
9219
+ var productionBranchOf = (names) => names.has("main") ? "main" : names.has("master") ? "master" : "main";
9220
+ var detectBranchPolicy = (workspaceRoot) => {
9221
+ const names = branchNames(workspaceRoot);
9222
+ const develop = names.has("develop");
9223
+ const main = names.has("main");
8800
9224
  const master = names.has("master");
8801
9225
  const root = main ? "main" : master ? "master" : null;
8802
9226
  if (develop) {
@@ -8845,7 +9269,7 @@ var repoBranchPreset = (workspaceRoot) => {
8845
9269
  var releaseTagPrefixOf = (workspaceRoot, policyPrefix) => {
8846
9270
  if (typeof policyPrefix === "string")
8847
9271
  return policyPrefix.trim();
8848
- const r = spawnSync2("git", ["config", "--get", "gitflow.prefix.versiontag"], {
9272
+ const r = spawnSync4("git", ["config", "--get", "gitflow.prefix.versiontag"], {
8849
9273
  cwd: workspaceRoot,
8850
9274
  encoding: "utf8"
8851
9275
  });
@@ -8910,7 +9334,7 @@ var releaseTrackSchema = object({
8910
9334
  context.addIssue({ code: "custom", path: [field], message: "entries must be unique" });
8911
9335
  }
8912
9336
  }
8913
- if (track.versionSource?.kind === "package-json" && (path3.posix.isAbsolute(track.versionSource.path.replaceAll("\\", "/")) || /^[A-Za-z]:\//u.test(track.versionSource.path.replaceAll("\\", "/")) || track.versionSource.path.replaceAll("\\", "/").split("/").includes(".."))) {
9337
+ if (track.versionSource?.kind === "package-json" && (path4.posix.isAbsolute(track.versionSource.path.replaceAll("\\", "/")) || /^[A-Za-z]:\//u.test(track.versionSource.path.replaceAll("\\", "/")) || track.versionSource.path.replaceAll("\\", "/").split("/").includes(".."))) {
8914
9338
  context.addIssue({
8915
9339
  code: "custom",
8916
9340
  path: ["versionSource", "path"],
@@ -8931,6 +9355,8 @@ var workspaceConfigSchema = object({
8931
9355
  defaultEndpoint: unknown().optional(),
8932
9356
  verification: _enum(["self", "independent"]).optional(),
8933
9357
  trailers: boolean2().optional(),
9358
+ liveSessionGuard: boolean2().optional(),
9359
+ fanout: object({ tierModels: unknown().optional() }).passthrough().optional(),
8934
9360
  autonomy: object(Object.fromEntries(["push", "pr", "merge", "release", "rerun"].map((kind) => [
8935
9361
  kind,
8936
9362
  union([boolean2(), literal("verified")]).optional()
@@ -8971,7 +9397,7 @@ var workspacesFileSchema = object({ workspaces: array(workspaceConfigSchema).opt
8971
9397
  names.add(workspace.name);
8972
9398
  }
8973
9399
  });
8974
- var workspacesRevision = (contents) => createHash3("sha256").update(contents ?? "<missing>").digest("hex");
9400
+ var workspacesRevision = (contents) => createHash4("sha256").update(contents ?? "<missing>").digest("hex");
8975
9401
  var validateWorkspacesDocument = (parsed, file) => {
8976
9402
  if (!isConfigObject(parsed)) {
8977
9403
  return { status: "malformed", path: file, entries: [], error: `${file} is not a JSON object` };
@@ -9009,7 +9435,7 @@ var validateWorkspacesDocument = (parsed, file) => {
9009
9435
  };
9010
9436
  };
9011
9437
  var readWorkspacesResult = (dir = configDir()) => {
9012
- const file = path3.join(dir, "workspaces.json");
9438
+ const file = path4.join(dir, "workspaces.json");
9013
9439
  let raw;
9014
9440
  try {
9015
9441
  raw = readFileSync2(file, "utf8");
@@ -9108,7 +9534,7 @@ var mainCheckoutOf = (cwd) => {
9108
9534
  for (const name of ["GIT_DIR", "GIT_WORK_TREE", "GIT_COMMON_DIR", "GIT_INDEX_FILE"])
9109
9535
  delete env[name];
9110
9536
  const git = (args) => {
9111
- const run = spawnSync3("git", args, {
9537
+ const run = spawnSync5("git", args, {
9112
9538
  cwd,
9113
9539
  env,
9114
9540
  encoding: "utf8",
@@ -9120,9 +9546,9 @@ var mainCheckoutOf = (cwd) => {
9120
9546
  let main = null;
9121
9547
  const dirs = git(["rev-parse", "--path-format=absolute", "--git-dir", "--git-common-dir"]);
9122
9548
  const [gitDir, common] = (dirs ?? "").trim().split(/\r?\n/u);
9123
- if (gitDir && common && path3.resolve(gitDir) !== path3.resolve(common)) {
9549
+ if (gitDir && common && path4.resolve(gitDir) !== path4.resolve(common)) {
9124
9550
  const listed = /^worktree (.+)$/mu.exec(git(["worktree", "list", "--porcelain"]) ?? "")?.[1];
9125
- main = listed?.trim() || (path3.basename(common) === ".git" ? path3.dirname(common) : common);
9551
+ main = listed?.trim() || (path4.basename(common) === ".git" ? path4.dirname(common) : common);
9126
9552
  }
9127
9553
  mainCheckouts.set(cwd, main);
9128
9554
  return main;
@@ -9213,7 +9639,7 @@ var runtimeWorkspaceIndexSchema = object({
9213
9639
  workspaces: array(object({ name: nonBlank, glob: nonBlank }).passthrough()).optional()
9214
9640
  }).passthrough();
9215
9641
  var matchRuntimeWorkspace = (cwd, dir = configDir()) => {
9216
- const file = path3.join(dir, "workspaces.json");
9642
+ const file = path4.join(dir, "workspaces.json");
9217
9643
  let raw;
9218
9644
  try {
9219
9645
  raw = readFileSync2(file, "utf8");
@@ -9248,24 +9674,39 @@ var resolveRuntimeReleaseTracks = (cwd) => {
9248
9674
  const matched = matchRuntimeWorkspace(cwd);
9249
9675
  return matched ? { workspace: matched.selected.name, releaseTracks: matched.selected.releaseTracks } : null;
9250
9676
  };
9251
- var workspaceTrailers = (cwd, dir) => {
9677
+ var inheritedSetting = (cwd, dir, has) => {
9252
9678
  let matched;
9253
9679
  try {
9254
9680
  matched = matchRuntimeWorkspace(cwd, dir);
9255
9681
  } catch {
9256
- return { enabled: true, workspace: null, from: null };
9682
+ return null;
9257
9683
  }
9258
9684
  if (!matched)
9259
- return { enabled: true, workspace: null, from: null };
9685
+ return null;
9260
9686
  const { selected, matches } = matched;
9261
9687
  const enclosing = matches.filter((entry) => entry !== selected).toSorted((a, b) => globSpecificity(b.glob) - globSpecificity(a.glob));
9262
- const source = [selected, ...enclosing].find((entry) => typeof entry.trailers === "boolean");
9688
+ return { workspace: selected.name, source: [selected, ...enclosing].find(has) ?? null };
9689
+ };
9690
+ var workspaceTrailers = (cwd, dir) => {
9691
+ const found = inheritedSetting(cwd, dir, (entry) => typeof entry.trailers === "boolean");
9263
9692
  return {
9264
- enabled: source?.trailers !== false,
9265
- workspace: selected.name,
9266
- from: source?.name ?? null
9693
+ enabled: found?.source?.trailers !== false,
9694
+ workspace: found?.workspace ?? null,
9695
+ from: found?.source?.name ?? null
9267
9696
  };
9268
9697
  };
9698
+ var workspaceLiveSessionGuard = (cwd, dir) => inheritedSetting(cwd, dir, (entry) => typeof entry.liveSessionGuard === "boolean")?.source?.liveSessionGuard !== false;
9699
+ var TIER_KEYS = new Set(["mundane", "standard", "hard"]);
9700
+ var TIER_HOSTS = new Set([
9701
+ "claude_code",
9702
+ "opencode",
9703
+ "codex",
9704
+ "codex_cli",
9705
+ "codex_desktop",
9706
+ "cursor",
9707
+ "pi",
9708
+ "default"
9709
+ ]);
9269
9710
  var resolveRuntimeWorkspaceCandidate = (cwd, kind) => {
9270
9711
  const matched = matchRuntimeWorkspace(cwd);
9271
9712
  if (!matched)
@@ -9306,8 +9747,8 @@ function workspaceDefaultTarget(cwd, globalDefault) {
9306
9747
  }
9307
9748
 
9308
9749
  // packages/workit-core/src/core/release-tracks.ts
9309
- import { createHash as createHash4 } from "node:crypto";
9310
- import { spawnSync as spawnSync4 } from "node:child_process";
9750
+ import { createHash as createHash5 } from "node:crypto";
9751
+ import { spawnSync as spawnSync6 } from "node:child_process";
9311
9752
  class ReleaseTrackError extends Error {
9312
9753
  }
9313
9754
  var KNOWN_FIELDS = new Set([
@@ -9592,7 +10033,7 @@ function resolveReleaseTrack(input) {
9592
10033
  return fallback(`${branch} has the same or unrelated fork points with ${[...new Set(newest.map((fork) => `${fork.ref} (${fork.track.name === IMPLICIT ? "workspace default" : fork.track.name})`))].join(" and ")}`);
9593
10034
  }
9594
10035
  var git2 = (cwd, args) => {
9595
- const run = spawnSync4("git", args, {
10036
+ const run = spawnSync6("git", args, {
9596
10037
  cwd,
9597
10038
  encoding: "utf8",
9598
10039
  timeout: 20000,
@@ -9689,7 +10130,7 @@ function resolveTrackFor(cwd, options) {
9689
10130
  ]) ?? "",
9690
10131
  git2(cwd, ["config", "--get", recordedBaseKey(branch)]) ?? ""
9691
10132
  ] : [];
9692
- key = createHash4("sha256").update(JSON.stringify({ cwd, input, refs, head, own })).digest("hex");
10133
+ key = createHash5("sha256").update(JSON.stringify({ cwd, input, refs, head, own })).digest("hex");
9693
10134
  const hit = memo2.get(key);
9694
10135
  if (hit)
9695
10136
  return hit;
@@ -9704,472 +10145,192 @@ function resolveTrackFor(cwd, options) {
9704
10145
  }
9705
10146
 
9706
10147
  // packages/workit-core/src/core/vcs-config.ts
9707
- var workspacesPath = () => path4.join(configDir(), "workspaces.json");
10148
+ var workspacesPath = () => path5.join(configDir(), "workspaces.json");
9708
10149
  var vcsCwd = (cwd) => cwd ?? process.env.WORKFLOW_WORKSPACE_ROOT ?? process.cwd();
9709
10150
  var remoteProvider = (cwd) => {
9710
- const r = spawnSync5("git", ["remote", "get-url", "origin"], { cwd, encoding: "utf8" });
9711
- if (r.status !== 0)
9712
- return null;
9713
- const url = (r.stdout ?? "").trim();
9714
- if (/github\.com[:/]/.test(url))
9715
- return "github";
9716
- if (/gitlab\.com[:/]/.test(url))
9717
- return "gitlab";
9718
- return null;
9719
- };
9720
- var readVcsConfig = () => {
9721
- const file = vcsConfigPath();
9722
- let raw;
9723
- try {
9724
- raw = fs2.readFileSync(file, "utf8");
9725
- } catch {
9726
- return { status: "missing", path: file, config: {} };
9727
- }
9728
- let parsed;
9729
- try {
9730
- parsed = JSON.parse(raw);
9731
- } catch {
9732
- return { status: "malformed", path: file, config: {}, error: `${file} is not valid JSON` };
9733
- }
9734
- if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
9735
- return { status: "valid", path: file, config: parsed };
9736
- }
9737
- return { status: "malformed", path: file, config: {}, error: `${file} is not a JSON object` };
9738
- };
9739
- var trackSummary = (resolution) => resolution.status === "resolved" ? {
9740
- name: resolution.track?.name ?? null,
9741
- source: resolution.source,
9742
- detail: resolution.detail,
9743
- warnings: resolution.warnings,
9744
- track: resolution.track,
9745
- tracks: resolution.tracks,
9746
- blocking: resolution.blocking
9747
- } : null;
9748
- function vcsConfig(mode, cwd, options = {}) {
9749
- const ws = resolveRuntimeWorkspaceVcs(vcsCwd(cwd));
9750
- const wsVcs = ws?.vcs ?? {};
9751
- const wsYt = ws?.youtrack ?? {};
9752
- const wsIssues = ws?.issues ?? {};
9753
- const { status: cfgStatus, path: cfgPath, config: cfg, error: cfgError } = readVcsConfig();
9754
- const root = vcsCwd(cwd);
9755
- const determinateRoot = cwd !== undefined || process.env.WORKFLOW_WORKSPACE_ROOT !== undefined;
9756
- const rawProvider = wsVcs.provider ?? (determinateRoot ? remoteProvider(root) : null) ?? cfg.provider ?? null;
9757
- const provider = rawProvider === null || rawProvider === undefined ? null : String(rawProvider).toLowerCase() || null;
9758
- const workspaceDefault = workspaceDefaultTarget(root, cfg.defaultTargetBranch);
9759
- const linkIssues = typeof wsYt.link_issues === "boolean" ? wsYt.link_issues : null;
9760
- const youtrackBaseUrl = typeof wsYt.baseUrl === "string" ? wsYt.baseUrl : null;
9761
- let issuesProvider = null;
9762
- let linkOnPr = null;
9763
- if (provider === "github" && typeof wsIssues.provider === "string" && wsIssues.provider.toLowerCase() === "github") {
9764
- issuesProvider = "github";
9765
- linkOnPr = typeof wsIssues.link_on_pr === "boolean" ? wsIssues.link_on_pr : null;
9766
- }
9767
- if (mode === "resolve") {
9768
- if (cfgStatus === "malformed")
9769
- return { ok: false, error: cfgError, configPath: cfgPath };
9770
- const trackResolution = resolveTrackFor(root, {
9771
- track: options.track,
9772
- ...options.branch !== undefined ? { branch: options.branch } : {},
9773
- defaultBranch: workspaceDefault
9774
- });
9775
- if (trackResolution.status === "invalid")
9776
- return { ok: false, error: trackResolution.error, configPath: workspacesPath() };
9777
- const releaseTrack = trackSummary(trackResolution);
9778
- const defaultTarget = releaseTrack?.track?.pullRequestTarget ?? workspaceDefault;
9779
- const baseBranch = releaseTrack?.track?.baseBranch ?? defaultTarget;
9780
- return {
9781
- ok: true,
9782
- workspace_name: ws?.name ?? null,
9783
- provider,
9784
- defaultTargetBranch: defaultTarget,
9785
- workspaceDefaultTargetBranch: workspaceDefault,
9786
- baseBranch,
9787
- releaseTrack,
9788
- link_issues: linkIssues,
9789
- youtrack_base_url: youtrackBaseUrl,
9790
- issues_provider: issuesProvider,
9791
- link_on_pr: linkOnPr
9792
- };
9793
- }
9794
- if (cfgStatus === "malformed") {
9795
- return { ok: false, error: cfgError, configPath: cfgPath };
9796
- }
9797
- if (cfgStatus === "missing" && provider === null)
9798
- return { ok: false, error: `vcs.json is missing: ${cfgPath}`, configPath: cfgPath };
9799
- if (provider === null) {
9800
- return {
9801
- ok: false,
9802
- error: `no vcs provider configured (set vcs.provider in ${cfgPath}, match a workspace entry, or run inside a checkout with a recognized origin remote)`,
9803
- configPath: cfgPath
9804
- };
9805
- }
9806
- const prov = cfg[provider] ?? {};
9807
- const out = {
9808
- ok: true,
9809
- configPath: path4.resolve(cfgPath),
9810
- provider,
9811
- defaultTargetBranch: workspaceDefault,
9812
- pr: cfg.pr ?? {},
9813
- workspace_name: ws?.name ?? null,
9814
- link_issues: linkIssues,
9815
- youtrack_base_url: youtrackBaseUrl,
9816
- issues_provider: issuesProvider,
9817
- link_on_pr: linkOnPr
9818
- };
9819
- if (provider === "gitlab") {
9820
- out.gitlab = {
9821
- host: prov.host ?? "gitlab.com",
9822
- apiUrl: prov.apiUrl ?? "https://gitlab.com/api/v4"
9823
- };
9824
- } else if (provider === "github") {
9825
- out.github = { host: prov.host ?? "github.com" };
9826
- }
9827
- return out;
9828
- }
9829
-
9830
- // packages/workit-core/src/host-session.ts
9831
- import { createHash as createHash6 } from "node:crypto";
9832
- import fs4 from "node:fs";
9833
- import path7 from "node:path";
9834
-
9835
- // packages/workit-core/src/core/logger.ts
9836
- import os3 from "node:os";
9837
- import path5 from "node:path";
9838
- var SENSITIVE_WORDS = new Set([
9839
- "token",
9840
- "secret",
9841
- "password",
9842
- "passwd",
9843
- "authorization",
9844
- "credential",
9845
- "credentials",
9846
- "cookie",
9847
- "api",
9848
- "apikey",
9849
- "apitoken",
9850
- "accesstoken",
9851
- "clientsecret",
9852
- "refreshtoken",
9853
- "key",
9854
- "bearer",
9855
- "prompt",
9856
- "message",
9857
- "messages",
9858
- "content",
9859
- "body",
9860
- "args",
9861
- "argument",
9862
- "result",
9863
- "results",
9864
- "output",
9865
- "issue",
9866
- "description",
9867
- "summary",
9868
- "payload",
9869
- "script",
9870
- "command",
9871
- "text",
9872
- "query"
9873
- ]);
9874
- var STACK_WORDS = new Set(["stack", "stacktrace", "trace"]);
9875
- var escapeRegExp2 = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
9876
- var resolveStateDir = () => {
9877
- const override = process.env.WORKFLOW_TOOLKIT_STATE;
9878
- if (override)
9879
- return override;
9880
- const home = os3.homedir();
9881
- if (process.env.XDG_STATE_HOME)
9882
- return path5.join(process.env.XDG_STATE_HOME, "workit");
9883
- switch (os3.platform()) {
9884
- case "darwin":
9885
- return path5.join(home, "Library", "Application Support", "workit");
9886
- case "win32":
9887
- return path5.join(process.env.LOCALAPPDATA ?? path5.join(home, "AppData", "Local"), "workit");
9888
- default:
9889
- return path5.join(process.env.HOME ?? home, ".local", "state", "workit");
9890
- }
9891
- };
9892
- var homePattern = new RegExp(`${escapeRegExp2(os3.homedir())}(?=[/\\\\]|$)|\\$HOME(?=[/\\\\]|$)`, "g");
9893
-
9894
- // packages/workit-core/src/store/paths.ts
9895
- import { spawnSync as spawnSync6 } from "node:child_process";
9896
- import { createHash as createHash5 } from "node:crypto";
9897
- import fs3 from "node:fs";
9898
- import path6 from "node:path";
9899
- var NOT_A_REPO = /not a git repository \(or any (of the parent directories|parent up to)/u;
9900
- var gitRun = (cwd, args) => {
9901
- const run = spawnSync6("git", args, {
9902
- cwd,
9903
- encoding: "utf8",
9904
- timeout: 20000,
9905
- killSignal: "SIGKILL",
9906
- windowsHide: true,
9907
- env: {
9908
- ...process.env,
9909
- GIT_TERMINAL_PROMPT: "0",
9910
- GIT_OPTIONAL_LOCKS: "0",
9911
- LC_ALL: "C",
9912
- LANGUAGE: "C"
9913
- }
9914
- });
9915
- const error = run.error;
9916
- return {
9917
- ok: run.status === 0,
9918
- stdout: run.stdout ?? "",
9919
- stderr: error ? String(error.message) : run.stderr ?? "",
9920
- missing: error?.code === "ENOENT"
9921
- };
10151
+ const r = spawnSync7("git", ["remote", "get-url", "origin"], { cwd, encoding: "utf8" });
10152
+ if (r.status !== 0)
10153
+ return null;
10154
+ const url = (r.stdout ?? "").trim();
10155
+ if (/github\.com[:/]/.test(url))
10156
+ return "github";
10157
+ if (/gitlab\.com[:/]/.test(url))
10158
+ return "gitlab";
10159
+ return null;
9922
10160
  };
9923
- var shortHash = (value) => createHash5("sha256").update(value).digest("hex").slice(0, 12);
9924
- var real = (file) => {
10161
+ var readVcsConfig = () => {
10162
+ const file = vcsConfigPath();
10163
+ let raw;
9925
10164
  try {
9926
- return fs3.realpathSync.native(file);
10165
+ raw = fs4.readFileSync(file, "utf8");
9927
10166
  } catch {
9928
- return file;
10167
+ return { status: "missing", path: file, config: {} };
9929
10168
  }
9930
- };
9931
- var DISCOVERY_ENV = [
9932
- "GIT_DIR",
9933
- "GIT_COMMON_DIR",
9934
- "GIT_WORK_TREE",
9935
- "GIT_CEILING_DIRECTORIES",
9936
- "GIT_DISCOVERY_ACROSS_FILESYSTEM"
9937
- ];
9938
- var isGitDir = (dir) => {
10169
+ let parsed;
9939
10170
  try {
9940
- return fs3.statSync(path6.join(dir, "HEAD")).isFile() && fs3.statSync(path6.join(dir, "objects")).isDirectory() && fs3.statSync(path6.join(dir, "refs")).isDirectory();
10171
+ parsed = JSON.parse(raw);
9941
10172
  } catch {
9942
- return false;
10173
+ return { status: "malformed", path: file, config: {}, error: `${file} is not valid JSON` };
9943
10174
  }
9944
- };
9945
- var reftableRefs = (common) => {
9946
- try {
9947
- return /^\s*refstorage\s*=\s*reftable\s*$/imu.test(fs3.readFileSync(path6.join(common, "config"), "utf8"));
9948
- } catch {
9949
- return false;
10175
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
10176
+ return { status: "valid", path: file, config: parsed };
9950
10177
  }
10178
+ return { status: "malformed", path: file, config: {}, error: `${file} is not a JSON object` };
9951
10179
  };
9952
- function fastGit(root) {
9953
- if (DISCOVERY_ENV.some((name) => process.env[name]))
9954
- return null;
9955
- let dir = real(root);
9956
- let device = null;
9957
- for (;; ) {
9958
- let stat;
9959
- try {
9960
- stat = fs3.statSync(dir);
9961
- } catch {
9962
- return null;
9963
- }
9964
- if (device !== null && stat.dev !== device)
9965
- return { kind: "none" };
9966
- device = stat.dev;
9967
- const dotGit = path6.join(dir, ".git");
9968
- let entry = null;
9969
- try {
9970
- entry = fs3.statSync(dotGit);
9971
- } catch {}
9972
- let gitDir = null;
9973
- let top = null;
9974
- if (entry) {
9975
- top = dir;
9976
- if (entry.isDirectory())
9977
- gitDir = dotGit;
9978
- else if (entry.isFile()) {
9979
- let text = "";
9980
- try {
9981
- text = fs3.readFileSync(dotGit, "utf8");
9982
- } catch {
9983
- return null;
9984
- }
9985
- const match = /^gitdir: (.+)$/mu.exec(text);
9986
- if (!match)
9987
- return null;
9988
- gitDir = path6.resolve(dir, match[1].trim());
9989
- } else
9990
- return null;
9991
- if (!fs3.existsSync(path6.join(gitDir, "HEAD")))
9992
- return null;
9993
- } else if (isGitDir(dir))
9994
- gitDir = dir;
9995
- if (gitDir) {
9996
- let common = gitDir;
9997
- try {
9998
- common = path6.resolve(gitDir, fs3.readFileSync(path6.join(gitDir, "commondir"), "utf8").trim());
9999
- } catch {}
10000
- if (!fs3.existsSync(path6.join(common, "objects")))
10001
- return null;
10002
- return {
10003
- kind: "repo",
10004
- common: real(common),
10005
- gitDir: real(gitDir),
10006
- top: top === null ? null : real(top),
10007
- reftable: reftableRefs(common)
10008
- };
10009
- }
10010
- const parent = path6.dirname(dir);
10011
- if (parent === dir)
10012
- return { kind: "none" };
10013
- dir = parent;
10180
+ var trackSummary = (resolution) => resolution.status === "resolved" ? {
10181
+ name: resolution.track?.name ?? null,
10182
+ source: resolution.source,
10183
+ detail: resolution.detail,
10184
+ warnings: resolution.warnings,
10185
+ track: resolution.track,
10186
+ tracks: resolution.tracks,
10187
+ blocking: resolution.blocking
10188
+ } : null;
10189
+ function vcsConfig(mode, cwd, options = {}) {
10190
+ const ws = resolveRuntimeWorkspaceVcs(vcsCwd(cwd));
10191
+ const wsVcs = ws?.vcs ?? {};
10192
+ const wsYt = ws?.youtrack ?? {};
10193
+ const wsIssues = ws?.issues ?? {};
10194
+ const { status: cfgStatus, path: cfgPath, config: cfg, error: cfgError } = readVcsConfig();
10195
+ const root = vcsCwd(cwd);
10196
+ const determinateRoot = cwd !== undefined || process.env.WORKFLOW_WORKSPACE_ROOT !== undefined;
10197
+ const rawProvider = wsVcs.provider ?? (determinateRoot ? remoteProvider(root) : null) ?? cfg.provider ?? null;
10198
+ const provider = rawProvider === null || rawProvider === undefined ? null : String(rawProvider).toLowerCase() || null;
10199
+ const workspaceDefault = workspaceDefaultTarget(root, cfg.defaultTargetBranch);
10200
+ const linkIssues = typeof wsYt.link_issues === "boolean" ? wsYt.link_issues : null;
10201
+ const youtrackBaseUrl = typeof wsYt.baseUrl === "string" ? wsYt.baseUrl : null;
10202
+ let issuesProvider = null;
10203
+ let linkOnPr = null;
10204
+ if (provider === "github" && typeof wsIssues.provider === "string" && wsIssues.provider.toLowerCase() === "github") {
10205
+ issuesProvider = "github";
10206
+ linkOnPr = typeof wsIssues.link_on_pr === "boolean" ? wsIssues.link_on_pr : null;
10014
10207
  }
10015
- }
10016
- function resolveStore(root) {
10017
- root = path6.resolve(root);
10018
- let directory = false;
10019
- try {
10020
- directory = fs3.statSync(root).isDirectory();
10021
- } catch {}
10022
- if (!directory)
10023
- return { dir: path6.join(root, ".workit"), shared: false, top: null };
10024
- const fast = fastGit(root);
10025
- if (fast?.kind === "none")
10026
- return { dir: path6.join(root, ".workit"), shared: false, top: null };
10027
- if (fast?.kind === "repo")
10028
- return {
10029
- dir: path6.join(fast.common, "workit"),
10030
- shared: true,
10031
- top: fast.top,
10032
- git: { gitDir: fast.gitDir, top: fast.top, reftable: fast.reftable }
10033
- };
10034
- const run = gitRun(root, ["rev-parse", "--path-format=absolute", "--git-common-dir"]);
10035
- const common = run.stdout.trim();
10036
- if (run.ok && common) {
10037
- const top = gitRun(root, ["rev-parse", "--path-format=absolute", "--show-toplevel"]);
10208
+ if (mode === "resolve") {
10209
+ if (cfgStatus === "malformed")
10210
+ return { ok: false, error: cfgError, configPath: cfgPath };
10211
+ const trackResolution = resolveTrackFor(root, {
10212
+ track: options.track,
10213
+ ...options.branch !== undefined ? { branch: options.branch } : {},
10214
+ defaultBranch: workspaceDefault
10215
+ });
10216
+ if (trackResolution.status === "invalid")
10217
+ return { ok: false, error: trackResolution.error, configPath: workspacesPath() };
10218
+ const releaseTrack = trackSummary(trackResolution);
10219
+ const defaultTarget = releaseTrack?.track?.pullRequestTarget ?? workspaceDefault;
10220
+ const baseBranch = releaseTrack?.track?.baseBranch ?? defaultTarget;
10038
10221
  return {
10039
- dir: path6.join(real(path6.resolve(root, common)), "workit"),
10040
- shared: true,
10041
- top: top.ok && top.stdout.trim() ? real(top.stdout.trim()) : null
10222
+ ok: true,
10223
+ workspace_name: ws?.name ?? null,
10224
+ provider,
10225
+ defaultTargetBranch: defaultTarget,
10226
+ workspaceDefaultTargetBranch: workspaceDefault,
10227
+ baseBranch,
10228
+ releaseTrack,
10229
+ link_issues: linkIssues,
10230
+ youtrack_base_url: youtrackBaseUrl,
10231
+ issues_provider: issuesProvider,
10232
+ link_on_pr: linkOnPr
10042
10233
  };
10043
10234
  }
10044
- if (run.missing || !run.ok && NOT_A_REPO.test(run.stderr))
10045
- return { dir: path6.join(root, ".workit"), shared: false, top: null };
10046
- return new Error(`cannot locate the workit store: git rev-parse failed (${run.stderr.trim().split(`
10047
- `)[0] || "no output"}); fix the repository (git status should work here)`);
10048
- }
10049
- var rebasingBranch = (gitDir) => {
10050
- for (const state of ["rebase-merge", "rebase-apply"]) {
10051
- try {
10052
- const ref = fs3.readFileSync(path6.join(gitDir, state, "head-name"), "utf8").trim();
10053
- const match = /^refs\/heads\/(.+)$/u.exec(ref);
10054
- if (match)
10055
- return match[1];
10056
- } catch {}
10057
- }
10058
- return null;
10059
- };
10060
- function resolveTaskKey(root, location) {
10061
- if (!location.shared)
10062
- return { key: `dir-${shortHash(root)}`, kind: "dir", branch: null };
10063
- const branchKey = (name) => ({ key: name, kind: "branch", branch: name });
10064
- if (location.git && !location.git.reftable) {
10065
- let head = "";
10066
- try {
10067
- head = fs3.readFileSync(path6.join(location.git.gitDir, "HEAD"), "utf8").trim();
10068
- } catch {}
10069
- const ref = /^ref: refs\/heads\/(.+)$/u.exec(head);
10070
- if (ref && ref[1] !== ".invalid")
10071
- return branchKey(ref[1]);
10072
- if (/^[0-9a-f]{40,64}$/u.test(head)) {
10073
- const rebasing = rebasingBranch(location.git.gitDir);
10074
- if (rebasing)
10075
- return branchKey(rebasing);
10076
- return {
10077
- key: `detached-${shortHash(location.git.top ?? location.git.gitDir)}`,
10078
- kind: "detached",
10079
- branch: null
10080
- };
10081
- }
10082
- }
10083
- const branch = gitRun(root, ["symbolic-ref", "-q", "--short", "HEAD"]);
10084
- const name = branch.ok ? branch.stdout.trim() : "";
10085
- if (name && name !== ".invalid")
10086
- return branchKey(name);
10087
- const gitDir = gitRun(root, ["rev-parse", "--path-format=absolute", "--git-dir"]);
10088
- if (gitDir.ok && gitDir.stdout.trim()) {
10089
- const rebasing = rebasingBranch(gitDir.stdout.trim());
10090
- if (rebasing)
10091
- return branchKey(rebasing);
10092
- }
10093
- const top = gitRun(root, ["rev-parse", "--path-format=absolute", "--show-toplevel"]);
10094
- const worktree = top.ok && top.stdout.trim() ? top.stdout.trim() : root;
10095
- return { key: `detached-${shortHash(real(worktree))}`, kind: "detached", branch: null };
10096
- }
10097
- function checkoutRootOf(root, location) {
10098
- const resolved = path6.resolve(root);
10099
- let canonical = resolved;
10100
- try {
10101
- canonical = fs3.realpathSync(resolved);
10102
- } catch {
10103
- return resolved;
10235
+ if (cfgStatus === "malformed") {
10236
+ return { ok: false, error: cfgError, configPath: cfgPath };
10104
10237
  }
10105
- const found = location ?? resolveStore(canonical);
10106
- if (found instanceof Error || !found.shared || !found.top)
10107
- return canonical;
10108
- if (real(canonical) === found.top)
10109
- return canonical;
10110
- try {
10111
- return fs3.realpathSync(found.top);
10112
- } catch {
10113
- return canonical;
10238
+ if (cfgStatus === "missing" && provider === null)
10239
+ return { ok: false, error: `vcs.json is missing: ${cfgPath}`, configPath: cfgPath };
10240
+ if (provider === null) {
10241
+ return {
10242
+ ok: false,
10243
+ error: `no vcs provider configured (set vcs.provider in ${cfgPath}, match a workspace entry, or run inside a checkout with a recognized origin remote)`,
10244
+ configPath: cfgPath
10245
+ };
10114
10246
  }
10115
- }
10116
- function branchRenames(location) {
10117
- if (!location.shared)
10118
- return [];
10119
- const files = [];
10120
- const walk = (dir) => {
10121
- let entries;
10122
- try {
10123
- entries = fs3.readdirSync(dir, { withFileTypes: true });
10124
- } catch {
10125
- return;
10126
- }
10127
- for (const entry of entries)
10128
- if (entry.isDirectory())
10129
- walk(path6.join(dir, entry.name));
10130
- else
10131
- files.push(path6.join(dir, entry.name));
10247
+ const prov = cfg[provider] ?? {};
10248
+ const out = {
10249
+ ok: true,
10250
+ configPath: path5.resolve(cfgPath),
10251
+ provider,
10252
+ defaultTargetBranch: workspaceDefault,
10253
+ pr: cfg.pr ?? {},
10254
+ workspace_name: ws?.name ?? null,
10255
+ link_issues: linkIssues,
10256
+ youtrack_base_url: youtrackBaseUrl,
10257
+ issues_provider: issuesProvider,
10258
+ link_on_pr: linkOnPr
10132
10259
  };
10133
- walk(path6.join(path6.dirname(location.dir), "logs", "refs", "heads"));
10134
- if (location.git)
10135
- files.push(path6.join(location.git.gitDir, "logs", "HEAD"));
10136
- const seen = new Set;
10137
- const renames = [];
10138
- for (const file of files) {
10139
- let text = "";
10140
- try {
10141
- text = fs3.readFileSync(file, "utf8");
10142
- } catch {
10143
- continue;
10144
- }
10145
- for (const match of text.matchAll(/ (\d+) [+-]\d{4}\tBranch: renamed refs\/heads\/(\S+) to refs\/heads\/(\S+)/gu)) {
10146
- const rename = { from: match[2], to: match[3], at: Number(match[1]) };
10147
- const id = `${rename.at}\x00${rename.from}\x00${rename.to}`;
10148
- if (seen.has(id))
10149
- continue;
10150
- seen.add(id);
10151
- renames.push(rename);
10152
- }
10260
+ if (provider === "gitlab") {
10261
+ out.gitlab = {
10262
+ host: prov.host ?? "gitlab.com",
10263
+ apiUrl: prov.apiUrl ?? "https://gitlab.com/api/v4"
10264
+ };
10265
+ } else if (provider === "github") {
10266
+ out.github = { host: prov.host ?? "github.com" };
10153
10267
  }
10154
- return renames.toSorted((left, right) => left.at - right.at);
10155
- }
10156
- function followRenames(key, boundAt, renames) {
10157
- let current = key;
10158
- let since = Math.floor(Date.parse(boundAt) / 1000);
10159
- for (const rename of renames)
10160
- if (rename.from === current && rename.at >= since) {
10161
- current = rename.to;
10162
- since = rename.at;
10163
- }
10164
- return current;
10268
+ return out;
10165
10269
  }
10166
- var checkoutSlug = (root) => {
10167
- let canonical = real(root);
10168
- if (process.platform === "win32")
10169
- canonical = canonical.toLowerCase();
10170
- const base = path6.basename(canonical).replace(/[^A-Za-z0-9._-]/g, "_").slice(0, 40) || "root";
10171
- return `${base}-${shortHash(canonical)}`;
10270
+
10271
+ // packages/workit-core/src/host-session.ts
10272
+ import { createHash as createHash6, randomBytes, timingSafeEqual } from "node:crypto";
10273
+ import fs5 from "node:fs";
10274
+ import path7 from "node:path";
10275
+
10276
+ // packages/workit-core/src/core/logger.ts
10277
+ import os3 from "node:os";
10278
+ import path6 from "node:path";
10279
+ var SENSITIVE_WORDS = new Set([
10280
+ "token",
10281
+ "secret",
10282
+ "password",
10283
+ "passwd",
10284
+ "authorization",
10285
+ "credential",
10286
+ "credentials",
10287
+ "cookie",
10288
+ "api",
10289
+ "apikey",
10290
+ "apitoken",
10291
+ "accesstoken",
10292
+ "clientsecret",
10293
+ "refreshtoken",
10294
+ "key",
10295
+ "bearer",
10296
+ "prompt",
10297
+ "message",
10298
+ "messages",
10299
+ "content",
10300
+ "body",
10301
+ "args",
10302
+ "argument",
10303
+ "result",
10304
+ "results",
10305
+ "output",
10306
+ "issue",
10307
+ "description",
10308
+ "summary",
10309
+ "payload",
10310
+ "script",
10311
+ "command",
10312
+ "text",
10313
+ "query"
10314
+ ]);
10315
+ var STACK_WORDS = new Set(["stack", "stacktrace", "trace"]);
10316
+ var escapeRegExp2 = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
10317
+ var resolveStateDir = () => {
10318
+ const override = process.env.WORKFLOW_TOOLKIT_STATE;
10319
+ if (override)
10320
+ return override;
10321
+ const home = os3.homedir();
10322
+ if (process.env.XDG_STATE_HOME)
10323
+ return path6.join(process.env.XDG_STATE_HOME, "workit");
10324
+ switch (os3.platform()) {
10325
+ case "darwin":
10326
+ return path6.join(home, "Library", "Application Support", "workit");
10327
+ case "win32":
10328
+ return path6.join(process.env.LOCALAPPDATA ?? path6.join(home, "AppData", "Local"), "workit");
10329
+ default:
10330
+ return path6.join(process.env.HOME ?? home, ".local", "state", "workit");
10331
+ }
10172
10332
  };
10333
+ var homePattern = new RegExp(`${escapeRegExp2(os3.homedir())}(?=[/\\\\]|$)|\\$HOME(?=[/\\\\]|$)`, "g");
10173
10334
 
10174
10335
  // packages/workit-core/src/host-session.ts
10175
10336
  var HOST_SESSION_VARS = [
@@ -10200,9 +10361,10 @@ function hostSessionFromEnv(env) {
10200
10361
  return inner ? { host: inner.host, session: inner.session } : { host: declared, session: null };
10201
10362
  }
10202
10363
  var ISSUED_PER_LEAD = 64;
10203
- var ISSUED_SESSIONS_MARKER = "workit-issued-sessions/2";
10364
+ var ISSUED_SESSIONS_MARKER = "workit-issued-sessions/3";
10204
10365
  var hash = (value) => createHash6("sha256").update(value).digest("hex").slice(0, 32);
10205
10366
  var userIssuedRoot = () => path7.join(resolveStateDir(), "issued");
10367
+ var userTokenRoot = () => path7.join(resolveStateDir(), "issued-tokens");
10206
10368
  function migrateRepoIssued(cwd) {
10207
10369
  if (cwd === undefined)
10208
10370
  return;
@@ -10212,7 +10374,7 @@ function migrateRepoIssued(cwd) {
10212
10374
  if (location instanceof Error)
10213
10375
  return;
10214
10376
  old = path7.join(location.dir, "hooks", "issued");
10215
- if (!fs4.existsSync(old))
10377
+ if (!fs5.existsSync(old))
10216
10378
  return;
10217
10379
  } catch {
10218
10380
  return;
@@ -10220,13 +10382,18 @@ function migrateRepoIssued(cwd) {
10220
10382
  for (const lead of safeList(old)) {
10221
10383
  try {
10222
10384
  const from = path7.join(old, lead);
10223
- if (!fs4.statSync(from).isDirectory())
10385
+ if (!fs5.statSync(from).isDirectory())
10224
10386
  continue;
10225
10387
  const to = path7.join(userIssuedRoot(), lead);
10226
- fs4.mkdirSync(to, { recursive: true, mode: 448 });
10227
- for (const id of fs4.readdirSync(from)) {
10228
- fs4.copyFileSync(path7.join(from, id), path7.join(to, id));
10229
- fs4.rmSync(path7.join(from, id), { force: true });
10388
+ fs5.mkdirSync(to, { recursive: true, mode: 448 });
10389
+ for (const id of fs5.readdirSync(from)) {
10390
+ try {
10391
+ fs5.copyFileSync(path7.join(from, id), path7.join(to, id), fs5.constants.COPYFILE_EXCL);
10392
+ } catch (error) {
10393
+ if (error.code !== "EEXIST")
10394
+ throw error;
10395
+ }
10396
+ fs5.rmSync(path7.join(from, id), { force: true });
10230
10397
  }
10231
10398
  removeIfEmpty(from);
10232
10399
  } catch {}
@@ -10235,31 +10402,81 @@ function migrateRepoIssued(cwd) {
10235
10402
  }
10236
10403
  var safeList = (dir) => {
10237
10404
  try {
10238
- return fs4.readdirSync(dir);
10405
+ return fs5.readdirSync(dir);
10239
10406
  } catch {
10240
10407
  return [];
10241
10408
  }
10242
10409
  };
10243
10410
  var removeIfEmpty = (dir) => {
10244
10411
  try {
10245
- fs4.rmdirSync(dir);
10412
+ fs5.rmdirSync(dir);
10246
10413
  } catch {}
10247
10414
  };
10415
+ var tokenHash = (token) => createHash6("sha256").update(token).digest("hex");
10248
10416
  function recordIssuedSession(cwd, lead, issued) {
10417
+ const token = randomBytes(18).toString("base64url");
10249
10418
  try {
10250
10419
  migrateRepoIssued(cwd);
10251
- const dir = path7.join(userIssuedRoot(), hash(lead));
10252
- fs4.mkdirSync(dir, { recursive: true, mode: 448 });
10253
10420
  const own = hash(issued);
10254
- fs4.writeFileSync(path7.join(dir, own), ISSUED_SESSIONS_MARKER);
10255
- const entries = fs4.readdirSync(dir);
10421
+ const tokens = path7.join(userTokenRoot(), hash(lead));
10422
+ fs5.mkdirSync(tokens, { recursive: true, mode: 448 });
10423
+ fs5.writeFileSync(path7.join(tokens, own), `${tokenHash(token)}
10424
+ `, { mode: 384, flag: "wx" });
10425
+ const dir = path7.join(userIssuedRoot(), hash(lead));
10426
+ fs5.mkdirSync(dir, { recursive: true, mode: 448 });
10427
+ fs5.writeFileSync(path7.join(dir, own), `${ISSUED_SESSIONS_MARKER}
10428
+ `, { mode: 384 });
10429
+ pruneIssued(dir, own, tokens);
10430
+ return token;
10431
+ } catch {
10432
+ return null;
10433
+ }
10434
+ }
10435
+ function pruneIssued(dir, own, tokens) {
10436
+ try {
10437
+ const entries = fs5.readdirSync(dir);
10256
10438
  if (entries.length <= ISSUED_PER_LEAD)
10257
10439
  return;
10258
- const byAge = entries.filter((name) => name !== own).map((name) => ({ name, at: fs4.statSync(path7.join(dir, name)).mtimeMs })).toSorted((a, b) => a.at - b.at);
10259
- for (const { name } of byAge.slice(0, entries.length - ISSUED_PER_LEAD))
10260
- fs4.rmSync(path7.join(dir, name), { force: true });
10440
+ 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);
10441
+ for (const { name } of byAge.slice(0, entries.length - ISSUED_PER_LEAD)) {
10442
+ fs5.rmSync(path7.join(dir, name), { force: true });
10443
+ fs5.rmSync(path7.join(tokens, name), { force: true });
10444
+ }
10261
10445
  } catch {}
10262
10446
  }
10447
+ var issuedRecord = (cwd, claimed) => {
10448
+ const cut = claimed.lastIndexOf(":");
10449
+ if (cut <= 0)
10450
+ return null;
10451
+ migrateRepoIssued(cwd);
10452
+ try {
10453
+ return fs5.readFileSync(path7.join(userIssuedRoot(), hash(claimed.slice(0, cut)), hash(claimed)), "utf8");
10454
+ } catch {
10455
+ return null;
10456
+ }
10457
+ };
10458
+ var TOKEN_HASH = /^([0-9a-f]{64})$/mu;
10459
+ var issuedToken = (claimed) => {
10460
+ const cut = claimed.lastIndexOf(":");
10461
+ if (cut <= 0)
10462
+ return { stored: null, leadBound: false };
10463
+ const dir = path7.join(userTokenRoot(), hash(claimed.slice(0, cut)));
10464
+ try {
10465
+ const text = fs5.readFileSync(path7.join(dir, hash(claimed)), "utf8");
10466
+ return { stored: TOKEN_HASH.exec(text)?.[1] ?? null, leadBound: true };
10467
+ } catch {
10468
+ return { stored: null, leadBound: safeList(dir).length > 0 };
10469
+ }
10470
+ };
10471
+ function issuedBinding(cwd, claimed) {
10472
+ const record = issuedRecord(cwd, claimed);
10473
+ const { stored, leadBound } = issuedToken(claimed);
10474
+ if (stored)
10475
+ return "token";
10476
+ if (record === null)
10477
+ return null;
10478
+ return leadBound ? "token" : "legacy";
10479
+ }
10263
10480
  var OWN_CHILD_SHELL = new Set(["opencode", "pi"]);
10264
10481
 
10265
10482
  // packages/workit-core/src/ledger.ts
@@ -10276,7 +10493,7 @@ var err = (code, error, unblock) => ({
10276
10493
  ...unblock ? { unblock } : {}
10277
10494
  });
10278
10495
  var gitRun2 = (cwd, args, timeoutMs = GIT_TIMEOUTS.local) => {
10279
- const run = spawnSync7("git", args, {
10496
+ const run = spawnSync8("git", args, {
10280
10497
  cwd,
10281
10498
  encoding: "utf8",
10282
10499
  timeout: timeoutMs,
@@ -10388,7 +10605,7 @@ function ledgerPath(cwd) {
10388
10605
  const root = storeRoot(cwd);
10389
10606
  return root.ok ? { ok: true, value: path8.join(root.value.root, "ledger", "ledger.jsonl") } : root;
10390
10607
  }
10391
- var newId2 = () => `${Date.now().toString(36)}-${randomBytes(6).toString("hex")}`;
10608
+ var newId2 = () => `${Date.now().toString(36)}-${randomBytes2(6).toString("hex")}`;
10392
10609
  var NETWORK_FS_TYPES = new Set([
10393
10610
  26985,
10394
10611
  20859,
@@ -10406,7 +10623,7 @@ var onNetworkFs = (dir) => {
10406
10623
  if (process.platform !== "linux")
10407
10624
  return false;
10408
10625
  try {
10409
- return isNetworkFsType(fs5.statfsSync(dir).type);
10626
+ return isNetworkFsType(fs6.statfsSync(dir).type);
10410
10627
  } catch {
10411
10628
  return false;
10412
10629
  }
@@ -10416,7 +10633,7 @@ var LOCK_STALE_MS = 30000;
10416
10633
  var LOCK_OWNER = "owner";
10417
10634
  var lockToken = (lock) => {
10418
10635
  try {
10419
- return fs5.readFileSync(path8.join(lock, LOCK_OWNER), "utf8");
10636
+ return fs6.readFileSync(path8.join(lock, LOCK_OWNER), "utf8");
10420
10637
  } catch {
10421
10638
  return null;
10422
10639
  }
@@ -10424,7 +10641,7 @@ var lockToken = (lock) => {
10424
10641
  var lockAgeMs = (lock) => {
10425
10642
  for (const target of [path8.join(lock, LOCK_OWNER), lock]) {
10426
10643
  try {
10427
- return Date.now() - fs5.statSync(target).mtimeMs;
10644
+ return Date.now() - fs6.statSync(target).mtimeMs;
10428
10645
  } catch {}
10429
10646
  }
10430
10647
  return null;
@@ -10432,55 +10649,55 @@ var lockAgeMs = (lock) => {
10432
10649
  var ledgerLock = {
10433
10650
  acquire(lock) {
10434
10651
  try {
10435
- fs5.mkdirSync(lock);
10652
+ fs6.mkdirSync(lock);
10436
10653
  } catch (error) {
10437
10654
  if (error.code === "EEXIST")
10438
10655
  return null;
10439
10656
  throw error;
10440
10657
  }
10441
- const token = `${process.pid}-${randomBytes(8).toString("hex")}`;
10442
- fs5.writeFileSync(path8.join(lock, LOCK_OWNER), token);
10658
+ const token = `${process.pid}-${randomBytes2(8).toString("hex")}`;
10659
+ fs6.writeFileSync(path8.join(lock, LOCK_OWNER), token);
10443
10660
  return token;
10444
10661
  },
10445
10662
  release(lock, token) {
10446
10663
  if (lockToken(lock) !== token)
10447
10664
  return false;
10448
- const tomb = `${lock}.release-${randomBytes(6).toString("hex")}`;
10665
+ const tomb = `${lock}.release-${randomBytes2(6).toString("hex")}`;
10449
10666
  try {
10450
- fs5.renameSync(lock, tomb);
10667
+ fs6.renameSync(lock, tomb);
10451
10668
  } catch {
10452
10669
  return false;
10453
10670
  }
10454
10671
  if (lockToken(tomb) !== token) {
10455
10672
  try {
10456
- fs5.renameSync(tomb, lock);
10673
+ fs6.renameSync(tomb, lock);
10457
10674
  } catch {}
10458
10675
  return false;
10459
10676
  }
10460
- fs5.rmSync(tomb, { recursive: true, force: true });
10677
+ fs6.rmSync(tomb, { recursive: true, force: true });
10461
10678
  return true;
10462
10679
  },
10463
10680
  token: lockToken,
10464
10681
  reap(lock, observed) {
10465
- const tomb = `${lock}.reap-${randomBytes(6).toString("hex")}`;
10682
+ const tomb = `${lock}.reap-${randomBytes2(6).toString("hex")}`;
10466
10683
  try {
10467
- fs5.renameSync(lock, tomb);
10684
+ fs6.renameSync(lock, tomb);
10468
10685
  } catch {
10469
10686
  return false;
10470
10687
  }
10471
10688
  if (lockToken(tomb) === observed) {
10472
- fs5.rmSync(tomb, { recursive: true, force: true });
10689
+ fs6.rmSync(tomb, { recursive: true, force: true });
10473
10690
  return true;
10474
10691
  }
10475
10692
  try {
10476
- fs5.renameSync(tomb, lock);
10693
+ fs6.renameSync(tomb, lock);
10477
10694
  } catch {
10478
- fs5.rmSync(tomb, { recursive: true, force: true });
10695
+ fs6.rmSync(tomb, { recursive: true, force: true });
10479
10696
  }
10480
10697
  return false;
10481
10698
  }
10482
10699
  };
10483
- function withLedgerLock(file, fn, waitMs = LOCK_WAIT_MS) {
10700
+ function withLedgerLock(file, fn, waitMs = hostAwareLockTimeout(LOCK_WAIT_MS)) {
10484
10701
  const lock = `${file}.lock`;
10485
10702
  const deadline = Date.now() + waitMs;
10486
10703
  let token = null;
@@ -10509,7 +10726,7 @@ var TAIL_BYTES = 64 * 1024;
10509
10726
  var lastLine = (fd, size) => {
10510
10727
  for (let span = Math.min(size, TAIL_BYTES);span > 0; span = Math.min(size, span * 2)) {
10511
10728
  const buffer = Buffer.alloc(span);
10512
- fs5.readSync(fd, buffer, 0, span, size - span);
10729
+ fs6.readSync(fd, buffer, 0, span, size - span);
10513
10730
  const end = buffer.lastIndexOf(10);
10514
10731
  if (end < 0) {
10515
10732
  if (span === size)
@@ -10538,15 +10755,23 @@ var lastLine = (fd, size) => {
10538
10755
  }
10539
10756
  return null;
10540
10757
  };
10758
+ var tighten = (fd) => {
10759
+ try {
10760
+ if ((fs6.fstatSync(fd).mode & 63) !== 0)
10761
+ fs6.fchmodSync(fd, 384);
10762
+ } catch {}
10763
+ };
10541
10764
  function appendLine(file, build, options) {
10542
10765
  const write = () => {
10543
- const fd = fs5.openSync(file, "a+");
10766
+ const fd = fs6.openSync(file, "a+", 384);
10544
10767
  try {
10545
- const size = fs5.fstatSync(fd).size;
10768
+ if (process.platform !== "win32")
10769
+ tighten(fd);
10770
+ const size = fs6.fstatSync(fd).size;
10546
10771
  let fence = "";
10547
10772
  if (size > 0) {
10548
10773
  const last = Buffer.alloc(1);
10549
- fs5.readSync(fd, last, 0, 1, size - 1);
10774
+ fs6.readSync(fd, last, 0, 1, size - 1);
10550
10775
  if (last[0] !== 10)
10551
10776
  fence = `
10552
10777
  `;
@@ -10555,16 +10780,16 @@ function appendLine(file, build, options) {
10555
10780
  if (!line.ok)
10556
10781
  return line;
10557
10782
  const bytes = Buffer.from(fence + line.value);
10558
- const written = fs5.writeSync(fd, bytes);
10783
+ const written = fs6.writeSync(fd, bytes);
10559
10784
  if (written !== bytes.length)
10560
10785
  return err("unavailable", `short write to ${file}: ${written} of ${bytes.length} bytes; the row is torn and will be skipped`);
10561
10786
  return { ok: true, value: undefined };
10562
10787
  } finally {
10563
- fs5.closeSync(fd);
10788
+ fs6.closeSync(fd);
10564
10789
  }
10565
10790
  };
10566
10791
  try {
10567
- fs5.mkdirSync(path8.dirname(file), { recursive: true });
10792
+ fs6.mkdirSync(path8.dirname(file), { recursive: true });
10568
10793
  if (!options.forceLock && !onNetworkFs(path8.dirname(file)))
10569
10794
  return write();
10570
10795
  const locked = withLedgerLock(file, write);
@@ -10636,8 +10861,10 @@ function readLedger(cwd, options = {}) {
10636
10861
  if (!file.ok)
10637
10862
  return file;
10638
10863
  let text = "";
10864
+ let stat = null;
10639
10865
  try {
10640
- text = fs5.readFileSync(file.value, "utf8");
10866
+ stat = fs6.statSync(file.value);
10867
+ text = fs6.readFileSync(file.value, "utf8");
10641
10868
  } catch (error) {
10642
10869
  if (error.code === "ENOENT")
10643
10870
  return {
@@ -10656,6 +10883,7 @@ function readLedger(cwd, options = {}) {
10656
10883
  const integrity = { chained: 0, legacy: 0, unverified: [] };
10657
10884
  const earlier = new Set([null]);
10658
10885
  let chainStarted = false;
10886
+ const ids = new Set;
10659
10887
  for (const raw of text.split(`
10660
10888
  `)) {
10661
10889
  const trimmed = raw.trim();
@@ -10689,6 +10917,10 @@ function readLedger(cwd, options = {}) {
10689
10917
  reason = "unsigned";
10690
10918
  else
10691
10919
  rowIntegrity = "legacy";
10920
+ if (options.integrity && !reason && ids.has(parsed.id))
10921
+ reason = "duplicate_id";
10922
+ if (options.integrity)
10923
+ ids.add(parsed.id);
10692
10924
  if (reason)
10693
10925
  rowIntegrity = "unverified";
10694
10926
  const pr = typeof parsed.pr === "number" && Number.isInteger(parsed.pr) ? parsed.pr : undefined;
@@ -10712,7 +10944,8 @@ function readLedger(cwd, options = {}) {
10712
10944
  seq: rows.length + 1,
10713
10945
  superseded: false,
10714
10946
  supersedeIgnored: false,
10715
- ...rowIntegrity ? { integrity: rowIntegrity } : {}
10947
+ ...rowIntegrity ? { integrity: rowIntegrity } : {},
10948
+ ...reason ? { integrityReason: reason } : {}
10716
10949
  };
10717
10950
  rows.push(row);
10718
10951
  if (!options.integrity)
@@ -10740,18 +10973,48 @@ function readLedger(cwd, options = {}) {
10740
10973
  const byId = new Map;
10741
10974
  for (const row of rows) {
10742
10975
  const target = row.supersedes ? byId.get(row.supersedes) : undefined;
10743
- if (target && supersedeAllowed(row, target, renameAwareBranch(scopeOf, row.branch)) === null)
10976
+ if (target && row.integrity !== "unverified" && supersedeAllowed(row, target, renameAwareBranch(scopeOf, row.branch)) === null)
10744
10977
  target.superseded = true;
10745
10978
  else if (row.supersedes)
10746
10979
  row.supersedeIgnored = true;
10747
10980
  if (!byId.has(row.id))
10748
10981
  byId.set(row.id, row);
10749
10982
  }
10983
+ if (options.integrity && stat && Buffer.byteLength(text) === stat.size)
10984
+ tamperCache.set(file.value, {
10985
+ size: stat.size,
10986
+ stamp: fileStamp(stat),
10987
+ breaks: integrity.unverified.filter((row) => TAMPER_REASONS.has(row.reason))
10988
+ });
10750
10989
  return {
10751
10990
  ok: true,
10752
10991
  value: { path: file.value, rows, skipped, integrity: options.integrity ? integrity : null }
10753
10992
  };
10754
10993
  }
10994
+ var TAMPER_REASONS = new Set(["hash_mismatch", "broken_link"]);
10995
+ var tamperCache = new Map;
10996
+ var fileStamp = (stat) => `${stat.ino}:${stat.mtimeMs}:${stat.ctimeMs}`;
10997
+ function ledgerTampering(cwd) {
10998
+ const file = ledgerPath(cwd);
10999
+ if (!file.ok)
11000
+ return [];
11001
+ try {
11002
+ const stat = fs6.statSync(file.value);
11003
+ const cached = tamperCache.get(file.value);
11004
+ if (cached && cached.size === stat.size && cached.stamp === fileStamp(stat))
11005
+ return cached.breaks;
11006
+ } catch {
11007
+ return [];
11008
+ }
11009
+ const read = readLedger(cwd, { integrity: true });
11010
+ return read.ok ? (read.value.integrity?.unverified ?? []).filter((row) => TAMPER_REASONS.has(row.reason)) : [];
11011
+ }
11012
+ function tamperedUnblock(cwd) {
11013
+ const file = ledgerPath(cwd);
11014
+ const where = file.ok ? file.value : "the ledger file";
11015
+ 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`;
11016
+ }
11017
+ var tamperedWarning = (cwd, breaks) => `the ledger was edited, or a row was removed or moved (${breaks.slice(0, 3).map((row) => `${row.reason} at row ${row.seq}`).join(", ")}${breaks.length > 3 ? ", …" : ""}): no verdict is accepted until it is repaired. ${tamperedUnblock(cwd)}`;
10755
11018
  function verdictBasis(verdict, current) {
10756
11019
  if (verdict.head && verdict.head === current.head)
10757
11020
  return "fresh";
@@ -10759,7 +11022,7 @@ function verdictBasis(verdict, current) {
10759
11022
  return "carried";
10760
11023
  return "stale";
10761
11024
  }
10762
- var isLiveVerdict = (row) => row.type === "verdict" && typeof row.result === "string" && !row.superseded;
11025
+ var isLiveVerdict = (row, retired) => row.type === "verdict" && typeof row.result === "string" && !retired.has(row.id);
10763
11026
  function trailerSessions(cwd, base, head) {
10764
11027
  if (!base || !head || !safeRef(base) || !safeRef(head))
10765
11028
  return [];
@@ -10840,6 +11103,56 @@ var authoredBy = (authors, session) => {
10840
11103
  return true;
10841
11104
  return false;
10842
11105
  };
11106
+ var TRUST_REASONS = new Set([
11107
+ "unsigned",
11108
+ "hash_mismatch",
11109
+ "broken_link",
11110
+ "duplicate_id",
11111
+ "unissued_session",
11112
+ "unbound_session"
11113
+ ]);
11114
+ var SHELL_VOUCHED_HOSTS = new Set([
11115
+ "cli",
11116
+ "opencode",
11117
+ "pi",
11118
+ "codex_cli",
11119
+ "codex_desktop"
11120
+ ]);
11121
+ function sessionTrust(cwd, row) {
11122
+ const session = row.actor.session;
11123
+ if (!session || row.self === true)
11124
+ return null;
11125
+ const source = typeof row.sessionSource === "string" ? row.sessionSource : "env";
11126
+ const host = row.actor.host;
11127
+ const issuedOnly = source === "flag" ? host !== "cli" : source === "env" && session.includes(":") && !SHELL_VOUCHED_HOSTS.has(host);
11128
+ if (!issuedOnly)
11129
+ return null;
11130
+ const binding = issuedBinding(cwd, session);
11131
+ if (binding === null)
11132
+ return "unissued_session";
11133
+ return binding === "token" && row.sessionBound !== true ? "unbound_session" : null;
11134
+ }
11135
+ function trustReasons(cwd, row) {
11136
+ if (row.integrity === "unverified")
11137
+ return [row.integrityReason ?? "unsigned"];
11138
+ if (row.integrity !== "verified")
11139
+ return ["unsigned"];
11140
+ const session = sessionTrust(cwd, row);
11141
+ return session ? [session] : [];
11142
+ }
11143
+ function withIntegrity(cwd, rows) {
11144
+ if (rows.every((row) => row.integrity !== undefined))
11145
+ return rows;
11146
+ const read = readLedger(cwd, { integrity: true });
11147
+ const bySeq = new Map(read.ok ? read.value.rows.map((row) => [row.seq, row]) : []);
11148
+ return rows.map((row) => {
11149
+ if (row.integrity !== undefined)
11150
+ return row;
11151
+ const fresh = bySeq.get(row.seq);
11152
+ return fresh && fresh.id === row.id && fresh.at === row.at ? fresh : row;
11153
+ });
11154
+ }
11155
+ var untrusted = (reasons) => reasons.some((reason) => TRUST_REASONS.has(reason));
10843
11156
  var independenceReasons = (row, authors) => {
10844
11157
  const reasons = [];
10845
11158
  if (!row.actor.session)
@@ -10850,15 +11163,17 @@ var independenceReasons = (row, authors) => {
10850
11163
  reasons.push("self");
10851
11164
  return reasons;
10852
11165
  };
10853
- function effectiveOf(rows, authors, sameCode, isCurrent) {
11166
+ function effectiveOf(rows, reasonsOf, sameCode, isCurrent) {
10854
11167
  let effective = null;
10855
11168
  for (const row of rows) {
10856
11169
  if (!effective) {
10857
11170
  effective = row;
10858
11171
  continue;
10859
11172
  }
10860
- const rowIndependent = independenceReasons(row, authors).length === 0;
10861
- const effIndependent = independenceReasons(effective, authors).length === 0;
11173
+ if (untrusted(reasonsOf(row)) && !untrusted(reasonsOf(effective)))
11174
+ continue;
11175
+ const rowIndependent = reasonsOf(row).length === 0;
11176
+ const effIndependent = reasonsOf(effective).length === 0;
10862
11177
  if (effIndependent && !rowIndependent && isCurrent(effective))
10863
11178
  continue;
10864
11179
  if (effIndependent && FAILING.has(String(effective.result)) && row.actor.session === effective.actor.session && sameCode(effective, row))
@@ -10872,7 +11187,7 @@ function restackCarries(rows, branch, from, to) {
10872
11187
  return false;
10873
11188
  const edges = new Map;
10874
11189
  for (const row of rows)
10875
- if (row.type === "stack.restacked" && row.observer === "workit_cli" && row.branch === branch && row.patchEqual === true && typeof row.fromHead === "string" && row.head)
11190
+ if (row.type === "stack.restacked" && row.observer === "workit_cli" && row.integrity === "verified" && row.branch === branch && row.patchEqual === true && typeof row.fromHead === "string" && row.head)
10876
11191
  edges.set(row.fromHead, [...edges.get(row.fromHead) ?? [], row.head]);
10877
11192
  const seen = new Set([from]);
10878
11193
  const queue = [from];
@@ -10888,7 +11203,8 @@ function restackCarries(rows, branch, from, to) {
10888
11203
  }
10889
11204
  return false;
10890
11205
  }
10891
- function checkVerdicts(cwd, branch, rows) {
11206
+ function checkVerdicts(cwd, branch, ledgerRows) {
11207
+ const rows = withIntegrity(cwd, ledgerRows);
10892
11208
  const head = branchHead(cwd, branch);
10893
11209
  const fallbackBase = defaultBase(cwd);
10894
11210
  const scope = branchScope(cwd, branch);
@@ -10918,21 +11234,47 @@ function checkVerdicts(cwd, branch, rows) {
10918
11234
  return verdictBasis(failed, { head: row.head, ...keys }) !== "stale" || restackCarries(rows, branch, failed.head, row.head);
10919
11235
  };
10920
11236
  const authors = authorSessions(cwd, rows, branch, { base: fallbackBase, head }, scope);
11237
+ const failureAuthorCache = new Map;
11238
+ const failureAuthors = (row) => {
11239
+ let set = failureAuthorCache.get(row.seq);
11240
+ if (!set) {
11241
+ set = authorSessions(cwd, rows.filter((other) => other.integrity === "verified" && other.seq < row.seq), branch, { base: null, head: null }, scope);
11242
+ failureAuthorCache.set(row.seq, set);
11243
+ }
11244
+ return set;
11245
+ };
11246
+ const reasonCache = new Map;
11247
+ const reasonsOf = (row) => {
11248
+ let reasons = reasonCache.get(row);
11249
+ if (!reasons) {
11250
+ const failing = FAILING.has(String(row.result));
11251
+ reasons = [
11252
+ ...trustReasons(cwd, row),
11253
+ ...independenceReasons(row, failing ? failureAuthors(row) : authors)
11254
+ ];
11255
+ reasonCache.set(row, reasons);
11256
+ }
11257
+ return reasons;
11258
+ };
11259
+ const retired = new Set;
11260
+ for (const row of rows)
11261
+ if (row.type === "verdict" && row.supersedes && !row.supersedeIgnored && trustReasons(cwd, row).length === 0)
11262
+ retired.add(row.supersedes);
10921
11263
  const byKind = new Map;
10922
11264
  for (const row of rows)
10923
- if (isLiveVerdict(row) && scope(row)) {
11265
+ if (isLiveVerdict(row, retired) && scope(row)) {
10924
11266
  const kind = typeof row.kind === "string" ? row.kind : "review";
10925
11267
  byKind.set(kind, [...byKind.get(kind) ?? [], row]);
10926
11268
  }
10927
11269
  const verdicts = [];
10928
11270
  const warnings = [];
10929
11271
  for (const [kind, list] of byKind) {
10930
- const row = effectiveOf(list, authors, sameCode, (candidate) => basisOf(candidate) !== "stale");
11272
+ const row = effectiveOf(list, reasonsOf, sameCode, (candidate) => basisOf(candidate) !== "stale");
10931
11273
  if (!row)
10932
11274
  continue;
10933
11275
  const basis = basisOf(row);
10934
11276
  const current = basis !== "stale";
10935
- const independence = independenceReasons(row, authors);
11277
+ const independence = reasonsOf(row);
10936
11278
  const reasons = [
10937
11279
  ...current ? [] : ["stale"],
10938
11280
  ...PASSING.has(String(row.result)) ? [] : ["not_passing"],
@@ -10949,23 +11291,31 @@ function checkVerdicts(cwd, branch, rows) {
10949
11291
  });
10950
11292
  if (reasons.length === 0) {
10951
11293
  for (const later of list)
10952
- if (later.seq > row.seq && independenceReasons(later, authors).length > 0 && FAILING.has(String(later.result)) && basisOf(later) !== "stale")
11294
+ if (later.seq > row.seq && reasonsOf(later).length > 0 && !untrusted(reasonsOf(later)) && FAILING.has(String(later.result)) && basisOf(later) !== "stale")
10953
11295
  warnings.push(`the author's own (self) ${String(later.result)} ${kind} verdict ${later.id} by ${later.actor.session ?? "no session"} on the current code is newer than the accepted pass ${row.id}: read it before merging`);
10954
11296
  }
10955
11297
  }
10956
11298
  for (const row of rows)
10957
11299
  if (row.integrity === "unverified" && scope(row))
10958
11300
  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)`);
11301
+ const tampered = ledgerTampering(cwd);
11302
+ if (tampered.length) {
11303
+ warnings.push(tamperedWarning(cwd, tampered));
11304
+ for (const entry of verdicts) {
11305
+ entry.accepted = false;
11306
+ entry.reasons = [...entry.reasons, "ledger_tampered"];
11307
+ }
11308
+ }
10959
11309
  verdicts.sort((a, b) => a.verdict.seq - b.verdict.seq);
10960
11310
  const newestCurrent = verdicts.findLast((entry) => entry.current) ?? verdicts.at(-1) ?? null;
10961
11311
  const failing = verdicts.find((entry) => entry.current && entry.independent && FAILING.has(String(entry.verdict.result)));
10962
11312
  const newestAccepted = verdicts.findLast((entry) => entry.accepted && STRONG_RESULTS.has(String(entry.verdict.result))) ?? null;
10963
- const accepted = newestAccepted && !failing ? { accepted: true, verdict: newestAccepted.verdict, reasons: [] } : {
11313
+ const accepted = tampered.length ? { accepted: false, verdict: null, reasons: ["ledger_tampered"] } : newestAccepted && !failing ? { accepted: true, verdict: newestAccepted.verdict, reasons: [] } : {
10964
11314
  accepted: false,
10965
11315
  verdict: failing?.verdict ?? null,
10966
11316
  reasons: failing ? ["failing_verdict"] : !newestCurrent ? ["no_verdict"] : newestCurrent.reasons.length ? newestCurrent.reasons : ["type_check_only"]
10967
11317
  };
10968
- const selfEntry = failing ? undefined : verdicts.findLast((entry) => entry.current && !entry.independent && STRONG_RESULTS.has(String(entry.verdict.result)));
11318
+ const selfEntry = failing ? undefined : verdicts.findLast((entry) => entry.current && !entry.independent && !untrusted(entry.reasons) && STRONG_RESULTS.has(String(entry.verdict.result)));
10969
11319
  return {
10970
11320
  branch,
10971
11321
  head,
@@ -11931,12 +12281,12 @@ function sameFileIdentity(left, right, platform = process.platform) {
11931
12281
  }
11932
12282
 
11933
12283
  // node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.js
11934
- import { randomBytes as randomBytes2 } from "node:crypto";
12284
+ import { randomBytes as randomBytes3 } from "node:crypto";
11935
12285
  import fsSync from "node:fs";
11936
12286
  import path11 from "node:path";
11937
12287
 
11938
12288
  // node_modules/@openclaw/fs-safe/dist/bounded-read.js
11939
- import fs6 from "node:fs";
12289
+ import fs7 from "node:fs";
11940
12290
 
11941
12291
  // node_modules/@openclaw/fs-safe/dist/byte-budget.js
11942
12292
  function normalizeMaxBytes(value, options = {}) {
@@ -11973,7 +12323,7 @@ function readFileDescriptorBoundedSync(fd, maxBytes) {
11973
12323
  let total = 0;
11974
12324
  while (true) {
11975
12325
  const length = nextReadLength(total, maxBytes, scratch.length);
11976
- const bytesRead = fs6.readSync(fd, scratch, 0, length, null);
12326
+ const bytesRead = fs7.readSync(fd, scratch, 0, length, null);
11977
12327
  if (bytesRead === 0) {
11978
12328
  return Buffer.concat(chunks, total);
11979
12329
  }
@@ -11982,9 +12332,9 @@ function readFileDescriptorBoundedSync(fd, maxBytes) {
11982
12332
  }
11983
12333
 
11984
12334
  // node_modules/@openclaw/fs-safe/dist/read-open-flags.js
11985
- import fs7 from "node:fs";
12335
+ import fs8 from "node:fs";
11986
12336
  function resolveReadOpenFlags(options) {
11987
- const constants = options?.constants ?? fs7.constants;
12337
+ const constants = options?.constants ?? fs8.constants;
11988
12338
  const noFollow = process.platform !== "win32" && options?.followSymlinks !== true && typeof constants.O_NOFOLLOW === "number" ? constants.O_NOFOLLOW : 0;
11989
12339
  const nonBlocking = process.platform !== "win32" && typeof constants.O_NONBLOCK === "number" ? constants.O_NONBLOCK : 0;
11990
12340
  return constants.O_RDONLY | noFollow | nonBlocking;
@@ -12001,7 +12351,7 @@ function parseSidecarLockSnapshot(snapshot, parser) {
12001
12351
  }
12002
12352
  function createSidecarLockOwnershipToken() {
12003
12353
  let token = SIDECAR_LOCK_OWNERSHIP_TOKEN_PREFIX;
12004
- for (const byte of randomBytes2(SIDECAR_LOCK_OWNERSHIP_TOKEN_BYTES)) {
12354
+ for (const byte of randomBytes3(SIDECAR_LOCK_OWNERSHIP_TOKEN_BYTES)) {
12005
12355
  for (let bit = 7;bit >= 0; bit -= 1) {
12006
12356
  token += byte & 1 << bit ? "\t" : " ";
12007
12357
  }
@@ -12175,7 +12525,7 @@ function getFsSafeLockConfig() {
12175
12525
  }
12176
12526
 
12177
12527
  // node_modules/@openclaw/fs-safe/dist/file-lock-sync.js
12178
- import fs8 from "node:fs";
12528
+ import fs9 from "node:fs";
12179
12529
  import path12 from "node:path";
12180
12530
 
12181
12531
  // node_modules/@openclaw/fs-safe/dist/timing.js
@@ -12206,7 +12556,7 @@ function releaseAllSyncHeldLocks() {
12206
12556
  }
12207
12557
  try {
12208
12558
  if (held.fd !== undefined)
12209
- fs8.closeSync(held.fd);
12559
+ fs9.closeSync(held.fd);
12210
12560
  } catch {}
12211
12561
  try {
12212
12562
  removeSidecarLockIfUnchangedSync(held.lockPath, held.snapshot);
@@ -12241,7 +12591,7 @@ function releaseSyncHeldLock(held) {
12241
12591
  if (held.fd !== undefined) {
12242
12592
  const fd = held.fd;
12243
12593
  held.fd = undefined;
12244
- fs8.closeSync(fd);
12594
+ fs9.closeSync(fd);
12245
12595
  }
12246
12596
  removeSidecarLockIfUnchangedSync(held.lockPath, held.snapshot);
12247
12597
  heldLocks.delete(held.normalizedTargetPath);
@@ -12264,11 +12614,11 @@ function createSyncHeldLockHandle(held) {
12264
12614
  };
12265
12615
  }
12266
12616
  function canonicalLockParentSync(parent) {
12267
- return process.platform === "win32" ? fs8.realpathSync.native(parent) : fs8.realpathSync(parent);
12617
+ return process.platform === "win32" ? fs9.realpathSync.native(parent) : fs9.realpathSync(parent);
12268
12618
  }
12269
12619
  function normalizeTargetPath(targetPath) {
12270
12620
  const resolved = path12.resolve(targetPath);
12271
- fs8.mkdirSync(path12.dirname(resolved), { recursive: true });
12621
+ fs9.mkdirSync(path12.dirname(resolved), { recursive: true });
12272
12622
  try {
12273
12623
  return path12.join(canonicalLockParentSync(path12.dirname(resolved)), path12.basename(resolved));
12274
12624
  } catch {
@@ -12296,7 +12646,7 @@ function defaultShouldReclaim(snapshot, staleMs, nowMs) {
12296
12646
  }
12297
12647
  function reclaimGuardExists(reclaimGuardPath) {
12298
12648
  try {
12299
- fs8.lstatSync(reclaimGuardPath);
12649
+ fs9.lstatSync(reclaimGuardPath);
12300
12650
  return true;
12301
12651
  } catch (error) {
12302
12652
  if (error.code === "ENOENT")
@@ -12360,19 +12710,19 @@ function acquireFileLockSync(targetPath, options) {
12360
12710
  const { raw, ownershipToken } = serializeSidecarLockPayload(payload);
12361
12711
  let lockFileCreateDenied = false;
12362
12712
  try {
12363
- const noFollow = process.platform !== "win32" && typeof fs8.constants.O_NOFOLLOW === "number" ? fs8.constants.O_NOFOLLOW : 0;
12713
+ const noFollow = process.platform !== "win32" && typeof fs9.constants.O_NOFOLLOW === "number" ? fs9.constants.O_NOFOLLOW : 0;
12364
12714
  try {
12365
- fd = fs8.openSync(lockPath, fs8.constants.O_WRONLY | fs8.constants.O_CREAT | fs8.constants.O_EXCL | noFollow, 384);
12715
+ fd = fs9.openSync(lockPath, fs9.constants.O_WRONLY | fs9.constants.O_CREAT | fs9.constants.O_EXCL | noFollow, 384);
12366
12716
  } catch (error) {
12367
12717
  lockFileCreateDenied = isTransientLockFileDenial(error, lockPath);
12368
12718
  throw error;
12369
12719
  }
12370
- fs8.writeFileSync(fd, raw, "utf8");
12371
- fs8.fsyncSync(fd);
12720
+ fs9.writeFileSync(fd, raw, "utf8");
12721
+ fs9.fsyncSync(fd);
12372
12722
  const snapshot = {
12373
12723
  raw,
12374
12724
  payload,
12375
- stat: fs8.fstatSync(fd),
12725
+ stat: fs9.fstatSync(fd),
12376
12726
  ownershipToken
12377
12727
  };
12378
12728
  const createdHeld = {
@@ -12407,8 +12757,8 @@ function acquireFileLockSync(targetPath, options) {
12407
12757
  return returnedHandle;
12408
12758
  } catch (error) {
12409
12759
  if (fd !== undefined) {
12410
- const failed = { payload: null, stat: fs8.fstatSync(fd) };
12411
- fs8.closeSync(fd);
12760
+ const failed = { payload: null, stat: fs9.fstatSync(fd) };
12761
+ fs9.closeSync(fd);
12412
12762
  fd = undefined;
12413
12763
  removeSidecarLockIfUnchangedSync(lockPath, failed);
12414
12764
  }
@@ -12457,185 +12807,45 @@ function acquireFileLockSync(targetPath, options) {
12457
12807
  })) {
12458
12808
  let ownsReclaimGuard = false;
12459
12809
  try {
12460
- fs8.mkdirSync(reclaimGuardPath);
12810
+ fs9.mkdirSync(reclaimGuardPath);
12461
12811
  ownsReclaimGuard = true;
12462
12812
  if (removeSidecarLockIfUnchangedSync(lockPath, snapshot))
12463
- continue;
12464
- } catch (reclaimError) {
12465
- if (reclaimError.code !== "EEXIST") {
12466
- throw reclaimError;
12467
- }
12468
- waitForRetry();
12469
- continue;
12470
- } finally {
12471
- if (ownsReclaimGuard) {
12472
- try {
12473
- fs8.rmdirSync(reclaimGuardPath);
12474
- } catch {}
12475
- }
12476
- }
12477
- }
12478
- throw Object.assign(new Error(`file lock stale for ${normalizedTargetPath}`), {
12479
- code: "file_lock_stale",
12480
- lockPath,
12481
- normalizedTargetPath
12482
- });
12483
- }
12484
- waitForRetry();
12485
- }
12486
- }
12487
- }
12488
- // packages/workit-core/src/core/package-root.ts
12489
- import { existsSync as existsSync3 } from "node:fs";
12490
- import path13 from "node:path";
12491
- import { fileURLToPath } from "node:url";
12492
- var packageRoot = () => {
12493
- let dir = path13.dirname(fileURLToPath(import.meta.url));
12494
- while (true) {
12495
- const parent = path13.dirname(dir);
12496
- if (existsSync3(path13.join(dir, "package.json")) || parent === dir)
12497
- return dir;
12498
- dir = parent;
12499
- }
12500
- };
12501
-
12502
- // packages/workit-core/src/core/store-lock.ts
12503
- import { spawnSync as spawnSync8 } from "node:child_process";
12504
- import fs9 from "node:fs";
12505
- import { hostname } from "node:os";
12506
- var metadataLockSchema = object({
12507
- pid: number2().int().nonnegative().safe(),
12508
- processStart: string2().nullable(),
12509
- host: string2().min(1),
12510
- nonce: string2().min(1),
12511
- externalAction: literal(true).optional()
12512
- }).strict();
12513
- var FOREIGN_LOCK_TTL_MS = 10 * 60000;
12514
- var UNREADABLE_LOCK_TTL_MS = 30000;
12515
- var RECLAIM_GUARD_TTL_MS = 30000;
12516
- var defaultLockTimeoutMs = 250;
12517
- var defaultLockTimeout = () => defaultLockTimeoutMs;
12518
- var parseMetadataLock = (raw) => {
12519
- let value;
12520
- try {
12521
- value = JSON.parse(raw);
12522
- } catch {
12523
- throw Object.assign(new Error("metadata lock is invalid"), { code: "metadata_lock_invalid" });
12524
- }
12525
- const parsed = metadataLockSchema.safeParse(value);
12526
- if (!parsed.success)
12527
- throw Object.assign(new Error("metadata lock is invalid"), { code: "metadata_lock_invalid" });
12528
- return parsed.data;
12529
- };
12530
- var parseMetadataLockOrNull = (raw) => {
12531
- try {
12532
- return parseMetadataLock(raw);
12533
- } catch {
12534
- return null;
12535
- }
12536
- };
12537
- var parseProcStatStart = (stat) => {
12538
- const close = stat.lastIndexOf(")");
12539
- if (close < 0)
12540
- return null;
12541
- return stat.slice(close + 1).trim().split(/\s+/)[19] ?? null;
12542
- };
12543
- var processStartOf = (pid) => {
12544
- if (process.platform === "linux") {
12545
- try {
12546
- return parseProcStatStart(fs9.readFileSync(`/proc/${pid}/stat`, "utf8"));
12547
- } catch {
12548
- return null;
12549
- }
12550
- }
12551
- if (process.platform === "darwin" || process.platform === "freebsd") {
12552
- try {
12553
- const run = spawnSync8("ps", ["-o", "lstart=", "-p", String(pid)], {
12554
- encoding: "utf8",
12555
- timeout: 1000
12556
- });
12557
- const value = run.status === 0 ? run.stdout.trim() : "";
12558
- return value || null;
12559
- } catch {
12560
- return null;
12813
+ continue;
12814
+ } catch (reclaimError) {
12815
+ if (reclaimError.code !== "EEXIST") {
12816
+ throw reclaimError;
12817
+ }
12818
+ waitForRetry();
12819
+ continue;
12820
+ } finally {
12821
+ if (ownsReclaimGuard) {
12822
+ try {
12823
+ fs9.rmdirSync(reclaimGuardPath);
12824
+ } catch {}
12825
+ }
12826
+ }
12827
+ }
12828
+ throw Object.assign(new Error(`file lock stale for ${normalizedTargetPath}`), {
12829
+ code: "file_lock_stale",
12830
+ lockPath,
12831
+ normalizedTargetPath
12832
+ });
12833
+ }
12834
+ waitForRetry();
12561
12835
  }
12562
12836
  }
12563
- return null;
12564
- };
12565
- var readTrimmed = (file) => {
12566
- try {
12567
- return fs9.readFileSync(file, "utf8").trim() || null;
12568
- } catch {
12569
- return null;
12570
- }
12571
- };
12572
- var cachedLockHost = null;
12573
- var localLockHost = () => {
12574
- if (cachedLockHost !== null)
12575
- return cachedLockHost;
12576
- let pidns = null;
12577
- try {
12578
- pidns = /\[(\d+)\]/.exec(fs9.readlinkSync("/proc/self/ns/pid"))?.[1] ?? null;
12579
- } catch {}
12580
- const boot = readTrimmed("/proc/sys/kernel/random/boot_id");
12581
- cachedLockHost = pidns || boot ? `${hostname()}#${pidns ?? "?"}:${boot ?? "?"}` : hostname();
12582
- return cachedLockHost;
12583
- };
12584
- var pidAlive = (pid) => {
12585
- if (!Number.isSafeInteger(pid) || pid <= 0)
12586
- return false;
12587
- try {
12588
- process.kill(pid, 0);
12589
- return true;
12590
- } catch (error) {
12591
- return error.code === "EPERM";
12592
- }
12593
- };
12594
- var TASK_STORE_LOCK = { reclaimUnverifiedAfterTtl: true };
12595
- var classifyLockOwner = (payload, ageMs, localHost = localLockHost(), options = {}) => {
12596
- const lock = metadataLockSchema.safeParse(payload);
12597
- if (!lock.success)
12598
- return ageMs !== null && ageMs > UNREADABLE_LOCK_TTL_MS ? { state: "stale", reason: "unreadable lock left behind" } : { state: "unknown", reason: "lock is being written" };
12599
- const { pid, processStart, host } = lock.data;
12600
- const [lockName, lockSpace] = host.split("#");
12601
- const [localName, localSpace] = localHost.split("#");
12602
- if (lockSpace && localSpace && lockName === localName) {
12603
- const [lockNs, lockBoot] = lockSpace.split(":");
12604
- const [localNs, localBoot] = localSpace.split(":");
12605
- if (lockNs === localNs && lockNs !== "?" && lockBoot !== "?" && lockBoot !== localBoot)
12606
- return { state: "stale", reason: "lock was taken before this machine rebooted" };
12607
- }
12608
- if (host !== localHost)
12609
- return ageMs !== null && ageMs > FOREIGN_LOCK_TTL_MS ? { state: "stale", reason: `lock from ${host} is older than its TTL` } : { state: "unknown", reason: `lock is held from ${host}; its pid cannot be checked here` };
12610
- if (!pidAlive(pid))
12611
- return { state: "stale", reason: `pid ${pid} is not running` };
12612
- const currentStart = processStartOf(pid);
12613
- if (processStart !== null && currentStart !== null && processStart !== currentStart)
12614
- return { state: "stale", reason: `pid ${pid} now belongs to a different process` };
12615
- if (options.reclaimUnverifiedAfterTtl === true && (processStart === null || currentStart === null) && ageMs !== null && ageMs > FOREIGN_LOCK_TTL_MS)
12616
- return {
12617
- state: "stale",
12618
- reason: `pid ${pid} is running but the lock is older than its TTL and the pid's start time cannot be checked (reused pid)`
12619
- };
12620
- return { state: "live", reason: `held by running pid ${pid}` };
12621
- };
12622
- var ageOf = (file, nowMs) => {
12623
- try {
12624
- return nowMs - fs9.lstatSync(file).mtimeMs;
12625
- } catch {
12626
- return null;
12627
- }
12628
- };
12629
- var clearAbandonedReclaimGuard = (lockPath, nowMs = Date.now()) => {
12630
- const guard = `${lockPath}.reclaim`;
12631
- const age = ageOf(guard, nowMs);
12632
- if (age === null || age <= RECLAIM_GUARD_TTL_MS)
12633
- return false;
12634
- try {
12635
- fs9.rmdirSync(guard);
12636
- return true;
12637
- } catch {
12638
- return false;
12837
+ }
12838
+ // packages/workit-core/src/core/package-root.ts
12839
+ import { existsSync as existsSync3 } from "node:fs";
12840
+ import path13 from "node:path";
12841
+ import { fileURLToPath } from "node:url";
12842
+ var packageRoot = () => {
12843
+ let dir = path13.dirname(fileURLToPath(import.meta.url));
12844
+ while (true) {
12845
+ const parent = path13.dirname(dir);
12846
+ if (existsSync3(path13.join(dir, "package.json")) || parent === dir)
12847
+ return dir;
12848
+ dir = parent;
12639
12849
  }
12640
12850
  };
12641
12851
 
@@ -20027,27 +20237,231 @@ var shellPolicy = (cwd, command, dialect = "posix") => {
20027
20237
  };
20028
20238
  // packages/workit-core/src/hooks/write-gate.ts
20029
20239
  import { realpathSync as realpathSync5 } from "node:fs";
20240
+ import os7 from "node:os";
20241
+ import path23 from "node:path";
20242
+
20243
+ // packages/workit-core/src/core/live-session.ts
20244
+ import { spawnSync as spawnSync13 } from "node:child_process";
20245
+ import { createHash as createHash15 } from "node:crypto";
20246
+ import fs17 from "node:fs";
20247
+ import os6 from "node:os";
20030
20248
  import path22 from "node:path";
20249
+ var LIVE_SESSION_TTL_MS = 15 * 60000;
20250
+ var HEARTBEAT_EVERY_MS = 30000;
20251
+ var liveProbe = {
20252
+ platform: process.platform,
20253
+ run: (args) => spawnSync13("ps", args, { encoding: "utf8", timeout: 2000 }).stdout ?? "",
20254
+ start: (pid) => processStartOf(pid)
20255
+ };
20256
+ var leadOf = (session) => session.split(":")[0].replace(/-w-.*$/su, "") || session;
20257
+ var familyOf = (host) => host.replace(/_(?:cli|desktop)$/u, "");
20258
+ var processAlive = (pid) => {
20259
+ try {
20260
+ process.kill(pid, 0);
20261
+ return true;
20262
+ } catch (error) {
20263
+ return error.code === "EPERM";
20264
+ }
20265
+ };
20266
+ var processInfo = (pid) => {
20267
+ try {
20268
+ if (liveProbe.platform === "linux") {
20269
+ const stat = fs17.readFileSync(`/proc/${pid}/stat`, "utf8");
20270
+ const ppid = Number(stat.slice(stat.lastIndexOf(")") + 2).split(" ")[1]);
20271
+ const command = fs17.readFileSync(`/proc/${pid}/cmdline`, "utf8").replaceAll("\x00", " ");
20272
+ return Number.isInteger(ppid) ? { ppid, command: command.trim() } : null;
20273
+ }
20274
+ if (liveProbe.platform === "win32")
20275
+ return null;
20276
+ const match = /^\s*(\d+)\s+(.*)$/su.exec(liveProbe.run(["-o", "ppid=,command=", "-p", String(pid)]));
20277
+ return match ? { ppid: Number(match[1]), command: match[2].trim() } : null;
20278
+ } catch {
20279
+ return null;
20280
+ }
20281
+ };
20282
+ var WRAPPER = /^(?:\S*\/)?(?:sh|bash|dash|zsh|ash|ksh|fish|env|timeout|npx|npm|bunx|cmd\.exe|pwsh|powershell)(?:\s|$)|launch\.mjs|npx-cli\.js/u;
20283
+ var PIDS = new Map;
20284
+ var hostPid = (transport) => {
20285
+ if (transport !== "hook-process")
20286
+ return process.pid;
20287
+ const key = `${liveProbe.platform}\x00${process.ppid}`;
20288
+ if (PIDS.has(key))
20289
+ return PIDS.get(key) ?? null;
20290
+ if (liveProbe.platform === "win32") {
20291
+ PIDS.set(key, null);
20292
+ return null;
20293
+ }
20294
+ let pid = process.ppid;
20295
+ for (let hop = 0;hop < 4; hop++) {
20296
+ const info = processInfo(pid);
20297
+ if (!info || !WRAPPER.test(info.command) || info.ppid <= 1)
20298
+ break;
20299
+ pid = info.ppid;
20300
+ }
20301
+ PIDS.set(key, pid);
20302
+ return pid;
20303
+ };
20304
+ var sessionSafe = (value) => value.replace(/[^A-Za-z0-9_.:@/+-]/gu, "-");
20305
+ var liveOwner = (input, transport, env = process.env) => {
20306
+ const session = input.session.id?.trim();
20307
+ if (!session)
20308
+ return null;
20309
+ const leads = new Set([leadOf(sessionSafe(session))]);
20310
+ if (input.session.parentId)
20311
+ leads.add(leadOf(sessionSafe(input.session.parentId)));
20312
+ if (env.WORKIT_PI_WORKER_SESSION && env.WORKIT_SESSION_ID)
20313
+ leads.add(leadOf(sessionSafe(env.WORKIT_SESSION_ID)));
20314
+ return {
20315
+ host: input.host,
20316
+ session: sessionSafe(session),
20317
+ leads: [...leads],
20318
+ pid: () => hostPid(transport)
20319
+ };
20320
+ };
20321
+ var checkoutOf = (target) => {
20322
+ let dir = path22.resolve(target);
20323
+ while (!fs17.existsSync(dir) && path22.dirname(dir) !== dir)
20324
+ dir = path22.dirname(dir);
20325
+ if (fs17.existsSync(dir) && !fs17.statSync(dir).isDirectory())
20326
+ dir = path22.dirname(dir);
20327
+ const parts = dir.split(path22.sep);
20328
+ const dotGit = parts.lastIndexOf(".git");
20329
+ if (dotGit > 0)
20330
+ dir = parts.slice(0, dotGit).join(path22.sep) || path22.sep;
20331
+ const location = resolveStore(dir);
20332
+ if (location instanceof Error || !location.shared || !location.top)
20333
+ return null;
20334
+ let top = location.top;
20335
+ try {
20336
+ top = fs17.realpathSync.native(top);
20337
+ } catch {}
20338
+ return top;
20339
+ };
20340
+ var markerFile2 = (top) => path22.join(resolveStateDir(), "live-sessions", `${createHash15("sha256").update(checkoutKey(top)).digest("hex").slice(0, 32)}.json`);
20341
+ var checkoutKey = (top, platform = process.platform) => {
20342
+ let key = path22.resolve(top);
20343
+ try {
20344
+ key = fs17.realpathSync.native(key);
20345
+ } catch {}
20346
+ return platform === "win32" ? key.replaceAll("/", "\\").toLowerCase() : key;
20347
+ };
20348
+ var readMarker = (file) => {
20349
+ try {
20350
+ const value = JSON.parse(fs17.readFileSync(file, "utf8"));
20351
+ if (value?.version !== 1 || typeof value.session !== "string" || typeof value.host !== "string" || typeof value.heartbeatAt !== "string" || Number.isNaN(Date.parse(value.heartbeatAt)))
20352
+ return null;
20353
+ return {
20354
+ version: 1,
20355
+ session: value.session,
20356
+ leads: Array.isArray(value.leads) ? value.leads.filter((item) => typeof item === "string") : [],
20357
+ host: value.host,
20358
+ pid: typeof value.pid === "number" ? value.pid : null,
20359
+ pidStart: typeof value.pidStart === "string" ? value.pidStart : null,
20360
+ machine: typeof value.machine === "string" ? value.machine : "",
20361
+ checkout: typeof value.checkout === "string" ? value.checkout : "",
20362
+ heartbeatAt: value.heartbeatAt
20363
+ };
20364
+ } catch {
20365
+ return null;
20366
+ }
20367
+ };
20368
+ var ageOf2 = (marker, now) => now.getTime() - Date.parse(marker.heartbeatAt);
20369
+ var isLive = (marker, deps) => {
20370
+ const age = ageOf2(marker, deps.now ?? new Date);
20371
+ if (age > (deps.ttlMs ?? LIVE_SESSION_TTL_MS))
20372
+ return false;
20373
+ if (marker.machine !== (deps.machine ?? os6.hostname()) || marker.pid === null)
20374
+ return true;
20375
+ if (!(deps.alive ?? processAlive)(marker.pid))
20376
+ return false;
20377
+ if (marker.pidStart === null)
20378
+ return true;
20379
+ const start = liveProbe.start(marker.pid);
20380
+ return start === null || start === marker.pidStart;
20381
+ };
20382
+ var sameLead = (marker, owner) => marker.session === owner.session || owner.leads.some((lead) => lead === marker.session || marker.leads.includes(lead));
20383
+ var sameOwner = (marker, owner, machine) => {
20384
+ if (sameLead(marker, owner))
20385
+ return true;
20386
+ if (marker.machine !== machine)
20387
+ return false;
20388
+ const pid = owner.pid();
20389
+ if (pid === null || marker.pid === null || marker.pidStart === null)
20390
+ return familyOf(marker.host) === familyOf(owner.host);
20391
+ return marker.pid === pid;
20392
+ };
20393
+ var liveConflict = (target, owner, deps = {}) => {
20394
+ try {
20395
+ const checkout = checkoutOf(target);
20396
+ if (!checkout)
20397
+ return null;
20398
+ const file = markerFile2(checkout);
20399
+ const marker = readMarker(file);
20400
+ if (!marker || sameLead(marker, owner) || !isLive(marker, deps))
20401
+ return null;
20402
+ if (sameOwner(marker, owner, deps.machine ?? os6.hostname()))
20403
+ return null;
20404
+ return { marker, file, ageMs: ageOf2(marker, deps.now ?? new Date), checkout };
20405
+ } catch {
20406
+ return null;
20407
+ }
20408
+ };
20409
+ var claimLiveSession = (target, owner, deps = {}) => {
20410
+ try {
20411
+ const checkout = checkoutOf(target);
20412
+ if (!checkout)
20413
+ return;
20414
+ const file = markerFile2(checkout);
20415
+ const now = deps.now ?? new Date;
20416
+ const machine = deps.machine ?? os6.hostname();
20417
+ const current = readMarker(file);
20418
+ if (current) {
20419
+ const age = ageOf2(current, now);
20420
+ if (sameLead(current, owner) && age >= 0 && age < HEARTBEAT_EVERY_MS)
20421
+ return;
20422
+ if (!sameLead(current, owner) && isLive(current, deps) && !sameOwner(current, owner, machine))
20423
+ return;
20424
+ }
20425
+ const pid = owner.pid();
20426
+ const marker = {
20427
+ version: 1,
20428
+ session: owner.session,
20429
+ leads: owner.leads,
20430
+ host: owner.host,
20431
+ pid,
20432
+ pidStart: pid === null ? null : liveProbe.start(pid),
20433
+ machine,
20434
+ checkout,
20435
+ heartbeatAt: now.toISOString()
20436
+ };
20437
+ fs17.mkdirSync(path22.dirname(file), { recursive: true });
20438
+ const temp = `${file}.${process.pid}.tmp`;
20439
+ fs17.writeFileSync(temp, JSON.stringify(marker));
20440
+ fs17.renameSync(temp, file);
20441
+ } catch {}
20442
+ };
20443
+
20444
+ // packages/workit-core/src/hooks/write-gate.ts
20031
20445
  var NONE3 = { kind: "none" };
20032
20446
  var CASE_INSENSITIVE = process.platform === "darwin" || process.platform === "win32";
20033
20447
  var canonical2 = (file) => {
20034
20448
  let rest = "";
20035
- for (let dir = path22.resolve(file);; dir = path22.dirname(dir)) {
20449
+ for (let dir = path23.resolve(file);; dir = path23.dirname(dir)) {
20036
20450
  try {
20037
- return path22.join(realpathSync5(dir), rest);
20451
+ return path23.join(realpathSync5(dir), rest);
20038
20452
  } catch {
20039
- if (path22.dirname(dir) === dir)
20040
- return path22.resolve(file);
20041
- rest = rest ? path22.join(path22.basename(dir), rest) : path22.basename(dir);
20453
+ if (path23.dirname(dir) === dir)
20454
+ return path23.resolve(file);
20455
+ rest = rest ? path23.join(path23.basename(dir), rest) : path23.basename(dir);
20042
20456
  }
20043
20457
  }
20044
20458
  };
20045
20459
  var exempt = (cwd, root, file, plans) => {
20046
20460
  const fold = (value) => CASE_INSENSITIVE ? value.toLowerCase() : value;
20047
- const relative = path22.relative(fold(canonical2(root)), fold(canonical2(path22.resolve(cwd, file))));
20048
- if (relative.startsWith("..") || path22.isAbsolute(relative))
20461
+ const relative = path23.relative(fold(canonical2(root)), fold(canonical2(path23.resolve(cwd, file))));
20462
+ if (relative.startsWith("..") || path23.isAbsolute(relative))
20049
20463
  return true;
20050
- const posix = relative.split(path22.sep).join("/");
20464
+ const posix = relative.split(path23.sep).join("/");
20051
20465
  const segments = posix.split("/");
20052
20466
  return /\.mdx?$/i.test(posix) || segments[0] === "docs" || segments.includes("plans") || [...plans].some((plan) => fold(plan) === posix);
20053
20467
  };
@@ -20145,7 +20559,7 @@ var shellWrites = (command) => {
20145
20559
  return { writes: true, targets: unknown ? null : targets };
20146
20560
  };
20147
20561
  var cache2 = new Map;
20148
- var planKeyOf = (root, plans) => [...plans].map((file) => fileSignature(path22.resolve(root, file)) ?? "-").join("\x00");
20562
+ var planKeyOf = (root, plans) => [...plans].map((file) => fileSignature(path23.resolve(root, file)) ?? "-").join("\x00");
20149
20563
  var gateFor = (cwd) => {
20150
20564
  const store = new TaskStore(cwd);
20151
20565
  const listed = store.listTaskIndex();
@@ -20183,7 +20597,218 @@ var gateFor = (cwd) => {
20183
20597
  cache2.delete(cache2.keys().next().value);
20184
20598
  return gate;
20185
20599
  };
20186
- function writeGate(cwd, paths) {
20600
+ var MUTATING_GIT = new Set([
20601
+ "add",
20602
+ "am",
20603
+ "apply",
20604
+ "checkout",
20605
+ "cherry-pick",
20606
+ "clean",
20607
+ "commit",
20608
+ "merge",
20609
+ "mv",
20610
+ "pull",
20611
+ "rebase",
20612
+ "reset",
20613
+ "restore",
20614
+ "revert",
20615
+ "rm",
20616
+ "stash",
20617
+ "switch"
20618
+ ]);
20619
+ var DRY_N = new Set(["add", "clean", "rm", "mv"]);
20620
+ var MUTATING_WORKIT_GIT = new Set(["commit", "branch"]);
20621
+ var DIR_TARGET = ".workit-live-target";
20622
+ var OVERRIDE = "WORKIT_LIVE_SESSION=override";
20623
+ var inert = (args, sub) => args.some((arg) => arg === "--help" || arg === "-h" || arg === "--dry-run" || DRY_N.has(sub) && /^-[a-zA-Z]*n[a-zA-Z]*$/.test(arg) && !arg.startsWith("--"));
20624
+ var gitMutationDirs = (cwd, command, dialect) => {
20625
+ let invocations;
20626
+ try {
20627
+ invocations = rawInvocations(command, dialect);
20628
+ } catch {
20629
+ return [];
20630
+ }
20631
+ return invocations.flatMap((invocation) => {
20632
+ if (invocation.tool !== "git" || invocation.dir === undefined)
20633
+ return [];
20634
+ const at = invocation.args.findIndex((arg) => !arg.startsWith("-"));
20635
+ const sub = invocation.args[at] ?? "";
20636
+ const rest = invocation.args.slice(at + 1);
20637
+ if (!MUTATING_GIT.has(sub) || inert(rest, sub))
20638
+ return [];
20639
+ if (sub === "stash" && ["list", "show"].includes(rest[0] ?? ""))
20640
+ return [];
20641
+ return [path23.resolve(cwd, invocation.dir ?? ".")];
20642
+ });
20643
+ };
20644
+ var CD2 = new Set(["cd", "chdir", "set-location", "sl"]);
20645
+ var PUSHD2 = new Set(["pushd", "push-location"]);
20646
+ var POPD2 = new Set(["popd", "pop-location"]);
20647
+ var cdInto = (dir, words) => {
20648
+ const target = words.slice(1).find((word) => !word.startsWith("-") || word === "-");
20649
+ if (dir === undefined || target === "-" || target && /^~|[$`*?]/.test(target))
20650
+ return;
20651
+ return target === undefined ? os7.homedir() : path23.resolve(dir, target);
20652
+ };
20653
+ var quoteWord = (word) => /^[\w@%+=:,./-]+$/.test(word) ? word : `'${word.replaceAll("'", "'\\''")}'`;
20654
+ var segmentTargets = (cwd, command, dialect) => {
20655
+ const targets = [];
20656
+ let dir = cwd;
20657
+ let stack = [];
20658
+ const groups = [];
20659
+ const at = (file) => path23.isAbsolute(file) ? file : dir === undefined ? null : path23.resolve(dir, file);
20660
+ for (const segment of segmentsOf(command, dialect)) {
20661
+ if (segment.group === "open") {
20662
+ groups.push({ dir, stack: [...stack] });
20663
+ continue;
20664
+ }
20665
+ if (segment.group === "close") {
20666
+ const saved = groups.pop();
20667
+ if (saved)
20668
+ ({ dir, stack } = saved);
20669
+ continue;
20670
+ }
20671
+ const lead = segment.words.findIndex((word) => !/^\w+=/.test(word));
20672
+ const words = lead < 0 ? [] : segment.words.slice(lead);
20673
+ const name = (words[0]?.split(/[\\/]/).at(-1) ?? "").toLowerCase();
20674
+ if (CD2.has(name)) {
20675
+ dir = cdInto(dir, words);
20676
+ continue;
20677
+ }
20678
+ if (PUSHD2.has(name)) {
20679
+ stack.push(dir);
20680
+ dir = cdInto(dir, words);
20681
+ continue;
20682
+ }
20683
+ if (POPD2.has(name)) {
20684
+ dir = stack.length ? stack.pop() : undefined;
20685
+ continue;
20686
+ }
20687
+ for (const file of segment.redirects)
20688
+ if (!/^\/dev\/(?:null|stdout|stderr|tty)$/.test(file)) {
20689
+ const resolved = at(file);
20690
+ if (resolved)
20691
+ targets.push(resolved);
20692
+ }
20693
+ const edits = editsOf(segment.words);
20694
+ if (edits === null) {
20695
+ if (dir !== undefined)
20696
+ targets.push(path23.join(dir, DIR_TARGET));
20697
+ } else
20698
+ for (const file of edits) {
20699
+ const resolved = at(file);
20700
+ if (resolved)
20701
+ targets.push(resolved);
20702
+ }
20703
+ if (dir !== undefined && workitInvocations(segment.words.map(quoteWord).join(" "), "posix").some((args) => args[0] === "git" && MUTATING_WORKIT_GIT.has(args[1] ?? "") && !inert(args.slice(2), "")))
20704
+ targets.push(path23.join(dir, DIR_TARGET));
20705
+ }
20706
+ return targets;
20707
+ };
20708
+ var overridden = (command, dialect) => {
20709
+ const segments = segmentsOf(command, dialect);
20710
+ if (segments.length !== 1 || segments[0].group || segments[0].subs?.length)
20711
+ return false;
20712
+ const words = segments[0].words;
20713
+ const lead = words.findIndex((word) => !/^\w+=/.test(word));
20714
+ return lead > 0 && words.slice(0, lead).includes(OVERRIDE);
20715
+ };
20716
+ var HOST_LABELS = {
20717
+ claude_code: "Claude Code",
20718
+ opencode: "OpenCode",
20719
+ codex_cli: "Codex CLI",
20720
+ codex_desktop: "Codex Desktop",
20721
+ cursor: "Cursor",
20722
+ pi: "Pi"
20723
+ };
20724
+ var ageText = (ms) => {
20725
+ const seconds = Math.max(0, Math.round(ms / 1000));
20726
+ return seconds < 120 ? `${seconds}s` : `${Math.round(seconds / 60)} min`;
20727
+ };
20728
+ var liveDeny = ({ marker, checkout, ageMs, file }) => {
20729
+ const unblock = `Work in your own worktree (\`git worktree add <path> -b <branch>\`, or \`workit fanout worktree create <slice>\`). Only if the user confirms that session is finished: release it with \`rm '${file}'\`, or prefix one command with ${OVERRIDE}.`;
20730
+ return {
20731
+ kind: "deny",
20732
+ reason: `workit live-session guard: ${checkout} belongs to another live agent session, ${HOST_LABELS[marker.host] ?? marker.host} session ${marker.session}${marker.pid === null ? "" : ` (pid ${marker.pid})`}, last active ${ageText(ageMs)} ago. Do not edit a checkout another session is working in; read-only commands stay allowed. ${unblock}`,
20733
+ unblock
20734
+ };
20735
+ };
20736
+ var guarded = (targets) => {
20737
+ const seen = new Set;
20738
+ return targets.filter((target) => {
20739
+ const dir = path23.dirname(target);
20740
+ if (seen.has(dir))
20741
+ return false;
20742
+ seen.add(dir);
20743
+ return true;
20744
+ });
20745
+ };
20746
+ var liveGate = (owner, targets, deps) => {
20747
+ try {
20748
+ for (const target of guarded(targets)) {
20749
+ const conflict = liveConflict(target, owner, deps);
20750
+ if (conflict && workspaceLiveSessionGuard(conflict.checkout))
20751
+ return liveDeny(conflict);
20752
+ }
20753
+ } catch {}
20754
+ return NONE3;
20755
+ };
20756
+ var claimAll = (owner, targets, deps) => {
20757
+ for (const target of guarded(targets))
20758
+ claimLiveSession(target, owner, deps);
20759
+ };
20760
+ var liveTargets = (cwd, paths) => paths?.length ? paths.map((file) => path23.resolve(cwd, file)) : [path23.join(cwd, DIR_TARGET)];
20761
+ function writeGate(cwd, paths, owner, deps) {
20762
+ if (!owner)
20763
+ return beforeWriteGate(cwd, paths);
20764
+ const targets = liveTargets(cwd, paths);
20765
+ const live = liveGate(owner, targets, deps);
20766
+ if (live.kind !== "none")
20767
+ return live;
20768
+ const decision = beforeWriteGate(cwd, paths);
20769
+ if (decision.kind === "none")
20770
+ claimAll(owner, targets, deps);
20771
+ return decision;
20772
+ }
20773
+ function shellGate(cwd, command, owner, dialect = "posix", deps) {
20774
+ const writes = shellWrites(command);
20775
+ let targets = [];
20776
+ if (owner) {
20777
+ try {
20778
+ targets = [
20779
+ ...segmentTargets(cwd, command, dialect),
20780
+ ...gitMutationDirs(cwd, command, dialect).map((dir) => path23.join(dir, DIR_TARGET))
20781
+ ];
20782
+ } catch {
20783
+ targets = [];
20784
+ }
20785
+ }
20786
+ if (owner && targets.length) {
20787
+ const live = liveGate(owner, targets, deps);
20788
+ if (live.kind !== "none") {
20789
+ if (!overridden(command, dialect))
20790
+ return live;
20791
+ noteOverride(cwd, owner, command);
20792
+ }
20793
+ }
20794
+ const decision = writes.writes ? beforeWriteGate(cwd, writes.targets) : NONE3;
20795
+ if (owner && targets.length && decision.kind === "none")
20796
+ claimAll(owner, targets, deps);
20797
+ return decision;
20798
+ }
20799
+ var noteOverride = (cwd, owner, command) => {
20800
+ try {
20801
+ if (!inWorkitWorkspace(cwd))
20802
+ return;
20803
+ appendHookObserved(cwd, {
20804
+ type: "live_session.override",
20805
+ actor: { host: owner.host, session: owner.session, agentId: null },
20806
+ session: owner.session,
20807
+ command: command.slice(0, 200)
20808
+ });
20809
+ } catch {}
20810
+ };
20811
+ function beforeWriteGate(cwd, paths) {
20187
20812
  try {
20188
20813
  const gate = gateFor(cwd);
20189
20814
  if (!gate || gate.blockers.length === 0)
@@ -20201,7 +20826,7 @@ function writeGate(cwd, paths) {
20201
20826
  }
20202
20827
  }
20203
20828
  // packages/workit-core/src/hooks/stop.ts
20204
- import { spawnSync as spawnSync13 } from "node:child_process";
20829
+ import { spawnSync as spawnSync14 } from "node:child_process";
20205
20830
  var NONE4 = { kind: "none" };
20206
20831
  var QUESTION = /[??؟](?=[\s*_`"')\]>]*(?:\s|$))|¿/;
20207
20832
  var OFFER = /\b(?:let me know|want me to|should I|shall I|would you like|do you want|if you(?:'d| would) like|may I|can I go ahead)\b/i;
@@ -20222,7 +20847,7 @@ var asksUser = (message) => {
20222
20847
  };
20223
20848
  var short = (sha) => (sha ?? "?").slice(0, 12);
20224
20849
  var gitList = (cwd, args) => {
20225
- const run = spawnSync13("git", args, {
20850
+ const run = spawnSync14("git", args, {
20226
20851
  cwd,
20227
20852
  encoding: "utf8",
20228
20853
  timeout: 3000,
@@ -20313,10 +20938,10 @@ function stopObligation(input, descriptor) {
20313
20938
  const branch = currentBranch(input.cwd);
20314
20939
  if (!branch || guardedBranch(input.cwd, branch))
20315
20940
  return null;
20316
- const ledger = readLedger(input.cwd);
20941
+ const ledger = readLedger(input.cwd, { integrity: true });
20317
20942
  if (!ledger.ok)
20318
20943
  return null;
20319
- const all = ledger.value.rows;
20944
+ const all = ledger.value.rows.filter((row) => row.integrity !== "unverified");
20320
20945
  const rows = all.filter((row) => row.branch === branch);
20321
20946
  const mine = rows.filter((row) => sessionOf(row) === session);
20322
20947
  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"));
@@ -20586,12 +21211,12 @@ ${line}` };
20586
21211
  };
20587
21212
  // packages/workit-core/src/hooks/hosts/fields.ts
20588
21213
  import { existsSync as existsSync7, realpathSync as realpathSync6, statSync as statSync2 } from "node:fs";
20589
- import path23 from "node:path";
21214
+ import path24 from "node:path";
20590
21215
  var isRecord4 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
20591
21216
  var nonEmpty2 = (value) => typeof value === "string" && value.trim() !== "";
20592
21217
  var optionalText = (value) => typeof value === "string" ? value : null;
20593
21218
  var existingDirectory = (value) => {
20594
- if (!nonEmpty2(value) || !path23.isAbsolute(value) || !existsSync7(value))
21219
+ if (!nonEmpty2(value) || !path24.isAbsolute(value) || !existsSync7(value))
20595
21220
  return null;
20596
21221
  try {
20597
21222
  return statSync2(value).isDirectory() ? realpathSync6(value) : null;
@@ -20692,9 +21317,9 @@ ${subagentStartText({ ...input, cwd: "" }, descriptor, event)}`;
20692
21317
  if (role !== "judge" || !event.agentId)
20693
21318
  return readOnly;
20694
21319
  const own = subagentSession(session, event.agentId);
20695
- if (session)
20696
- recordIssuedSession(input.cwd || process.cwd(), session, own);
20697
- return `${readOnly} Its own Workit session is ${own}: record verdicts with \`workit ledger verdict <result> --session ${own} ...\` so the ledger tells it apart from the author.`;
21320
+ const token = session ? recordIssuedSession(input.cwd || process.cwd(), session, own) : null;
21321
+ const flags = token ? `--session ${own} --token ${token}` : `--session ${own}`;
21322
+ return `${readOnly} Its own Workit session is ${own}: record verdicts with \`workit ledger verdict <result> ${flags} ...\` so the ledger tells it apart from the author.${token ? " The token is this subagent's alone: never write it into a report, a file or a commit. --token needs a workit CLI at least as new as this plugin; if the CLI answers that --token is an unknown option, update the workit CLI (workit upgrade --cli) and record again." : ""}`;
20698
21323
  };
20699
21324
  function handleHook(input, deps) {
20700
21325
  const { descriptor } = deps;
@@ -20736,8 +21361,7 @@ function handleHook(input, deps) {
20736
21361
  const raw = canDeny ? rawGitPre(input, event.command, { nudge: false }) : NONE5;
20737
21362
  if (raw.kind === "deny")
20738
21363
  return raw;
20739
- const writes = usable2(descriptor.events["write.pre"].support) ? shellWrites(event.command) : { writes: false, targets: null };
20740
- const gate = writes.writes ? writeGate(input.cwd, writes.targets) : NONE5;
21364
+ const gate = usable2(descriptor.events["write.pre"].support) ? shellGate(input.cwd, event.command, liveOwner(input, descriptor.transport), event.dialect) : NONE5;
20741
21365
  if (gate.kind === "deny")
20742
21366
  return gate;
20743
21367
  noteRawCommit(input, event.command, postTool ? callKey(event.toolUseId, event.command) : NEXT_COMMAND);
@@ -20750,7 +21374,7 @@ function handleHook(input, deps) {
20750
21374
  rawGitPost(input, event.command, callKey(event.toolUseId, event.command));
20751
21375
  return NONE5;
20752
21376
  case "write.pre":
20753
- return usable2(descriptor.events["write.pre"].support) ? writeGate(input.cwd, event.paths) : NONE5;
21377
+ return usable2(descriptor.events["write.pre"].support) ? writeGate(input.cwd, event.paths, liveOwner(input, descriptor.transport)) : NONE5;
20754
21378
  case "subagent.start":
20755
21379
  return {
20756
21380
  kind: "context",
@@ -21329,8 +21953,8 @@ var codexAdapter = {
21329
21953
  };
21330
21954
 
21331
21955
  // packages/workit-core/src/hooks/hosts/cursor.ts
21332
- import fs17 from "node:fs";
21333
- import path24 from "node:path";
21956
+ import fs18 from "node:fs";
21957
+ import path25 from "node:path";
21334
21958
  var undocumented = { support: "undocumented", native: null };
21335
21959
  var none = { support: "none", native: null };
21336
21960
  var CURSOR_DESCRIPTOR = {
@@ -21527,17 +22151,17 @@ var protocolEvent2 = (input) => {
21527
22151
  };
21528
22152
  var TRANSCRIPT_TAIL_BYTES = 256 * 1024;
21529
22153
  var lastAssistantText = (file) => {
21530
- if (!file || !path24.isAbsolute(file))
22154
+ if (!file || !path25.isAbsolute(file))
21531
22155
  return null;
21532
22156
  try {
21533
- const size = fs17.statSync(file).size;
22157
+ const size = fs18.statSync(file).size;
21534
22158
  const start = Math.max(0, size - TRANSCRIPT_TAIL_BYTES);
21535
22159
  const buffer = Buffer.alloc(size - start);
21536
- const fd = fs17.openSync(file, "r");
22160
+ const fd = fs18.openSync(file, "r");
21537
22161
  try {
21538
- fs17.readSync(fd, buffer, 0, buffer.length, start);
22162
+ fs18.readSync(fd, buffer, 0, buffer.length, start);
21539
22163
  } finally {
21540
- fs17.closeSync(fd);
22164
+ fs18.closeSync(fd);
21541
22165
  }
21542
22166
  const lines = buffer.toString("utf8").split(`
21543
22167
  `);
@@ -21564,7 +22188,7 @@ var lastAssistantText = (file) => {
21564
22188
  return null;
21565
22189
  }
21566
22190
  };
21567
- var within = (root, dir) => dir === root || dir.startsWith(root.endsWith(path24.sep) ? root : `${root}${path24.sep}`);
22191
+ var within = (root, dir) => dir === root || dir.startsWith(root.endsWith(path25.sep) ? root : `${root}${path25.sep}`);
21568
22192
  var hookCwd = (input) => {
21569
22193
  const cwd = existingDirectory(input.cwd);
21570
22194
  if (cwd && input.hook_event_name === "beforeShellExecution")