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