@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.
- package/.cursor-plugin/plugin.json +1 -1
- package/dist/cursor-session-start.js +2310 -1722
- package/dist/mcp-server.js +639 -369
- package/dist/workit-hook.js +1587 -963
- package/package.json +4 -4
- package/skills/workit-fanout/SKILL.md +4 -4
- package/skills/workit-fanout/references/orchestration.md +8 -5
- package/skills/workit-review/SKILL.md +1 -1
package/dist/workit-hook.js
CHANGED
|
@@ -8561,242 +8561,666 @@ function pushRemoteName(cwd, branch) {
|
|
|
8561
8561
|
}
|
|
8562
8562
|
|
|
8563
8563
|
// packages/workit-core/src/ledger.ts
|
|
8564
|
-
import { spawnSync as
|
|
8565
|
-
import { createHash as createHash7, randomBytes } from "node:crypto";
|
|
8566
|
-
import
|
|
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/
|
|
8570
|
-
import
|
|
8571
|
-
import
|
|
8572
|
-
import {
|
|
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/
|
|
8575
|
-
import {
|
|
8576
|
-
|
|
8577
|
-
|
|
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
|
-
|
|
8588
|
-
|
|
8589
|
-
|
|
8590
|
-
|
|
8591
|
-
|
|
8592
|
-
|
|
8593
|
-
|
|
8594
|
-
|
|
8595
|
-
|
|
8596
|
-
|
|
8597
|
-
|
|
8598
|
-
|
|
8599
|
-
|
|
8600
|
-
}
|
|
8601
|
-
|
|
8602
|
-
|
|
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
|
-
|
|
8625
|
-
|
|
8626
|
-
|
|
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
|
|
8630
|
-
var
|
|
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
|
|
8606
|
+
return fs2.realpathSync.native(file);
|
|
8687
8607
|
} catch {
|
|
8688
|
-
return
|
|
8608
|
+
return file;
|
|
8689
8609
|
}
|
|
8690
8610
|
};
|
|
8691
|
-
var
|
|
8692
|
-
|
|
8693
|
-
|
|
8694
|
-
|
|
8695
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
8756
|
-
|
|
8757
|
-
|
|
8758
|
-
|
|
8759
|
-
|
|
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
|
-
|
|
8796
|
-
|
|
8797
|
-
|
|
8798
|
-
|
|
8799
|
-
|
|
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 =
|
|
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" && (
|
|
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) =>
|
|
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 =
|
|
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 =
|
|
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 &&
|
|
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() || (
|
|
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 =
|
|
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
|
|
9677
|
+
var inheritedSetting = (cwd, dir, has) => {
|
|
9252
9678
|
let matched;
|
|
9253
9679
|
try {
|
|
9254
9680
|
matched = matchRuntimeWorkspace(cwd, dir);
|
|
9255
9681
|
} catch {
|
|
9256
|
-
return
|
|
9682
|
+
return null;
|
|
9257
9683
|
}
|
|
9258
9684
|
if (!matched)
|
|
9259
|
-
return
|
|
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
|
-
|
|
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:
|
|
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
|
|
9310
|
-
import { spawnSync as
|
|
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 =
|
|
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 =
|
|
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 = () =>
|
|
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 =
|
|
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
|
|
9924
|
-
|
|
10161
|
+
var readVcsConfig = () => {
|
|
10162
|
+
const file = vcsConfigPath();
|
|
10163
|
+
let raw;
|
|
9925
10164
|
try {
|
|
9926
|
-
|
|
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
|
-
|
|
10171
|
+
parsed = JSON.parse(raw);
|
|
9941
10172
|
} catch {
|
|
9942
|
-
return
|
|
10173
|
+
return { status: "malformed", path: file, config: {}, error: `${file} is not valid JSON` };
|
|
9943
10174
|
}
|
|
9944
|
-
|
|
9945
|
-
|
|
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
|
-
|
|
9953
|
-
|
|
9954
|
-
|
|
9955
|
-
|
|
9956
|
-
|
|
9957
|
-
|
|
9958
|
-
|
|
9959
|
-
|
|
9960
|
-
|
|
9961
|
-
|
|
9962
|
-
|
|
9963
|
-
|
|
9964
|
-
|
|
9965
|
-
|
|
9966
|
-
|
|
9967
|
-
|
|
9968
|
-
|
|
9969
|
-
|
|
9970
|
-
|
|
9971
|
-
|
|
9972
|
-
|
|
9973
|
-
|
|
9974
|
-
|
|
9975
|
-
|
|
9976
|
-
|
|
9977
|
-
|
|
9978
|
-
|
|
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
|
-
|
|
10017
|
-
|
|
10018
|
-
|
|
10019
|
-
|
|
10020
|
-
|
|
10021
|
-
|
|
10022
|
-
|
|
10023
|
-
|
|
10024
|
-
|
|
10025
|
-
|
|
10026
|
-
|
|
10027
|
-
|
|
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
|
-
|
|
10040
|
-
|
|
10041
|
-
|
|
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 (
|
|
10045
|
-
return {
|
|
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
|
-
|
|
10106
|
-
|
|
10107
|
-
|
|
10108
|
-
|
|
10109
|
-
|
|
10110
|
-
|
|
10111
|
-
|
|
10112
|
-
|
|
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
|
-
|
|
10117
|
-
|
|
10118
|
-
|
|
10119
|
-
|
|
10120
|
-
|
|
10121
|
-
|
|
10122
|
-
|
|
10123
|
-
|
|
10124
|
-
|
|
10125
|
-
|
|
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
|
-
|
|
10134
|
-
|
|
10135
|
-
|
|
10136
|
-
|
|
10137
|
-
|
|
10138
|
-
|
|
10139
|
-
|
|
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
|
|
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
|
-
|
|
10167
|
-
|
|
10168
|
-
|
|
10169
|
-
|
|
10170
|
-
|
|
10171
|
-
|
|
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/
|
|
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 (!
|
|
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 (!
|
|
10385
|
+
if (!fs5.statSync(from).isDirectory())
|
|
10224
10386
|
continue;
|
|
10225
10387
|
const to = path7.join(userIssuedRoot(), lead);
|
|
10226
|
-
|
|
10227
|
-
for (const id of
|
|
10228
|
-
|
|
10229
|
-
|
|
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
|
|
10405
|
+
return fs5.readdirSync(dir);
|
|
10239
10406
|
} catch {
|
|
10240
10407
|
return [];
|
|
10241
10408
|
}
|
|
10242
10409
|
};
|
|
10243
10410
|
var removeIfEmpty = (dir) => {
|
|
10244
10411
|
try {
|
|
10245
|
-
|
|
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
|
-
|
|
10255
|
-
|
|
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:
|
|
10259
|
-
for (const { name } of byAge.slice(0, entries.length - ISSUED_PER_LEAD))
|
|
10260
|
-
|
|
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 =
|
|
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)}-${
|
|
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(
|
|
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
|
|
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() -
|
|
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
|
-
|
|
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}-${
|
|
10442
|
-
|
|
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-${
|
|
10665
|
+
const tomb = `${lock}.release-${randomBytes2(6).toString("hex")}`;
|
|
10449
10666
|
try {
|
|
10450
|
-
|
|
10667
|
+
fs6.renameSync(lock, tomb);
|
|
10451
10668
|
} catch {
|
|
10452
10669
|
return false;
|
|
10453
10670
|
}
|
|
10454
10671
|
if (lockToken(tomb) !== token) {
|
|
10455
10672
|
try {
|
|
10456
|
-
|
|
10673
|
+
fs6.renameSync(tomb, lock);
|
|
10457
10674
|
} catch {}
|
|
10458
10675
|
return false;
|
|
10459
10676
|
}
|
|
10460
|
-
|
|
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-${
|
|
10682
|
+
const tomb = `${lock}.reap-${randomBytes2(6).toString("hex")}`;
|
|
10466
10683
|
try {
|
|
10467
|
-
|
|
10684
|
+
fs6.renameSync(lock, tomb);
|
|
10468
10685
|
} catch {
|
|
10469
10686
|
return false;
|
|
10470
10687
|
}
|
|
10471
10688
|
if (lockToken(tomb) === observed) {
|
|
10472
|
-
|
|
10689
|
+
fs6.rmSync(tomb, { recursive: true, force: true });
|
|
10473
10690
|
return true;
|
|
10474
10691
|
}
|
|
10475
10692
|
try {
|
|
10476
|
-
|
|
10693
|
+
fs6.renameSync(tomb, lock);
|
|
10477
10694
|
} catch {
|
|
10478
|
-
|
|
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
|
-
|
|
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 =
|
|
10766
|
+
const fd = fs6.openSync(file, "a+", 384);
|
|
10544
10767
|
try {
|
|
10545
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
10788
|
+
fs6.closeSync(fd);
|
|
10564
10789
|
}
|
|
10565
10790
|
};
|
|
10566
10791
|
try {
|
|
10567
|
-
|
|
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
|
-
|
|
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.
|
|
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,
|
|
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
|
-
|
|
10861
|
-
|
|
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,
|
|
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,
|
|
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 =
|
|
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 &&
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
12335
|
+
import fs8 from "node:fs";
|
|
11986
12336
|
function resolveReadOpenFlags(options) {
|
|
11987
|
-
const constants = options?.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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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" ?
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
12713
|
+
const noFollow = process.platform !== "win32" && typeof fs9.constants.O_NOFOLLOW === "number" ? fs9.constants.O_NOFOLLOW : 0;
|
|
12364
12714
|
try {
|
|
12365
|
-
fd =
|
|
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
|
-
|
|
12371
|
-
|
|
12720
|
+
fs9.writeFileSync(fd, raw, "utf8");
|
|
12721
|
+
fs9.fsyncSync(fd);
|
|
12372
12722
|
const snapshot = {
|
|
12373
12723
|
raw,
|
|
12374
12724
|
payload,
|
|
12375
|
-
stat:
|
|
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:
|
|
12411
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
12564
|
-
|
|
12565
|
-
|
|
12566
|
-
|
|
12567
|
-
|
|
12568
|
-
|
|
12569
|
-
|
|
12570
|
-
|
|
12571
|
-
|
|
12572
|
-
|
|
12573
|
-
|
|
12574
|
-
|
|
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 =
|
|
20449
|
+
for (let dir = path23.resolve(file);; dir = path23.dirname(dir)) {
|
|
20036
20450
|
try {
|
|
20037
|
-
return
|
|
20451
|
+
return path23.join(realpathSync5(dir), rest);
|
|
20038
20452
|
} catch {
|
|
20039
|
-
if (
|
|
20040
|
-
return
|
|
20041
|
-
rest = rest ?
|
|
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 =
|
|
20048
|
-
if (relative.startsWith("..") ||
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
|
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) || !
|
|
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
|
-
|
|
20696
|
-
|
|
20697
|
-
return `${readOnly} Its own Workit session is ${own}: record verdicts with \`workit ledger verdict <result>
|
|
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
|
|
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
|
|
21333
|
-
import
|
|
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 || !
|
|
22154
|
+
if (!file || !path25.isAbsolute(file))
|
|
21531
22155
|
return null;
|
|
21532
22156
|
try {
|
|
21533
|
-
const 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 =
|
|
22160
|
+
const fd = fs18.openSync(file, "r");
|
|
21537
22161
|
try {
|
|
21538
|
-
|
|
22162
|
+
fs18.readSync(fd, buffer, 0, buffer.length, start);
|
|
21539
22163
|
} finally {
|
|
21540
|
-
|
|
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(
|
|
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")
|