@brainervirus/workit-cursor 8.13.0 → 8.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cursor-plugin/plugin.json +1 -1
- package/dist/cursor-session-start.js +1837 -1654
- package/dist/mcp-server.js +592 -369
- package/dist/workit-hook.js +1215 -1022
- package/package.json +4 -4
- package/skills/workit-fanout/references/orchestration.md +4 -3
- 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"],
|
|
@@ -8971,7 +9395,7 @@ var workspacesFileSchema = object({ workspaces: array(workspaceConfigSchema).opt
|
|
|
8971
9395
|
names.add(workspace.name);
|
|
8972
9396
|
}
|
|
8973
9397
|
});
|
|
8974
|
-
var workspacesRevision = (contents) =>
|
|
9398
|
+
var workspacesRevision = (contents) => createHash4("sha256").update(contents ?? "<missing>").digest("hex");
|
|
8975
9399
|
var validateWorkspacesDocument = (parsed, file) => {
|
|
8976
9400
|
if (!isConfigObject(parsed)) {
|
|
8977
9401
|
return { status: "malformed", path: file, entries: [], error: `${file} is not a JSON object` };
|
|
@@ -9009,7 +9433,7 @@ var validateWorkspacesDocument = (parsed, file) => {
|
|
|
9009
9433
|
};
|
|
9010
9434
|
};
|
|
9011
9435
|
var readWorkspacesResult = (dir = configDir()) => {
|
|
9012
|
-
const file =
|
|
9436
|
+
const file = path4.join(dir, "workspaces.json");
|
|
9013
9437
|
let raw;
|
|
9014
9438
|
try {
|
|
9015
9439
|
raw = readFileSync2(file, "utf8");
|
|
@@ -9108,7 +9532,7 @@ var mainCheckoutOf = (cwd) => {
|
|
|
9108
9532
|
for (const name of ["GIT_DIR", "GIT_WORK_TREE", "GIT_COMMON_DIR", "GIT_INDEX_FILE"])
|
|
9109
9533
|
delete env[name];
|
|
9110
9534
|
const git = (args) => {
|
|
9111
|
-
const run =
|
|
9535
|
+
const run = spawnSync5("git", args, {
|
|
9112
9536
|
cwd,
|
|
9113
9537
|
env,
|
|
9114
9538
|
encoding: "utf8",
|
|
@@ -9120,9 +9544,9 @@ var mainCheckoutOf = (cwd) => {
|
|
|
9120
9544
|
let main = null;
|
|
9121
9545
|
const dirs = git(["rev-parse", "--path-format=absolute", "--git-dir", "--git-common-dir"]);
|
|
9122
9546
|
const [gitDir, common] = (dirs ?? "").trim().split(/\r?\n/u);
|
|
9123
|
-
if (gitDir && common &&
|
|
9547
|
+
if (gitDir && common && path4.resolve(gitDir) !== path4.resolve(common)) {
|
|
9124
9548
|
const listed = /^worktree (.+)$/mu.exec(git(["worktree", "list", "--porcelain"]) ?? "")?.[1];
|
|
9125
|
-
main = listed?.trim() || (
|
|
9549
|
+
main = listed?.trim() || (path4.basename(common) === ".git" ? path4.dirname(common) : common);
|
|
9126
9550
|
}
|
|
9127
9551
|
mainCheckouts.set(cwd, main);
|
|
9128
9552
|
return main;
|
|
@@ -9213,7 +9637,7 @@ var runtimeWorkspaceIndexSchema = object({
|
|
|
9213
9637
|
workspaces: array(object({ name: nonBlank, glob: nonBlank }).passthrough()).optional()
|
|
9214
9638
|
}).passthrough();
|
|
9215
9639
|
var matchRuntimeWorkspace = (cwd, dir = configDir()) => {
|
|
9216
|
-
const file =
|
|
9640
|
+
const file = path4.join(dir, "workspaces.json");
|
|
9217
9641
|
let raw;
|
|
9218
9642
|
try {
|
|
9219
9643
|
raw = readFileSync2(file, "utf8");
|
|
@@ -9306,8 +9730,8 @@ function workspaceDefaultTarget(cwd, globalDefault) {
|
|
|
9306
9730
|
}
|
|
9307
9731
|
|
|
9308
9732
|
// packages/workit-core/src/core/release-tracks.ts
|
|
9309
|
-
import { createHash as
|
|
9310
|
-
import { spawnSync as
|
|
9733
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
9734
|
+
import { spawnSync as spawnSync6 } from "node:child_process";
|
|
9311
9735
|
class ReleaseTrackError extends Error {
|
|
9312
9736
|
}
|
|
9313
9737
|
var KNOWN_FIELDS = new Set([
|
|
@@ -9573,603 +9997,323 @@ function resolveReleaseTrack(input) {
|
|
|
9573
9997
|
return fallback(`no track branch could be compared with ${branch} (fetch the track branches)`);
|
|
9574
9998
|
const ancestry = new Map;
|
|
9575
9999
|
const descends = (older, newer) => {
|
|
9576
|
-
if (older === newer)
|
|
9577
|
-
return false;
|
|
9578
|
-
const key = `${older}>${newer}`;
|
|
9579
|
-
if (!ancestry.has(key))
|
|
9580
|
-
ancestry.set(key, input.probe.isAncestor(older, newer));
|
|
9581
|
-
return ancestry.get(key);
|
|
9582
|
-
};
|
|
9583
|
-
const winners = forks.filter((fork) => forks.every((other) => other.track === fork.track || descends(other.sha, fork.sha)));
|
|
9584
|
-
const winningTracks = [...new Set(winners.map((fork) => fork.track))];
|
|
9585
|
-
if (winningTracks.length === 1 && forks.some((fork) => fork.track !== winningTracks[0])) {
|
|
9586
|
-
const win = winners[0];
|
|
9587
|
-
return done(win.track, "ancestry", `${branch} forked from ${win.ref} at ${win.sha.slice(0, 12)}, after every other line's fork point`);
|
|
9588
|
-
}
|
|
9589
|
-
if (winningTracks.length === 1)
|
|
9590
|
-
return fallback(`only ${label(winningTracks[0])} could be compared with ${branch}`);
|
|
9591
|
-
const newest = forks.filter((fork) => !forks.some((other) => descends(fork.sha, other.sha)));
|
|
9592
|
-
return fallback(`${branch} has the same or unrelated fork points with ${[...new Set(newest.map((fork) => `${fork.ref} (${fork.track.name === IMPLICIT ? "workspace default" : fork.track.name})`))].join(" and ")}`);
|
|
9593
|
-
}
|
|
9594
|
-
var git2 = (cwd, args) => {
|
|
9595
|
-
const run = spawnSync4("git", args, {
|
|
9596
|
-
cwd,
|
|
9597
|
-
encoding: "utf8",
|
|
9598
|
-
timeout: 20000,
|
|
9599
|
-
windowsHide: true,
|
|
9600
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
9601
|
-
});
|
|
9602
|
-
return run.status === 0 ? (run.stdout ?? "").trim() : null;
|
|
9603
|
-
};
|
|
9604
|
-
var recordedBaseKey = (branch) => `branch.${branch}.workitBase`;
|
|
9605
|
-
var escapeRegExp = (text) => text.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
9606
|
-
var reflog = (cwd, ref) => (git2(cwd, ["log", "-g", "--date=unix", "--format=%H%x09%gd%x09%gs", ref, "--"]) ?? "").split(`
|
|
9607
|
-
`).map((line) => {
|
|
9608
|
-
const [sha = "", selector = "", ...subject] = line.split("\t");
|
|
9609
|
-
return {
|
|
9610
|
-
sha,
|
|
9611
|
-
time: Number(/@\{(\d+)\}$/u.exec(selector)?.[1] ?? Number.NaN),
|
|
9612
|
-
subject: subject.join("\t")
|
|
9613
|
-
};
|
|
9614
|
-
}).filter((entry) => entry.sha && Number.isFinite(entry.time));
|
|
9615
|
-
function gitTrackProbe(cwd) {
|
|
9616
|
-
let remote;
|
|
9617
|
-
let remotes;
|
|
9618
|
-
const tip = (branch) => {
|
|
9619
|
-
remote ??= pushRemoteName(cwd) ?? "origin";
|
|
9620
|
-
return resolveRef(cwd, `refs/remotes/${remote}/${branch}`) ?? resolveRef(cwd, `refs/heads/${branch}`);
|
|
9621
|
-
};
|
|
9622
|
-
return {
|
|
9623
|
-
remotes: () => remotes ??= remoteNames(cwd),
|
|
9624
|
-
recordedBase: (branch) => git2(cwd, ["config", "--get", recordedBaseKey(branch)]) || null,
|
|
9625
|
-
createdFrom: (branch) => {
|
|
9626
|
-
const log = git2(cwd, ["reflog", "show", "--format=%gs", `refs/heads/${branch}`, "--"]);
|
|
9627
|
-
const oldest = log?.split(`
|
|
9628
|
-
`).findLast(Boolean) ?? "";
|
|
9629
|
-
const match = /^branch: Created from (\S+)$/u.exec(oldest);
|
|
9630
|
-
return match && match[1] !== "HEAD" ? match[1] : null;
|
|
9631
|
-
},
|
|
9632
|
-
checkedOutFrom: (branch) => {
|
|
9633
|
-
const own = reflog(cwd, `refs/heads/${branch}`);
|
|
9634
|
-
const created = own.at(-1);
|
|
9635
|
-
if (!created || created.subject !== "branch: Created from HEAD")
|
|
9636
|
-
return null;
|
|
9637
|
-
const pattern = new RegExp(`^checkout: moving from (\\S+) to ${escapeRegExp(branch)}$`, "u");
|
|
9638
|
-
const entry = reflog(cwd, "HEAD").findLast((item) => item.time >= created.time && item.sha === created.sha && pattern.test(item.subject));
|
|
9639
|
-
return entry ? pattern.exec(entry.subject)?.[1] ?? null : null;
|
|
9640
|
-
},
|
|
9641
|
-
mergeBase: (branch, trackBranch) => {
|
|
9642
|
-
const base = tip(trackBranch);
|
|
9643
|
-
return base && resolveRef(cwd, `refs/heads/${branch}`) ? mergeBase(cwd, base, `refs/heads/${branch}`) : null;
|
|
9644
|
-
},
|
|
9645
|
-
isAncestor: (ancestor, descendant) => git2(cwd, ["merge-base", "--is-ancestor", ancestor, descendant]) !== null
|
|
9646
|
-
};
|
|
9647
|
-
}
|
|
9648
|
-
function workspaceReleaseTracks(cwd) {
|
|
9649
|
-
const raw = resolveRuntimeReleaseTracks(cwd);
|
|
9650
|
-
return { workspace: raw?.workspace ?? null, ...readReleaseTracks(raw?.releaseTracks) };
|
|
9651
|
-
}
|
|
9652
|
-
var memo2 = new Map;
|
|
9653
|
-
var MEMO_LIMIT = 64;
|
|
9654
|
-
function resolveTrackFor(cwd, options) {
|
|
9655
|
-
const read = workspaceReleaseTracks(cwd);
|
|
9656
|
-
if (read.error)
|
|
9657
|
-
return { status: "invalid", error: read.error, issues: read.issues };
|
|
9658
|
-
if (read.tracks.length === 0 && !options.track?.trim())
|
|
9659
|
-
return { status: "none", issues: read.issues };
|
|
9660
|
-
const branch = options.branch === undefined ? currentBranch(cwd) : options.branch;
|
|
9661
|
-
const envRequested = process.env.WORKFLOW_RELEASE_TRACK?.trim() || null;
|
|
9662
|
-
const input = {
|
|
9663
|
-
tracks: read.tracks,
|
|
9664
|
-
requested: options.track ?? null,
|
|
9665
|
-
envRequested,
|
|
9666
|
-
branch,
|
|
9667
|
-
defaultBranch: options.defaultBranch,
|
|
9668
|
-
issues: read.issues
|
|
9669
|
-
};
|
|
9670
|
-
let key = null;
|
|
9671
|
-
if (!options.probe) {
|
|
9672
|
-
const refs = git2(cwd, [
|
|
9673
|
-
"for-each-ref",
|
|
9674
|
-
"--format=%(refname) %(objectname)",
|
|
9675
|
-
"refs/heads",
|
|
9676
|
-
"refs/remotes"
|
|
9677
|
-
]) ?? "";
|
|
9678
|
-
const head = git2(cwd, ["reflog", "show", "-n", "1", "--format=%H %gs", "HEAD", "--"]) ?? "";
|
|
9679
|
-
const own = branch ? [
|
|
9680
|
-
git2(cwd, [
|
|
9681
|
-
"log",
|
|
9682
|
-
"-g",
|
|
9683
|
-
"-n",
|
|
9684
|
-
"1",
|
|
9685
|
-
"--date=unix",
|
|
9686
|
-
"--format=%H %gd %gs",
|
|
9687
|
-
`refs/heads/${branch}`,
|
|
9688
|
-
"--"
|
|
9689
|
-
]) ?? "",
|
|
9690
|
-
git2(cwd, ["config", "--get", recordedBaseKey(branch)]) ?? ""
|
|
9691
|
-
] : [];
|
|
9692
|
-
key = createHash4("sha256").update(JSON.stringify({ cwd, input, refs, head, own })).digest("hex");
|
|
9693
|
-
const hit = memo2.get(key);
|
|
9694
|
-
if (hit)
|
|
9695
|
-
return hit;
|
|
9696
|
-
}
|
|
9697
|
-
const resolution = resolveReleaseTrack({ ...input, probe: options.probe ?? gitTrackProbe(cwd) });
|
|
9698
|
-
if (key) {
|
|
9699
|
-
if (memo2.size >= MEMO_LIMIT)
|
|
9700
|
-
memo2.delete(memo2.keys().next().value);
|
|
9701
|
-
memo2.set(key, resolution);
|
|
9702
|
-
}
|
|
9703
|
-
return resolution;
|
|
9704
|
-
}
|
|
9705
|
-
|
|
9706
|
-
// packages/workit-core/src/core/vcs-config.ts
|
|
9707
|
-
var workspacesPath = () => path4.join(configDir(), "workspaces.json");
|
|
9708
|
-
var vcsCwd = (cwd) => cwd ?? process.env.WORKFLOW_WORKSPACE_ROOT ?? process.cwd();
|
|
9709
|
-
var remoteProvider = (cwd) => {
|
|
9710
|
-
const r = spawnSync5("git", ["remote", "get-url", "origin"], { cwd, encoding: "utf8" });
|
|
9711
|
-
if (r.status !== 0)
|
|
9712
|
-
return null;
|
|
9713
|
-
const url = (r.stdout ?? "").trim();
|
|
9714
|
-
if (/github\.com[:/]/.test(url))
|
|
9715
|
-
return "github";
|
|
9716
|
-
if (/gitlab\.com[:/]/.test(url))
|
|
9717
|
-
return "gitlab";
|
|
9718
|
-
return null;
|
|
9719
|
-
};
|
|
9720
|
-
var readVcsConfig = () => {
|
|
9721
|
-
const file = vcsConfigPath();
|
|
9722
|
-
let raw;
|
|
9723
|
-
try {
|
|
9724
|
-
raw = fs2.readFileSync(file, "utf8");
|
|
9725
|
-
} catch {
|
|
9726
|
-
return { status: "missing", path: file, config: {} };
|
|
9727
|
-
}
|
|
9728
|
-
let parsed;
|
|
9729
|
-
try {
|
|
9730
|
-
parsed = JSON.parse(raw);
|
|
9731
|
-
} catch {
|
|
9732
|
-
return { status: "malformed", path: file, config: {}, error: `${file} is not valid JSON` };
|
|
9733
|
-
}
|
|
9734
|
-
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
9735
|
-
return { status: "valid", path: file, config: parsed };
|
|
9736
|
-
}
|
|
9737
|
-
return { status: "malformed", path: file, config: {}, error: `${file} is not a JSON object` };
|
|
9738
|
-
};
|
|
9739
|
-
var trackSummary = (resolution) => resolution.status === "resolved" ? {
|
|
9740
|
-
name: resolution.track?.name ?? null,
|
|
9741
|
-
source: resolution.source,
|
|
9742
|
-
detail: resolution.detail,
|
|
9743
|
-
warnings: resolution.warnings,
|
|
9744
|
-
track: resolution.track,
|
|
9745
|
-
tracks: resolution.tracks,
|
|
9746
|
-
blocking: resolution.blocking
|
|
9747
|
-
} : null;
|
|
9748
|
-
function vcsConfig(mode, cwd, options = {}) {
|
|
9749
|
-
const ws = resolveRuntimeWorkspaceVcs(vcsCwd(cwd));
|
|
9750
|
-
const wsVcs = ws?.vcs ?? {};
|
|
9751
|
-
const wsYt = ws?.youtrack ?? {};
|
|
9752
|
-
const wsIssues = ws?.issues ?? {};
|
|
9753
|
-
const { status: cfgStatus, path: cfgPath, config: cfg, error: cfgError } = readVcsConfig();
|
|
9754
|
-
const root = vcsCwd(cwd);
|
|
9755
|
-
const determinateRoot = cwd !== undefined || process.env.WORKFLOW_WORKSPACE_ROOT !== undefined;
|
|
9756
|
-
const rawProvider = wsVcs.provider ?? (determinateRoot ? remoteProvider(root) : null) ?? cfg.provider ?? null;
|
|
9757
|
-
const provider = rawProvider === null || rawProvider === undefined ? null : String(rawProvider).toLowerCase() || null;
|
|
9758
|
-
const workspaceDefault = workspaceDefaultTarget(root, cfg.defaultTargetBranch);
|
|
9759
|
-
const linkIssues = typeof wsYt.link_issues === "boolean" ? wsYt.link_issues : null;
|
|
9760
|
-
const youtrackBaseUrl = typeof wsYt.baseUrl === "string" ? wsYt.baseUrl : null;
|
|
9761
|
-
let issuesProvider = null;
|
|
9762
|
-
let linkOnPr = null;
|
|
9763
|
-
if (provider === "github" && typeof wsIssues.provider === "string" && wsIssues.provider.toLowerCase() === "github") {
|
|
9764
|
-
issuesProvider = "github";
|
|
9765
|
-
linkOnPr = typeof wsIssues.link_on_pr === "boolean" ? wsIssues.link_on_pr : null;
|
|
9766
|
-
}
|
|
9767
|
-
if (mode === "resolve") {
|
|
9768
|
-
if (cfgStatus === "malformed")
|
|
9769
|
-
return { ok: false, error: cfgError, configPath: cfgPath };
|
|
9770
|
-
const trackResolution = resolveTrackFor(root, {
|
|
9771
|
-
track: options.track,
|
|
9772
|
-
...options.branch !== undefined ? { branch: options.branch } : {},
|
|
9773
|
-
defaultBranch: workspaceDefault
|
|
9774
|
-
});
|
|
9775
|
-
if (trackResolution.status === "invalid")
|
|
9776
|
-
return { ok: false, error: trackResolution.error, configPath: workspacesPath() };
|
|
9777
|
-
const releaseTrack = trackSummary(trackResolution);
|
|
9778
|
-
const defaultTarget = releaseTrack?.track?.pullRequestTarget ?? workspaceDefault;
|
|
9779
|
-
const baseBranch = releaseTrack?.track?.baseBranch ?? defaultTarget;
|
|
9780
|
-
return {
|
|
9781
|
-
ok: true,
|
|
9782
|
-
workspace_name: ws?.name ?? null,
|
|
9783
|
-
provider,
|
|
9784
|
-
defaultTargetBranch: defaultTarget,
|
|
9785
|
-
workspaceDefaultTargetBranch: workspaceDefault,
|
|
9786
|
-
baseBranch,
|
|
9787
|
-
releaseTrack,
|
|
9788
|
-
link_issues: linkIssues,
|
|
9789
|
-
youtrack_base_url: youtrackBaseUrl,
|
|
9790
|
-
issues_provider: issuesProvider,
|
|
9791
|
-
link_on_pr: linkOnPr
|
|
9792
|
-
};
|
|
9793
|
-
}
|
|
9794
|
-
if (cfgStatus === "malformed") {
|
|
9795
|
-
return { ok: false, error: cfgError, configPath: cfgPath };
|
|
9796
|
-
}
|
|
9797
|
-
if (cfgStatus === "missing" && provider === null)
|
|
9798
|
-
return { ok: false, error: `vcs.json is missing: ${cfgPath}`, configPath: cfgPath };
|
|
9799
|
-
if (provider === null) {
|
|
9800
|
-
return {
|
|
9801
|
-
ok: false,
|
|
9802
|
-
error: `no vcs provider configured (set vcs.provider in ${cfgPath}, match a workspace entry, or run inside a checkout with a recognized origin remote)`,
|
|
9803
|
-
configPath: cfgPath
|
|
9804
|
-
};
|
|
9805
|
-
}
|
|
9806
|
-
const prov = cfg[provider] ?? {};
|
|
9807
|
-
const out = {
|
|
9808
|
-
ok: true,
|
|
9809
|
-
configPath: path4.resolve(cfgPath),
|
|
9810
|
-
provider,
|
|
9811
|
-
defaultTargetBranch: workspaceDefault,
|
|
9812
|
-
pr: cfg.pr ?? {},
|
|
9813
|
-
workspace_name: ws?.name ?? null,
|
|
9814
|
-
link_issues: linkIssues,
|
|
9815
|
-
youtrack_base_url: youtrackBaseUrl,
|
|
9816
|
-
issues_provider: issuesProvider,
|
|
9817
|
-
link_on_pr: linkOnPr
|
|
10000
|
+
if (older === newer)
|
|
10001
|
+
return false;
|
|
10002
|
+
const key = `${older}>${newer}`;
|
|
10003
|
+
if (!ancestry.has(key))
|
|
10004
|
+
ancestry.set(key, input.probe.isAncestor(older, newer));
|
|
10005
|
+
return ancestry.get(key);
|
|
9818
10006
|
};
|
|
9819
|
-
|
|
9820
|
-
|
|
9821
|
-
|
|
9822
|
-
|
|
9823
|
-
};
|
|
9824
|
-
} else if (provider === "github") {
|
|
9825
|
-
out.github = { host: prov.host ?? "github.com" };
|
|
10007
|
+
const winners = forks.filter((fork) => forks.every((other) => other.track === fork.track || descends(other.sha, fork.sha)));
|
|
10008
|
+
const winningTracks = [...new Set(winners.map((fork) => fork.track))];
|
|
10009
|
+
if (winningTracks.length === 1 && forks.some((fork) => fork.track !== winningTracks[0])) {
|
|
10010
|
+
const win = winners[0];
|
|
10011
|
+
return done(win.track, "ancestry", `${branch} forked from ${win.ref} at ${win.sha.slice(0, 12)}, after every other line's fork point`);
|
|
9826
10012
|
}
|
|
9827
|
-
|
|
10013
|
+
if (winningTracks.length === 1)
|
|
10014
|
+
return fallback(`only ${label(winningTracks[0])} could be compared with ${branch}`);
|
|
10015
|
+
const newest = forks.filter((fork) => !forks.some((other) => descends(fork.sha, other.sha)));
|
|
10016
|
+
return fallback(`${branch} has the same or unrelated fork points with ${[...new Set(newest.map((fork) => `${fork.ref} (${fork.track.name === IMPLICIT ? "workspace default" : fork.track.name})`))].join(" and ")}`);
|
|
9828
10017
|
}
|
|
9829
|
-
|
|
9830
|
-
// packages/workit-core/src/host-session.ts
|
|
9831
|
-
import { createHash as createHash6 } from "node:crypto";
|
|
9832
|
-
import fs4 from "node:fs";
|
|
9833
|
-
import path7 from "node:path";
|
|
9834
|
-
|
|
9835
|
-
// packages/workit-core/src/core/logger.ts
|
|
9836
|
-
import os3 from "node:os";
|
|
9837
|
-
import path5 from "node:path";
|
|
9838
|
-
var SENSITIVE_WORDS = new Set([
|
|
9839
|
-
"token",
|
|
9840
|
-
"secret",
|
|
9841
|
-
"password",
|
|
9842
|
-
"passwd",
|
|
9843
|
-
"authorization",
|
|
9844
|
-
"credential",
|
|
9845
|
-
"credentials",
|
|
9846
|
-
"cookie",
|
|
9847
|
-
"api",
|
|
9848
|
-
"apikey",
|
|
9849
|
-
"apitoken",
|
|
9850
|
-
"accesstoken",
|
|
9851
|
-
"clientsecret",
|
|
9852
|
-
"refreshtoken",
|
|
9853
|
-
"key",
|
|
9854
|
-
"bearer",
|
|
9855
|
-
"prompt",
|
|
9856
|
-
"message",
|
|
9857
|
-
"messages",
|
|
9858
|
-
"content",
|
|
9859
|
-
"body",
|
|
9860
|
-
"args",
|
|
9861
|
-
"argument",
|
|
9862
|
-
"result",
|
|
9863
|
-
"results",
|
|
9864
|
-
"output",
|
|
9865
|
-
"issue",
|
|
9866
|
-
"description",
|
|
9867
|
-
"summary",
|
|
9868
|
-
"payload",
|
|
9869
|
-
"script",
|
|
9870
|
-
"command",
|
|
9871
|
-
"text",
|
|
9872
|
-
"query"
|
|
9873
|
-
]);
|
|
9874
|
-
var STACK_WORDS = new Set(["stack", "stacktrace", "trace"]);
|
|
9875
|
-
var escapeRegExp2 = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
9876
|
-
var resolveStateDir = () => {
|
|
9877
|
-
const override = process.env.WORKFLOW_TOOLKIT_STATE;
|
|
9878
|
-
if (override)
|
|
9879
|
-
return override;
|
|
9880
|
-
const home = os3.homedir();
|
|
9881
|
-
if (process.env.XDG_STATE_HOME)
|
|
9882
|
-
return path5.join(process.env.XDG_STATE_HOME, "workit");
|
|
9883
|
-
switch (os3.platform()) {
|
|
9884
|
-
case "darwin":
|
|
9885
|
-
return path5.join(home, "Library", "Application Support", "workit");
|
|
9886
|
-
case "win32":
|
|
9887
|
-
return path5.join(process.env.LOCALAPPDATA ?? path5.join(home, "AppData", "Local"), "workit");
|
|
9888
|
-
default:
|
|
9889
|
-
return path5.join(process.env.HOME ?? home, ".local", "state", "workit");
|
|
9890
|
-
}
|
|
9891
|
-
};
|
|
9892
|
-
var homePattern = new RegExp(`${escapeRegExp2(os3.homedir())}(?=[/\\\\]|$)|\\$HOME(?=[/\\\\]|$)`, "g");
|
|
9893
|
-
|
|
9894
|
-
// packages/workit-core/src/store/paths.ts
|
|
9895
|
-
import { spawnSync as spawnSync6 } from "node:child_process";
|
|
9896
|
-
import { createHash as createHash5 } from "node:crypto";
|
|
9897
|
-
import fs3 from "node:fs";
|
|
9898
|
-
import path6 from "node:path";
|
|
9899
|
-
var NOT_A_REPO = /not a git repository \(or any (of the parent directories|parent up to)/u;
|
|
9900
|
-
var gitRun = (cwd, args) => {
|
|
10018
|
+
var git2 = (cwd, args) => {
|
|
9901
10019
|
const run = spawnSync6("git", args, {
|
|
9902
10020
|
cwd,
|
|
9903
10021
|
encoding: "utf8",
|
|
9904
10022
|
timeout: 20000,
|
|
9905
|
-
killSignal: "SIGKILL",
|
|
9906
10023
|
windowsHide: true,
|
|
9907
|
-
|
|
9908
|
-
...process.env,
|
|
9909
|
-
GIT_TERMINAL_PROMPT: "0",
|
|
9910
|
-
GIT_OPTIONAL_LOCKS: "0",
|
|
9911
|
-
LC_ALL: "C",
|
|
9912
|
-
LANGUAGE: "C"
|
|
9913
|
-
}
|
|
10024
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
9914
10025
|
});
|
|
9915
|
-
|
|
10026
|
+
return run.status === 0 ? (run.stdout ?? "").trim() : null;
|
|
10027
|
+
};
|
|
10028
|
+
var recordedBaseKey = (branch) => `branch.${branch}.workitBase`;
|
|
10029
|
+
var escapeRegExp = (text) => text.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
10030
|
+
var reflog = (cwd, ref) => (git2(cwd, ["log", "-g", "--date=unix", "--format=%H%x09%gd%x09%gs", ref, "--"]) ?? "").split(`
|
|
10031
|
+
`).map((line) => {
|
|
10032
|
+
const [sha = "", selector = "", ...subject] = line.split("\t");
|
|
9916
10033
|
return {
|
|
9917
|
-
|
|
9918
|
-
|
|
9919
|
-
|
|
9920
|
-
missing: error?.code === "ENOENT"
|
|
10034
|
+
sha,
|
|
10035
|
+
time: Number(/@\{(\d+)\}$/u.exec(selector)?.[1] ?? Number.NaN),
|
|
10036
|
+
subject: subject.join("\t")
|
|
9921
10037
|
};
|
|
9922
|
-
};
|
|
9923
|
-
|
|
9924
|
-
|
|
9925
|
-
|
|
9926
|
-
|
|
9927
|
-
|
|
9928
|
-
return
|
|
9929
|
-
}
|
|
9930
|
-
|
|
9931
|
-
|
|
9932
|
-
|
|
9933
|
-
|
|
9934
|
-
|
|
9935
|
-
|
|
9936
|
-
|
|
9937
|
-
|
|
9938
|
-
|
|
9939
|
-
|
|
9940
|
-
|
|
9941
|
-
|
|
9942
|
-
|
|
9943
|
-
|
|
9944
|
-
};
|
|
9945
|
-
var reftableRefs = (common) => {
|
|
9946
|
-
try {
|
|
9947
|
-
return /^\s*refstorage\s*=\s*reftable\s*$/imu.test(fs3.readFileSync(path6.join(common, "config"), "utf8"));
|
|
9948
|
-
} catch {
|
|
9949
|
-
return false;
|
|
9950
|
-
}
|
|
9951
|
-
};
|
|
9952
|
-
function fastGit(root) {
|
|
9953
|
-
if (DISCOVERY_ENV.some((name) => process.env[name]))
|
|
9954
|
-
return null;
|
|
9955
|
-
let dir = real(root);
|
|
9956
|
-
let device = null;
|
|
9957
|
-
for (;; ) {
|
|
9958
|
-
let stat;
|
|
9959
|
-
try {
|
|
9960
|
-
stat = fs3.statSync(dir);
|
|
9961
|
-
} catch {
|
|
9962
|
-
return null;
|
|
9963
|
-
}
|
|
9964
|
-
if (device !== null && stat.dev !== device)
|
|
9965
|
-
return { kind: "none" };
|
|
9966
|
-
device = stat.dev;
|
|
9967
|
-
const dotGit = path6.join(dir, ".git");
|
|
9968
|
-
let entry = null;
|
|
9969
|
-
try {
|
|
9970
|
-
entry = fs3.statSync(dotGit);
|
|
9971
|
-
} catch {}
|
|
9972
|
-
let gitDir = null;
|
|
9973
|
-
let top = null;
|
|
9974
|
-
if (entry) {
|
|
9975
|
-
top = dir;
|
|
9976
|
-
if (entry.isDirectory())
|
|
9977
|
-
gitDir = dotGit;
|
|
9978
|
-
else if (entry.isFile()) {
|
|
9979
|
-
let text = "";
|
|
9980
|
-
try {
|
|
9981
|
-
text = fs3.readFileSync(dotGit, "utf8");
|
|
9982
|
-
} catch {
|
|
9983
|
-
return null;
|
|
9984
|
-
}
|
|
9985
|
-
const match = /^gitdir: (.+)$/mu.exec(text);
|
|
9986
|
-
if (!match)
|
|
9987
|
-
return null;
|
|
9988
|
-
gitDir = path6.resolve(dir, match[1].trim());
|
|
9989
|
-
} else
|
|
9990
|
-
return null;
|
|
9991
|
-
if (!fs3.existsSync(path6.join(gitDir, "HEAD")))
|
|
9992
|
-
return null;
|
|
9993
|
-
} else if (isGitDir(dir))
|
|
9994
|
-
gitDir = dir;
|
|
9995
|
-
if (gitDir) {
|
|
9996
|
-
let common = gitDir;
|
|
9997
|
-
try {
|
|
9998
|
-
common = path6.resolve(gitDir, fs3.readFileSync(path6.join(gitDir, "commondir"), "utf8").trim());
|
|
9999
|
-
} catch {}
|
|
10000
|
-
if (!fs3.existsSync(path6.join(common, "objects")))
|
|
10038
|
+
}).filter((entry) => entry.sha && Number.isFinite(entry.time));
|
|
10039
|
+
function gitTrackProbe(cwd) {
|
|
10040
|
+
let remote;
|
|
10041
|
+
let remotes;
|
|
10042
|
+
const tip = (branch) => {
|
|
10043
|
+
remote ??= pushRemoteName(cwd) ?? "origin";
|
|
10044
|
+
return resolveRef(cwd, `refs/remotes/${remote}/${branch}`) ?? resolveRef(cwd, `refs/heads/${branch}`);
|
|
10045
|
+
};
|
|
10046
|
+
return {
|
|
10047
|
+
remotes: () => remotes ??= remoteNames(cwd),
|
|
10048
|
+
recordedBase: (branch) => git2(cwd, ["config", "--get", recordedBaseKey(branch)]) || null,
|
|
10049
|
+
createdFrom: (branch) => {
|
|
10050
|
+
const log = git2(cwd, ["reflog", "show", "--format=%gs", `refs/heads/${branch}`, "--"]);
|
|
10051
|
+
const oldest = log?.split(`
|
|
10052
|
+
`).findLast(Boolean) ?? "";
|
|
10053
|
+
const match = /^branch: Created from (\S+)$/u.exec(oldest);
|
|
10054
|
+
return match && match[1] !== "HEAD" ? match[1] : null;
|
|
10055
|
+
},
|
|
10056
|
+
checkedOutFrom: (branch) => {
|
|
10057
|
+
const own = reflog(cwd, `refs/heads/${branch}`);
|
|
10058
|
+
const created = own.at(-1);
|
|
10059
|
+
if (!created || created.subject !== "branch: Created from HEAD")
|
|
10001
10060
|
return null;
|
|
10002
|
-
|
|
10003
|
-
|
|
10004
|
-
|
|
10005
|
-
|
|
10006
|
-
|
|
10007
|
-
|
|
10008
|
-
};
|
|
10009
|
-
}
|
|
10010
|
-
|
|
10011
|
-
|
|
10012
|
-
|
|
10013
|
-
|
|
10061
|
+
const pattern = new RegExp(`^checkout: moving from (\\S+) to ${escapeRegExp(branch)}$`, "u");
|
|
10062
|
+
const entry = reflog(cwd, "HEAD").findLast((item) => item.time >= created.time && item.sha === created.sha && pattern.test(item.subject));
|
|
10063
|
+
return entry ? pattern.exec(entry.subject)?.[1] ?? null : null;
|
|
10064
|
+
},
|
|
10065
|
+
mergeBase: (branch, trackBranch) => {
|
|
10066
|
+
const base = tip(trackBranch);
|
|
10067
|
+
return base && resolveRef(cwd, `refs/heads/${branch}`) ? mergeBase(cwd, base, `refs/heads/${branch}`) : null;
|
|
10068
|
+
},
|
|
10069
|
+
isAncestor: (ancestor, descendant) => git2(cwd, ["merge-base", "--is-ancestor", ancestor, descendant]) !== null
|
|
10070
|
+
};
|
|
10071
|
+
}
|
|
10072
|
+
function workspaceReleaseTracks(cwd) {
|
|
10073
|
+
const raw = resolveRuntimeReleaseTracks(cwd);
|
|
10074
|
+
return { workspace: raw?.workspace ?? null, ...readReleaseTracks(raw?.releaseTracks) };
|
|
10075
|
+
}
|
|
10076
|
+
var memo2 = new Map;
|
|
10077
|
+
var MEMO_LIMIT = 64;
|
|
10078
|
+
function resolveTrackFor(cwd, options) {
|
|
10079
|
+
const read = workspaceReleaseTracks(cwd);
|
|
10080
|
+
if (read.error)
|
|
10081
|
+
return { status: "invalid", error: read.error, issues: read.issues };
|
|
10082
|
+
if (read.tracks.length === 0 && !options.track?.trim())
|
|
10083
|
+
return { status: "none", issues: read.issues };
|
|
10084
|
+
const branch = options.branch === undefined ? currentBranch(cwd) : options.branch;
|
|
10085
|
+
const envRequested = process.env.WORKFLOW_RELEASE_TRACK?.trim() || null;
|
|
10086
|
+
const input = {
|
|
10087
|
+
tracks: read.tracks,
|
|
10088
|
+
requested: options.track ?? null,
|
|
10089
|
+
envRequested,
|
|
10090
|
+
branch,
|
|
10091
|
+
defaultBranch: options.defaultBranch,
|
|
10092
|
+
issues: read.issues
|
|
10093
|
+
};
|
|
10094
|
+
let key = null;
|
|
10095
|
+
if (!options.probe) {
|
|
10096
|
+
const refs = git2(cwd, [
|
|
10097
|
+
"for-each-ref",
|
|
10098
|
+
"--format=%(refname) %(objectname)",
|
|
10099
|
+
"refs/heads",
|
|
10100
|
+
"refs/remotes"
|
|
10101
|
+
]) ?? "";
|
|
10102
|
+
const head = git2(cwd, ["reflog", "show", "-n", "1", "--format=%H %gs", "HEAD", "--"]) ?? "";
|
|
10103
|
+
const own = branch ? [
|
|
10104
|
+
git2(cwd, [
|
|
10105
|
+
"log",
|
|
10106
|
+
"-g",
|
|
10107
|
+
"-n",
|
|
10108
|
+
"1",
|
|
10109
|
+
"--date=unix",
|
|
10110
|
+
"--format=%H %gd %gs",
|
|
10111
|
+
`refs/heads/${branch}`,
|
|
10112
|
+
"--"
|
|
10113
|
+
]) ?? "",
|
|
10114
|
+
git2(cwd, ["config", "--get", recordedBaseKey(branch)]) ?? ""
|
|
10115
|
+
] : [];
|
|
10116
|
+
key = createHash5("sha256").update(JSON.stringify({ cwd, input, refs, head, own })).digest("hex");
|
|
10117
|
+
const hit = memo2.get(key);
|
|
10118
|
+
if (hit)
|
|
10119
|
+
return hit;
|
|
10014
10120
|
}
|
|
10015
|
-
}
|
|
10016
|
-
|
|
10017
|
-
|
|
10018
|
-
|
|
10019
|
-
|
|
10020
|
-
directory = fs3.statSync(root).isDirectory();
|
|
10021
|
-
} catch {}
|
|
10022
|
-
if (!directory)
|
|
10023
|
-
return { dir: path6.join(root, ".workit"), shared: false, top: null };
|
|
10024
|
-
const fast = fastGit(root);
|
|
10025
|
-
if (fast?.kind === "none")
|
|
10026
|
-
return { dir: path6.join(root, ".workit"), shared: false, top: null };
|
|
10027
|
-
if (fast?.kind === "repo")
|
|
10028
|
-
return {
|
|
10029
|
-
dir: path6.join(fast.common, "workit"),
|
|
10030
|
-
shared: true,
|
|
10031
|
-
top: fast.top,
|
|
10032
|
-
git: { gitDir: fast.gitDir, top: fast.top, reftable: fast.reftable }
|
|
10033
|
-
};
|
|
10034
|
-
const run = gitRun(root, ["rev-parse", "--path-format=absolute", "--git-common-dir"]);
|
|
10035
|
-
const common = run.stdout.trim();
|
|
10036
|
-
if (run.ok && common) {
|
|
10037
|
-
const top = gitRun(root, ["rev-parse", "--path-format=absolute", "--show-toplevel"]);
|
|
10038
|
-
return {
|
|
10039
|
-
dir: path6.join(real(path6.resolve(root, common)), "workit"),
|
|
10040
|
-
shared: true,
|
|
10041
|
-
top: top.ok && top.stdout.trim() ? real(top.stdout.trim()) : null
|
|
10042
|
-
};
|
|
10121
|
+
const resolution = resolveReleaseTrack({ ...input, probe: options.probe ?? gitTrackProbe(cwd) });
|
|
10122
|
+
if (key) {
|
|
10123
|
+
if (memo2.size >= MEMO_LIMIT)
|
|
10124
|
+
memo2.delete(memo2.keys().next().value);
|
|
10125
|
+
memo2.set(key, resolution);
|
|
10043
10126
|
}
|
|
10044
|
-
|
|
10045
|
-
return { dir: path6.join(root, ".workit"), shared: false, top: null };
|
|
10046
|
-
return new Error(`cannot locate the workit store: git rev-parse failed (${run.stderr.trim().split(`
|
|
10047
|
-
`)[0] || "no output"}); fix the repository (git status should work here)`);
|
|
10127
|
+
return resolution;
|
|
10048
10128
|
}
|
|
10049
|
-
|
|
10050
|
-
|
|
10051
|
-
|
|
10052
|
-
|
|
10053
|
-
|
|
10054
|
-
|
|
10055
|
-
|
|
10056
|
-
|
|
10057
|
-
|
|
10129
|
+
|
|
10130
|
+
// packages/workit-core/src/core/vcs-config.ts
|
|
10131
|
+
var workspacesPath = () => path5.join(configDir(), "workspaces.json");
|
|
10132
|
+
var vcsCwd = (cwd) => cwd ?? process.env.WORKFLOW_WORKSPACE_ROOT ?? process.cwd();
|
|
10133
|
+
var remoteProvider = (cwd) => {
|
|
10134
|
+
const r = spawnSync7("git", ["remote", "get-url", "origin"], { cwd, encoding: "utf8" });
|
|
10135
|
+
if (r.status !== 0)
|
|
10136
|
+
return null;
|
|
10137
|
+
const url = (r.stdout ?? "").trim();
|
|
10138
|
+
if (/github\.com[:/]/.test(url))
|
|
10139
|
+
return "github";
|
|
10140
|
+
if (/gitlab\.com[:/]/.test(url))
|
|
10141
|
+
return "gitlab";
|
|
10058
10142
|
return null;
|
|
10059
10143
|
};
|
|
10060
|
-
|
|
10061
|
-
|
|
10062
|
-
|
|
10063
|
-
const branchKey = (name) => ({ key: name, kind: "branch", branch: name });
|
|
10064
|
-
if (location.git && !location.git.reftable) {
|
|
10065
|
-
let head = "";
|
|
10066
|
-
try {
|
|
10067
|
-
head = fs3.readFileSync(path6.join(location.git.gitDir, "HEAD"), "utf8").trim();
|
|
10068
|
-
} catch {}
|
|
10069
|
-
const ref = /^ref: refs\/heads\/(.+)$/u.exec(head);
|
|
10070
|
-
if (ref && ref[1] !== ".invalid")
|
|
10071
|
-
return branchKey(ref[1]);
|
|
10072
|
-
if (/^[0-9a-f]{40,64}$/u.test(head)) {
|
|
10073
|
-
const rebasing = rebasingBranch(location.git.gitDir);
|
|
10074
|
-
if (rebasing)
|
|
10075
|
-
return branchKey(rebasing);
|
|
10076
|
-
return {
|
|
10077
|
-
key: `detached-${shortHash(location.git.top ?? location.git.gitDir)}`,
|
|
10078
|
-
kind: "detached",
|
|
10079
|
-
branch: null
|
|
10080
|
-
};
|
|
10081
|
-
}
|
|
10082
|
-
}
|
|
10083
|
-
const branch = gitRun(root, ["symbolic-ref", "-q", "--short", "HEAD"]);
|
|
10084
|
-
const name = branch.ok ? branch.stdout.trim() : "";
|
|
10085
|
-
if (name && name !== ".invalid")
|
|
10086
|
-
return branchKey(name);
|
|
10087
|
-
const gitDir = gitRun(root, ["rev-parse", "--path-format=absolute", "--git-dir"]);
|
|
10088
|
-
if (gitDir.ok && gitDir.stdout.trim()) {
|
|
10089
|
-
const rebasing = rebasingBranch(gitDir.stdout.trim());
|
|
10090
|
-
if (rebasing)
|
|
10091
|
-
return branchKey(rebasing);
|
|
10092
|
-
}
|
|
10093
|
-
const top = gitRun(root, ["rev-parse", "--path-format=absolute", "--show-toplevel"]);
|
|
10094
|
-
const worktree = top.ok && top.stdout.trim() ? top.stdout.trim() : root;
|
|
10095
|
-
return { key: `detached-${shortHash(real(worktree))}`, kind: "detached", branch: null };
|
|
10096
|
-
}
|
|
10097
|
-
function checkoutRootOf(root, location) {
|
|
10098
|
-
const resolved = path6.resolve(root);
|
|
10099
|
-
let canonical = resolved;
|
|
10144
|
+
var readVcsConfig = () => {
|
|
10145
|
+
const file = vcsConfigPath();
|
|
10146
|
+
let raw;
|
|
10100
10147
|
try {
|
|
10101
|
-
|
|
10148
|
+
raw = fs4.readFileSync(file, "utf8");
|
|
10102
10149
|
} catch {
|
|
10103
|
-
return
|
|
10150
|
+
return { status: "missing", path: file, config: {} };
|
|
10104
10151
|
}
|
|
10105
|
-
|
|
10106
|
-
if (found instanceof Error || !found.shared || !found.top)
|
|
10107
|
-
return canonical;
|
|
10108
|
-
if (real(canonical) === found.top)
|
|
10109
|
-
return canonical;
|
|
10152
|
+
let parsed;
|
|
10110
10153
|
try {
|
|
10111
|
-
|
|
10154
|
+
parsed = JSON.parse(raw);
|
|
10112
10155
|
} catch {
|
|
10113
|
-
return
|
|
10156
|
+
return { status: "malformed", path: file, config: {}, error: `${file} is not valid JSON` };
|
|
10114
10157
|
}
|
|
10115
|
-
|
|
10116
|
-
|
|
10117
|
-
|
|
10118
|
-
|
|
10119
|
-
|
|
10120
|
-
|
|
10121
|
-
|
|
10122
|
-
|
|
10123
|
-
|
|
10124
|
-
|
|
10125
|
-
|
|
10126
|
-
|
|
10127
|
-
|
|
10128
|
-
|
|
10129
|
-
|
|
10130
|
-
|
|
10131
|
-
|
|
10158
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
10159
|
+
return { status: "valid", path: file, config: parsed };
|
|
10160
|
+
}
|
|
10161
|
+
return { status: "malformed", path: file, config: {}, error: `${file} is not a JSON object` };
|
|
10162
|
+
};
|
|
10163
|
+
var trackSummary = (resolution) => resolution.status === "resolved" ? {
|
|
10164
|
+
name: resolution.track?.name ?? null,
|
|
10165
|
+
source: resolution.source,
|
|
10166
|
+
detail: resolution.detail,
|
|
10167
|
+
warnings: resolution.warnings,
|
|
10168
|
+
track: resolution.track,
|
|
10169
|
+
tracks: resolution.tracks,
|
|
10170
|
+
blocking: resolution.blocking
|
|
10171
|
+
} : null;
|
|
10172
|
+
function vcsConfig(mode, cwd, options = {}) {
|
|
10173
|
+
const ws = resolveRuntimeWorkspaceVcs(vcsCwd(cwd));
|
|
10174
|
+
const wsVcs = ws?.vcs ?? {};
|
|
10175
|
+
const wsYt = ws?.youtrack ?? {};
|
|
10176
|
+
const wsIssues = ws?.issues ?? {};
|
|
10177
|
+
const { status: cfgStatus, path: cfgPath, config: cfg, error: cfgError } = readVcsConfig();
|
|
10178
|
+
const root = vcsCwd(cwd);
|
|
10179
|
+
const determinateRoot = cwd !== undefined || process.env.WORKFLOW_WORKSPACE_ROOT !== undefined;
|
|
10180
|
+
const rawProvider = wsVcs.provider ?? (determinateRoot ? remoteProvider(root) : null) ?? cfg.provider ?? null;
|
|
10181
|
+
const provider = rawProvider === null || rawProvider === undefined ? null : String(rawProvider).toLowerCase() || null;
|
|
10182
|
+
const workspaceDefault = workspaceDefaultTarget(root, cfg.defaultTargetBranch);
|
|
10183
|
+
const linkIssues = typeof wsYt.link_issues === "boolean" ? wsYt.link_issues : null;
|
|
10184
|
+
const youtrackBaseUrl = typeof wsYt.baseUrl === "string" ? wsYt.baseUrl : null;
|
|
10185
|
+
let issuesProvider = null;
|
|
10186
|
+
let linkOnPr = null;
|
|
10187
|
+
if (provider === "github" && typeof wsIssues.provider === "string" && wsIssues.provider.toLowerCase() === "github") {
|
|
10188
|
+
issuesProvider = "github";
|
|
10189
|
+
linkOnPr = typeof wsIssues.link_on_pr === "boolean" ? wsIssues.link_on_pr : null;
|
|
10190
|
+
}
|
|
10191
|
+
if (mode === "resolve") {
|
|
10192
|
+
if (cfgStatus === "malformed")
|
|
10193
|
+
return { ok: false, error: cfgError, configPath: cfgPath };
|
|
10194
|
+
const trackResolution = resolveTrackFor(root, {
|
|
10195
|
+
track: options.track,
|
|
10196
|
+
...options.branch !== undefined ? { branch: options.branch } : {},
|
|
10197
|
+
defaultBranch: workspaceDefault
|
|
10198
|
+
});
|
|
10199
|
+
if (trackResolution.status === "invalid")
|
|
10200
|
+
return { ok: false, error: trackResolution.error, configPath: workspacesPath() };
|
|
10201
|
+
const releaseTrack = trackSummary(trackResolution);
|
|
10202
|
+
const defaultTarget = releaseTrack?.track?.pullRequestTarget ?? workspaceDefault;
|
|
10203
|
+
const baseBranch = releaseTrack?.track?.baseBranch ?? defaultTarget;
|
|
10204
|
+
return {
|
|
10205
|
+
ok: true,
|
|
10206
|
+
workspace_name: ws?.name ?? null,
|
|
10207
|
+
provider,
|
|
10208
|
+
defaultTargetBranch: defaultTarget,
|
|
10209
|
+
workspaceDefaultTargetBranch: workspaceDefault,
|
|
10210
|
+
baseBranch,
|
|
10211
|
+
releaseTrack,
|
|
10212
|
+
link_issues: linkIssues,
|
|
10213
|
+
youtrack_base_url: youtrackBaseUrl,
|
|
10214
|
+
issues_provider: issuesProvider,
|
|
10215
|
+
link_on_pr: linkOnPr
|
|
10216
|
+
};
|
|
10217
|
+
}
|
|
10218
|
+
if (cfgStatus === "malformed") {
|
|
10219
|
+
return { ok: false, error: cfgError, configPath: cfgPath };
|
|
10220
|
+
}
|
|
10221
|
+
if (cfgStatus === "missing" && provider === null)
|
|
10222
|
+
return { ok: false, error: `vcs.json is missing: ${cfgPath}`, configPath: cfgPath };
|
|
10223
|
+
if (provider === null) {
|
|
10224
|
+
return {
|
|
10225
|
+
ok: false,
|
|
10226
|
+
error: `no vcs provider configured (set vcs.provider in ${cfgPath}, match a workspace entry, or run inside a checkout with a recognized origin remote)`,
|
|
10227
|
+
configPath: cfgPath
|
|
10228
|
+
};
|
|
10229
|
+
}
|
|
10230
|
+
const prov = cfg[provider] ?? {};
|
|
10231
|
+
const out = {
|
|
10232
|
+
ok: true,
|
|
10233
|
+
configPath: path5.resolve(cfgPath),
|
|
10234
|
+
provider,
|
|
10235
|
+
defaultTargetBranch: workspaceDefault,
|
|
10236
|
+
pr: cfg.pr ?? {},
|
|
10237
|
+
workspace_name: ws?.name ?? null,
|
|
10238
|
+
link_issues: linkIssues,
|
|
10239
|
+
youtrack_base_url: youtrackBaseUrl,
|
|
10240
|
+
issues_provider: issuesProvider,
|
|
10241
|
+
link_on_pr: linkOnPr
|
|
10132
10242
|
};
|
|
10133
|
-
|
|
10134
|
-
|
|
10135
|
-
|
|
10136
|
-
|
|
10137
|
-
|
|
10138
|
-
|
|
10139
|
-
|
|
10140
|
-
|
|
10141
|
-
|
|
10142
|
-
|
|
10143
|
-
|
|
10144
|
-
|
|
10145
|
-
|
|
10146
|
-
|
|
10147
|
-
|
|
10148
|
-
|
|
10149
|
-
|
|
10150
|
-
|
|
10151
|
-
|
|
10152
|
-
|
|
10243
|
+
if (provider === "gitlab") {
|
|
10244
|
+
out.gitlab = {
|
|
10245
|
+
host: prov.host ?? "gitlab.com",
|
|
10246
|
+
apiUrl: prov.apiUrl ?? "https://gitlab.com/api/v4"
|
|
10247
|
+
};
|
|
10248
|
+
} else if (provider === "github") {
|
|
10249
|
+
out.github = { host: prov.host ?? "github.com" };
|
|
10250
|
+
}
|
|
10251
|
+
return out;
|
|
10252
|
+
}
|
|
10253
|
+
|
|
10254
|
+
// packages/workit-core/src/host-session.ts
|
|
10255
|
+
import { createHash as createHash6, randomBytes, timingSafeEqual } from "node:crypto";
|
|
10256
|
+
import fs5 from "node:fs";
|
|
10257
|
+
import path7 from "node:path";
|
|
10258
|
+
|
|
10259
|
+
// packages/workit-core/src/core/logger.ts
|
|
10260
|
+
import os3 from "node:os";
|
|
10261
|
+
import path6 from "node:path";
|
|
10262
|
+
var SENSITIVE_WORDS = new Set([
|
|
10263
|
+
"token",
|
|
10264
|
+
"secret",
|
|
10265
|
+
"password",
|
|
10266
|
+
"passwd",
|
|
10267
|
+
"authorization",
|
|
10268
|
+
"credential",
|
|
10269
|
+
"credentials",
|
|
10270
|
+
"cookie",
|
|
10271
|
+
"api",
|
|
10272
|
+
"apikey",
|
|
10273
|
+
"apitoken",
|
|
10274
|
+
"accesstoken",
|
|
10275
|
+
"clientsecret",
|
|
10276
|
+
"refreshtoken",
|
|
10277
|
+
"key",
|
|
10278
|
+
"bearer",
|
|
10279
|
+
"prompt",
|
|
10280
|
+
"message",
|
|
10281
|
+
"messages",
|
|
10282
|
+
"content",
|
|
10283
|
+
"body",
|
|
10284
|
+
"args",
|
|
10285
|
+
"argument",
|
|
10286
|
+
"result",
|
|
10287
|
+
"results",
|
|
10288
|
+
"output",
|
|
10289
|
+
"issue",
|
|
10290
|
+
"description",
|
|
10291
|
+
"summary",
|
|
10292
|
+
"payload",
|
|
10293
|
+
"script",
|
|
10294
|
+
"command",
|
|
10295
|
+
"text",
|
|
10296
|
+
"query"
|
|
10297
|
+
]);
|
|
10298
|
+
var STACK_WORDS = new Set(["stack", "stacktrace", "trace"]);
|
|
10299
|
+
var escapeRegExp2 = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
10300
|
+
var resolveStateDir = () => {
|
|
10301
|
+
const override = process.env.WORKFLOW_TOOLKIT_STATE;
|
|
10302
|
+
if (override)
|
|
10303
|
+
return override;
|
|
10304
|
+
const home = os3.homedir();
|
|
10305
|
+
if (process.env.XDG_STATE_HOME)
|
|
10306
|
+
return path6.join(process.env.XDG_STATE_HOME, "workit");
|
|
10307
|
+
switch (os3.platform()) {
|
|
10308
|
+
case "darwin":
|
|
10309
|
+
return path6.join(home, "Library", "Application Support", "workit");
|
|
10310
|
+
case "win32":
|
|
10311
|
+
return path6.join(process.env.LOCALAPPDATA ?? path6.join(home, "AppData", "Local"), "workit");
|
|
10312
|
+
default:
|
|
10313
|
+
return path6.join(process.env.HOME ?? home, ".local", "state", "workit");
|
|
10153
10314
|
}
|
|
10154
|
-
return renames.toSorted((left, right) => left.at - right.at);
|
|
10155
|
-
}
|
|
10156
|
-
function followRenames(key, boundAt, renames) {
|
|
10157
|
-
let current = key;
|
|
10158
|
-
let since = Math.floor(Date.parse(boundAt) / 1000);
|
|
10159
|
-
for (const rename of renames)
|
|
10160
|
-
if (rename.from === current && rename.at >= since) {
|
|
10161
|
-
current = rename.to;
|
|
10162
|
-
since = rename.at;
|
|
10163
|
-
}
|
|
10164
|
-
return current;
|
|
10165
|
-
}
|
|
10166
|
-
var checkoutSlug = (root) => {
|
|
10167
|
-
let canonical = real(root);
|
|
10168
|
-
if (process.platform === "win32")
|
|
10169
|
-
canonical = canonical.toLowerCase();
|
|
10170
|
-
const base = path6.basename(canonical).replace(/[^A-Za-z0-9._-]/g, "_").slice(0, 40) || "root";
|
|
10171
|
-
return `${base}-${shortHash(canonical)}`;
|
|
10172
10315
|
};
|
|
10316
|
+
var homePattern = new RegExp(`${escapeRegExp2(os3.homedir())}(?=[/\\\\]|$)|\\$HOME(?=[/\\\\]|$)`, "g");
|
|
10173
10317
|
|
|
10174
10318
|
// packages/workit-core/src/host-session.ts
|
|
10175
10319
|
var HOST_SESSION_VARS = [
|
|
@@ -10200,9 +10344,10 @@ function hostSessionFromEnv(env) {
|
|
|
10200
10344
|
return inner ? { host: inner.host, session: inner.session } : { host: declared, session: null };
|
|
10201
10345
|
}
|
|
10202
10346
|
var ISSUED_PER_LEAD = 64;
|
|
10203
|
-
var ISSUED_SESSIONS_MARKER = "workit-issued-sessions/
|
|
10347
|
+
var ISSUED_SESSIONS_MARKER = "workit-issued-sessions/3";
|
|
10204
10348
|
var hash = (value) => createHash6("sha256").update(value).digest("hex").slice(0, 32);
|
|
10205
10349
|
var userIssuedRoot = () => path7.join(resolveStateDir(), "issued");
|
|
10350
|
+
var userTokenRoot = () => path7.join(resolveStateDir(), "issued-tokens");
|
|
10206
10351
|
function migrateRepoIssued(cwd) {
|
|
10207
10352
|
if (cwd === undefined)
|
|
10208
10353
|
return;
|
|
@@ -10212,7 +10357,7 @@ function migrateRepoIssued(cwd) {
|
|
|
10212
10357
|
if (location instanceof Error)
|
|
10213
10358
|
return;
|
|
10214
10359
|
old = path7.join(location.dir, "hooks", "issued");
|
|
10215
|
-
if (!
|
|
10360
|
+
if (!fs5.existsSync(old))
|
|
10216
10361
|
return;
|
|
10217
10362
|
} catch {
|
|
10218
10363
|
return;
|
|
@@ -10220,13 +10365,18 @@ function migrateRepoIssued(cwd) {
|
|
|
10220
10365
|
for (const lead of safeList(old)) {
|
|
10221
10366
|
try {
|
|
10222
10367
|
const from = path7.join(old, lead);
|
|
10223
|
-
if (!
|
|
10368
|
+
if (!fs5.statSync(from).isDirectory())
|
|
10224
10369
|
continue;
|
|
10225
10370
|
const to = path7.join(userIssuedRoot(), lead);
|
|
10226
|
-
|
|
10227
|
-
for (const id of
|
|
10228
|
-
|
|
10229
|
-
|
|
10371
|
+
fs5.mkdirSync(to, { recursive: true, mode: 448 });
|
|
10372
|
+
for (const id of fs5.readdirSync(from)) {
|
|
10373
|
+
try {
|
|
10374
|
+
fs5.copyFileSync(path7.join(from, id), path7.join(to, id), fs5.constants.COPYFILE_EXCL);
|
|
10375
|
+
} catch (error) {
|
|
10376
|
+
if (error.code !== "EEXIST")
|
|
10377
|
+
throw error;
|
|
10378
|
+
}
|
|
10379
|
+
fs5.rmSync(path7.join(from, id), { force: true });
|
|
10230
10380
|
}
|
|
10231
10381
|
removeIfEmpty(from);
|
|
10232
10382
|
} catch {}
|
|
@@ -10235,31 +10385,81 @@ function migrateRepoIssued(cwd) {
|
|
|
10235
10385
|
}
|
|
10236
10386
|
var safeList = (dir) => {
|
|
10237
10387
|
try {
|
|
10238
|
-
return
|
|
10388
|
+
return fs5.readdirSync(dir);
|
|
10239
10389
|
} catch {
|
|
10240
10390
|
return [];
|
|
10241
10391
|
}
|
|
10242
10392
|
};
|
|
10243
10393
|
var removeIfEmpty = (dir) => {
|
|
10244
10394
|
try {
|
|
10245
|
-
|
|
10395
|
+
fs5.rmdirSync(dir);
|
|
10246
10396
|
} catch {}
|
|
10247
10397
|
};
|
|
10398
|
+
var tokenHash = (token) => createHash6("sha256").update(token).digest("hex");
|
|
10248
10399
|
function recordIssuedSession(cwd, lead, issued) {
|
|
10400
|
+
const token = randomBytes(18).toString("base64url");
|
|
10249
10401
|
try {
|
|
10250
10402
|
migrateRepoIssued(cwd);
|
|
10251
|
-
const dir = path7.join(userIssuedRoot(), hash(lead));
|
|
10252
|
-
fs4.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
10253
10403
|
const own = hash(issued);
|
|
10254
|
-
|
|
10255
|
-
|
|
10404
|
+
const tokens = path7.join(userTokenRoot(), hash(lead));
|
|
10405
|
+
fs5.mkdirSync(tokens, { recursive: true, mode: 448 });
|
|
10406
|
+
fs5.writeFileSync(path7.join(tokens, own), `${tokenHash(token)}
|
|
10407
|
+
`, { mode: 384, flag: "wx" });
|
|
10408
|
+
const dir = path7.join(userIssuedRoot(), hash(lead));
|
|
10409
|
+
fs5.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
10410
|
+
fs5.writeFileSync(path7.join(dir, own), `${ISSUED_SESSIONS_MARKER}
|
|
10411
|
+
`, { mode: 384 });
|
|
10412
|
+
pruneIssued(dir, own, tokens);
|
|
10413
|
+
return token;
|
|
10414
|
+
} catch {
|
|
10415
|
+
return null;
|
|
10416
|
+
}
|
|
10417
|
+
}
|
|
10418
|
+
function pruneIssued(dir, own, tokens) {
|
|
10419
|
+
try {
|
|
10420
|
+
const entries = fs5.readdirSync(dir);
|
|
10256
10421
|
if (entries.length <= ISSUED_PER_LEAD)
|
|
10257
10422
|
return;
|
|
10258
|
-
const byAge = entries.filter((name) => name !== own).map((name) => ({ name, at:
|
|
10259
|
-
for (const { name } of byAge.slice(0, entries.length - ISSUED_PER_LEAD))
|
|
10260
|
-
|
|
10423
|
+
const byAge = entries.filter((name) => name !== own).map((name) => ({ name, at: fs5.statSync(path7.join(dir, name)).mtimeMs })).toSorted((a, b) => a.at - b.at);
|
|
10424
|
+
for (const { name } of byAge.slice(0, entries.length - ISSUED_PER_LEAD)) {
|
|
10425
|
+
fs5.rmSync(path7.join(dir, name), { force: true });
|
|
10426
|
+
fs5.rmSync(path7.join(tokens, name), { force: true });
|
|
10427
|
+
}
|
|
10261
10428
|
} catch {}
|
|
10262
10429
|
}
|
|
10430
|
+
var issuedRecord = (cwd, claimed) => {
|
|
10431
|
+
const cut = claimed.lastIndexOf(":");
|
|
10432
|
+
if (cut <= 0)
|
|
10433
|
+
return null;
|
|
10434
|
+
migrateRepoIssued(cwd);
|
|
10435
|
+
try {
|
|
10436
|
+
return fs5.readFileSync(path7.join(userIssuedRoot(), hash(claimed.slice(0, cut)), hash(claimed)), "utf8");
|
|
10437
|
+
} catch {
|
|
10438
|
+
return null;
|
|
10439
|
+
}
|
|
10440
|
+
};
|
|
10441
|
+
var TOKEN_HASH = /^([0-9a-f]{64})$/mu;
|
|
10442
|
+
var issuedToken = (claimed) => {
|
|
10443
|
+
const cut = claimed.lastIndexOf(":");
|
|
10444
|
+
if (cut <= 0)
|
|
10445
|
+
return { stored: null, leadBound: false };
|
|
10446
|
+
const dir = path7.join(userTokenRoot(), hash(claimed.slice(0, cut)));
|
|
10447
|
+
try {
|
|
10448
|
+
const text = fs5.readFileSync(path7.join(dir, hash(claimed)), "utf8");
|
|
10449
|
+
return { stored: TOKEN_HASH.exec(text)?.[1] ?? null, leadBound: true };
|
|
10450
|
+
} catch {
|
|
10451
|
+
return { stored: null, leadBound: safeList(dir).length > 0 };
|
|
10452
|
+
}
|
|
10453
|
+
};
|
|
10454
|
+
function issuedBinding(cwd, claimed) {
|
|
10455
|
+
const record = issuedRecord(cwd, claimed);
|
|
10456
|
+
const { stored, leadBound } = issuedToken(claimed);
|
|
10457
|
+
if (stored)
|
|
10458
|
+
return "token";
|
|
10459
|
+
if (record === null)
|
|
10460
|
+
return null;
|
|
10461
|
+
return leadBound ? "token" : "legacy";
|
|
10462
|
+
}
|
|
10263
10463
|
var OWN_CHILD_SHELL = new Set(["opencode", "pi"]);
|
|
10264
10464
|
|
|
10265
10465
|
// packages/workit-core/src/ledger.ts
|
|
@@ -10276,7 +10476,7 @@ var err = (code, error, unblock) => ({
|
|
|
10276
10476
|
...unblock ? { unblock } : {}
|
|
10277
10477
|
});
|
|
10278
10478
|
var gitRun2 = (cwd, args, timeoutMs = GIT_TIMEOUTS.local) => {
|
|
10279
|
-
const run =
|
|
10479
|
+
const run = spawnSync8("git", args, {
|
|
10280
10480
|
cwd,
|
|
10281
10481
|
encoding: "utf8",
|
|
10282
10482
|
timeout: timeoutMs,
|
|
@@ -10388,7 +10588,7 @@ function ledgerPath(cwd) {
|
|
|
10388
10588
|
const root = storeRoot(cwd);
|
|
10389
10589
|
return root.ok ? { ok: true, value: path8.join(root.value.root, "ledger", "ledger.jsonl") } : root;
|
|
10390
10590
|
}
|
|
10391
|
-
var newId2 = () => `${Date.now().toString(36)}-${
|
|
10591
|
+
var newId2 = () => `${Date.now().toString(36)}-${randomBytes2(6).toString("hex")}`;
|
|
10392
10592
|
var NETWORK_FS_TYPES = new Set([
|
|
10393
10593
|
26985,
|
|
10394
10594
|
20859,
|
|
@@ -10406,7 +10606,7 @@ var onNetworkFs = (dir) => {
|
|
|
10406
10606
|
if (process.platform !== "linux")
|
|
10407
10607
|
return false;
|
|
10408
10608
|
try {
|
|
10409
|
-
return isNetworkFsType(
|
|
10609
|
+
return isNetworkFsType(fs6.statfsSync(dir).type);
|
|
10410
10610
|
} catch {
|
|
10411
10611
|
return false;
|
|
10412
10612
|
}
|
|
@@ -10416,7 +10616,7 @@ var LOCK_STALE_MS = 30000;
|
|
|
10416
10616
|
var LOCK_OWNER = "owner";
|
|
10417
10617
|
var lockToken = (lock) => {
|
|
10418
10618
|
try {
|
|
10419
|
-
return
|
|
10619
|
+
return fs6.readFileSync(path8.join(lock, LOCK_OWNER), "utf8");
|
|
10420
10620
|
} catch {
|
|
10421
10621
|
return null;
|
|
10422
10622
|
}
|
|
@@ -10424,7 +10624,7 @@ var lockToken = (lock) => {
|
|
|
10424
10624
|
var lockAgeMs = (lock) => {
|
|
10425
10625
|
for (const target of [path8.join(lock, LOCK_OWNER), lock]) {
|
|
10426
10626
|
try {
|
|
10427
|
-
return Date.now() -
|
|
10627
|
+
return Date.now() - fs6.statSync(target).mtimeMs;
|
|
10428
10628
|
} catch {}
|
|
10429
10629
|
}
|
|
10430
10630
|
return null;
|
|
@@ -10432,55 +10632,55 @@ var lockAgeMs = (lock) => {
|
|
|
10432
10632
|
var ledgerLock = {
|
|
10433
10633
|
acquire(lock) {
|
|
10434
10634
|
try {
|
|
10435
|
-
|
|
10635
|
+
fs6.mkdirSync(lock);
|
|
10436
10636
|
} catch (error) {
|
|
10437
10637
|
if (error.code === "EEXIST")
|
|
10438
10638
|
return null;
|
|
10439
10639
|
throw error;
|
|
10440
10640
|
}
|
|
10441
|
-
const token = `${process.pid}-${
|
|
10442
|
-
|
|
10641
|
+
const token = `${process.pid}-${randomBytes2(8).toString("hex")}`;
|
|
10642
|
+
fs6.writeFileSync(path8.join(lock, LOCK_OWNER), token);
|
|
10443
10643
|
return token;
|
|
10444
10644
|
},
|
|
10445
10645
|
release(lock, token) {
|
|
10446
10646
|
if (lockToken(lock) !== token)
|
|
10447
10647
|
return false;
|
|
10448
|
-
const tomb = `${lock}.release-${
|
|
10648
|
+
const tomb = `${lock}.release-${randomBytes2(6).toString("hex")}`;
|
|
10449
10649
|
try {
|
|
10450
|
-
|
|
10650
|
+
fs6.renameSync(lock, tomb);
|
|
10451
10651
|
} catch {
|
|
10452
10652
|
return false;
|
|
10453
10653
|
}
|
|
10454
10654
|
if (lockToken(tomb) !== token) {
|
|
10455
10655
|
try {
|
|
10456
|
-
|
|
10656
|
+
fs6.renameSync(tomb, lock);
|
|
10457
10657
|
} catch {}
|
|
10458
10658
|
return false;
|
|
10459
10659
|
}
|
|
10460
|
-
|
|
10660
|
+
fs6.rmSync(tomb, { recursive: true, force: true });
|
|
10461
10661
|
return true;
|
|
10462
10662
|
},
|
|
10463
10663
|
token: lockToken,
|
|
10464
10664
|
reap(lock, observed) {
|
|
10465
|
-
const tomb = `${lock}.reap-${
|
|
10665
|
+
const tomb = `${lock}.reap-${randomBytes2(6).toString("hex")}`;
|
|
10466
10666
|
try {
|
|
10467
|
-
|
|
10667
|
+
fs6.renameSync(lock, tomb);
|
|
10468
10668
|
} catch {
|
|
10469
10669
|
return false;
|
|
10470
10670
|
}
|
|
10471
10671
|
if (lockToken(tomb) === observed) {
|
|
10472
|
-
|
|
10672
|
+
fs6.rmSync(tomb, { recursive: true, force: true });
|
|
10473
10673
|
return true;
|
|
10474
10674
|
}
|
|
10475
10675
|
try {
|
|
10476
|
-
|
|
10676
|
+
fs6.renameSync(tomb, lock);
|
|
10477
10677
|
} catch {
|
|
10478
|
-
|
|
10678
|
+
fs6.rmSync(tomb, { recursive: true, force: true });
|
|
10479
10679
|
}
|
|
10480
10680
|
return false;
|
|
10481
10681
|
}
|
|
10482
10682
|
};
|
|
10483
|
-
function withLedgerLock(file, fn, waitMs = LOCK_WAIT_MS) {
|
|
10683
|
+
function withLedgerLock(file, fn, waitMs = hostAwareLockTimeout(LOCK_WAIT_MS)) {
|
|
10484
10684
|
const lock = `${file}.lock`;
|
|
10485
10685
|
const deadline = Date.now() + waitMs;
|
|
10486
10686
|
let token = null;
|
|
@@ -10509,7 +10709,7 @@ var TAIL_BYTES = 64 * 1024;
|
|
|
10509
10709
|
var lastLine = (fd, size) => {
|
|
10510
10710
|
for (let span = Math.min(size, TAIL_BYTES);span > 0; span = Math.min(size, span * 2)) {
|
|
10511
10711
|
const buffer = Buffer.alloc(span);
|
|
10512
|
-
|
|
10712
|
+
fs6.readSync(fd, buffer, 0, span, size - span);
|
|
10513
10713
|
const end = buffer.lastIndexOf(10);
|
|
10514
10714
|
if (end < 0) {
|
|
10515
10715
|
if (span === size)
|
|
@@ -10538,15 +10738,23 @@ var lastLine = (fd, size) => {
|
|
|
10538
10738
|
}
|
|
10539
10739
|
return null;
|
|
10540
10740
|
};
|
|
10741
|
+
var tighten = (fd) => {
|
|
10742
|
+
try {
|
|
10743
|
+
if ((fs6.fstatSync(fd).mode & 63) !== 0)
|
|
10744
|
+
fs6.fchmodSync(fd, 384);
|
|
10745
|
+
} catch {}
|
|
10746
|
+
};
|
|
10541
10747
|
function appendLine(file, build, options) {
|
|
10542
10748
|
const write = () => {
|
|
10543
|
-
const fd =
|
|
10749
|
+
const fd = fs6.openSync(file, "a+", 384);
|
|
10544
10750
|
try {
|
|
10545
|
-
|
|
10751
|
+
if (process.platform !== "win32")
|
|
10752
|
+
tighten(fd);
|
|
10753
|
+
const size = fs6.fstatSync(fd).size;
|
|
10546
10754
|
let fence = "";
|
|
10547
10755
|
if (size > 0) {
|
|
10548
10756
|
const last = Buffer.alloc(1);
|
|
10549
|
-
|
|
10757
|
+
fs6.readSync(fd, last, 0, 1, size - 1);
|
|
10550
10758
|
if (last[0] !== 10)
|
|
10551
10759
|
fence = `
|
|
10552
10760
|
`;
|
|
@@ -10555,16 +10763,16 @@ function appendLine(file, build, options) {
|
|
|
10555
10763
|
if (!line.ok)
|
|
10556
10764
|
return line;
|
|
10557
10765
|
const bytes = Buffer.from(fence + line.value);
|
|
10558
|
-
const written =
|
|
10766
|
+
const written = fs6.writeSync(fd, bytes);
|
|
10559
10767
|
if (written !== bytes.length)
|
|
10560
10768
|
return err("unavailable", `short write to ${file}: ${written} of ${bytes.length} bytes; the row is torn and will be skipped`);
|
|
10561
10769
|
return { ok: true, value: undefined };
|
|
10562
10770
|
} finally {
|
|
10563
|
-
|
|
10771
|
+
fs6.closeSync(fd);
|
|
10564
10772
|
}
|
|
10565
10773
|
};
|
|
10566
10774
|
try {
|
|
10567
|
-
|
|
10775
|
+
fs6.mkdirSync(path8.dirname(file), { recursive: true });
|
|
10568
10776
|
if (!options.forceLock && !onNetworkFs(path8.dirname(file)))
|
|
10569
10777
|
return write();
|
|
10570
10778
|
const locked = withLedgerLock(file, write);
|
|
@@ -10636,8 +10844,10 @@ function readLedger(cwd, options = {}) {
|
|
|
10636
10844
|
if (!file.ok)
|
|
10637
10845
|
return file;
|
|
10638
10846
|
let text = "";
|
|
10847
|
+
let stat = null;
|
|
10639
10848
|
try {
|
|
10640
|
-
|
|
10849
|
+
stat = fs6.statSync(file.value);
|
|
10850
|
+
text = fs6.readFileSync(file.value, "utf8");
|
|
10641
10851
|
} catch (error) {
|
|
10642
10852
|
if (error.code === "ENOENT")
|
|
10643
10853
|
return {
|
|
@@ -10656,6 +10866,7 @@ function readLedger(cwd, options = {}) {
|
|
|
10656
10866
|
const integrity = { chained: 0, legacy: 0, unverified: [] };
|
|
10657
10867
|
const earlier = new Set([null]);
|
|
10658
10868
|
let chainStarted = false;
|
|
10869
|
+
const ids = new Set;
|
|
10659
10870
|
for (const raw of text.split(`
|
|
10660
10871
|
`)) {
|
|
10661
10872
|
const trimmed = raw.trim();
|
|
@@ -10689,6 +10900,10 @@ function readLedger(cwd, options = {}) {
|
|
|
10689
10900
|
reason = "unsigned";
|
|
10690
10901
|
else
|
|
10691
10902
|
rowIntegrity = "legacy";
|
|
10903
|
+
if (options.integrity && !reason && ids.has(parsed.id))
|
|
10904
|
+
reason = "duplicate_id";
|
|
10905
|
+
if (options.integrity)
|
|
10906
|
+
ids.add(parsed.id);
|
|
10692
10907
|
if (reason)
|
|
10693
10908
|
rowIntegrity = "unverified";
|
|
10694
10909
|
const pr = typeof parsed.pr === "number" && Number.isInteger(parsed.pr) ? parsed.pr : undefined;
|
|
@@ -10712,7 +10927,8 @@ function readLedger(cwd, options = {}) {
|
|
|
10712
10927
|
seq: rows.length + 1,
|
|
10713
10928
|
superseded: false,
|
|
10714
10929
|
supersedeIgnored: false,
|
|
10715
|
-
...rowIntegrity ? { integrity: rowIntegrity } : {}
|
|
10930
|
+
...rowIntegrity ? { integrity: rowIntegrity } : {},
|
|
10931
|
+
...reason ? { integrityReason: reason } : {}
|
|
10716
10932
|
};
|
|
10717
10933
|
rows.push(row);
|
|
10718
10934
|
if (!options.integrity)
|
|
@@ -10740,18 +10956,48 @@ function readLedger(cwd, options = {}) {
|
|
|
10740
10956
|
const byId = new Map;
|
|
10741
10957
|
for (const row of rows) {
|
|
10742
10958
|
const target = row.supersedes ? byId.get(row.supersedes) : undefined;
|
|
10743
|
-
if (target && supersedeAllowed(row, target, renameAwareBranch(scopeOf, row.branch)) === null)
|
|
10959
|
+
if (target && row.integrity !== "unverified" && supersedeAllowed(row, target, renameAwareBranch(scopeOf, row.branch)) === null)
|
|
10744
10960
|
target.superseded = true;
|
|
10745
10961
|
else if (row.supersedes)
|
|
10746
10962
|
row.supersedeIgnored = true;
|
|
10747
10963
|
if (!byId.has(row.id))
|
|
10748
10964
|
byId.set(row.id, row);
|
|
10749
10965
|
}
|
|
10966
|
+
if (options.integrity && stat && Buffer.byteLength(text) === stat.size)
|
|
10967
|
+
tamperCache.set(file.value, {
|
|
10968
|
+
size: stat.size,
|
|
10969
|
+
stamp: fileStamp(stat),
|
|
10970
|
+
breaks: integrity.unverified.filter((row) => TAMPER_REASONS.has(row.reason))
|
|
10971
|
+
});
|
|
10750
10972
|
return {
|
|
10751
10973
|
ok: true,
|
|
10752
10974
|
value: { path: file.value, rows, skipped, integrity: options.integrity ? integrity : null }
|
|
10753
10975
|
};
|
|
10754
10976
|
}
|
|
10977
|
+
var TAMPER_REASONS = new Set(["hash_mismatch", "broken_link"]);
|
|
10978
|
+
var tamperCache = new Map;
|
|
10979
|
+
var fileStamp = (stat) => `${stat.ino}:${stat.mtimeMs}:${stat.ctimeMs}`;
|
|
10980
|
+
function ledgerTampering(cwd) {
|
|
10981
|
+
const file = ledgerPath(cwd);
|
|
10982
|
+
if (!file.ok)
|
|
10983
|
+
return [];
|
|
10984
|
+
try {
|
|
10985
|
+
const stat = fs6.statSync(file.value);
|
|
10986
|
+
const cached = tamperCache.get(file.value);
|
|
10987
|
+
if (cached && cached.size === stat.size && cached.stamp === fileStamp(stat))
|
|
10988
|
+
return cached.breaks;
|
|
10989
|
+
} catch {
|
|
10990
|
+
return [];
|
|
10991
|
+
}
|
|
10992
|
+
const read = readLedger(cwd, { integrity: true });
|
|
10993
|
+
return read.ok ? (read.value.integrity?.unverified ?? []).filter((row) => TAMPER_REASONS.has(row.reason)) : [];
|
|
10994
|
+
}
|
|
10995
|
+
function tamperedUnblock(cwd) {
|
|
10996
|
+
const file = ledgerPath(cwd);
|
|
10997
|
+
const where = file.ok ? file.value : "the ledger file";
|
|
10998
|
+
return `workit ledger verify-integrity # lists the edited or missing rows; if the edit is understood, move ${where} aside (mv ${where} ${where}.tampered-<date>) and record the verdicts again`;
|
|
10999
|
+
}
|
|
11000
|
+
var tamperedWarning = (cwd, breaks) => `the ledger was edited, or a row was removed or moved (${breaks.slice(0, 3).map((row) => `${row.reason} at row ${row.seq}`).join(", ")}${breaks.length > 3 ? ", …" : ""}): no verdict is accepted until it is repaired. ${tamperedUnblock(cwd)}`;
|
|
10755
11001
|
function verdictBasis(verdict, current) {
|
|
10756
11002
|
if (verdict.head && verdict.head === current.head)
|
|
10757
11003
|
return "fresh";
|
|
@@ -10759,7 +11005,7 @@ function verdictBasis(verdict, current) {
|
|
|
10759
11005
|
return "carried";
|
|
10760
11006
|
return "stale";
|
|
10761
11007
|
}
|
|
10762
|
-
var isLiveVerdict = (row) => row.type === "verdict" && typeof row.result === "string" && !row.
|
|
11008
|
+
var isLiveVerdict = (row, retired) => row.type === "verdict" && typeof row.result === "string" && !retired.has(row.id);
|
|
10763
11009
|
function trailerSessions(cwd, base, head) {
|
|
10764
11010
|
if (!base || !head || !safeRef(base) || !safeRef(head))
|
|
10765
11011
|
return [];
|
|
@@ -10840,6 +11086,56 @@ var authoredBy = (authors, session) => {
|
|
|
10840
11086
|
return true;
|
|
10841
11087
|
return false;
|
|
10842
11088
|
};
|
|
11089
|
+
var TRUST_REASONS = new Set([
|
|
11090
|
+
"unsigned",
|
|
11091
|
+
"hash_mismatch",
|
|
11092
|
+
"broken_link",
|
|
11093
|
+
"duplicate_id",
|
|
11094
|
+
"unissued_session",
|
|
11095
|
+
"unbound_session"
|
|
11096
|
+
]);
|
|
11097
|
+
var SHELL_VOUCHED_HOSTS = new Set([
|
|
11098
|
+
"cli",
|
|
11099
|
+
"opencode",
|
|
11100
|
+
"pi",
|
|
11101
|
+
"codex_cli",
|
|
11102
|
+
"codex_desktop"
|
|
11103
|
+
]);
|
|
11104
|
+
function sessionTrust(cwd, row) {
|
|
11105
|
+
const session = row.actor.session;
|
|
11106
|
+
if (!session || row.self === true)
|
|
11107
|
+
return null;
|
|
11108
|
+
const source = typeof row.sessionSource === "string" ? row.sessionSource : "env";
|
|
11109
|
+
const host = row.actor.host;
|
|
11110
|
+
const issuedOnly = source === "flag" ? host !== "cli" : source === "env" && session.includes(":") && !SHELL_VOUCHED_HOSTS.has(host);
|
|
11111
|
+
if (!issuedOnly)
|
|
11112
|
+
return null;
|
|
11113
|
+
const binding = issuedBinding(cwd, session);
|
|
11114
|
+
if (binding === null)
|
|
11115
|
+
return "unissued_session";
|
|
11116
|
+
return binding === "token" && row.sessionBound !== true ? "unbound_session" : null;
|
|
11117
|
+
}
|
|
11118
|
+
function trustReasons(cwd, row) {
|
|
11119
|
+
if (row.integrity === "unverified")
|
|
11120
|
+
return [row.integrityReason ?? "unsigned"];
|
|
11121
|
+
if (row.integrity !== "verified")
|
|
11122
|
+
return ["unsigned"];
|
|
11123
|
+
const session = sessionTrust(cwd, row);
|
|
11124
|
+
return session ? [session] : [];
|
|
11125
|
+
}
|
|
11126
|
+
function withIntegrity(cwd, rows) {
|
|
11127
|
+
if (rows.every((row) => row.integrity !== undefined))
|
|
11128
|
+
return rows;
|
|
11129
|
+
const read = readLedger(cwd, { integrity: true });
|
|
11130
|
+
const bySeq = new Map(read.ok ? read.value.rows.map((row) => [row.seq, row]) : []);
|
|
11131
|
+
return rows.map((row) => {
|
|
11132
|
+
if (row.integrity !== undefined)
|
|
11133
|
+
return row;
|
|
11134
|
+
const fresh = bySeq.get(row.seq);
|
|
11135
|
+
return fresh && fresh.id === row.id && fresh.at === row.at ? fresh : row;
|
|
11136
|
+
});
|
|
11137
|
+
}
|
|
11138
|
+
var untrusted = (reasons) => reasons.some((reason) => TRUST_REASONS.has(reason));
|
|
10843
11139
|
var independenceReasons = (row, authors) => {
|
|
10844
11140
|
const reasons = [];
|
|
10845
11141
|
if (!row.actor.session)
|
|
@@ -10850,15 +11146,17 @@ var independenceReasons = (row, authors) => {
|
|
|
10850
11146
|
reasons.push("self");
|
|
10851
11147
|
return reasons;
|
|
10852
11148
|
};
|
|
10853
|
-
function effectiveOf(rows,
|
|
11149
|
+
function effectiveOf(rows, reasonsOf, sameCode, isCurrent) {
|
|
10854
11150
|
let effective = null;
|
|
10855
11151
|
for (const row of rows) {
|
|
10856
11152
|
if (!effective) {
|
|
10857
11153
|
effective = row;
|
|
10858
11154
|
continue;
|
|
10859
11155
|
}
|
|
10860
|
-
|
|
10861
|
-
|
|
11156
|
+
if (untrusted(reasonsOf(row)) && !untrusted(reasonsOf(effective)))
|
|
11157
|
+
continue;
|
|
11158
|
+
const rowIndependent = reasonsOf(row).length === 0;
|
|
11159
|
+
const effIndependent = reasonsOf(effective).length === 0;
|
|
10862
11160
|
if (effIndependent && !rowIndependent && isCurrent(effective))
|
|
10863
11161
|
continue;
|
|
10864
11162
|
if (effIndependent && FAILING.has(String(effective.result)) && row.actor.session === effective.actor.session && sameCode(effective, row))
|
|
@@ -10872,7 +11170,7 @@ function restackCarries(rows, branch, from, to) {
|
|
|
10872
11170
|
return false;
|
|
10873
11171
|
const edges = new Map;
|
|
10874
11172
|
for (const row of rows)
|
|
10875
|
-
if (row.type === "stack.restacked" && row.observer === "workit_cli" && row.branch === branch && row.patchEqual === true && typeof row.fromHead === "string" && row.head)
|
|
11173
|
+
if (row.type === "stack.restacked" && row.observer === "workit_cli" && row.integrity === "verified" && row.branch === branch && row.patchEqual === true && typeof row.fromHead === "string" && row.head)
|
|
10876
11174
|
edges.set(row.fromHead, [...edges.get(row.fromHead) ?? [], row.head]);
|
|
10877
11175
|
const seen = new Set([from]);
|
|
10878
11176
|
const queue = [from];
|
|
@@ -10888,7 +11186,8 @@ function restackCarries(rows, branch, from, to) {
|
|
|
10888
11186
|
}
|
|
10889
11187
|
return false;
|
|
10890
11188
|
}
|
|
10891
|
-
function checkVerdicts(cwd, branch,
|
|
11189
|
+
function checkVerdicts(cwd, branch, ledgerRows) {
|
|
11190
|
+
const rows = withIntegrity(cwd, ledgerRows);
|
|
10892
11191
|
const head = branchHead(cwd, branch);
|
|
10893
11192
|
const fallbackBase = defaultBase(cwd);
|
|
10894
11193
|
const scope = branchScope(cwd, branch);
|
|
@@ -10918,21 +11217,47 @@ function checkVerdicts(cwd, branch, rows) {
|
|
|
10918
11217
|
return verdictBasis(failed, { head: row.head, ...keys }) !== "stale" || restackCarries(rows, branch, failed.head, row.head);
|
|
10919
11218
|
};
|
|
10920
11219
|
const authors = authorSessions(cwd, rows, branch, { base: fallbackBase, head }, scope);
|
|
11220
|
+
const failureAuthorCache = new Map;
|
|
11221
|
+
const failureAuthors = (row) => {
|
|
11222
|
+
let set = failureAuthorCache.get(row.seq);
|
|
11223
|
+
if (!set) {
|
|
11224
|
+
set = authorSessions(cwd, rows.filter((other) => other.integrity === "verified" && other.seq < row.seq), branch, { base: null, head: null }, scope);
|
|
11225
|
+
failureAuthorCache.set(row.seq, set);
|
|
11226
|
+
}
|
|
11227
|
+
return set;
|
|
11228
|
+
};
|
|
11229
|
+
const reasonCache = new Map;
|
|
11230
|
+
const reasonsOf = (row) => {
|
|
11231
|
+
let reasons = reasonCache.get(row);
|
|
11232
|
+
if (!reasons) {
|
|
11233
|
+
const failing = FAILING.has(String(row.result));
|
|
11234
|
+
reasons = [
|
|
11235
|
+
...trustReasons(cwd, row),
|
|
11236
|
+
...independenceReasons(row, failing ? failureAuthors(row) : authors)
|
|
11237
|
+
];
|
|
11238
|
+
reasonCache.set(row, reasons);
|
|
11239
|
+
}
|
|
11240
|
+
return reasons;
|
|
11241
|
+
};
|
|
11242
|
+
const retired = new Set;
|
|
11243
|
+
for (const row of rows)
|
|
11244
|
+
if (row.type === "verdict" && row.supersedes && !row.supersedeIgnored && trustReasons(cwd, row).length === 0)
|
|
11245
|
+
retired.add(row.supersedes);
|
|
10921
11246
|
const byKind = new Map;
|
|
10922
11247
|
for (const row of rows)
|
|
10923
|
-
if (isLiveVerdict(row) && scope(row)) {
|
|
11248
|
+
if (isLiveVerdict(row, retired) && scope(row)) {
|
|
10924
11249
|
const kind = typeof row.kind === "string" ? row.kind : "review";
|
|
10925
11250
|
byKind.set(kind, [...byKind.get(kind) ?? [], row]);
|
|
10926
11251
|
}
|
|
10927
11252
|
const verdicts = [];
|
|
10928
11253
|
const warnings = [];
|
|
10929
11254
|
for (const [kind, list] of byKind) {
|
|
10930
|
-
const row = effectiveOf(list,
|
|
11255
|
+
const row = effectiveOf(list, reasonsOf, sameCode, (candidate) => basisOf(candidate) !== "stale");
|
|
10931
11256
|
if (!row)
|
|
10932
11257
|
continue;
|
|
10933
11258
|
const basis = basisOf(row);
|
|
10934
11259
|
const current = basis !== "stale";
|
|
10935
|
-
const independence =
|
|
11260
|
+
const independence = reasonsOf(row);
|
|
10936
11261
|
const reasons = [
|
|
10937
11262
|
...current ? [] : ["stale"],
|
|
10938
11263
|
...PASSING.has(String(row.result)) ? [] : ["not_passing"],
|
|
@@ -10949,23 +11274,31 @@ function checkVerdicts(cwd, branch, rows) {
|
|
|
10949
11274
|
});
|
|
10950
11275
|
if (reasons.length === 0) {
|
|
10951
11276
|
for (const later of list)
|
|
10952
|
-
if (later.seq > row.seq &&
|
|
11277
|
+
if (later.seq > row.seq && reasonsOf(later).length > 0 && !untrusted(reasonsOf(later)) && FAILING.has(String(later.result)) && basisOf(later) !== "stale")
|
|
10953
11278
|
warnings.push(`the author's own (self) ${String(later.result)} ${kind} verdict ${later.id} by ${later.actor.session ?? "no session"} on the current code is newer than the accepted pass ${row.id}: read it before merging`);
|
|
10954
11279
|
}
|
|
10955
11280
|
}
|
|
10956
11281
|
for (const row of rows)
|
|
10957
11282
|
if (row.integrity === "unverified" && scope(row))
|
|
10958
11283
|
warnings.push(`ledger row ${row.id} (${row.type}) on ${row.branch ?? branch} is unverified: the workit CLI did not write it or it changed afterwards (workit ledger verify-integrity)`);
|
|
11284
|
+
const tampered = ledgerTampering(cwd);
|
|
11285
|
+
if (tampered.length) {
|
|
11286
|
+
warnings.push(tamperedWarning(cwd, tampered));
|
|
11287
|
+
for (const entry of verdicts) {
|
|
11288
|
+
entry.accepted = false;
|
|
11289
|
+
entry.reasons = [...entry.reasons, "ledger_tampered"];
|
|
11290
|
+
}
|
|
11291
|
+
}
|
|
10959
11292
|
verdicts.sort((a, b) => a.verdict.seq - b.verdict.seq);
|
|
10960
11293
|
const newestCurrent = verdicts.findLast((entry) => entry.current) ?? verdicts.at(-1) ?? null;
|
|
10961
11294
|
const failing = verdicts.find((entry) => entry.current && entry.independent && FAILING.has(String(entry.verdict.result)));
|
|
10962
11295
|
const newestAccepted = verdicts.findLast((entry) => entry.accepted && STRONG_RESULTS.has(String(entry.verdict.result))) ?? null;
|
|
10963
|
-
const accepted = newestAccepted && !failing ? { accepted: true, verdict: newestAccepted.verdict, reasons: [] } : {
|
|
11296
|
+
const accepted = tampered.length ? { accepted: false, verdict: null, reasons: ["ledger_tampered"] } : newestAccepted && !failing ? { accepted: true, verdict: newestAccepted.verdict, reasons: [] } : {
|
|
10964
11297
|
accepted: false,
|
|
10965
11298
|
verdict: failing?.verdict ?? null,
|
|
10966
11299
|
reasons: failing ? ["failing_verdict"] : !newestCurrent ? ["no_verdict"] : newestCurrent.reasons.length ? newestCurrent.reasons : ["type_check_only"]
|
|
10967
11300
|
};
|
|
10968
|
-
const selfEntry = failing ? undefined : verdicts.findLast((entry) => entry.current && !entry.independent && STRONG_RESULTS.has(String(entry.verdict.result)));
|
|
11301
|
+
const selfEntry = failing ? undefined : verdicts.findLast((entry) => entry.current && !entry.independent && !untrusted(entry.reasons) && STRONG_RESULTS.has(String(entry.verdict.result)));
|
|
10969
11302
|
return {
|
|
10970
11303
|
branch,
|
|
10971
11304
|
head,
|
|
@@ -11931,12 +12264,12 @@ function sameFileIdentity(left, right, platform = process.platform) {
|
|
|
11931
12264
|
}
|
|
11932
12265
|
|
|
11933
12266
|
// node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.js
|
|
11934
|
-
import { randomBytes as
|
|
12267
|
+
import { randomBytes as randomBytes3 } from "node:crypto";
|
|
11935
12268
|
import fsSync from "node:fs";
|
|
11936
12269
|
import path11 from "node:path";
|
|
11937
12270
|
|
|
11938
12271
|
// node_modules/@openclaw/fs-safe/dist/bounded-read.js
|
|
11939
|
-
import
|
|
12272
|
+
import fs7 from "node:fs";
|
|
11940
12273
|
|
|
11941
12274
|
// node_modules/@openclaw/fs-safe/dist/byte-budget.js
|
|
11942
12275
|
function normalizeMaxBytes(value, options = {}) {
|
|
@@ -11973,7 +12306,7 @@ function readFileDescriptorBoundedSync(fd, maxBytes) {
|
|
|
11973
12306
|
let total = 0;
|
|
11974
12307
|
while (true) {
|
|
11975
12308
|
const length = nextReadLength(total, maxBytes, scratch.length);
|
|
11976
|
-
const bytesRead =
|
|
12309
|
+
const bytesRead = fs7.readSync(fd, scratch, 0, length, null);
|
|
11977
12310
|
if (bytesRead === 0) {
|
|
11978
12311
|
return Buffer.concat(chunks, total);
|
|
11979
12312
|
}
|
|
@@ -11982,9 +12315,9 @@ function readFileDescriptorBoundedSync(fd, maxBytes) {
|
|
|
11982
12315
|
}
|
|
11983
12316
|
|
|
11984
12317
|
// node_modules/@openclaw/fs-safe/dist/read-open-flags.js
|
|
11985
|
-
import
|
|
12318
|
+
import fs8 from "node:fs";
|
|
11986
12319
|
function resolveReadOpenFlags(options) {
|
|
11987
|
-
const constants = options?.constants ??
|
|
12320
|
+
const constants = options?.constants ?? fs8.constants;
|
|
11988
12321
|
const noFollow = process.platform !== "win32" && options?.followSymlinks !== true && typeof constants.O_NOFOLLOW === "number" ? constants.O_NOFOLLOW : 0;
|
|
11989
12322
|
const nonBlocking = process.platform !== "win32" && typeof constants.O_NONBLOCK === "number" ? constants.O_NONBLOCK : 0;
|
|
11990
12323
|
return constants.O_RDONLY | noFollow | nonBlocking;
|
|
@@ -12001,7 +12334,7 @@ function parseSidecarLockSnapshot(snapshot, parser) {
|
|
|
12001
12334
|
}
|
|
12002
12335
|
function createSidecarLockOwnershipToken() {
|
|
12003
12336
|
let token = SIDECAR_LOCK_OWNERSHIP_TOKEN_PREFIX;
|
|
12004
|
-
for (const byte of
|
|
12337
|
+
for (const byte of randomBytes3(SIDECAR_LOCK_OWNERSHIP_TOKEN_BYTES)) {
|
|
12005
12338
|
for (let bit = 7;bit >= 0; bit -= 1) {
|
|
12006
12339
|
token += byte & 1 << bit ? "\t" : " ";
|
|
12007
12340
|
}
|
|
@@ -12175,7 +12508,7 @@ function getFsSafeLockConfig() {
|
|
|
12175
12508
|
}
|
|
12176
12509
|
|
|
12177
12510
|
// node_modules/@openclaw/fs-safe/dist/file-lock-sync.js
|
|
12178
|
-
import
|
|
12511
|
+
import fs9 from "node:fs";
|
|
12179
12512
|
import path12 from "node:path";
|
|
12180
12513
|
|
|
12181
12514
|
// node_modules/@openclaw/fs-safe/dist/timing.js
|
|
@@ -12206,7 +12539,7 @@ function releaseAllSyncHeldLocks() {
|
|
|
12206
12539
|
}
|
|
12207
12540
|
try {
|
|
12208
12541
|
if (held.fd !== undefined)
|
|
12209
|
-
|
|
12542
|
+
fs9.closeSync(held.fd);
|
|
12210
12543
|
} catch {}
|
|
12211
12544
|
try {
|
|
12212
12545
|
removeSidecarLockIfUnchangedSync(held.lockPath, held.snapshot);
|
|
@@ -12241,7 +12574,7 @@ function releaseSyncHeldLock(held) {
|
|
|
12241
12574
|
if (held.fd !== undefined) {
|
|
12242
12575
|
const fd = held.fd;
|
|
12243
12576
|
held.fd = undefined;
|
|
12244
|
-
|
|
12577
|
+
fs9.closeSync(fd);
|
|
12245
12578
|
}
|
|
12246
12579
|
removeSidecarLockIfUnchangedSync(held.lockPath, held.snapshot);
|
|
12247
12580
|
heldLocks.delete(held.normalizedTargetPath);
|
|
@@ -12264,11 +12597,11 @@ function createSyncHeldLockHandle(held) {
|
|
|
12264
12597
|
};
|
|
12265
12598
|
}
|
|
12266
12599
|
function canonicalLockParentSync(parent) {
|
|
12267
|
-
return process.platform === "win32" ?
|
|
12600
|
+
return process.platform === "win32" ? fs9.realpathSync.native(parent) : fs9.realpathSync(parent);
|
|
12268
12601
|
}
|
|
12269
12602
|
function normalizeTargetPath(targetPath) {
|
|
12270
12603
|
const resolved = path12.resolve(targetPath);
|
|
12271
|
-
|
|
12604
|
+
fs9.mkdirSync(path12.dirname(resolved), { recursive: true });
|
|
12272
12605
|
try {
|
|
12273
12606
|
return path12.join(canonicalLockParentSync(path12.dirname(resolved)), path12.basename(resolved));
|
|
12274
12607
|
} catch {
|
|
@@ -12296,7 +12629,7 @@ function defaultShouldReclaim(snapshot, staleMs, nowMs) {
|
|
|
12296
12629
|
}
|
|
12297
12630
|
function reclaimGuardExists(reclaimGuardPath) {
|
|
12298
12631
|
try {
|
|
12299
|
-
|
|
12632
|
+
fs9.lstatSync(reclaimGuardPath);
|
|
12300
12633
|
return true;
|
|
12301
12634
|
} catch (error) {
|
|
12302
12635
|
if (error.code === "ENOENT")
|
|
@@ -12360,19 +12693,19 @@ function acquireFileLockSync(targetPath, options) {
|
|
|
12360
12693
|
const { raw, ownershipToken } = serializeSidecarLockPayload(payload);
|
|
12361
12694
|
let lockFileCreateDenied = false;
|
|
12362
12695
|
try {
|
|
12363
|
-
const noFollow = process.platform !== "win32" && typeof
|
|
12696
|
+
const noFollow = process.platform !== "win32" && typeof fs9.constants.O_NOFOLLOW === "number" ? fs9.constants.O_NOFOLLOW : 0;
|
|
12364
12697
|
try {
|
|
12365
|
-
fd =
|
|
12698
|
+
fd = fs9.openSync(lockPath, fs9.constants.O_WRONLY | fs9.constants.O_CREAT | fs9.constants.O_EXCL | noFollow, 384);
|
|
12366
12699
|
} catch (error) {
|
|
12367
12700
|
lockFileCreateDenied = isTransientLockFileDenial(error, lockPath);
|
|
12368
12701
|
throw error;
|
|
12369
12702
|
}
|
|
12370
|
-
|
|
12371
|
-
|
|
12703
|
+
fs9.writeFileSync(fd, raw, "utf8");
|
|
12704
|
+
fs9.fsyncSync(fd);
|
|
12372
12705
|
const snapshot = {
|
|
12373
12706
|
raw,
|
|
12374
12707
|
payload,
|
|
12375
|
-
stat:
|
|
12708
|
+
stat: fs9.fstatSync(fd),
|
|
12376
12709
|
ownershipToken
|
|
12377
12710
|
};
|
|
12378
12711
|
const createdHeld = {
|
|
@@ -12407,8 +12740,8 @@ function acquireFileLockSync(targetPath, options) {
|
|
|
12407
12740
|
return returnedHandle;
|
|
12408
12741
|
} catch (error) {
|
|
12409
12742
|
if (fd !== undefined) {
|
|
12410
|
-
const failed = { payload: null, stat:
|
|
12411
|
-
|
|
12743
|
+
const failed = { payload: null, stat: fs9.fstatSync(fd) };
|
|
12744
|
+
fs9.closeSync(fd);
|
|
12412
12745
|
fd = undefined;
|
|
12413
12746
|
removeSidecarLockIfUnchangedSync(lockPath, failed);
|
|
12414
12747
|
}
|
|
@@ -12457,7 +12790,7 @@ function acquireFileLockSync(targetPath, options) {
|
|
|
12457
12790
|
})) {
|
|
12458
12791
|
let ownsReclaimGuard = false;
|
|
12459
12792
|
try {
|
|
12460
|
-
|
|
12793
|
+
fs9.mkdirSync(reclaimGuardPath);
|
|
12461
12794
|
ownsReclaimGuard = true;
|
|
12462
12795
|
if (removeSidecarLockIfUnchangedSync(lockPath, snapshot))
|
|
12463
12796
|
continue;
|
|
@@ -12470,7 +12803,7 @@ function acquireFileLockSync(targetPath, options) {
|
|
|
12470
12803
|
} finally {
|
|
12471
12804
|
if (ownsReclaimGuard) {
|
|
12472
12805
|
try {
|
|
12473
|
-
|
|
12806
|
+
fs9.rmdirSync(reclaimGuardPath);
|
|
12474
12807
|
} catch {}
|
|
12475
12808
|
}
|
|
12476
12809
|
}
|
|
@@ -12499,146 +12832,6 @@ var packageRoot = () => {
|
|
|
12499
12832
|
}
|
|
12500
12833
|
};
|
|
12501
12834
|
|
|
12502
|
-
// packages/workit-core/src/core/store-lock.ts
|
|
12503
|
-
import { spawnSync as spawnSync8 } from "node:child_process";
|
|
12504
|
-
import fs9 from "node:fs";
|
|
12505
|
-
import { hostname } from "node:os";
|
|
12506
|
-
var metadataLockSchema = object({
|
|
12507
|
-
pid: number2().int().nonnegative().safe(),
|
|
12508
|
-
processStart: string2().nullable(),
|
|
12509
|
-
host: string2().min(1),
|
|
12510
|
-
nonce: string2().min(1),
|
|
12511
|
-
externalAction: literal(true).optional()
|
|
12512
|
-
}).strict();
|
|
12513
|
-
var FOREIGN_LOCK_TTL_MS = 10 * 60000;
|
|
12514
|
-
var UNREADABLE_LOCK_TTL_MS = 30000;
|
|
12515
|
-
var RECLAIM_GUARD_TTL_MS = 30000;
|
|
12516
|
-
var defaultLockTimeoutMs = 250;
|
|
12517
|
-
var defaultLockTimeout = () => defaultLockTimeoutMs;
|
|
12518
|
-
var parseMetadataLock = (raw) => {
|
|
12519
|
-
let value;
|
|
12520
|
-
try {
|
|
12521
|
-
value = JSON.parse(raw);
|
|
12522
|
-
} catch {
|
|
12523
|
-
throw Object.assign(new Error("metadata lock is invalid"), { code: "metadata_lock_invalid" });
|
|
12524
|
-
}
|
|
12525
|
-
const parsed = metadataLockSchema.safeParse(value);
|
|
12526
|
-
if (!parsed.success)
|
|
12527
|
-
throw Object.assign(new Error("metadata lock is invalid"), { code: "metadata_lock_invalid" });
|
|
12528
|
-
return parsed.data;
|
|
12529
|
-
};
|
|
12530
|
-
var parseMetadataLockOrNull = (raw) => {
|
|
12531
|
-
try {
|
|
12532
|
-
return parseMetadataLock(raw);
|
|
12533
|
-
} catch {
|
|
12534
|
-
return null;
|
|
12535
|
-
}
|
|
12536
|
-
};
|
|
12537
|
-
var parseProcStatStart = (stat) => {
|
|
12538
|
-
const close = stat.lastIndexOf(")");
|
|
12539
|
-
if (close < 0)
|
|
12540
|
-
return null;
|
|
12541
|
-
return stat.slice(close + 1).trim().split(/\s+/)[19] ?? null;
|
|
12542
|
-
};
|
|
12543
|
-
var processStartOf = (pid) => {
|
|
12544
|
-
if (process.platform === "linux") {
|
|
12545
|
-
try {
|
|
12546
|
-
return parseProcStatStart(fs9.readFileSync(`/proc/${pid}/stat`, "utf8"));
|
|
12547
|
-
} catch {
|
|
12548
|
-
return null;
|
|
12549
|
-
}
|
|
12550
|
-
}
|
|
12551
|
-
if (process.platform === "darwin" || process.platform === "freebsd") {
|
|
12552
|
-
try {
|
|
12553
|
-
const run = spawnSync8("ps", ["-o", "lstart=", "-p", String(pid)], {
|
|
12554
|
-
encoding: "utf8",
|
|
12555
|
-
timeout: 1000
|
|
12556
|
-
});
|
|
12557
|
-
const value = run.status === 0 ? run.stdout.trim() : "";
|
|
12558
|
-
return value || null;
|
|
12559
|
-
} catch {
|
|
12560
|
-
return null;
|
|
12561
|
-
}
|
|
12562
|
-
}
|
|
12563
|
-
return null;
|
|
12564
|
-
};
|
|
12565
|
-
var readTrimmed = (file) => {
|
|
12566
|
-
try {
|
|
12567
|
-
return fs9.readFileSync(file, "utf8").trim() || null;
|
|
12568
|
-
} catch {
|
|
12569
|
-
return null;
|
|
12570
|
-
}
|
|
12571
|
-
};
|
|
12572
|
-
var cachedLockHost = null;
|
|
12573
|
-
var localLockHost = () => {
|
|
12574
|
-
if (cachedLockHost !== null)
|
|
12575
|
-
return cachedLockHost;
|
|
12576
|
-
let pidns = null;
|
|
12577
|
-
try {
|
|
12578
|
-
pidns = /\[(\d+)\]/.exec(fs9.readlinkSync("/proc/self/ns/pid"))?.[1] ?? null;
|
|
12579
|
-
} catch {}
|
|
12580
|
-
const boot = readTrimmed("/proc/sys/kernel/random/boot_id");
|
|
12581
|
-
cachedLockHost = pidns || boot ? `${hostname()}#${pidns ?? "?"}:${boot ?? "?"}` : hostname();
|
|
12582
|
-
return cachedLockHost;
|
|
12583
|
-
};
|
|
12584
|
-
var pidAlive = (pid) => {
|
|
12585
|
-
if (!Number.isSafeInteger(pid) || pid <= 0)
|
|
12586
|
-
return false;
|
|
12587
|
-
try {
|
|
12588
|
-
process.kill(pid, 0);
|
|
12589
|
-
return true;
|
|
12590
|
-
} catch (error) {
|
|
12591
|
-
return error.code === "EPERM";
|
|
12592
|
-
}
|
|
12593
|
-
};
|
|
12594
|
-
var TASK_STORE_LOCK = { reclaimUnverifiedAfterTtl: true };
|
|
12595
|
-
var classifyLockOwner = (payload, ageMs, localHost = localLockHost(), options = {}) => {
|
|
12596
|
-
const lock = metadataLockSchema.safeParse(payload);
|
|
12597
|
-
if (!lock.success)
|
|
12598
|
-
return ageMs !== null && ageMs > UNREADABLE_LOCK_TTL_MS ? { state: "stale", reason: "unreadable lock left behind" } : { state: "unknown", reason: "lock is being written" };
|
|
12599
|
-
const { pid, processStart, host } = lock.data;
|
|
12600
|
-
const [lockName, lockSpace] = host.split("#");
|
|
12601
|
-
const [localName, localSpace] = localHost.split("#");
|
|
12602
|
-
if (lockSpace && localSpace && lockName === localName) {
|
|
12603
|
-
const [lockNs, lockBoot] = lockSpace.split(":");
|
|
12604
|
-
const [localNs, localBoot] = localSpace.split(":");
|
|
12605
|
-
if (lockNs === localNs && lockNs !== "?" && lockBoot !== "?" && lockBoot !== localBoot)
|
|
12606
|
-
return { state: "stale", reason: "lock was taken before this machine rebooted" };
|
|
12607
|
-
}
|
|
12608
|
-
if (host !== localHost)
|
|
12609
|
-
return ageMs !== null && ageMs > FOREIGN_LOCK_TTL_MS ? { state: "stale", reason: `lock from ${host} is older than its TTL` } : { state: "unknown", reason: `lock is held from ${host}; its pid cannot be checked here` };
|
|
12610
|
-
if (!pidAlive(pid))
|
|
12611
|
-
return { state: "stale", reason: `pid ${pid} is not running` };
|
|
12612
|
-
const currentStart = processStartOf(pid);
|
|
12613
|
-
if (processStart !== null && currentStart !== null && processStart !== currentStart)
|
|
12614
|
-
return { state: "stale", reason: `pid ${pid} now belongs to a different process` };
|
|
12615
|
-
if (options.reclaimUnverifiedAfterTtl === true && (processStart === null || currentStart === null) && ageMs !== null && ageMs > FOREIGN_LOCK_TTL_MS)
|
|
12616
|
-
return {
|
|
12617
|
-
state: "stale",
|
|
12618
|
-
reason: `pid ${pid} is running but the lock is older than its TTL and the pid's start time cannot be checked (reused pid)`
|
|
12619
|
-
};
|
|
12620
|
-
return { state: "live", reason: `held by running pid ${pid}` };
|
|
12621
|
-
};
|
|
12622
|
-
var ageOf = (file, nowMs) => {
|
|
12623
|
-
try {
|
|
12624
|
-
return nowMs - fs9.lstatSync(file).mtimeMs;
|
|
12625
|
-
} catch {
|
|
12626
|
-
return null;
|
|
12627
|
-
}
|
|
12628
|
-
};
|
|
12629
|
-
var clearAbandonedReclaimGuard = (lockPath, nowMs = Date.now()) => {
|
|
12630
|
-
const guard = `${lockPath}.reclaim`;
|
|
12631
|
-
const age = ageOf(guard, nowMs);
|
|
12632
|
-
if (age === null || age <= RECLAIM_GUARD_TTL_MS)
|
|
12633
|
-
return false;
|
|
12634
|
-
try {
|
|
12635
|
-
fs9.rmdirSync(guard);
|
|
12636
|
-
return true;
|
|
12637
|
-
} catch {
|
|
12638
|
-
return false;
|
|
12639
|
-
}
|
|
12640
|
-
};
|
|
12641
|
-
|
|
12642
12835
|
// packages/workit-core/src/store/event-log.ts
|
|
12643
12836
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
12644
12837
|
import fs10 from "node:fs";
|
|
@@ -20313,10 +20506,10 @@ function stopObligation(input, descriptor) {
|
|
|
20313
20506
|
const branch = currentBranch(input.cwd);
|
|
20314
20507
|
if (!branch || guardedBranch(input.cwd, branch))
|
|
20315
20508
|
return null;
|
|
20316
|
-
const ledger = readLedger(input.cwd);
|
|
20509
|
+
const ledger = readLedger(input.cwd, { integrity: true });
|
|
20317
20510
|
if (!ledger.ok)
|
|
20318
20511
|
return null;
|
|
20319
|
-
const all = ledger.value.rows;
|
|
20512
|
+
const all = ledger.value.rows.filter((row) => row.integrity !== "unverified");
|
|
20320
20513
|
const rows = all.filter((row) => row.branch === branch);
|
|
20321
20514
|
const mine = rows.filter((row) => sessionOf(row) === session);
|
|
20322
20515
|
const commits = new Set(mine.filter((row) => row.type === "commit.recorded").map((row) => typeof row.sha === "string" ? row.sha : row.head).filter((sha) => typeof sha === "string"));
|
|
@@ -20692,9 +20885,9 @@ ${subagentStartText({ ...input, cwd: "" }, descriptor, event)}`;
|
|
|
20692
20885
|
if (role !== "judge" || !event.agentId)
|
|
20693
20886
|
return readOnly;
|
|
20694
20887
|
const own = subagentSession(session, event.agentId);
|
|
20695
|
-
|
|
20696
|
-
|
|
20697
|
-
return `${readOnly} Its own Workit session is ${own}: record verdicts with \`workit ledger verdict <result>
|
|
20888
|
+
const token = session ? recordIssuedSession(input.cwd || process.cwd(), session, own) : null;
|
|
20889
|
+
const flags = token ? `--session ${own} --token ${token}` : `--session ${own}`;
|
|
20890
|
+
return `${readOnly} Its own Workit session is ${own}: record verdicts with \`workit ledger verdict <result> ${flags} ...\` so the ledger tells it apart from the author.${token ? " The token is this subagent's alone: never write it into a report, a file or a commit. --token needs a workit CLI at least as new as this plugin; if the CLI answers that --token is an unknown option, update the workit CLI (workit upgrade --cli) and record again." : ""}`;
|
|
20698
20891
|
};
|
|
20699
20892
|
function handleHook(input, deps) {
|
|
20700
20893
|
const { descriptor } = deps;
|