@brainervirus/workit-cursor 8.24.0 → 8.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6981,6 +6981,26 @@ function resolveRef(cwd, ref) {
6981
6981
  return null;
6982
6982
  return line(git(cwd, ["rev-parse", "--verify", "-q", `${ref}^{commit}`]));
6983
6983
  }
6984
+ function originHeadRef(cwd) {
6985
+ return line(git(cwd, ["symbolic-ref", "-q", "--short", "refs/remotes/origin/HEAD"]));
6986
+ }
6987
+ function ownChangeDrift(cwd, range) {
6988
+ const before = diffRaw(cwd, range.fromFork, range.from);
6989
+ const after = diffRaw(cwd, range.toFork, range.to);
6990
+ if (!before || !after)
6991
+ return null;
6992
+ const judged = new Map(before.map((entry) => [entry.path, entry]));
6993
+ const current = new Map(after.map((entry) => [entry.path, entry]));
6994
+ const same = (a, b) => a !== undefined && b !== undefined && a.oldMode === b.oldMode && a.oldBlob === b.oldBlob && a.newMode === b.newMode && a.newBlob === b.newBlob;
6995
+ return [...new Set([...judged.keys(), ...current.keys()])].filter((file) => !same(judged.get(file), current.get(file))).toSorted();
6996
+ }
6997
+ function sameOwnChange(cwd, range) {
6998
+ const fromFork = mergeBase(cwd, range.fromBase, range.from);
6999
+ const toFork = mergeBase(cwd, range.toBase, range.to);
7000
+ if (!fromFork || !toFork)
7001
+ return false;
7002
+ return ownChangeDrift(cwd, { from: range.from, to: range.to, fromFork, toFork })?.length === 0;
7003
+ }
6984
7004
  function diffNumstat(cwd, fork, head) {
6985
7005
  const range = laneRange(fork, head);
6986
7006
  if (!range)
@@ -8832,2054 +8852,2144 @@ var init_vcs_config = __esm(() => {
8832
8852
  TRACKERS = new Set(["github", "gitlab", "youtrack"]);
8833
8853
  });
8834
8854
 
8835
- // packages/workit-core/src/core/commit-flavors.ts
8836
- var init_commit_flavors = () => {};
8837
-
8838
- // packages/workit-core/src/core/git.ts
8839
- var init_git = () => {};
8840
-
8841
- // packages/workit-core/src/core/branch.ts
8842
- import { createHash as createHash6 } from "node:crypto";
8843
- function effectiveWorkspacePolicy(workspaceRoot, kind, profileName) {
8844
- const selected = resolveRuntimeWorkspacePolicy(workspaceRoot, kind, profileName?.trim() || process.env.WORKFLOW_PROFILE?.trim() || undefined);
8845
- const config = readConfig();
8846
- return kind === "branch" ? {
8847
- branchPolicy: withReleaseTracks(workspaceRoot, resolveBranchPolicy(config, selected.policy ? { branchPolicy: selected.policy } : null, () => repoBranchPreset(workspaceRoot))),
8848
- provenance: { branchPolicy: selected.source }
8849
- } : {
8850
- commitPolicy: resolveCommitPolicy(config, selected.policy ? { commitPolicy: selected.policy } : null),
8851
- provenance: { commitPolicy: selected.source }
8852
- };
8853
- }
8854
- var withReleaseTracks = (workspaceRoot, policy) => {
8855
- const read = workspaceReleaseTracks(workspaceRoot);
8856
- if (read.error)
8857
- throw new ReleaseTrackError(read.error);
8858
- if (read.tracks.length === 0 && read.protectedBranches.length === 0)
8859
- return policy;
8860
- const known = new Set(policy.allowed.map((pattern) => pattern.source));
8861
- const allowed = [...policy.allowed];
8862
- for (const glob of read.tracks.flatMap((track) => Object.values(track.naming).map(namingGlob))) {
8863
- const pattern = branchGlobPattern(glob);
8864
- if (!known.has(pattern.source)) {
8865
- known.add(pattern.source);
8866
- allowed.push(pattern);
8867
- }
8868
- }
8869
- return {
8870
- ...policy,
8871
- allowed,
8872
- protected: new Set([
8873
- ...policy.protected,
8874
- ...[...trackBranches(read.tracks), ...read.protectedBranches].map((name) => name.toLowerCase())
8875
- ])
8876
- };
8877
- }, KIND_SYNONYMS, allowedKinds = (allowed) => [
8878
- ...new Set(allowed.map((pattern) => /^\^([\w.-]+)\\?\/\.\*\$$/u.exec(pattern.source)?.[1]?.toLowerCase()).filter((kind) => Boolean(kind)))
8879
- ], editDistance = (a, b) => {
8880
- const row = Array.from({ length: b.length + 1 }, (_, index) => index);
8881
- for (let i = 1;i <= a.length; i++) {
8882
- let previous = row[0];
8883
- row[0] = i;
8884
- for (let j = 1;j <= b.length; j++) {
8885
- const saved = row[j];
8886
- row[j] = Math.min(row[j] + 1, row[j - 1] + 1, previous + (a[i - 1] === b[j - 1] ? 0 : 1));
8887
- previous = saved;
8888
- }
8889
- }
8890
- return row[b.length];
8891
- }, suggestBranchName = (name, kinds) => {
8892
- if (!kinds.length)
8893
- return null;
8894
- const slash = name.indexOf("/");
8895
- const prefix = slash > 0 ? name.slice(0, slash).toLowerCase() : "";
8896
- const rest = (slash > 0 ? name.slice(slash + 1) : name) || "name";
8897
- const synonym = KIND_SYNONYMS[prefix];
8898
- const near = kinds.map((kind) => ({ kind, distance: editDistance(prefix, kind) })).filter((entry) => prefix && entry.distance <= 2).toSorted((a, b) => a.distance - b.distance)[0]?.kind;
8899
- const kind = (synonym && kinds.includes(synonym) ? synonym : null) ?? near ?? (kinds.includes("feature") ? "feature" : kinds[0]);
8900
- return `${kind}/${rest}`;
8901
- }, kindList = (kinds) => kinds.map((kind) => `${kind}/`).join(", "), aliasNote = (kinds) => {
8902
- const notes = [
8903
- ["fix", "bugfix"],
8904
- ["chore", "feature"]
8905
- ].filter(([, kind]) => kinds.includes(kind)).map(([alias, kind]) => `${alias}/ is ${kind}/`);
8906
- return notes.length ? ` (${notes.join(", ")})` : "";
8907
- }, validateBranchNamePolicy = (name, policy, source) => {
8908
- const allowed = policy.allowed.map((pattern) => `/${pattern.source}/${pattern.flags}`);
8909
- const kinds = allowedKinds(policy.allowed);
8910
- if (policy.protected.has(name.toLowerCase())) {
8911
- const correction = kinds.length ? `choose a non-protected branch of an allowed kind (${kindList(kinds)}), e.g. ${kinds[0]}/<slug>: workit git branch --kind ${kinds[0]} --slug <slug>` : `choose a non-protected branch matching one of: ${allowed.join(", ")}`;
8912
- return {
8913
- ok: false,
8914
- rule: "protected_ref",
8915
- source,
8916
- attempted: name,
8917
- correction,
8918
- error: `protected_ref: branch ${JSON.stringify(name)} is protected by ${source}; ${correction}`
8919
- };
8920
- }
8921
- if (!policy.allowed.some((pattern) => pattern.test(name))) {
8922
- const suggestion = suggestBranchName(name, kinds);
8923
- const correction = suggestion && policy.allowed.some((pattern) => pattern.test(suggestion)) ? `use ${JSON.stringify(suggestion)}; the allowed kinds are ${kindList(kinds)} (workit git branch ${suggestion})` : `choose a branch matching one of: ${allowed.join(", ")}`;
8924
- return {
8925
- ok: false,
8926
- rule: "allowed_pattern",
8927
- source,
8928
- attempted: name,
8929
- correction,
8930
- error: `allowed_pattern: branch ${JSON.stringify(name)} violates ${source} policy; ${correction}`
8931
- };
8932
- }
8933
- return { ok: true };
8934
- }, branchPolicySnapshotFor = (workspaceRoot) => {
8935
- const resolved = effectiveWorkspacePolicy(workspaceRoot, "branch");
8936
- const branchPolicy = resolved.branchPolicy;
8937
- const source = resolved.provenance.branchPolicy;
8938
- const allowed = branchPolicy.allowed.map((pattern) => `${pattern.source}/${pattern.flags}`).toSorted();
8939
- const protectedRefs = [...branchPolicy.protected].map((name) => name.toLowerCase()).toSorted();
8940
- const fingerprint = createHash6("sha256").update(JSON.stringify({ allowed, protectedRefs, source })).digest("hex");
8941
- return {
8942
- branchPolicy,
8943
- namePolicy: { allowed: branchPolicy.allowed, protected: branchPolicy.protected },
8944
- source,
8945
- fingerprint
8946
- };
8947
- }, validateBranchNameFor = (workspaceRoot, name) => {
8948
- try {
8949
- const resolved = branchPolicySnapshotFor(workspaceRoot);
8950
- return validateBranchNamePolicy(name, resolved.namePolicy, resolved.source);
8951
- } catch (error) {
8952
- const correction = "repair or select a valid workspace policy before creating a branch";
8953
- return {
8954
- ok: false,
8955
- rule: "policy_unavailable",
8956
- source: "workspaces.json",
8957
- attempted: name,
8958
- correction,
8959
- error: `policy_unavailable: cannot validate branch ${JSON.stringify(name)} because ${error instanceof Error ? error.message : String(error)}; ${correction}`
8960
- };
8855
+ // packages/workit-core/src/git/default-base.ts
8856
+ function defaultBase(cwd) {
8857
+ const trunk = trackTrunk(cwd);
8858
+ if (trunk) {
8859
+ for (const candidate of [`origin/${trunk}`, trunk])
8860
+ if (resolveRef(cwd, candidate))
8861
+ return candidate;
8961
8862
  }
8962
- }, resolveBranchPolicyFor = (workspaceRoot, profileName) => effectiveWorkspacePolicy(workspaceRoot, "branch", profileName).branchPolicy, gitFlowOf = (root, integration) => {
8863
+ const originHead = originHeadRef(cwd);
8864
+ if (originHead && resolveRef(cwd, originHead))
8865
+ return originHead;
8866
+ for (const candidate of [
8867
+ "origin/main",
8868
+ "origin/master",
8869
+ "main",
8870
+ "master",
8871
+ "origin/develop",
8872
+ "develop"
8873
+ ])
8874
+ if (resolveRef(cwd, candidate))
8875
+ return candidate;
8876
+ return null;
8877
+ }
8878
+ var safeRef = (value) => value.length > 0 && !value.startsWith("-"), trackTrunk = (cwd) => {
8963
8879
  try {
8964
- if (resolveBranchPolicyFor(root).preset !== "gitflow")
8880
+ const resolved = vcsConfig("resolve", cwd);
8881
+ if (resolved.ok === false || !resolved.releaseTrack || resolved.releaseTrack.blocking)
8965
8882
  return null;
8966
- const selected = resolveRuntimeWorkspacePolicy(root, "branch", process.env.WORKFLOW_PROFILE?.trim() || undefined).policy;
8967
- const names = branchNames(root);
8968
- const production = productionBranchOf(names);
8969
- const develop = selected?.developBranch?.trim() || (integration && integration.toLowerCase() !== production ? integration : "develop");
8970
- return { develop, production, developExists: names.has(develop) };
8883
+ const target = String(resolved.defaultTargetBranch ?? "").trim();
8884
+ return target && safeRef(target) ? target : null;
8971
8885
  } catch {
8972
8886
  return null;
8973
8887
  }
8974
- }, releaseBranchesOf = (cwd) => {
8975
- try {
8976
- const resolved = resolveBranchPolicyFor(cwd);
8977
- const flow = resolved.preset === "gitflow" ? gitFlowOf(cwd) : null;
8978
- return {
8979
- preset: resolved.preset,
8980
- branches: flow ? [flow.production, flow.develop] : [],
8981
- production: flow ? flow.production : resolved.defaultTargetBranch
8982
- };
8983
- } catch {
8984
- return { preset: "unknown", branches: [], production: null };
8888
+ };
8889
+ var init_default_base = __esm(() => {
8890
+ init_vcs_config();
8891
+ init_rev();
8892
+ });
8893
+
8894
+ // packages/workit-core/src/core/policy/attributes.ts
8895
+ function patternRegex(dir, pattern) {
8896
+ if (!pattern || pattern.endsWith("/") || pattern.startsWith('"') || pattern.startsWith("!"))
8897
+ return null;
8898
+ const anchored = pattern.includes("/");
8899
+ const body = pattern.replace(/^\//u, "");
8900
+ let source = "";
8901
+ for (let index = 0;index < body.length; index += 1) {
8902
+ const char = body[index];
8903
+ if (char === "*" && body[index + 1] === "*") {
8904
+ const slash = body[index + 2] === "/";
8905
+ source += slash ? "(?:.*/)?" : ".*";
8906
+ index += slash ? 2 : 1;
8907
+ } else if (char === "*")
8908
+ source += "[^/]*";
8909
+ else if (char === "?")
8910
+ source += "[^/]";
8911
+ else if (char === "[") {
8912
+ const end = body.indexOf("]", index + 2);
8913
+ if (end < 0)
8914
+ return null;
8915
+ const set = body.slice(index + 1, end).replace(/^!/u, "^").replace(/\\/gu, "\\\\");
8916
+ source += `[${set}]`;
8917
+ index = end;
8918
+ } else if (char === "\\" && index + 1 < body.length) {
8919
+ source += body[index + 1].replace(/[.*+?^${}()|[\]\\/]/gu, "\\$&");
8920
+ index += 1;
8921
+ } else
8922
+ source += char.replace(/[.*+?^${}()|[\]\\/]/gu, "\\$&");
8985
8923
  }
8986
- }, releaseBranch = (cwd, branch) => releaseBranchesOf(cwd).branches.some((name) => name.toLowerCase() === branch.toLowerCase()), branchGuidance = (cwd) => {
8924
+ const prefix = dir ? `${dir.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&")}/` : "";
8987
8925
  try {
8988
- const patterns = branchPolicySnapshotFor(cwd).namePolicy.allowed;
8989
- const kinds = allowedKinds(patterns);
8990
- const kindShaped = new Set(kinds.map((kind) => branchGlobPattern(`${kind}/*`).source));
8991
- const anyName = patterns.some((pattern) => pattern.source === "^.*$");
8992
- const others = patterns.filter((pattern) => !kindShaped.has(pattern.source)).map((pattern) => globOf(pattern));
8993
- const allowed = anyName ? "Any non-protected branch name is allowed here." : `${kinds.length ? `Allowed branch kinds here: ${kindList(kinds)}${aliasNote(kinds)}` : "Allowed branch names here:"}${others.length ? `${kinds.length ? "; also" : ""} names matching ${others.join(", ")}` : ""}; anything else is denied.`;
8994
- const resolved = vcsConfig("resolve", cwd);
8995
- const integration = resolved.ok === false ? null : String(resolved.defaultTargetBranch ?? "") || null;
8996
- const flow = resolved.releaseTrack?.track ? null : gitFlowOf(cwd, integration);
8997
- if (flow?.developExists)
8998
- return `${allowed} git-flow: feature/, bugfix/ and release/ start from origin/${flow.develop}, hotfix/ from origin/${flow.production}; feature/ and bugfix/ land on ${flow.develop}, release/ and hotfix/ on ${flow.production}.`;
8999
- const names = branchNames(cwd);
9000
- const configured = String(resolved.baseBranch ?? integration ?? "") || null;
9001
- const trunk = productionBranchOf(names);
9002
- const base = configured && names.has(configured) ? `Branch from origin/${configured}.` : names.has(trunk) ? `Branch from origin/${trunk}${configured ? ` (the default target ${configured} does not exist here)` : ""}.` : "No base branch exists here yet; pass the base explicitly.";
9003
- return `${allowed} ${base}`;
8926
+ return new RegExp(anchored ? `^${prefix}${source}$` : `^${prefix}(?:.*/)?${source}$`, "u");
9004
8927
  } catch {
9005
8928
  return null;
9006
8929
  }
9007
- }, globOf = (pattern) => pattern.source.replace(/^\^|\$$/gu, "").replaceAll(".*", "*").replace(/\\(.)/gu, "$1"), policy = (root) => resolveBranchPolicyFor(root), isProtected = (root, name) => policy(root).protected.has(name.toLowerCase()), isProtectedTarget = (root, name) => {
9008
- try {
9009
- return isProtected(root, name);
9010
- } catch {
9011
- return true;
8930
+ }
8931
+ function rulesOf(dir, text) {
8932
+ const out = [];
8933
+ for (const raw of text.split(`
8934
+ `)) {
8935
+ const line = raw.trim();
8936
+ if (!line || line.startsWith("#") || line.startsWith("[attr]"))
8937
+ continue;
8938
+ const [pattern, ...attributes] = line.split(/\s+/u);
8939
+ let generated = null;
8940
+ for (const attribute of attributes) {
8941
+ if (attribute === "linguist-generated" || attribute === "linguist-generated=true")
8942
+ generated = true;
8943
+ else if (attribute === "-linguist-generated" || attribute === "!linguist-generated" || attribute.startsWith("linguist-generated="))
8944
+ generated = false;
8945
+ }
8946
+ if (generated === null)
8947
+ continue;
8948
+ const regex = patternRegex(dir, pattern);
8949
+ if (regex)
8950
+ out.push({ regex, generated });
9012
8951
  }
9013
- };
9014
- var init_branch = __esm(() => {
9015
- init_branch_policy();
9016
- init_commit_flavors();
9017
- init_git();
9018
- init_config();
9019
- init_workspaces();
9020
- init_vcs_config();
9021
- init_release_tracks();
9022
- KIND_SYNONYMS = {
9023
- fix: "bugfix",
9024
- fixes: "bugfix",
9025
- bug: "bugfix",
9026
- bugs: "bugfix",
9027
- bugfixes: "bugfix",
9028
- feat: "feature",
9029
- features: "feature",
9030
- chore: "feature",
9031
- docs: "feature",
9032
- doc: "feature",
9033
- refactor: "feature",
9034
- test: "feature",
9035
- tests: "feature",
9036
- ci: "feature",
9037
- build: "feature",
9038
- perf: "feature",
9039
- style: "feature",
9040
- deps: "feature",
9041
- task: "feature",
9042
- enhancement: "feature",
9043
- hot: "hotfix",
9044
- hotfixes: "hotfix",
9045
- releases: "release",
9046
- rel: "release"
9047
- };
9048
- });
9049
-
9050
- // packages/workit-core/src/core/logger.ts
9051
- import os3 from "node:os";
9052
- import path6 from "node:path";
9053
- var SENSITIVE_WORDS, STACK_WORDS, escapeRegExp2 = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), resolveStateDir = () => {
9054
- const override = process.env.WORKFLOW_TOOLKIT_STATE;
9055
- if (override)
9056
- return override;
9057
- const home = os3.homedir();
9058
- if (process.env.XDG_STATE_HOME)
9059
- return path6.join(process.env.XDG_STATE_HOME, "workit");
9060
- switch (os3.platform()) {
9061
- case "darwin":
9062
- return path6.join(home, "Library", "Application Support", "workit");
9063
- case "win32":
9064
- return path6.join(process.env.LOCALAPPDATA ?? path6.join(home, "AppData", "Local"), "workit");
9065
- default:
9066
- return path6.join(process.env.HOME ?? home, ".local", "state", "workit");
9067
- }
9068
- }, homePattern;
9069
- var init_logger = __esm(() => {
9070
- SENSITIVE_WORDS = new Set([
9071
- "token",
9072
- "secret",
9073
- "password",
9074
- "passwd",
9075
- "authorization",
9076
- "credential",
9077
- "credentials",
9078
- "cookie",
9079
- "api",
9080
- "apikey",
9081
- "apitoken",
9082
- "accesstoken",
9083
- "clientsecret",
9084
- "refreshtoken",
9085
- "key",
9086
- "bearer",
9087
- "prompt",
9088
- "message",
9089
- "messages",
9090
- "content",
9091
- "body",
9092
- "args",
9093
- "argument",
9094
- "result",
9095
- "results",
9096
- "output",
9097
- "issue",
9098
- "description",
9099
- "summary",
9100
- "payload",
9101
- "script",
9102
- "command",
9103
- "text",
9104
- "query"
9105
- ]);
9106
- STACK_WORDS = new Set(["stack", "stacktrace", "trace"]);
9107
- homePattern = new RegExp(`${escapeRegExp2(os3.homedir())}(?=[/\\\\]|$)|\\$HOME(?=[/\\\\]|$)`, "g");
9108
- });
9109
-
9110
- // packages/workit-core/src/host-session.ts
9111
- import { createHash as createHash7, randomBytes, timingSafeEqual } from "node:crypto";
9112
- import fs5 from "node:fs";
9113
- import path7 from "node:path";
9114
- function hostSessionFromEnv(env) {
9115
- const value = (key) => env[key]?.trim() || null;
9116
- let inner = null;
9117
- for (const [key, host] of HOST_SESSION_VARS) {
9118
- const session = value(key);
9119
- if (session) {
9120
- inner = { host: host(env), session };
9121
- break;
9122
- }
9123
- }
9124
- const declared = value("WORKIT_HOST");
9125
- if (env.WORKIT_SESSION_ID !== undefined) {
9126
- const inherited = inner !== null && declared !== null && family(declared) !== family(inner.host);
9127
- if (!inherited)
9128
- return { host: declared, session: value("WORKIT_SESSION_ID") };
9129
- }
9130
- return inner ? { host: inner.host, session: inner.session } : { host: declared, session: null };
9131
- }
9132
- function migrateRepoIssued(cwd) {
9133
- if (cwd === undefined)
9134
- return;
9135
- let old;
9136
- try {
9137
- const location = resolveStore(cwd);
9138
- if (location instanceof Error)
9139
- return;
9140
- old = path7.join(location.dir, "hooks", "issued");
9141
- if (!fs5.existsSync(old))
9142
- return;
9143
- } catch {
9144
- return;
9145
- }
9146
- for (const lead of safeList(old)) {
9147
- try {
9148
- const from = path7.join(old, lead);
9149
- if (!fs5.statSync(from).isDirectory())
9150
- continue;
9151
- const to = path7.join(userIssuedRoot(), lead);
9152
- fs5.mkdirSync(to, { recursive: true, mode: 448 });
9153
- for (const id of fs5.readdirSync(from)) {
9154
- try {
9155
- fs5.copyFileSync(path7.join(from, id), path7.join(to, id), fs5.constants.COPYFILE_EXCL);
9156
- } catch (error) {
9157
- if (error.code !== "EEXIST")
9158
- throw error;
9159
- }
9160
- fs5.rmSync(path7.join(from, id), { force: true });
9161
- }
9162
- removeIfEmpty(from);
9163
- } catch {}
9164
- }
9165
- removeIfEmpty(old);
9166
- }
9167
- function recordIssuedSession(cwd, lead, issued) {
9168
- const token = randomBytes(18).toString("hex");
9169
- try {
9170
- migrateRepoIssued(cwd);
9171
- const own = hash(issued);
9172
- const tokens = path7.join(userTokenRoot(), hash(lead));
9173
- fs5.mkdirSync(tokens, { recursive: true, mode: 448 });
9174
- fs5.writeFileSync(path7.join(tokens, own), `${tokenHash(token)}
9175
- `, { mode: 384, flag: "wx" });
9176
- const dir = path7.join(userIssuedRoot(), hash(lead));
9177
- fs5.mkdirSync(dir, { recursive: true, mode: 448 });
9178
- fs5.writeFileSync(path7.join(dir, own), `${ISSUED_SESSIONS_MARKER}
9179
- `, { mode: 384 });
9180
- pruneIssued(dir, own, tokens);
9181
- return token;
9182
- } catch {
9183
- return null;
9184
- }
9185
- }
9186
- function judgeBranch(cwd) {
9187
- const branch = currentBranch(cwd);
9188
- return branch && !isProtectedTarget(cwd, branch) ? branch : null;
9189
- }
9190
- function verdictCommand(options) {
9191
- const kind = options.judge === "verifier" ? "<live|unit>" : options.judge === "reviewer" ? "review" : "<live|unit|review>";
9192
- return [
9193
- "workit ledger verdict <verified|failed|blocked>",
9194
- `--kind ${kind}`,
9195
- `--branch ${options.branch ?? "<branch>"}`,
9196
- options.session ? `--session ${options.session}` : null,
9197
- options.token ? `--token=${options.token}` : null,
9198
- '--how "<what you ran and saw>"'
9199
- ].filter(Boolean).join(" ");
9200
- }
9201
- function pruneIssued(dir, own, tokens) {
9202
- try {
9203
- const entries = fs5.readdirSync(dir);
9204
- if (entries.length <= ISSUED_PER_LEAD)
9205
- return;
9206
- 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);
9207
- for (const { name } of byAge.slice(0, entries.length - ISSUED_PER_LEAD)) {
9208
- fs5.rmSync(path7.join(dir, name), { force: true });
9209
- fs5.rmSync(path7.join(tokens, name), { force: true });
9210
- }
9211
- } catch {}
9212
- }
9213
- function issuedBinding(cwd, claimed) {
9214
- const record = issuedRecord(cwd, claimed);
9215
- const { stored, leadBound } = issuedToken(claimed);
9216
- if (stored)
9217
- return "token";
9218
- if (record === null)
9219
- return null;
9220
- return leadBound ? "token" : "legacy";
9221
- }
9222
- var HOST_SESSION_VARS, family = (host) => host?.replace(/_(?:cli|desktop)$/, "") ?? null, ISSUED_PER_LEAD = 64, ISSUED_SESSIONS_MARKER = "workit-issued-sessions/3", hash = (value) => createHash7("sha256").update(value).digest("hex").slice(0, 32), userIssuedRoot = () => path7.join(resolveStateDir(), "issued"), userTokenRoot = () => path7.join(resolveStateDir(), "issued-tokens"), safeList = (dir) => {
9223
- try {
9224
- return fs5.readdirSync(dir);
9225
- } catch {
9226
- return [];
9227
- }
9228
- }, removeIfEmpty = (dir) => {
9229
- try {
9230
- fs5.rmdirSync(dir);
9231
- } catch {}
9232
- }, tokenHash = (token) => createHash7("sha256").update(token).digest("hex"), issuedRecord = (cwd, claimed) => {
9233
- const cut = claimed.lastIndexOf(":");
9234
- if (cut <= 0)
9235
- return null;
9236
- migrateRepoIssued(cwd);
9237
- try {
9238
- return fs5.readFileSync(path7.join(userIssuedRoot(), hash(claimed.slice(0, cut)), hash(claimed)), "utf8");
9239
- } catch {
9240
- return null;
9241
- }
9242
- }, TOKEN_HASH, issuedToken = (claimed) => {
9243
- const cut = claimed.lastIndexOf(":");
9244
- if (cut <= 0)
9245
- return { stored: null, leadBound: false };
9246
- const dir = path7.join(userTokenRoot(), hash(claimed.slice(0, cut)));
9247
- try {
9248
- const text = fs5.readFileSync(path7.join(dir, hash(claimed)), "utf8");
9249
- return { stored: TOKEN_HASH.exec(text)?.[1] ?? null, leadBound: true };
9250
- } catch {
9251
- return { stored: null, leadBound: safeList(dir).length > 0 };
9252
- }
9253
- }, OWN_CHILD_SHELL;
9254
- var init_host_session = __esm(() => {
9255
- init_branch();
9256
- init_logger();
9257
- init_rev();
9258
- init_paths();
9259
- HOST_SESSION_VARS = [
9260
- ["OPENCODE_SESSION_ID", () => "opencode"],
9261
- ["PI_SESSION_ID", () => "pi"],
9262
- [
9263
- "CODEX_THREAD_ID",
9264
- (env) => env.CODEX_INTERNAL_ORIGINATOR_OVERRIDE === "Codex Desktop" || env.CODEX_ELECTRON_RESOURCES_PATH ? "codex_desktop" : "codex_cli"
9265
- ]
9266
- ];
9267
- TOKEN_HASH = /^([0-9a-f]{64})$/mu;
9268
- OWN_CHILD_SHELL = new Set(["opencode", "pi"]);
9269
- });
9270
-
9271
- // packages/workit-core/src/hooks/standing-signal.ts
9272
- import { createHash as createHash8 } from "node:crypto";
9273
- import fs6 from "node:fs";
9274
- import path8 from "node:path";
9275
- var gitStoreDir = (cwd) => {
9276
- try {
9277
- for (let dir = path8.resolve(cwd);; dir = path8.dirname(dir)) {
9278
- const dotGit = path8.join(dir, ".git");
9279
- if (fs6.existsSync(dotGit)) {
9280
- let gitDir = dotGit;
9281
- if (fs6.statSync(dotGit).isFile()) {
9282
- const target = /^gitdir:\s*(.+)$/mu.exec(fs6.readFileSync(dotGit, "utf8"))?.[1]?.trim();
9283
- if (!target)
9284
- return null;
9285
- gitDir = path8.resolve(dir, target);
9286
- const common = path8.join(gitDir, "commondir");
9287
- if (fs6.existsSync(common))
9288
- gitDir = path8.resolve(gitDir, fs6.readFileSync(common, "utf8").trim());
9289
- }
9290
- return path8.join(gitDir, "workit");
9291
- }
9292
- if (path8.dirname(dir) === dir)
9293
- return null;
9294
- }
9295
- } catch {
9296
- return null;
9297
- }
9298
- }, standingRevFile = (store) => path8.join(store, "standing", "rev"), standingSeenFile = (store, session, agentId) => path8.join(store, "standing", "seen", `${createHash8("sha256").update(`${session}\x00${agentId ?? ""}`).digest("hex").slice(0, 32)}.json`), readStandingRev = (store) => {
9299
- try {
9300
- return fs6.readFileSync(standingRevFile(store), "utf8").trim() || null;
9301
- } catch {
9302
- return null;
9303
- }
9304
- }, readStandingSeen = (file) => {
9305
- try {
9306
- const value = JSON.parse(fs6.readFileSync(file, "utf8"));
9307
- return {
9308
- rev: typeof value.rev === "string" ? value.rev : null,
9309
- ids: Array.isArray(value.ids) ? value.ids.filter((id) => typeof id === "string") : []
9310
- };
9311
- } catch {
9312
- return { rev: null, ids: [] };
9313
- }
9314
- }, writeStandingSeen = (file, seen) => {
9315
- try {
9316
- fs6.mkdirSync(path8.dirname(file), { recursive: true });
9317
- fs6.writeFileSync(file, JSON.stringify(seen));
9318
- } catch {}
9319
- };
9320
- var init_standing_signal = () => {};
9321
-
9322
- // packages/workit-core/src/ledger.ts
9323
- import { spawnSync as spawnSync8 } from "node:child_process";
9324
- import { createHash as createHash9, randomBytes as randomBytes2 } from "node:crypto";
9325
- import fs7 from "node:fs";
9326
- import path9 from "node:path";
9327
- function resolveCommit(cwd, ref) {
9328
- if (!safeRef(ref))
9329
- return null;
9330
- return git3(cwd, ["rev-parse", "--verify", "-q", `${ref}^{commit}`]);
9331
- }
9332
- function branchHead(cwd, branch) {
9333
- if (!safeRef(branch))
9334
- return null;
9335
- return resolveCommit(cwd, `refs/heads/${branch}`) ?? resolveCommit(cwd, `refs/remotes/origin/${branch}`);
9336
- }
9337
- function defaultBase(cwd) {
9338
- const trunk = trackTrunk(cwd);
9339
- if (trunk) {
9340
- for (const candidate of [`origin/${trunk}`, trunk])
9341
- if (resolveCommit(cwd, candidate))
9342
- return candidate;
9343
- }
9344
- const originHead = git3(cwd, ["symbolic-ref", "-q", "--short", "refs/remotes/origin/HEAD"]);
9345
- if (originHead && resolveCommit(cwd, originHead))
9346
- return originHead;
9347
- for (const candidate of [
9348
- "origin/main",
9349
- "origin/master",
9350
- "main",
9351
- "master",
9352
- "origin/develop",
9353
- "develop"
9354
- ])
9355
- if (resolveCommit(cwd, candidate))
9356
- return candidate;
9357
- return null;
9358
- }
9359
- function diffHash(cwd, base, head) {
9360
- if (!safeRef(base) || !safeRef(head))
9361
- return null;
9362
- const diff = gitRun2(cwd, [
9363
- "diff",
9364
- "-U3",
9365
- "--diff-algorithm=myers",
9366
- "--indent-heuristic",
9367
- "--no-color",
9368
- "--no-ext-diff",
9369
- "--no-textconv",
9370
- "--no-renames",
9371
- "--full-index",
9372
- "--binary",
9373
- "--src-prefix=a/",
9374
- "--dst-prefix=b/",
9375
- `${base}...${head}`,
9376
- "--"
9377
- ], GIT_TIMEOUTS.worktree);
9378
- if (!diff.ok || !diff.stdout)
9379
- return null;
9380
- const hash = createHash9("sha256");
9381
- for (const line of diff.stdout.split(`
9382
- `)) {
9383
- if (line.startsWith("index "))
9384
- continue;
9385
- hash.update(line.startsWith("@@ ") ? "@@" : line);
9386
- hash.update(`
9387
- `);
9388
- }
9389
- return `sha256:${hash.digest("hex")}`;
9390
- }
9391
- function storeRoot(cwd) {
9392
- const location = resolveStore(path9.resolve(cwd));
9393
- if (location instanceof Error)
9394
- return err("unavailable", location.message.replace(/; fix the repository.*$/u, ""), "fix the repository (git status should work here) or run from a non-git directory");
9395
- return { ok: true, value: { root: location.dir, shared: location.shared } };
9396
- }
9397
- function ledgerPath(cwd) {
9398
- const root = storeRoot(cwd);
9399
- return root.ok ? { ok: true, value: path9.join(root.value.root, "ledger", "ledger.jsonl") } : root;
9400
- }
9401
- function withLedgerLock(file, fn, waitMs = hostAwareLockTimeout(LOCK_WAIT_MS)) {
9402
- const lock = `${file}.lock`;
9403
- const deadline = Date.now() + waitMs;
9404
- let token = null;
9405
- for (;; ) {
9406
- token = ledgerLock.acquire(lock);
9407
- if (token)
9408
- break;
9409
- const observed = lockToken(lock);
9410
- const age = lockAgeMs(lock);
9411
- if (age !== null && age > LOCK_STALE_MS && ledgerLock.reap(lock, observed))
9412
- continue;
9413
- if (Date.now() >= deadline)
9414
- return null;
9415
- Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 25);
9416
- }
9417
- try {
9418
- return fn();
9419
- } finally {
9420
- ledgerLock.release(lock, token);
9421
- }
9422
- }
9423
- function appendLine(file, build, options) {
9424
- const write = () => {
9425
- const fd = fs7.openSync(file, "a+", 384);
9426
- try {
9427
- if (process.platform !== "win32")
9428
- tighten(fd);
9429
- const size = fs7.fstatSync(fd).size;
9430
- let fence = "";
9431
- if (size > 0) {
9432
- const last = Buffer.alloc(1);
9433
- fs7.readSync(fd, last, 0, 1, size - 1);
9434
- if (last[0] !== 10)
9435
- fence = `
9436
- `;
9437
- }
9438
- const line = build(lastLine(fd, size));
9439
- if (!line.ok)
9440
- return line;
9441
- const bytes = Buffer.from(fence + line.value);
9442
- const written = fs7.writeSync(fd, bytes);
9443
- if (written !== bytes.length)
9444
- return err("unavailable", `short write to ${file}: ${written} of ${bytes.length} bytes; the row is torn and will be skipped`);
9445
- return { ok: true, value: undefined };
9446
- } finally {
9447
- fs7.closeSync(fd);
9448
- }
9449
- };
9450
- try {
9451
- fs7.mkdirSync(path9.dirname(file), { recursive: true });
9452
- if (!options.forceLock && !onNetworkFs(path9.dirname(file)))
9453
- return write();
9454
- const locked = withLedgerLock(file, write);
9455
- return locked ?? err("busy", `ledger lock ${file}.lock is held`, "retry; remove the .lock directory if no workit process is running");
9456
- } catch (error) {
9457
- return err("unavailable", `cannot append to ${file}: ${error.message}`);
9458
- }
9459
- }
9460
- function appendRaw(cwd, row, options) {
9461
- const file = ledgerPath(cwd);
9462
- if (!file.ok)
9463
- return file;
9464
- const id = newId2();
9465
- const at = (options.now ?? new Date).toISOString();
9466
- let full = null;
9467
- const written = appendLine(file.value, (previous) => {
9468
- const next = sealed(row, id, at, previous);
9469
- const bytes = Buffer.byteLength(next.line);
9470
- if (bytes > MAX_LINE_BYTES)
9471
- return err("invalid_input", `ledger row is ${bytes} bytes; the limit is ${MAX_LINE_BYTES}. Shorten the text and pass details by --ref <path|url>`);
9472
- full = next.full;
9473
- return { ok: true, value: next.line };
9474
- }, options);
9475
- return written.ok ? { ok: true, value: full } : written;
9476
- }
9477
- function observedRowBytes(row) {
9478
- const sample = sealed({ ...row, observer: "workit_cli" }, newId2(), new Date().toISOString(), "");
9479
- return Buffer.byteLength(sample.line);
8952
+ return out;
9480
8953
  }
9481
- function appendObserved(cwd, row, options = {}) {
9482
- return appendRaw(cwd, { ...row, observer: "workit_cli" }, options);
8954
+ function generatedByAttributes(files, paths) {
8955
+ const rules = [...files].map(([file, text]) => {
8956
+ const dir = file.includes("/") ? file.slice(0, file.lastIndexOf("/")) : "";
8957
+ return {
8958
+ depth: dir ? dir.split("/").length : 0,
8959
+ rules: rulesOf(dir, text)
8960
+ };
8961
+ }).toSorted((left, right) => left.depth - right.depth).flatMap((entry) => entry.rules);
8962
+ const out = new Set;
8963
+ for (const file of paths) {
8964
+ const last = rules.findLast((rule) => rule.regex.test(file));
8965
+ if (last?.generated)
8966
+ out.add(file);
8967
+ }
8968
+ return out;
9483
8969
  }
9484
- function appendHookObserved(cwd, row) {
9485
- return appendRaw(cwd, { ...row, observer: "host_hook" }, {});
8970
+
8971
+ // packages/workit-core/src/core/policy/substance.ts
8972
+ function substanceOf(file, text) {
8973
+ const syntax = syntaxOf(file);
8974
+ if (!syntax)
8975
+ return plainSubstance(text);
8976
+ const markup = syntax.block.some(([open]) => open === "<!--");
8977
+ if (markup && (/<(?:script|style)\b|<!\[CDATA\[|<\?/iu.test(text) || /[^>\s][ \t]*<!--/u.test(text)))
8978
+ return plainSubstance(text);
8979
+ return lex(syntax, text);
9486
8980
  }
9487
- function supersedeAllowed(by, target, onBranch = (row) => (by.branch ?? null) === (row.branch ?? null)) {
9488
- if (by.type !== target.type)
9489
- return `a ${by.type} row cannot supersede a ${target.type} row`;
9490
- if (!by.actor.session || by.actor.session !== target.actor.session)
9491
- return "only the session that wrote a row can supersede it";
9492
- if (!onBranch(target))
9493
- return `the row is on another branch (${String(target.branch ?? "none")}); a row supersedes only one on its own branch`;
9494
- if (by.type === "verdict" && (by.kind ?? "review") !== (target.kind ?? "review"))
9495
- return `a ${String(by.kind ?? "review")} verdict cannot supersede a ${String(target.kind ?? "review")} verdict; record it with --kind ${String(target.kind ?? "review")}`;
9496
- if (by.type === "verdict" && by.self === true && target.self !== true)
9497
- return "a self verdict cannot replace an independent verdict";
9498
- return null;
8981
+ function plainSubstance(text) {
8982
+ return text.split(`
8983
+ `).map((line) => line.replace(/[ \t\r\f\v]+$/u, "")).filter(Boolean).join(`
8984
+ `);
9499
8985
  }
9500
- function readLedger(cwd, options = {}) {
9501
- const file = ledgerPath(cwd);
9502
- if (!file.ok)
9503
- return file;
9504
- let text = "";
9505
- let stat = null;
9506
- try {
9507
- stat = fs7.statSync(file.value);
9508
- text = fs7.readFileSync(file.value, "utf8");
9509
- } catch (error) {
9510
- if (error.code === "ENOENT")
9511
- return {
9512
- ok: true,
9513
- value: {
9514
- path: file.value,
9515
- rows: [],
9516
- skipped: 0,
9517
- integrity: options.integrity ? { chained: 0, legacy: 0, unverified: [] } : null
9518
- }
9519
- };
9520
- return err("unavailable", `cannot read ${file.value}: ${error.message}`);
8986
+ function lex(syntax, text) {
8987
+ const collapse = !syntax.indented;
8988
+ let out = "";
8989
+ let lineStart = 0;
8990
+ let kept = 0;
8991
+ let pendingSpace = false;
8992
+ const frames = [{ kind: "code", braces: 0 }];
8993
+ let index = 0;
8994
+ if (text.startsWith("#!")) {
8995
+ const end = text.indexOf(`
8996
+ `);
8997
+ out = end < 0 ? text : text.slice(0, end);
8998
+ kept = out.length;
8999
+ index = end < 0 ? text.length : end;
9521
9000
  }
9522
- const rows = [];
9523
- let skipped = 0;
9524
- const integrity = { chained: 0, legacy: 0, unverified: [] };
9525
- const earlier = new Set([null]);
9526
- let chainStarted = false;
9527
- const ids = new Set;
9528
- for (const raw of text.split(`
9529
- `)) {
9530
- const trimmed = raw.trim();
9531
- if (!trimmed)
9001
+ const emitCode = (chunk) => {
9002
+ if (collapse && pendingSpace && out.length > lineStart) {
9003
+ const before = out[out.length - 1];
9004
+ if (!NO_SPACE_AFTER.includes(before) && !NO_SPACE_BEFORE.includes(chunk[0]))
9005
+ out += " ";
9006
+ }
9007
+ pendingSpace = false;
9008
+ out += chunk;
9009
+ };
9010
+ const emitVerbatim = (chunk) => {
9011
+ emitCode(chunk);
9012
+ kept = out.length;
9013
+ };
9014
+ const newline = () => {
9015
+ if (!collapse) {
9016
+ let end = out.length;
9017
+ while (end > Math.max(lineStart, kept) && SPACE.test(out[end - 1]))
9018
+ end -= 1;
9019
+ out = out.slice(0, end);
9020
+ }
9021
+ pendingSpace = false;
9022
+ if (out.length > lineStart) {
9023
+ out += `
9024
+ `;
9025
+ lineStart = out.length;
9026
+ }
9027
+ };
9028
+ const regexAllowed = () => {
9029
+ let end = out.length;
9030
+ while (end > 0 && /\s/u.test(out[end - 1]))
9031
+ end -= 1;
9032
+ if (end === 0 || REGEX_AFTER.includes(out[end - 1]))
9033
+ return true;
9034
+ let start = end;
9035
+ while (start > 0 && WORD.test(out[start - 1]))
9036
+ start -= 1;
9037
+ return REGEX_KEYWORDS.has(out.slice(start, end));
9038
+ };
9039
+ const scanString = (quote, escapes, multiline) => {
9040
+ const prefix = text.slice(Math.max(0, index - 2), index).match(/[a-z]*$/iu)?.[0] ?? "";
9041
+ const fstring = syntax.fstring === true && /f/iu.test(prefix);
9042
+ let end = index + quote.length;
9043
+ while (end < text.length) {
9044
+ const char = text[end];
9045
+ if (escapes && char === "\\") {
9046
+ end += 2;
9047
+ continue;
9048
+ }
9049
+ if (text.startsWith(quote, end)) {
9050
+ if (fstring && unbalanced(text.slice(index + quote.length, end)))
9051
+ return false;
9052
+ emitVerbatim(text.slice(index, end + quote.length));
9053
+ index = end + quote.length;
9054
+ return true;
9055
+ }
9056
+ if (char === `
9057
+ ` && !multiline)
9058
+ return false;
9059
+ end += 1;
9060
+ }
9061
+ return false;
9062
+ };
9063
+ while (index < text.length) {
9064
+ const frame = frames[frames.length - 1];
9065
+ const char = text[index];
9066
+ if (frame.kind === "template") {
9067
+ if (char === "\\") {
9068
+ emitVerbatim(text.slice(index, index + 2));
9069
+ index += 2;
9070
+ } else if (char === "`") {
9071
+ emitVerbatim(char);
9072
+ frames.pop();
9073
+ index += 1;
9074
+ } else if (text.startsWith("${", index)) {
9075
+ emitVerbatim("${");
9076
+ frames.push({ kind: "code", braces: 0 });
9077
+ index += 2;
9078
+ } else {
9079
+ out += char;
9080
+ kept = out.length;
9081
+ index += 1;
9082
+ }
9532
9083
  continue;
9533
- const linked = (prevHash) => earlier.has(prevHash);
9534
- if (options.integrity)
9535
- earlier.add(sha2562(trimmed));
9536
- let parsed;
9537
- try {
9538
- parsed = JSON.parse(trimmed);
9539
- } catch {
9540
- skipped += 1;
9084
+ }
9085
+ if (char === `
9086
+ `) {
9087
+ newline();
9088
+ index += 1;
9541
9089
  continue;
9542
9090
  }
9543
- if (!isRecord2(parsed) || !str(parsed.id) || !str(parsed.type) || !str(parsed.at)) {
9544
- skipped += 1;
9091
+ if (SPACE.test(char)) {
9092
+ if (collapse)
9093
+ pendingSpace = true;
9094
+ else
9095
+ out += char;
9096
+ index += 1;
9097
+ continue;
9098
+ }
9099
+ const lineOpener = syntax.line.find((opener) => text.startsWith(opener, index));
9100
+ if (lineOpener) {
9101
+ const end = text.indexOf(`
9102
+ `, index);
9103
+ const comment = text.slice(index, end < 0 ? text.length : end);
9104
+ if (DIRECTIVE.test(comment.slice(lineOpener.length)))
9105
+ emitVerbatim(comment);
9106
+ else
9107
+ pendingSpace = true;
9108
+ index = end < 0 ? text.length : end;
9109
+ continue;
9110
+ }
9111
+ const block = syntax.block.find(([open]) => text.startsWith(open, index));
9112
+ if (block) {
9113
+ const end = text.indexOf(block[1], index + block[0].length);
9114
+ if (end < 0)
9115
+ return null;
9116
+ const comment = text.slice(index, end + block[1].length);
9117
+ if (DIRECTIVE.test(comment.slice(block[0].length, -block[1].length)))
9118
+ emitVerbatim(comment);
9119
+ else
9120
+ pendingSpace = true;
9121
+ if (comment.includes(`
9122
+ `))
9123
+ newline();
9124
+ index = end + block[1].length;
9125
+ continue;
9126
+ }
9127
+ if (syntax.triple && (char === '"' || char === "'") && text.startsWith(char.repeat(3), index)) {
9128
+ if (!scanString(char.repeat(3), !syntax.literal?.includes(char), true))
9129
+ return null;
9130
+ continue;
9131
+ }
9132
+ if (syntax.quotes.includes(char)) {
9133
+ if (!scanString(char, true, Boolean(syntax.multiline)))
9134
+ return null;
9135
+ continue;
9136
+ }
9137
+ if (syntax.literal?.includes(char)) {
9138
+ if (!scanString(char, false, false))
9139
+ return null;
9140
+ continue;
9141
+ }
9142
+ if (syntax.rawBacktick && char === "`") {
9143
+ if (!scanString("`", false, true))
9144
+ return null;
9145
+ continue;
9146
+ }
9147
+ if (syntax.js && char === "`") {
9148
+ emitVerbatim(char);
9149
+ frames.push({ kind: "template" });
9150
+ index += 1;
9545
9151
  continue;
9546
9152
  }
9547
- let rowIntegrity = options.integrity ? "verified" : undefined;
9548
- let reason = null;
9549
- if (!options.integrity) {} else if (typeof parsed.rowHash === "string") {
9550
- chainStarted = true;
9551
- const { rowHash, ...body } = parsed;
9552
- const prevHash = str(parsed.prevHash);
9553
- if (chainHash(prevHash, JSON.stringify(body)) !== rowHash)
9554
- reason = "hash_mismatch";
9555
- else if (!linked(prevHash))
9556
- reason = "broken_link";
9557
- } else if (chainStarted)
9558
- reason = "unsigned";
9559
- else
9560
- rowIntegrity = "legacy";
9561
- if (options.integrity && !reason && ids.has(parsed.id))
9562
- reason = "duplicate_id";
9563
- if (options.integrity)
9564
- ids.add(parsed.id);
9565
- if (reason)
9566
- rowIntegrity = "unverified";
9567
- const pr = typeof parsed.pr === "number" && Number.isInteger(parsed.pr) ? parsed.pr : undefined;
9568
- const row = {
9569
- ...parsed,
9570
- v: typeof parsed.v === "number" ? parsed.v : LEDGER_VERSION,
9571
- id: parsed.id,
9572
- at: parsed.at,
9573
- type: parsed.type,
9574
- actor: normalizeActor(parsed.actor),
9575
- branch: str(parsed.branch),
9576
- head: str(parsed.head),
9577
- base: str(parsed.base),
9578
- baseSha: str(parsed.baseSha),
9579
- patchId: str(parsed.patchId),
9580
- diffHash: str(parsed.diffHash),
9581
- tree: str(parsed.tree),
9582
- dirty: typeof parsed.dirty === "boolean" ? parsed.dirty : null,
9583
- ...pr === undefined ? {} : { pr },
9584
- ...str(parsed.supersedes) ? { supersedes: parsed.supersedes } : {},
9585
- seq: rows.length + 1,
9586
- superseded: false,
9587
- supersedeIgnored: false,
9588
- ...rowIntegrity ? { integrity: rowIntegrity } : {},
9589
- ...reason ? { integrityReason: reason } : {}
9590
- };
9591
- rows.push(row);
9592
- if (!options.integrity)
9153
+ if (syntax.cssUrl && /^url\(\s*[^"')\s]/iu.test(text.slice(index, index + 64)))
9154
+ return null;
9155
+ if (syntax.js && char === "/" && !regexAllowed() && out.trimEnd().endsWith(")")) {
9156
+ const lineEnd = text.indexOf(`
9157
+ `, index);
9158
+ if (text.slice(index + 1, lineEnd < 0 ? text.length : lineEnd).includes("/"))
9159
+ return null;
9160
+ }
9161
+ if (syntax.js && char === "/" && regexAllowed()) {
9162
+ let end = index + 1;
9163
+ let inClass = false;
9164
+ while (end < text.length && (inClass || text[end] !== "/")) {
9165
+ if (text[end] === `
9166
+ `)
9167
+ return null;
9168
+ if (text[end] === "\\")
9169
+ end += 1;
9170
+ else if (text[end] === "[")
9171
+ inClass = true;
9172
+ else if (text[end] === "]")
9173
+ inClass = false;
9174
+ end += 1;
9175
+ }
9176
+ if (end >= text.length)
9177
+ return null;
9178
+ emitVerbatim(text.slice(index, end + 1));
9179
+ index = end + 1;
9593
9180
  continue;
9594
- if (rowIntegrity === "verified")
9595
- integrity.chained += 1;
9596
- else if (rowIntegrity === "legacy")
9597
- integrity.legacy += 1;
9598
- else if (reason)
9599
- integrity.unverified.push({
9600
- seq: row.seq,
9601
- id: row.id,
9602
- type: row.type,
9603
- branch: row.branch,
9604
- reason
9605
- });
9181
+ }
9182
+ if (frames.length > 1 && frame.kind === "code") {
9183
+ if (char === "{")
9184
+ frame.braces += 1;
9185
+ else if (char === "}") {
9186
+ if (frame.braces === 0) {
9187
+ emitVerbatim(char);
9188
+ frames.pop();
9189
+ index += 1;
9190
+ continue;
9191
+ }
9192
+ frame.braces -= 1;
9193
+ }
9194
+ }
9195
+ emitCode(char);
9196
+ index += 1;
9606
9197
  }
9607
- const scopes = new Map;
9608
- const scopeOf = (branch) => {
9609
- let scope = scopes.get(branch);
9610
- if (!scope)
9611
- scopes.set(branch, scope = branchScope(cwd, branch));
9612
- return scope;
9613
- };
9614
- const byId = new Map;
9615
- for (const row of rows) {
9616
- const target = row.supersedes ? byId.get(row.supersedes) : undefined;
9617
- if (target && row.integrity !== "unverified" && supersedeAllowed(row, target, renameAwareBranch(scopeOf, row.branch)) === null)
9618
- target.superseded = true;
9619
- else if (row.supersedes)
9620
- row.supersedeIgnored = true;
9621
- if (!byId.has(row.id))
9622
- byId.set(row.id, row);
9198
+ if (frames.length !== 1)
9199
+ return null;
9200
+ newline();
9201
+ return out;
9202
+ }
9203
+ function unbalanced(body) {
9204
+ let depth = 0;
9205
+ for (const part of body.replaceAll("{{", "").replaceAll("}}", "")) {
9206
+ if (part === "{")
9207
+ depth += 1;
9208
+ else if (part === "}")
9209
+ depth = Math.max(0, depth - 1);
9623
9210
  }
9624
- if (options.integrity && stat && Buffer.byteLength(text) === stat.size)
9625
- tamperCache.set(file.value, {
9626
- size: stat.size,
9627
- stamp: fileStamp(stat),
9628
- breaks: integrity.unverified.filter((row) => TAMPER_REASONS.has(row.reason))
9629
- });
9630
- return {
9631
- ok: true,
9632
- value: { path: file.value, rows, skipped, integrity: options.integrity ? integrity : null }
9633
- };
9211
+ return depth > 0;
9634
9212
  }
9635
- function ledgerTampering(cwd) {
9636
- const file = ledgerPath(cwd);
9637
- if (!file.ok)
9638
- return [];
9213
+ function hasOpaqueLineBreak(text) {
9214
+ return /\r(?!\n)|[\u2028\u2029]/u.test(text);
9215
+ }
9216
+ function textOf(bytes) {
9217
+ if (bytes.includes(0))
9218
+ return null;
9639
9219
  try {
9640
- const stat = fs7.statSync(file.value);
9641
- const cached = tamperCache.get(file.value);
9642
- if (cached && cached.size === stat.size && cached.stamp === fileStamp(stat))
9643
- return cached.breaks;
9220
+ return decoder.decode(bytes);
9644
9221
  } catch {
9645
- return [];
9222
+ return null;
9646
9223
  }
9647
- const read = readLedger(cwd, { integrity: true });
9648
- return read.ok ? (read.value.integrity?.unverified ?? []).filter((row) => TAMPER_REASONS.has(row.reason)) : [];
9649
9224
  }
9650
- function tamperedUnblock(cwd) {
9651
- const file = ledgerPath(cwd);
9652
- const where = file.ok ? file.value : "the ledger file";
9653
- 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`;
9225
+ function cosmeticChange(file, before, after) {
9226
+ const left = textOf(before);
9227
+ const right = textOf(after);
9228
+ if (left === null || right === null)
9229
+ return false;
9230
+ if (hasOpaqueLineBreak(left) || hasOpaqueLineBreak(right))
9231
+ return false;
9232
+ const a = substanceOf(file, left);
9233
+ return a !== null && a === substanceOf(file, right);
9654
9234
  }
9655
- function verdictBasis(verdict, current) {
9656
- if (verdict.head && verdict.head === current.head)
9657
- return "fresh";
9658
- if (verdict.patchId && verdict.patchId === current.patchId && verdict.diffHash && verdict.diffHash === current.diffHash)
9659
- return "carried";
9660
- return "stale";
9235
+ var SLASH_BLOCK, C_LIKE, SYNTAXES, DIRECTIVE, extensionOf = (file) => {
9236
+ const name = file.slice(file.lastIndexOf("/") + 1);
9237
+ const dot = name.lastIndexOf(".");
9238
+ return dot > 0 ? name.slice(dot + 1).toLowerCase() : "";
9239
+ }, CI_PATH, syntaxOf = (file) => CI_PATH.test(file) ? null : SYNTAXES[extensionOf(file)] ?? null, SPACE, WORD, NO_SPACE_AFTER = "([{,;", NO_SPACE_BEFORE = ")]},;", REGEX_AFTER = "(,=:[!&|?{};+-*%<>~^", REGEX_KEYWORDS, decoder;
9240
+ var init_substance = __esm(() => {
9241
+ SLASH_BLOCK = [["/*", "*/"]];
9242
+ C_LIKE = { line: ["//"], block: SLASH_BLOCK, quotes: `"'` };
9243
+ SYNTAXES = Object.fromEntries([
9244
+ ...["ts", "mts", "cts", "js", "mjs", "cjs"].map((ext) => [ext, { ...C_LIKE, js: true }]),
9245
+ ...["java", "kt", "kts", "scala", "swift", "dart", "groovy"].map((ext) => [
9246
+ ext,
9247
+ { ...C_LIKE, triple: true }
9248
+ ]),
9249
+ ["go", { ...C_LIKE, rawBacktick: true }],
9250
+ ...["css", "scss", "less"].map((ext) => [
9251
+ ext,
9252
+ { line: [], block: SLASH_BLOCK, quotes: `"'`, cssUrl: true }
9253
+ ]),
9254
+ ...["py", "pyi"].map((ext) => [
9255
+ ext,
9256
+ { line: ["#"], block: [], quotes: `"'`, triple: true, indented: true, fstring: true }
9257
+ ]),
9258
+ ["toml", { line: ["#"], block: [], quotes: `"`, literal: "'", triple: true }],
9259
+ ...["html", "htm", "xml", "svg", "xhtml"].map((ext) => [
9260
+ ext,
9261
+ { line: [], block: [["<!--", "-->"]], quotes: "", indented: true }
9262
+ ])
9263
+ ]);
9264
+ DIRECTIVE = /@ts-|eslint|prettier|biome|oxlint|istanbul|\bc8\b|\bv8\b|@jsx|@flow|@vite|webpack|__PURE__|__NO_SIDE_EFFECTS__|source(?:Mapping)?URL|^\/\s*<|go:|\+build|nolint|noqa|type:|pyright|mypy|pylint|pragma|coding[:=]|fmt:|NOSONAR|@license|@preserve|@refresh|environment|language=|cython|ruff|isort|deno-|@vitest|@jest|swiftlint|ktlint|checkstyle|suppress|nullable|#(?:end)?region/iu;
9265
+ CI_PATH = /^\.github\/|(?:^|\/)(?:\.gitlab-ci\.ya?ml|Jenkinsfile|azure-pipelines\.ya?ml|\.pre-commit-config\.ya?ml|\.circleci\/.*)$/u;
9266
+ SPACE = /[ \t\r\f\v]/u;
9267
+ WORD = /[\w$]/u;
9268
+ REGEX_KEYWORDS = new Set([
9269
+ "return",
9270
+ "typeof",
9271
+ "instanceof",
9272
+ "case",
9273
+ "do",
9274
+ "else",
9275
+ "in",
9276
+ "of",
9277
+ "new",
9278
+ "delete",
9279
+ "void",
9280
+ "throw",
9281
+ "yield",
9282
+ "await"
9283
+ ]);
9284
+ decoder = new TextDecoder("utf-8", { fatal: true });
9285
+ });
9286
+
9287
+ // packages/workit-core/src/core/policy/lane.ts
9288
+ import { existsSync as existsSync2, readFileSync as readFileSync3 } from "node:fs";
9289
+ import path6 from "node:path";
9290
+ function kindOf(file) {
9291
+ if (file.binary)
9292
+ return "behavior";
9293
+ if (file.generated)
9294
+ return "generated";
9295
+ if (LOCKFILES.has(basename(file.path)))
9296
+ return "lockfile";
9297
+ if (isDoc(file.path))
9298
+ return "doc";
9299
+ if (file.cosmetic)
9300
+ return "cosmetic";
9301
+ return TEST_PATH.test(file.path) ? "test" : "behavior";
9302
+ }
9303
+ function globMatch(pattern, file) {
9304
+ let regex = globCache.get(pattern);
9305
+ if (!regex) {
9306
+ let source = "";
9307
+ for (let index = 0;index < pattern.length; index += 1) {
9308
+ const char = pattern[index];
9309
+ if (char === "*" && pattern[index + 1] === "*") {
9310
+ source += pattern[index + 2] === "/" ? "(?:[^/]*/)*" : ".*";
9311
+ index += pattern[index + 2] === "/" ? 2 : 1;
9312
+ } else if (char === "*")
9313
+ source += "[^/]*";
9314
+ else if (char === "?")
9315
+ source += "[^/]";
9316
+ else
9317
+ source += char.replace(/[.+^${}()|[\]\\]/gu, "\\$&");
9318
+ }
9319
+ regex = new RegExp(`^${source}$`, "u");
9320
+ globCache.set(pattern, regex);
9321
+ }
9322
+ return regex.test(file) || file.startsWith(`${pattern.replace(/\/$/u, "")}/`);
9661
9323
  }
9662
- function trailerSessions(cwd, base, head) {
9663
- if (!base || !head || !safeRef(base) || !safeRef(head))
9664
- return [];
9665
- const out = git3(cwd, [
9666
- "log",
9667
- "--format=%(trailers:key=Workit-Session,valueonly,separator=%x00)",
9668
- `${base}..${head}`
9669
- ]);
9670
- return (out ?? "").split(/[\0\n]/u).map((value) => value.trim()).filter(Boolean);
9324
+ function isRiskPath(file, extra = []) {
9325
+ if (extra.some((pattern) => globMatch(pattern, file)))
9326
+ return true;
9327
+ if (RISK_FILES.has(file) || RISK_NAMES.has(basename(file)))
9328
+ return true;
9329
+ if (TEST_PATH.test(file) || isDoc(file))
9330
+ return false;
9331
+ return file.split("/").map((segment, index, all) => index === all.length - 1 ? segment.replace(/\.[^.]+$/u, "") : segment).some((segment) => segment.toLowerCase().split(/[-_.]/u).some((token) => RISK_TOKENS.has(token)));
9671
9332
  }
9672
- function branchScope(cwd, branch) {
9673
- const renamedAt = new Map;
9674
- let since = Number.NEGATIVE_INFINITY;
9675
- if (safeRef(branch)) {
9676
- const log = git3(cwd, [
9677
- "log",
9678
- "-g",
9679
- "--date=unix",
9680
- "--format=%gd%x09%gs",
9681
- `refs/heads/${branch}`,
9682
- "--"
9333
+ function computeLane(input) {
9334
+ const riskFiles = input.files.filter((file) => isRiskPath(file.path, input.riskPaths)).map((file) => file.path);
9335
+ const risky = new Set(riskFiles);
9336
+ const kinds = input.files.map((file) => ({
9337
+ file,
9338
+ kind: risky.has(file.path) ? "behavior" : kindOf(file)
9339
+ }));
9340
+ const behavior = kinds.filter((entry) => entry.kind === "behavior" || entry.kind === "test" || entry.kind === "lockfile");
9341
+ const lines = behavior.filter((entry) => entry.kind !== "lockfile").reduce((sum, entry) => sum + entry.file.added + entry.file.deleted, 0);
9342
+ const opaque = behavior.filter((entry) => entry.kind === "lockfile" || entry.file.binary);
9343
+ const result = (lane, reasons) => ({
9344
+ lane,
9345
+ reasons,
9346
+ lines,
9347
+ files: behavior.length,
9348
+ riskFiles
9349
+ });
9350
+ const { selfMaxLines, selfMaxFiles, fullMinLines } = LANE_THRESHOLDS;
9351
+ if (input.riskTier === "high")
9352
+ return result("full", ["the task judgment records high risk"]);
9353
+ if (riskFiles.length)
9354
+ return result("full", [
9355
+ `touches a risk path: ${riskFiles.slice(0, 3).join(", ")}${riskFiles.length > 3 ? ", …" : ""}`
9683
9356
  ]);
9684
- for (const line of (log ?? "").split(`
9685
- `)) {
9686
- const [selector = "", subject = ""] = line.split("\t");
9687
- const seconds = Number(/@\{(\d+)\}$/u.exec(selector)?.[1]);
9688
- if (!Number.isFinite(seconds))
9689
- continue;
9690
- since = seconds * 1000;
9691
- const match = RENAMED.exec(subject);
9692
- if (!match || match[1] === branch)
9693
- continue;
9694
- const until = (seconds + 1) * 1000;
9695
- renamedAt.set(match[1], Math.max(renamedAt.get(match[1]) ?? 0, until));
9696
- }
9697
- }
9698
- return (row) => {
9699
- if (row.branch === branch)
9700
- return true;
9701
- const until = row.branch === null ? undefined : renamedAt.get(row.branch);
9702
- if (until === undefined)
9703
- return false;
9704
- const at = Date.parse(row.at);
9705
- return at >= since && at <= until;
9706
- };
9357
+ if (!behavior.length)
9358
+ return result("none", [
9359
+ input.files.length ? "only docs, comments, formatting or generated copies changed" : "no changes"
9360
+ ]);
9361
+ if (lines >= fullMinLines)
9362
+ return result("full", [`large diff: ${lines} lines (≥ ${fullMinLines})`]);
9363
+ if (lines > selfMaxLines)
9364
+ return result("focused", [`${lines} lines changed (> ${selfMaxLines})`]);
9365
+ if (behavior.length > selfMaxFiles)
9366
+ return result("focused", [`${behavior.length} behavior files changed (> ${selfMaxFiles})`]);
9367
+ if (opaque.length)
9368
+ return result("focused", [
9369
+ `a lockfile or binary changed: ${opaque.slice(0, 3).map((entry) => entry.file.path).join(", ")}${opaque.length > 3 ? ", …" : ""}`
9370
+ ]);
9371
+ return result("self", [
9372
+ `small change: ${lines} line${lines === 1 ? "" : "s"} in ${behavior.length} file${behavior.length === 1 ? "" : "s"}, no risk path`
9373
+ ]);
9707
9374
  }
9708
- function authorSessions(cwd, rows, branch, range = { base: null, head: null }, scope = branchScope(cwd, branch)) {
9709
- const sessions = new Set;
9710
- for (const row of rows) {
9711
- if (!scope(row) || row.type !== "commit.recorded" && row.type !== "task.opened")
9712
- continue;
9713
- const session = str(row.session) ?? row.actor.session;
9714
- if (session)
9715
- sessions.add(session);
9375
+ function untrackedStat(cwd, file) {
9376
+ try {
9377
+ const bytes = readFileSync3(path6.join(cwd, file));
9378
+ if (bytes.includes(0))
9379
+ return { path: file, added: 0, deleted: 0, binary: true };
9380
+ const text = bytes.toString("utf8");
9381
+ if (hasOpaqueLineBreak(text))
9382
+ return { path: file, added: 0, deleted: 0, binary: true };
9383
+ const lines = text.split(`
9384
+ `).length - (text.endsWith(`
9385
+ `) ? 1 : 0);
9386
+ return { path: file, added: lines, deleted: 0 };
9387
+ } catch {
9388
+ return { path: file, added: 0, deleted: 0, binary: true };
9716
9389
  }
9717
- for (const session of trailerSessions(cwd, range.base, range.head))
9718
- sessions.add(session);
9719
- return sessions;
9720
9390
  }
9721
- function sessionTrust(cwd, row) {
9722
- const session = row.actor.session;
9723
- if (!session || row.self === true)
9391
+ function cosmeticFiles(cwd, fork, head) {
9392
+ const raw = diffRaw(cwd, fork, head);
9393
+ if (!raw)
9724
9394
  return null;
9725
- const source = typeof row.sessionSource === "string" ? row.sessionSource : "env";
9726
- const host = row.actor.host;
9727
- const issuedOnly = source === "flag" ? host !== "cli" : source === "env" && session.includes(":") && !SHELL_VOUCHED_HOSTS.has(host);
9728
- if (!issuedOnly)
9395
+ const candidates = raw.filter((entry) => REGULAR.has(entry.oldMode) && entry.oldMode === entry.newMode && !ABSENT.test(entry.oldBlob) && (head === null || !ABSENT.test(entry.newBlob)));
9396
+ const blobs = readBlobs(cwd, candidates.flatMap((entry) => [entry.oldBlob, entry.newBlob]));
9397
+ if (!blobs)
9729
9398
  return null;
9730
- const binding = issuedBinding(cwd, session);
9731
- if (binding === null)
9732
- return "unissued_session";
9733
- return binding === "token" && row.sessionBound !== true ? "unbound_session" : null;
9399
+ const out = new Set;
9400
+ const opaque = new Set;
9401
+ for (const entry of candidates) {
9402
+ const before = blobs.get(entry.oldBlob);
9403
+ let after = blobs.get(entry.newBlob);
9404
+ if (!after && head === null && ABSENT.test(entry.newBlob))
9405
+ try {
9406
+ after = readFileSync3(path6.join(cwd, entry.path));
9407
+ } catch {
9408
+ after = undefined;
9409
+ }
9410
+ if (before && after && cosmeticChange(entry.path, before, after))
9411
+ out.add(entry.path);
9412
+ else if ([before, after].some((bytes) => bytes && hasOpaqueLineBreak(bytes.toString("utf8"))))
9413
+ opaque.add(entry.path);
9414
+ }
9415
+ return { cosmetic: out, opaque };
9734
9416
  }
9735
- function trustReasons(cwd, row) {
9736
- if (row.integrity === "unverified")
9737
- return [row.integrityReason ?? "unsigned"];
9738
- if (row.integrity !== "verified")
9739
- return ["unsigned"];
9740
- const session = sessionTrust(cwd, row);
9741
- return session ? [session] : [];
9417
+ function generatedAt(cwd, fork, paths) {
9418
+ const files = treeFilesNamed(cwd, fork, ".gitattributes");
9419
+ const blobs = files?.size ? readBlobs(cwd, [...files.values()]) : null;
9420
+ if (!files || !blobs)
9421
+ return new Set;
9422
+ const texts = new Map;
9423
+ for (const [file, id] of files) {
9424
+ const bytes = blobs.get(id);
9425
+ if (bytes)
9426
+ texts.set(file, bytes.toString("utf8"));
9427
+ }
9428
+ return generatedByAttributes(texts, paths);
9742
9429
  }
9743
- function withIntegrity(cwd, rows) {
9744
- if (rows.every((row) => row.integrity !== undefined))
9745
- return rows;
9746
- const read = readLedger(cwd, { integrity: true });
9747
- const bySeq = new Map(read.ok ? read.value.rows.map((row) => [row.seq, row]) : []);
9748
- return rows.map((row) => {
9749
- if (row.integrity !== undefined)
9750
- return row;
9751
- const fresh = bySeq.get(row.seq);
9752
- return fresh && fresh.id === row.id && fresh.at === row.at ? fresh : row;
9753
- });
9430
+ function laneFiles(cwd, fork, head, attributesAt = fork) {
9431
+ const stats = diffNumstat(cwd, fork, head);
9432
+ const cosmetic = stats ? cosmeticFiles(cwd, fork, head) : null;
9433
+ if (!stats || !cosmetic)
9434
+ return null;
9435
+ const files = [
9436
+ ...stats.map((entry) => ({
9437
+ ...entry,
9438
+ ...cosmetic.opaque.has(entry.path) ? { binary: true } : {},
9439
+ cosmetic: !entry.binary && cosmetic.cosmetic.has(entry.path)
9440
+ })),
9441
+ ...head === null ? untrackedFiles(cwd).map((file) => untrackedStat(cwd, file)) : []
9442
+ ];
9443
+ const generated = generatedAt(cwd, attributesAt, files.map((entry) => entry.path));
9444
+ return files.map((entry) => ({
9445
+ ...entry,
9446
+ generated: generated.has(entry.path)
9447
+ }));
9754
9448
  }
9755
- function effectiveOf(rows, reasonsOf, sameCode, isCurrent) {
9756
- let effective = null;
9757
- for (const row of rows) {
9758
- if (!effective) {
9759
- effective = row;
9760
- continue;
9761
- }
9762
- if (untrusted(reasonsOf(row)) && !untrusted(reasonsOf(effective)))
9763
- continue;
9764
- const rowIndependent = reasonsOf(row).length === 0;
9765
- const effIndependent = reasonsOf(effective).length === 0;
9766
- if (effIndependent && !rowIndependent && isCurrent(effective))
9767
- continue;
9768
- if (effIndependent && FAILING.has(String(effective.result)) && row.actor.session === effective.actor.session && sameCode(effective, row))
9769
- continue;
9770
- effective = row;
9771
- }
9772
- return effective;
9449
+ function driftFiles(cwd, range) {
9450
+ if (range.fromFork === range.toFork)
9451
+ return laneFiles(cwd, range.from, range.to, range.toFork);
9452
+ const drifted = ownChangeDrift(cwd, range);
9453
+ const stats = drifted ? diffNumstat(cwd, range.from, range.to) : null;
9454
+ if (!drifted || !stats)
9455
+ return null;
9456
+ const byPath = new Map(stats.map((entry) => [entry.path, entry]));
9457
+ const generated = generatedAt(cwd, range.toFork, drifted);
9458
+ return drifted.map((file) => ({
9459
+ path: file,
9460
+ added: byPath.get(file)?.added ?? 0,
9461
+ deleted: byPath.get(file)?.deleted ?? 0,
9462
+ ...byPath.get(file)?.binary ? { binary: true } : {},
9463
+ cosmetic: false,
9464
+ generated: generated.has(file)
9465
+ }));
9773
9466
  }
9774
- function restackCarries(rows, branch, from, to) {
9775
- if (!from || !to)
9776
- return false;
9777
- const edges = new Map;
9778
- for (const row of rows)
9779
- if (row.type === "stack.restacked" && row.observer === "workit_cli" && row.integrity === "verified" && row.branch === branch && row.patchEqual === true && typeof row.fromHead === "string" && row.head)
9780
- edges.set(row.fromHead, [...edges.get(row.fromHead) ?? [], row.head]);
9781
- const seen = new Set([from]);
9782
- const queue = [from];
9783
- while (queue.length) {
9784
- const next = queue.shift();
9785
- if (next === to)
9786
- return true;
9787
- for (const target of edges.get(next) ?? [])
9788
- if (!seen.has(target)) {
9789
- seen.add(target);
9790
- queue.push(target);
9791
- }
9467
+ function branchLane(cwd, options) {
9468
+ const candidates = options.base ? [`origin/${options.base}`, options.base] : [];
9469
+ const base = candidates.find((ref) => resolveRef(cwd, ref) !== null) ?? defaultBase(cwd) ?? null;
9470
+ const head = options.workingTree ? null : options.head;
9471
+ const fork = base && (options.workingTree || options.head) ? forkOf(cwd, base, head) : null;
9472
+ const files = fork ? laneFiles(cwd, fork, head) : null;
9473
+ const riskTier = options.judgment?.riskTier ?? null;
9474
+ if (!fork || !files) {
9475
+ const high = riskTier === "high";
9476
+ return {
9477
+ lane: high ? "full" : "focused",
9478
+ reasons: [
9479
+ `the diff of ${options.head?.slice(0, 12) ?? "(no head)"} against ${base ?? "(no base)"} cannot be read`,
9480
+ ...high ? ["the task judgment records high risk"] : []
9481
+ ],
9482
+ lines: 0,
9483
+ files: 0,
9484
+ riskFiles: [],
9485
+ base,
9486
+ head: options.head
9487
+ };
9792
9488
  }
9793
- return false;
9794
- }
9795
- function checkVerdicts(cwd, branch, ledgerRows) {
9796
- const rows = withIntegrity(cwd, ledgerRows);
9797
- const head = branchHead(cwd, branch);
9798
- const fallbackBase = defaultBase(cwd);
9799
- const scope = branchScope(cwd, branch);
9800
- const keyCache = new Map;
9801
- const keysAt = (base, at) => {
9802
- if (!base || !at)
9803
- return { patchId: null, diffHash: null };
9804
- const cacheKey = `${base}\x00${at}`;
9805
- let key = keyCache.get(cacheKey);
9806
- if (!key) {
9807
- key = { patchId: patchId(cwd, base, at), diffHash: diffHash(cwd, base, at) };
9808
- keyCache.set(cacheKey, key);
9809
- }
9810
- return key;
9811
- };
9812
- const keyFor = (base) => keysAt(base, head);
9813
- const basisOf = (row) => {
9814
- const basis = verdictBasis(row, { head, ...keyFor(row.base ?? fallbackBase) });
9815
- return basis === "stale" && restackCarries(rows, branch, row.head, head) ? "carried" : basis;
9816
- };
9817
- const sameCode = (failed, row) => {
9818
- if (failed.head && failed.head === row.head)
9819
- return true;
9820
- const base = failed.base ?? fallbackBase;
9821
- const stored = (row.base ?? fallbackBase) === base && row.baseSha !== null && row.baseSha === failed.baseSha;
9822
- const keys = stored ? { patchId: row.patchId, diffHash: row.diffHash } : keysAt(base, row.head);
9823
- return verdictBasis(failed, { head: row.head, ...keys }) !== "stale" || restackCarries(rows, branch, failed.head, row.head);
9489
+ const repoRisk = parseRiskPaths(fileAt(cwd, fork, RISK_PATHS_FILE) ?? "");
9490
+ return {
9491
+ ...computeLane({
9492
+ files,
9493
+ riskTier,
9494
+ riskPaths: [...repoRisk, ...options.riskPaths ?? []]
9495
+ }),
9496
+ base,
9497
+ head: options.head
9824
9498
  };
9825
- const authors = authorSessions(cwd, rows, branch, { base: fallbackBase, head }, scope);
9826
- const failureAuthorCache = new Map;
9827
- const failureAuthors = (row) => {
9828
- let set = failureAuthorCache.get(row.seq);
9829
- if (!set) {
9830
- set = authorSessions(cwd, rows.filter((other) => other.integrity === "verified" && other.seq < row.seq), branch, { base: null, head: null }, scope);
9831
- failureAuthorCache.set(row.seq, set);
9832
- }
9833
- return set;
9499
+ }
9500
+ var LANE_THRESHOLDS, RISK_TOKENS, RISK_FILES, RISK_NAMES, RISK_PATHS_FILE = ".workit/risk-paths", LOCKFILES, DOC_EXT, DOC_NAMES, INSTRUCTIONS, TEST_PATH, basename = (file) => file.slice(file.lastIndexOf("/") + 1), isDoc = (file) => !INSTRUCTIONS.test(file) && (DOC_EXT.test(file) || DOC_NAMES.test(basename(file))), globCache, parseRiskPaths = (text) => text.split(`
9501
+ `).map((line) => line.trim()).filter((line) => line && !line.startsWith("#")).map((line) => line.replace(/^\//u, "")), REGULAR, ABSENT, forkOf = (cwd, base, head) => mergeBase(cwd, base, head ?? "HEAD"), laneNeed = (lane) => lane === "none" ? "none" : lane === "self" ? "self" : "non-author";
9502
+ var init_lane = __esm(() => {
9503
+ init_rev();
9504
+ init_default_base();
9505
+ init_substance();
9506
+ LANE_THRESHOLDS = {
9507
+ selfMaxLines: 100,
9508
+ selfMaxFiles: 5,
9509
+ fullMinLines: 500
9834
9510
  };
9835
- const reasonCache = new Map;
9836
- const reasonsOf = (row) => {
9837
- let reasons = reasonCache.get(row);
9838
- if (!reasons) {
9839
- const failing = FAILING.has(String(row.result));
9840
- reasons = [
9841
- ...trustReasons(cwd, row),
9842
- ...independenceReasons(row, failing ? failureAuthors(row) : authors)
9843
- ];
9844
- reasonCache.set(row, reasons);
9845
- }
9846
- return reasons;
9511
+ RISK_TOKENS = new Set([
9512
+ "auth",
9513
+ "authn",
9514
+ "authz",
9515
+ "oauth",
9516
+ "security",
9517
+ "permission",
9518
+ "permissions",
9519
+ "secret",
9520
+ "secrets",
9521
+ "crypto",
9522
+ "gate",
9523
+ "gates",
9524
+ "policy",
9525
+ "policies",
9526
+ "hook",
9527
+ "hooks",
9528
+ "husky",
9529
+ "lefthook",
9530
+ "release",
9531
+ "releases",
9532
+ "migration",
9533
+ "migrations",
9534
+ "migrate",
9535
+ "workflows"
9536
+ ]);
9537
+ RISK_FILES = new Set([
9538
+ ".gitlab-ci.yml",
9539
+ "Jenkinsfile",
9540
+ "azure-pipelines.yml",
9541
+ ".pre-commit-config.yaml",
9542
+ ".workit/risk-paths"
9543
+ ]);
9544
+ RISK_NAMES = new Set([".gitattributes"]);
9545
+ LOCKFILES = new Set([
9546
+ "bun.lock",
9547
+ "bun.lockb",
9548
+ "package-lock.json",
9549
+ "npm-shrinkwrap.json",
9550
+ "yarn.lock",
9551
+ "pnpm-lock.yaml",
9552
+ "Cargo.lock",
9553
+ "go.sum",
9554
+ "poetry.lock",
9555
+ "uv.lock",
9556
+ "Pipfile.lock",
9557
+ "Gemfile.lock",
9558
+ "composer.lock"
9559
+ ]);
9560
+ DOC_EXT = /\.(?:md|mdx|markdown|rst|adoc)$/iu;
9561
+ DOC_NAMES = /^(?:license|licence|notice|authors|changelog|changes|contributors)$/iu;
9562
+ INSTRUCTIONS = /(?:^|\/)(?:AGENTS|CLAUDE|GEMINI|SKILL)\.md$|(?:^|\/)(?:skills|agents|commands|rules|prompts)\/[^/]+/iu;
9563
+ TEST_PATH = /(?:^|\/)(?:test|tests|__tests__)\/|\.(?:test|spec)\.[^/]+$|_test\.[^/]+$/iu;
9564
+ globCache = new Map;
9565
+ REGULAR = new Set(["100644", "100755"]);
9566
+ ABSENT = /^0+$/u;
9567
+ });
9568
+
9569
+ // packages/workit-core/src/core/policy/carry.ts
9570
+ function verdictDrift(cwd, input) {
9571
+ const since = input.from;
9572
+ const stale = (reason, files = null, focused = false) => ({
9573
+ carries: false,
9574
+ since,
9575
+ files,
9576
+ focused,
9577
+ reason
9578
+ });
9579
+ const fromFork = mergeBase(cwd, input.fromBase, input.from);
9580
+ const toFork = mergeBase(cwd, input.toBase, input.to);
9581
+ const files = fromFork && toFork ? driftFiles(cwd, { from: input.from, to: input.to, fromFork, toFork }) : null;
9582
+ if (!fromFork || !toFork || !files)
9583
+ return stale(`the change since ${since.slice(0, 12)} cannot be read`);
9584
+ const riskPaths = [fromFork, toFork].flatMap((fork) => parseRiskPaths(fileAt(cwd, fork, RISK_PATHS_FILE) ?? ""));
9585
+ const lane = computeLane({ files, riskPaths });
9586
+ const paths = files.map((file) => file.path);
9587
+ if (lane.lane === "none")
9588
+ return { carries: true, rule: "lane-none", since, files: paths };
9589
+ if (input.kind === "unit" && lane.lane === "self" && files.every((file) => TEST_ONLY_KINDS.has(kindOf(file))))
9590
+ return { carries: true, rule: "tests-only", since, files: paths };
9591
+ return stale(lane.reasons[0] ?? `lane ${lane.lane}`, paths, lane.lane === "self");
9592
+ }
9593
+ var TEST_ONLY_KINDS;
9594
+ var init_carry = __esm(() => {
9595
+ init_rev();
9596
+ init_lane();
9597
+ TEST_ONLY_KINDS = new Set(["doc", "cosmetic", "generated", "test"]);
9598
+ });
9599
+
9600
+ // packages/workit-core/src/core/commit-flavors.ts
9601
+ var init_commit_flavors = () => {};
9602
+
9603
+ // packages/workit-core/src/core/git.ts
9604
+ var init_git = () => {};
9605
+
9606
+ // packages/workit-core/src/core/branch.ts
9607
+ import { createHash as createHash6 } from "node:crypto";
9608
+ function effectiveWorkspacePolicy(workspaceRoot, kind, profileName) {
9609
+ const selected = resolveRuntimeWorkspacePolicy(workspaceRoot, kind, profileName?.trim() || process.env.WORKFLOW_PROFILE?.trim() || undefined);
9610
+ const config = readConfig();
9611
+ return kind === "branch" ? {
9612
+ branchPolicy: withReleaseTracks(workspaceRoot, resolveBranchPolicy(config, selected.policy ? { branchPolicy: selected.policy } : null, () => repoBranchPreset(workspaceRoot))),
9613
+ provenance: { branchPolicy: selected.source }
9614
+ } : {
9615
+ commitPolicy: resolveCommitPolicy(config, selected.policy ? { commitPolicy: selected.policy } : null),
9616
+ provenance: { commitPolicy: selected.source }
9847
9617
  };
9848
- const retired = new Set;
9849
- for (const row of rows)
9850
- if (row.type === "verdict" && row.supersedes && !row.supersedeIgnored && trustReasons(cwd, row).length === 0)
9851
- retired.add(row.supersedes);
9852
- const byKind = new Map;
9853
- for (const row of rows)
9854
- if (isLiveVerdict(row, retired) && scope(row)) {
9855
- const kind = typeof row.kind === "string" ? row.kind : "review";
9856
- byKind.set(kind, [...byKind.get(kind) ?? [], row]);
9857
- }
9858
- const verdicts = [];
9859
- const warnings = [];
9860
- for (const [kind, list] of byKind) {
9861
- const row = effectiveOf(list, reasonsOf, sameCode, (candidate) => basisOf(candidate) !== "stale");
9862
- if (!row)
9863
- continue;
9864
- const basis = basisOf(row);
9865
- const current = basis !== "stale";
9866
- const independence = reasonsOf(row);
9867
- const reasons = [
9868
- ...current ? [] : ["stale"],
9869
- ...PASSING.has(String(row.result)) ? [] : ["not_passing"],
9870
- ...independence
9871
- ];
9872
- verdicts.push({
9873
- kind,
9874
- basis,
9875
- current,
9876
- independent: independence.length === 0,
9877
- accepted: reasons.length === 0,
9878
- reasons,
9879
- verdict: row
9880
- });
9881
- if (reasons.length === 0) {
9882
- for (const later of list)
9883
- if (later.seq > row.seq && reasonsOf(later).length > 0 && !untrusted(reasonsOf(later)) && FAILING.has(String(later.result)) && basisOf(later) !== "stale")
9884
- 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`);
9885
- }
9886
- }
9887
- for (const row of rows)
9888
- if (row.integrity === "unverified" && scope(row))
9889
- 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)`);
9890
- const tampered = ledgerTampering(cwd);
9891
- if (tampered.length) {
9892
- warnings.push(tamperedWarning(cwd, tampered));
9893
- for (const entry of verdicts) {
9894
- entry.accepted = false;
9895
- entry.reasons = [...entry.reasons, "ledger_tampered"];
9618
+ }
9619
+ var withReleaseTracks = (workspaceRoot, policy) => {
9620
+ const read = workspaceReleaseTracks(workspaceRoot);
9621
+ if (read.error)
9622
+ throw new ReleaseTrackError(read.error);
9623
+ if (read.tracks.length === 0 && read.protectedBranches.length === 0)
9624
+ return policy;
9625
+ const known = new Set(policy.allowed.map((pattern) => pattern.source));
9626
+ const allowed = [...policy.allowed];
9627
+ for (const glob of read.tracks.flatMap((track) => Object.values(track.naming).map(namingGlob))) {
9628
+ const pattern = branchGlobPattern(glob);
9629
+ if (!known.has(pattern.source)) {
9630
+ known.add(pattern.source);
9631
+ allowed.push(pattern);
9896
9632
  }
9897
9633
  }
9898
- verdicts.sort((a, b) => a.verdict.seq - b.verdict.seq);
9899
- const newestCurrent = verdicts.findLast((entry) => entry.current) ?? verdicts.at(-1) ?? null;
9900
- const failing = verdicts.find((entry) => entry.current && entry.independent && FAILING.has(String(entry.verdict.result)));
9901
- const newestAccepted = verdicts.findLast((entry) => entry.accepted && STRONG_RESULTS.has(String(entry.verdict.result))) ?? null;
9902
- const accepted = tampered.length ? { accepted: false, verdict: null, reasons: ["ledger_tampered"] } : newestAccepted && !failing ? { accepted: true, verdict: newestAccepted.verdict, reasons: [] } : {
9903
- accepted: false,
9904
- verdict: failing?.verdict ?? null,
9905
- reasons: failing ? ["failing_verdict"] : !newestCurrent ? ["no_verdict"] : newestCurrent.reasons.length ? newestCurrent.reasons : ["type_check_only"]
9906
- };
9907
- const latestSelf = failing ? undefined : verdicts.findLast((entry) => entry.current && !entry.independent && !untrusted(entry.reasons) && (STRONG_RESULTS.has(String(entry.verdict.result)) || FAILING.has(String(entry.verdict.result))));
9908
- const selfEntry = latestSelf && STRONG_RESULTS.has(String(latestSelf.verdict.result)) ? latestSelf : undefined;
9909
9634
  return {
9910
- branch,
9911
- head,
9912
- review: accepted.accepted ? "verified" : selfEntry ? "self-reviewed" : "unreviewed",
9913
- selfVerdict: accepted.accepted ? null : selfEntry?.verdict ?? null,
9914
- base: newestCurrent?.verdict.base ?? fallbackBase,
9915
- patchId: keyFor(newestCurrent?.verdict.base ?? fallbackBase).patchId,
9916
- current: { basis: newestCurrent?.basis ?? "none", verdict: newestCurrent?.verdict ?? null },
9917
- accepted,
9918
- verdicts,
9919
- authors: [...authors].toSorted(),
9920
- warnings
9921
- };
9922
- }
9923
- function activeStanding(rows, fanout, leads = null) {
9924
- const active = [];
9925
- for (const row of rows) {
9926
- if (row.fanout !== fanout || !fromLead(row, leads))
9927
- continue;
9928
- if (row.type === "standing" && typeof row.what === "string" && row.what.trim()) {
9929
- if (!row.superseded)
9930
- active.push(row);
9931
- } else if (row.type === "standing.cleared") {
9932
- const target = str(row.target);
9933
- if (target === null)
9934
- active.length = 0;
9935
- else {
9936
- const index = active.findIndex((order) => order.id === target);
9937
- if (index >= 0)
9938
- active.splice(index, 1);
9939
- }
9635
+ ...policy,
9636
+ allowed,
9637
+ protected: new Set([
9638
+ ...policy.protected,
9639
+ ...[...trackBranches(read.tracks), ...read.protectedBranches].map((name) => name.toLowerCase())
9640
+ ])
9641
+ };
9642
+ }, KIND_SYNONYMS, allowedKinds = (allowed) => [
9643
+ ...new Set(allowed.map((pattern) => /^\^([\w.-]+)\\?\/\.\*\$$/u.exec(pattern.source)?.[1]?.toLowerCase()).filter((kind) => Boolean(kind)))
9644
+ ], editDistance = (a, b) => {
9645
+ const row = Array.from({ length: b.length + 1 }, (_, index) => index);
9646
+ for (let i = 1;i <= a.length; i++) {
9647
+ let previous = row[0];
9648
+ row[0] = i;
9649
+ for (let j = 1;j <= b.length; j++) {
9650
+ const saved = row[j];
9651
+ row[j] = Math.min(row[j] + 1, row[j - 1] + 1, previous + (a[i - 1] === b[j - 1] ? 0 : 1));
9652
+ previous = saved;
9940
9653
  }
9941
9654
  }
9942
- return active;
9943
- }
9944
- var LEDGER_VERSION = 1, MAX_LINE_BYTES = 4096, PASSING, FAILING, STRONG_RESULTS, PR_ROW_TYPES, err = (code, error, unblock) => ({
9945
- ok: false,
9946
- code,
9947
- error,
9948
- ...unblock ? { unblock } : {}
9949
- }), gitRun2 = (cwd, args, timeoutMs = GIT_TIMEOUTS.local) => {
9950
- const run = spawnSync8("git", args, {
9951
- cwd,
9952
- encoding: "utf8",
9953
- timeout: timeoutMs,
9954
- killSignal: "SIGKILL",
9955
- windowsHide: true,
9956
- maxBuffer: 256 * 1024 * 1024,
9957
- env: {
9958
- ...process.env,
9959
- GIT_TERMINAL_PROMPT: "0",
9960
- GIT_OPTIONAL_LOCKS: "0",
9961
- LC_ALL: "C",
9962
- LANGUAGE: "C"
9963
- }
9964
- });
9655
+ return row[b.length];
9656
+ }, suggestBranchName = (name, kinds) => {
9657
+ if (!kinds.length)
9658
+ return null;
9659
+ const slash = name.indexOf("/");
9660
+ const prefix = slash > 0 ? name.slice(0, slash).toLowerCase() : "";
9661
+ const rest = (slash > 0 ? name.slice(slash + 1) : name) || "name";
9662
+ const synonym = KIND_SYNONYMS[prefix];
9663
+ const near = kinds.map((kind) => ({ kind, distance: editDistance(prefix, kind) })).filter((entry) => prefix && entry.distance <= 2).toSorted((a, b) => a.distance - b.distance)[0]?.kind;
9664
+ const kind = (synonym && kinds.includes(synonym) ? synonym : null) ?? near ?? (kinds.includes("feature") ? "feature" : kinds[0]);
9665
+ return `${kind}/${rest}`;
9666
+ }, kindList = (kinds) => kinds.map((kind) => `${kind}/`).join(", "), aliasNote = (kinds) => {
9667
+ const notes = [
9668
+ ["fix", "bugfix"],
9669
+ ["chore", "feature"]
9670
+ ].filter(([, kind]) => kinds.includes(kind)).map(([alias, kind]) => `${alias}/ is ${kind}/`);
9671
+ return notes.length ? ` (${notes.join(", ")})` : "";
9672
+ }, validateBranchNamePolicy = (name, policy, source) => {
9673
+ const allowed = policy.allowed.map((pattern) => `/${pattern.source}/${pattern.flags}`);
9674
+ const kinds = allowedKinds(policy.allowed);
9675
+ if (policy.protected.has(name.toLowerCase())) {
9676
+ const correction = kinds.length ? `choose a non-protected branch of an allowed kind (${kindList(kinds)}), e.g. ${kinds[0]}/<slug>: workit git branch --kind ${kinds[0]} --slug <slug>` : `choose a non-protected branch matching one of: ${allowed.join(", ")}`;
9677
+ return {
9678
+ ok: false,
9679
+ rule: "protected_ref",
9680
+ source,
9681
+ attempted: name,
9682
+ correction,
9683
+ error: `protected_ref: branch ${JSON.stringify(name)} is protected by ${source}; ${correction}`
9684
+ };
9685
+ }
9686
+ if (!policy.allowed.some((pattern) => pattern.test(name))) {
9687
+ const suggestion = suggestBranchName(name, kinds);
9688
+ const correction = suggestion && policy.allowed.some((pattern) => pattern.test(suggestion)) ? `use ${JSON.stringify(suggestion)}; the allowed kinds are ${kindList(kinds)} (workit git branch ${suggestion})` : `choose a branch matching one of: ${allowed.join(", ")}`;
9689
+ return {
9690
+ ok: false,
9691
+ rule: "allowed_pattern",
9692
+ source,
9693
+ attempted: name,
9694
+ correction,
9695
+ error: `allowed_pattern: branch ${JSON.stringify(name)} violates ${source} policy; ${correction}`
9696
+ };
9697
+ }
9698
+ return { ok: true };
9699
+ }, branchPolicySnapshotFor = (workspaceRoot) => {
9700
+ const resolved = effectiveWorkspacePolicy(workspaceRoot, "branch");
9701
+ const branchPolicy = resolved.branchPolicy;
9702
+ const source = resolved.provenance.branchPolicy;
9703
+ const allowed = branchPolicy.allowed.map((pattern) => `${pattern.source}/${pattern.flags}`).toSorted();
9704
+ const protectedRefs = [...branchPolicy.protected].map((name) => name.toLowerCase()).toSorted();
9705
+ const fingerprint = createHash6("sha256").update(JSON.stringify({ allowed, protectedRefs, source })).digest("hex");
9965
9706
  return {
9966
- ok: run.status === 0,
9967
- stdout: run.stdout ?? "",
9968
- stderr: run.error ? String(run.error.message) : run.stderr ?? ""
9707
+ branchPolicy,
9708
+ namePolicy: { allowed: branchPolicy.allowed, protected: branchPolicy.protected },
9709
+ source,
9710
+ fingerprint
9969
9711
  };
9970
- }, git3 = (cwd, args) => {
9971
- const run = gitRun2(cwd, args);
9972
- const value = run.ok ? run.stdout.trim() : "";
9973
- return value ? value : null;
9974
- }, safeRef = (value) => value.length > 0 && !value.startsWith("-"), trackTrunk = (cwd) => {
9712
+ }, validateBranchNameFor = (workspaceRoot, name) => {
9975
9713
  try {
9976
- const resolved = vcsConfig("resolve", cwd);
9977
- if (resolved.ok === false || !resolved.releaseTrack || resolved.releaseTrack.blocking)
9714
+ const resolved = branchPolicySnapshotFor(workspaceRoot);
9715
+ return validateBranchNamePolicy(name, resolved.namePolicy, resolved.source);
9716
+ } catch (error) {
9717
+ const correction = "repair or select a valid workspace policy before creating a branch";
9718
+ return {
9719
+ ok: false,
9720
+ rule: "policy_unavailable",
9721
+ source: "workspaces.json",
9722
+ attempted: name,
9723
+ correction,
9724
+ error: `policy_unavailable: cannot validate branch ${JSON.stringify(name)} because ${error instanceof Error ? error.message : String(error)}; ${correction}`
9725
+ };
9726
+ }
9727
+ }, resolveBranchPolicyFor = (workspaceRoot, profileName) => effectiveWorkspacePolicy(workspaceRoot, "branch", profileName).branchPolicy, gitFlowOf = (root, integration) => {
9728
+ try {
9729
+ if (resolveBranchPolicyFor(root).preset !== "gitflow")
9978
9730
  return null;
9979
- const target = String(resolved.defaultTargetBranch ?? "").trim();
9980
- return target && safeRef(target) ? target : null;
9731
+ const selected = resolveRuntimeWorkspacePolicy(root, "branch", process.env.WORKFLOW_PROFILE?.trim() || undefined).policy;
9732
+ const names = branchNames(root);
9733
+ const production = productionBranchOf(names);
9734
+ const develop = selected?.developBranch?.trim() || (integration && integration.toLowerCase() !== production ? integration : "develop");
9735
+ return { develop, production, developExists: names.has(develop) };
9736
+ } catch {
9737
+ return null;
9738
+ }
9739
+ }, releaseBranchesOf = (cwd) => {
9740
+ try {
9741
+ const resolved = resolveBranchPolicyFor(cwd);
9742
+ const flow = resolved.preset === "gitflow" ? gitFlowOf(cwd) : null;
9743
+ return {
9744
+ preset: resolved.preset,
9745
+ branches: flow ? [flow.production, flow.develop] : [],
9746
+ production: flow ? flow.production : resolved.defaultTargetBranch
9747
+ };
9748
+ } catch {
9749
+ return { preset: "unknown", branches: [], production: null };
9750
+ }
9751
+ }, releaseBranch = (cwd, branch) => releaseBranchesOf(cwd).branches.some((name) => name.toLowerCase() === branch.toLowerCase()), branchGuidance = (cwd) => {
9752
+ try {
9753
+ const patterns = branchPolicySnapshotFor(cwd).namePolicy.allowed;
9754
+ const kinds = allowedKinds(patterns);
9755
+ const kindShaped = new Set(kinds.map((kind) => branchGlobPattern(`${kind}/*`).source));
9756
+ const anyName = patterns.some((pattern) => pattern.source === "^.*$");
9757
+ const others = patterns.filter((pattern) => !kindShaped.has(pattern.source)).map((pattern) => globOf(pattern));
9758
+ const allowed = anyName ? "Any non-protected branch name is allowed here." : `${kinds.length ? `Allowed branch kinds here: ${kindList(kinds)}${aliasNote(kinds)}` : "Allowed branch names here:"}${others.length ? `${kinds.length ? "; also" : ""} names matching ${others.join(", ")}` : ""}; anything else is denied.`;
9759
+ const resolved = vcsConfig("resolve", cwd);
9760
+ const integration = resolved.ok === false ? null : String(resolved.defaultTargetBranch ?? "") || null;
9761
+ const flow = resolved.releaseTrack?.track ? null : gitFlowOf(cwd, integration);
9762
+ if (flow?.developExists)
9763
+ return `${allowed} git-flow: feature/, bugfix/ and release/ start from origin/${flow.develop}, hotfix/ from origin/${flow.production}; feature/ and bugfix/ land on ${flow.develop}, release/ and hotfix/ on ${flow.production}.`;
9764
+ const names = branchNames(cwd);
9765
+ const configured = String(resolved.baseBranch ?? integration ?? "") || null;
9766
+ const trunk = productionBranchOf(names);
9767
+ const base = configured && names.has(configured) ? `Branch from origin/${configured}.` : names.has(trunk) ? `Branch from origin/${trunk}${configured ? ` (the default target ${configured} does not exist here)` : ""}.` : "No base branch exists here yet; pass the base explicitly.";
9768
+ return `${allowed} ${base}`;
9981
9769
  } catch {
9982
9770
  return null;
9983
9771
  }
9984
- }, newId2 = () => `${Date.now().toString(36)}-${randomBytes2(6).toString("hex")}`, NETWORK_FS_TYPES, isNetworkFsType = (type) => NETWORK_FS_TYPES.has(type >>> 0), onNetworkFs = (dir) => {
9985
- if (process.platform !== "linux")
9986
- return false;
9987
- try {
9988
- return isNetworkFsType(fs7.statfsSync(dir).type);
9989
- } catch {
9990
- return false;
9772
+ }, globOf = (pattern) => pattern.source.replace(/^\^|\$$/gu, "").replaceAll(".*", "*").replace(/\\(.)/gu, "$1"), policy = (root) => resolveBranchPolicyFor(root), isProtected = (root, name) => policy(root).protected.has(name.toLowerCase()), isProtectedTarget = (root, name) => {
9773
+ try {
9774
+ return isProtected(root, name);
9775
+ } catch {
9776
+ return true;
9777
+ }
9778
+ };
9779
+ var init_branch = __esm(() => {
9780
+ init_branch_policy();
9781
+ init_commit_flavors();
9782
+ init_git();
9783
+ init_config();
9784
+ init_workspaces();
9785
+ init_vcs_config();
9786
+ init_release_tracks();
9787
+ KIND_SYNONYMS = {
9788
+ fix: "bugfix",
9789
+ fixes: "bugfix",
9790
+ bug: "bugfix",
9791
+ bugs: "bugfix",
9792
+ bugfixes: "bugfix",
9793
+ feat: "feature",
9794
+ features: "feature",
9795
+ chore: "feature",
9796
+ docs: "feature",
9797
+ doc: "feature",
9798
+ refactor: "feature",
9799
+ test: "feature",
9800
+ tests: "feature",
9801
+ ci: "feature",
9802
+ build: "feature",
9803
+ perf: "feature",
9804
+ style: "feature",
9805
+ deps: "feature",
9806
+ task: "feature",
9807
+ enhancement: "feature",
9808
+ hot: "hotfix",
9809
+ hotfixes: "hotfix",
9810
+ releases: "release",
9811
+ rel: "release"
9812
+ };
9813
+ });
9814
+
9815
+ // packages/workit-core/src/core/logger.ts
9816
+ import os3 from "node:os";
9817
+ import path7 from "node:path";
9818
+ var SENSITIVE_WORDS, STACK_WORDS, escapeRegExp2 = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), resolveStateDir = () => {
9819
+ const override = process.env.WORKFLOW_TOOLKIT_STATE;
9820
+ if (override)
9821
+ return override;
9822
+ const home = os3.homedir();
9823
+ if (process.env.XDG_STATE_HOME)
9824
+ return path7.join(process.env.XDG_STATE_HOME, "workit");
9825
+ switch (os3.platform()) {
9826
+ case "darwin":
9827
+ return path7.join(home, "Library", "Application Support", "workit");
9828
+ case "win32":
9829
+ return path7.join(process.env.LOCALAPPDATA ?? path7.join(home, "AppData", "Local"), "workit");
9830
+ default:
9831
+ return path7.join(process.env.HOME ?? home, ".local", "state", "workit");
9832
+ }
9833
+ }, homePattern;
9834
+ var init_logger = __esm(() => {
9835
+ SENSITIVE_WORDS = new Set([
9836
+ "token",
9837
+ "secret",
9838
+ "password",
9839
+ "passwd",
9840
+ "authorization",
9841
+ "credential",
9842
+ "credentials",
9843
+ "cookie",
9844
+ "api",
9845
+ "apikey",
9846
+ "apitoken",
9847
+ "accesstoken",
9848
+ "clientsecret",
9849
+ "refreshtoken",
9850
+ "key",
9851
+ "bearer",
9852
+ "prompt",
9853
+ "message",
9854
+ "messages",
9855
+ "content",
9856
+ "body",
9857
+ "args",
9858
+ "argument",
9859
+ "result",
9860
+ "results",
9861
+ "output",
9862
+ "issue",
9863
+ "description",
9864
+ "summary",
9865
+ "payload",
9866
+ "script",
9867
+ "command",
9868
+ "text",
9869
+ "query"
9870
+ ]);
9871
+ STACK_WORDS = new Set(["stack", "stacktrace", "trace"]);
9872
+ homePattern = new RegExp(`${escapeRegExp2(os3.homedir())}(?=[/\\\\]|$)|\\$HOME(?=[/\\\\]|$)`, "g");
9873
+ });
9874
+
9875
+ // packages/workit-core/src/host-session.ts
9876
+ import { createHash as createHash7, randomBytes, timingSafeEqual } from "node:crypto";
9877
+ import fs5 from "node:fs";
9878
+ import path8 from "node:path";
9879
+ function hostSessionFromEnv(env) {
9880
+ const value = (key) => env[key]?.trim() || null;
9881
+ let inner = null;
9882
+ for (const [key, host] of HOST_SESSION_VARS) {
9883
+ const session = value(key);
9884
+ if (session) {
9885
+ inner = { host: host(env), session };
9886
+ break;
9887
+ }
9888
+ }
9889
+ const declared = value("WORKIT_HOST");
9890
+ if (env.WORKIT_SESSION_ID !== undefined) {
9891
+ const inherited = inner !== null && declared !== null && family(declared) !== family(inner.host);
9892
+ if (!inherited)
9893
+ return { host: declared, session: value("WORKIT_SESSION_ID") };
9991
9894
  }
9992
- }, LOCK_WAIT_MS = 5000, LOCK_STALE_MS = 30000, LOCK_OWNER = "owner", lockToken = (lock) => {
9895
+ return inner ? { host: inner.host, session: inner.session } : { host: declared, session: null };
9896
+ }
9897
+ function migrateRepoIssued(cwd) {
9898
+ if (cwd === undefined)
9899
+ return;
9900
+ let old;
9993
9901
  try {
9994
- return fs7.readFileSync(path9.join(lock, LOCK_OWNER), "utf8");
9902
+ const location = resolveStore(cwd);
9903
+ if (location instanceof Error)
9904
+ return;
9905
+ old = path8.join(location.dir, "hooks", "issued");
9906
+ if (!fs5.existsSync(old))
9907
+ return;
9995
9908
  } catch {
9996
- return null;
9909
+ return;
9997
9910
  }
9998
- }, lockAgeMs = (lock) => {
9999
- for (const target of [path9.join(lock, LOCK_OWNER), lock]) {
9911
+ for (const lead of safeList(old)) {
10000
9912
  try {
10001
- return Date.now() - fs7.statSync(target).mtimeMs;
10002
- } catch {}
10003
- }
10004
- return null;
10005
- }, ledgerLock, sha2562 = (text) => createHash9("sha256").update(text).digest("hex"), chainHash = (prevHash, body) => sha2562(`${prevHash ?? ""}
10006
- ${body}`), TAIL_BYTES, lastLine = (fd, size) => {
10007
- for (let span = Math.min(size, TAIL_BYTES);span > 0; span = Math.min(size, span * 2)) {
10008
- const buffer = Buffer.alloc(span);
10009
- fs7.readSync(fd, buffer, 0, span, size - span);
10010
- const end = buffer.lastIndexOf(10);
10011
- if (end < 0) {
10012
- if (span === size)
10013
- return null;
10014
- continue;
10015
- }
10016
- let stop = end;
10017
- let text = "";
10018
- while (stop >= 0) {
10019
- const start = buffer.lastIndexOf(10, stop - 1);
10020
- text = buffer.toString("utf8", start + 1, stop).trim();
10021
- if (text) {
10022
- if (start >= 0 || span === size)
10023
- return text;
10024
- break;
10025
- }
10026
- if (start < 0) {
10027
- if (span === size)
10028
- return null;
10029
- break;
9913
+ const from = path8.join(old, lead);
9914
+ if (!fs5.statSync(from).isDirectory())
9915
+ continue;
9916
+ const to = path8.join(userIssuedRoot(), lead);
9917
+ fs5.mkdirSync(to, { recursive: true, mode: 448 });
9918
+ for (const id of fs5.readdirSync(from)) {
9919
+ try {
9920
+ fs5.copyFileSync(path8.join(from, id), path8.join(to, id), fs5.constants.COPYFILE_EXCL);
9921
+ } catch (error) {
9922
+ if (error.code !== "EEXIST")
9923
+ throw error;
9924
+ }
9925
+ fs5.rmSync(path8.join(from, id), { force: true });
10030
9926
  }
10031
- stop = start;
10032
- }
10033
- if (span === size)
10034
- return text || null;
9927
+ removeIfEmpty(from);
9928
+ } catch {}
10035
9929
  }
10036
- return null;
10037
- }, tighten = (fd) => {
9930
+ removeIfEmpty(old);
9931
+ }
9932
+ function recordIssuedSession(cwd, lead, issued) {
9933
+ const token = randomBytes(18).toString("hex");
10038
9934
  try {
10039
- if ((fs7.fstatSync(fd).mode & 63) !== 0)
10040
- fs7.fchmodSync(fd, 384);
10041
- } catch {}
10042
- }, sealed = (row, id, at, previous) => {
10043
- const { prevHash: _prev, rowHash: _row, ...fields } = row;
10044
- const prevHash = previous === null ? null : sha2562(previous);
10045
- const body = { v: LEDGER_VERSION, id, at, ...fields, prevHash };
10046
- const full = { ...body, rowHash: chainHash(prevHash, JSON.stringify(body)) };
10047
- return { full, line: `${JSON.stringify(full)}
10048
- ` };
10049
- }, isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), str = (value) => typeof value === "string" ? value : null, normalizeActor = (value) => {
10050
- const actor = isRecord2(value) ? value : {};
10051
- return {
10052
- host: str(actor.host) ?? "unknown",
10053
- session: str(actor.session),
10054
- agentId: str(actor.agentId)
10055
- };
10056
- }, TAMPER_REASONS, tamperCache, fileStamp = (stat) => `${stat.ino}:${stat.mtimeMs}:${stat.ctimeMs}`, 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)}`, isLiveVerdict = (row, retired) => row.type === "verdict" && typeof row.result === "string" && !retired.has(row.id), RENAMED, renameAwareBranch = (scopeOf, branch) => {
10057
- return (target) => {
10058
- const own = typeof branch === "string" ? branch : null;
10059
- const theirs = typeof target.branch === "string" ? target.branch : null;
10060
- if (own === theirs)
10061
- return true;
10062
- if (own === null || theirs === null || typeof target.at !== "string")
10063
- return false;
10064
- return scopeOf(own)({ branch: theirs, at: target.at });
10065
- };
10066
- }, derivesFrom = (derived, bare) => !bare.includes(":") && derived.endsWith(`:${bare}`), authoredBy = (authors, session) => {
10067
- if (authors.has(session))
10068
- return true;
10069
- for (const author of authors)
10070
- if (derivesFrom(author, session) || derivesFrom(session, author))
10071
- return true;
10072
- return false;
10073
- }, TRUST_REASONS, SHELL_VOUCHED_HOSTS, untrusted = (reasons) => reasons.some((reason) => TRUST_REASONS.has(reason)), independenceReasons = (row, authors) => {
10074
- const reasons = [];
10075
- if (!row.actor.session)
10076
- reasons.push("no_session");
10077
- else if (authoredBy(authors, row.actor.session))
10078
- reasons.push("author_session");
10079
- if (row.self === true && !reasons.includes("no_session"))
10080
- reasons.push("self");
10081
- return reasons;
10082
- }, fromLead = (row, leads) => leads === null || row.actor.session !== null && leads.includes(row.actor.session), OBSERVED_ONLY;
10083
- var init_ledger = __esm(() => {
10084
- init_store_lock();
10085
- init_vcs_config();
10086
- init_rev();
10087
- init_host_session();
10088
- init_standing_signal();
10089
- init_paths();
10090
- PASSING = new Set(["verified", "tests-verified", "type-check-only"]);
10091
- FAILING = new Set(["failed", "blocked"]);
10092
- STRONG_RESULTS = new Set(["verified", "tests-verified"]);
10093
- PR_ROW_TYPES = new Set([
10094
- "pr.created",
10095
- "pr.status",
10096
- "pr.merged",
10097
- "pr.linked"
10098
- ]);
10099
- NETWORK_FS_TYPES = new Set([
10100
- 26985,
10101
- 20859,
10102
- 4266872130,
10103
- 4283649346,
10104
- 1702057286,
10105
- 1397113167,
10106
- 12805120,
10107
- 16914839,
10108
- 1196443219,
10109
- 198183888
10110
- ]);
10111
- ledgerLock = {
10112
- acquire(lock) {
10113
- try {
10114
- fs7.mkdirSync(lock);
10115
- } catch (error) {
10116
- if (error.code === "EEXIST")
10117
- return null;
10118
- throw error;
10119
- }
10120
- const token = `${process.pid}-${randomBytes2(8).toString("hex")}`;
10121
- fs7.writeFileSync(path9.join(lock, LOCK_OWNER), token);
10122
- return token;
10123
- },
10124
- release(lock, token) {
10125
- if (lockToken(lock) !== token)
10126
- return false;
10127
- const tomb = `${lock}.release-${randomBytes2(6).toString("hex")}`;
10128
- try {
10129
- fs7.renameSync(lock, tomb);
10130
- } catch {
10131
- return false;
10132
- }
10133
- if (lockToken(tomb) !== token) {
10134
- try {
10135
- fs7.renameSync(tomb, lock);
10136
- } catch {}
10137
- return false;
10138
- }
10139
- fs7.rmSync(tomb, { recursive: true, force: true });
10140
- return true;
10141
- },
10142
- token: lockToken,
10143
- reap(lock, observed) {
10144
- const tomb = `${lock}.reap-${randomBytes2(6).toString("hex")}`;
10145
- try {
10146
- fs7.renameSync(lock, tomb);
10147
- } catch {
10148
- return false;
10149
- }
10150
- if (lockToken(tomb) === observed) {
10151
- fs7.rmSync(tomb, { recursive: true, force: true });
10152
- return true;
10153
- }
10154
- try {
10155
- fs7.renameSync(tomb, lock);
10156
- } catch {
10157
- fs7.rmSync(tomb, { recursive: true, force: true });
10158
- }
10159
- return false;
10160
- }
10161
- };
10162
- TAIL_BYTES = 64 * 1024;
10163
- TAMPER_REASONS = new Set(["hash_mismatch", "broken_link"]);
10164
- tamperCache = new Map;
10165
- RENAMED = /^branch: renamed refs\/heads\/(.+) to refs\/heads\/(.+)$/iu;
10166
- TRUST_REASONS = new Set([
10167
- "unsigned",
10168
- "hash_mismatch",
10169
- "broken_link",
10170
- "duplicate_id",
10171
- "unissued_session",
10172
- "unbound_session"
10173
- ]);
10174
- SHELL_VOUCHED_HOSTS = new Set([
10175
- "cli",
10176
- "opencode",
10177
- "pi",
10178
- "codex_cli",
10179
- "codex_desktop"
10180
- ]);
10181
- OBSERVED_ONLY = new Set([
10182
- "check",
10183
- "merge.unverified",
10184
- "policy.judged",
10185
- ...PR_ROW_TYPES
10186
- ]);
10187
- });
10188
-
10189
- // packages/workit-core/src/forge/pr-body.ts
10190
- var init_pr_body = __esm(() => {
10191
- init_vcs_config();
10192
- });
10193
-
10194
- // packages/workit-core/src/core/pr-create.ts
10195
- var init_pr_create = __esm(() => {
10196
- init_vcs_config();
10197
- init_vcs_config();
10198
- init_branch();
10199
- init_pr_body();
10200
- });
10201
-
10202
- // packages/workit-core/src/forge/redact.ts
10203
- var KEY, ASSIGNMENT, QUOTED_ASSIGNMENT, ANSI;
10204
- var init_redact = __esm(() => {
10205
- KEY = String.raw`[A-Za-z0-9_.-]*?(?:token|secret|password|passwd|pwd|api[_-]?key|access[_-]?key|account[_-]?key|private[_-]?key|client[_-]?secret|credentials?|authorization|cookie|signature)[A-Za-z0-9_.-]*`;
10206
- ASSIGNMENT = new RegExp(String.raw`(?<![A-Za-z0-9])(["']?${KEY}["']?\s*[:=]\s*["']?)((?:bearer|basic|token|digest)\s+)?([^\s"',;&]+)`, "giu");
10207
- QUOTED_ASSIGNMENT = new RegExp(String.raw`(?<![A-Za-z0-9])(["']?${KEY}["']?\s*[:=]\s*)(["'])([^"'\n]*[^\s"'][^"'\n]*)\2`, "giu");
10208
- ANSI = new RegExp(String.raw`\u001b\[[0-9;?]*[ -/]*[@-~]|\u001b\][^\u0007]*\u0007`, "gu");
10209
- });
10210
- // packages/workit-core/src/git/ops.ts
10211
- var COMMIT_TIMEOUT_MS;
10212
- var init_ops = __esm(() => {
9935
+ migrateRepoIssued(cwd);
9936
+ const own = hash(issued);
9937
+ const tokens = path8.join(userTokenRoot(), hash(lead));
9938
+ fs5.mkdirSync(tokens, { recursive: true, mode: 448 });
9939
+ fs5.writeFileSync(path8.join(tokens, own), `${tokenHash(token)}
9940
+ `, { mode: 384, flag: "wx" });
9941
+ const dir = path8.join(userIssuedRoot(), hash(lead));
9942
+ fs5.mkdirSync(dir, { recursive: true, mode: 448 });
9943
+ fs5.writeFileSync(path8.join(dir, own), `${ISSUED_SESSIONS_MARKER}
9944
+ `, { mode: 384 });
9945
+ pruneIssued(dir, own, tokens);
9946
+ return token;
9947
+ } catch {
9948
+ return null;
9949
+ }
9950
+ }
9951
+ function judgeBranch(cwd) {
9952
+ const branch = currentBranch(cwd);
9953
+ return branch && !isProtectedTarget(cwd, branch) ? branch : null;
9954
+ }
9955
+ function verdictCommand(options) {
9956
+ const kind = options.judge === "verifier" ? "<live|unit>" : options.judge === "reviewer" ? "review" : "<live|unit|review>";
9957
+ return [
9958
+ "workit ledger verdict <verified|failed|blocked>",
9959
+ `--kind ${kind}`,
9960
+ `--branch ${options.branch ?? "<branch>"}`,
9961
+ options.session ? `--session ${options.session}` : null,
9962
+ options.token ? `--token=${options.token}` : null,
9963
+ '--how "<what you ran and saw>"'
9964
+ ].filter(Boolean).join(" ");
9965
+ }
9966
+ function pruneIssued(dir, own, tokens) {
9967
+ try {
9968
+ const entries = fs5.readdirSync(dir);
9969
+ if (entries.length <= ISSUED_PER_LEAD)
9970
+ return;
9971
+ const byAge = entries.filter((name) => name !== own).map((name) => ({ name, at: fs5.statSync(path8.join(dir, name)).mtimeMs })).toSorted((a, b) => a.at - b.at);
9972
+ for (const { name } of byAge.slice(0, entries.length - ISSUED_PER_LEAD)) {
9973
+ fs5.rmSync(path8.join(dir, name), { force: true });
9974
+ fs5.rmSync(path8.join(tokens, name), { force: true });
9975
+ }
9976
+ } catch {}
9977
+ }
9978
+ function issuedBinding(cwd, claimed) {
9979
+ const record = issuedRecord(cwd, claimed);
9980
+ const { stored, leadBound } = issuedToken(claimed);
9981
+ if (stored)
9982
+ return "token";
9983
+ if (record === null)
9984
+ return null;
9985
+ return leadBound ? "token" : "legacy";
9986
+ }
9987
+ var HOST_SESSION_VARS, family = (host) => host?.replace(/_(?:cli|desktop)$/, "") ?? null, ISSUED_PER_LEAD = 64, ISSUED_SESSIONS_MARKER = "workit-issued-sessions/3", hash = (value) => createHash7("sha256").update(value).digest("hex").slice(0, 32), userIssuedRoot = () => path8.join(resolveStateDir(), "issued"), userTokenRoot = () => path8.join(resolveStateDir(), "issued-tokens"), safeList = (dir) => {
9988
+ try {
9989
+ return fs5.readdirSync(dir);
9990
+ } catch {
9991
+ return [];
9992
+ }
9993
+ }, removeIfEmpty = (dir) => {
9994
+ try {
9995
+ fs5.rmdirSync(dir);
9996
+ } catch {}
9997
+ }, tokenHash = (token) => createHash7("sha256").update(token).digest("hex"), issuedRecord = (cwd, claimed) => {
9998
+ const cut = claimed.lastIndexOf(":");
9999
+ if (cut <= 0)
10000
+ return null;
10001
+ migrateRepoIssued(cwd);
10002
+ try {
10003
+ return fs5.readFileSync(path8.join(userIssuedRoot(), hash(claimed.slice(0, cut)), hash(claimed)), "utf8");
10004
+ } catch {
10005
+ return null;
10006
+ }
10007
+ }, TOKEN_HASH, issuedToken = (claimed) => {
10008
+ const cut = claimed.lastIndexOf(":");
10009
+ if (cut <= 0)
10010
+ return { stored: null, leadBound: false };
10011
+ const dir = path8.join(userTokenRoot(), hash(claimed.slice(0, cut)));
10012
+ try {
10013
+ const text = fs5.readFileSync(path8.join(dir, hash(claimed)), "utf8");
10014
+ return { stored: TOKEN_HASH.exec(text)?.[1] ?? null, leadBound: true };
10015
+ } catch {
10016
+ return { stored: null, leadBound: safeList(dir).length > 0 };
10017
+ }
10018
+ }, OWN_CHILD_SHELL;
10019
+ var init_host_session = __esm(() => {
10213
10020
  init_branch();
10214
- init_branch_policy();
10215
- init_commit_flavors();
10216
- init_pr_create();
10217
- init_release_tracks();
10218
- init_vcs_config();
10219
- init_workspaces();
10220
- init_redact();
10221
- init_host_session();
10222
- init_ledger();
10021
+ init_logger();
10223
10022
  init_rev();
10224
- COMMIT_TIMEOUT_MS = 10 * 60000;
10023
+ init_paths();
10024
+ HOST_SESSION_VARS = [
10025
+ ["OPENCODE_SESSION_ID", () => "opencode"],
10026
+ ["PI_SESSION_ID", () => "pi"],
10027
+ [
10028
+ "CODEX_THREAD_ID",
10029
+ (env) => env.CODEX_INTERNAL_ORIGINATOR_OVERRIDE === "Codex Desktop" || env.CODEX_ELECTRON_RESOURCES_PATH ? "codex_desktop" : "codex_cli"
10030
+ ]
10031
+ ];
10032
+ TOKEN_HASH = /^([0-9a-f]{64})$/mu;
10033
+ OWN_CHILD_SHELL = new Set(["opencode", "pi"]);
10225
10034
  });
10226
10035
 
10227
- // packages/workit-core/src/core/policy/attributes.ts
10228
- function patternRegex(dir, pattern) {
10229
- if (!pattern || pattern.endsWith("/") || pattern.startsWith('"') || pattern.startsWith("!"))
10230
- return null;
10231
- const anchored = pattern.includes("/");
10232
- const body = pattern.replace(/^\//u, "");
10233
- let source = "";
10234
- for (let index = 0;index < body.length; index += 1) {
10235
- const char = body[index];
10236
- if (char === "*" && body[index + 1] === "*") {
10237
- const slash = body[index + 2] === "/";
10238
- source += slash ? "(?:.*/)?" : ".*";
10239
- index += slash ? 2 : 1;
10240
- } else if (char === "*")
10241
- source += "[^/]*";
10242
- else if (char === "?")
10243
- source += "[^/]";
10244
- else if (char === "[") {
10245
- const end = body.indexOf("]", index + 2);
10246
- if (end < 0)
10036
+ // packages/workit-core/src/hooks/standing-signal.ts
10037
+ import { createHash as createHash8 } from "node:crypto";
10038
+ import fs6 from "node:fs";
10039
+ import path9 from "node:path";
10040
+ var gitStoreDir = (cwd) => {
10041
+ try {
10042
+ for (let dir = path9.resolve(cwd);; dir = path9.dirname(dir)) {
10043
+ const dotGit = path9.join(dir, ".git");
10044
+ if (fs6.existsSync(dotGit)) {
10045
+ let gitDir = dotGit;
10046
+ if (fs6.statSync(dotGit).isFile()) {
10047
+ const target = /^gitdir:\s*(.+)$/mu.exec(fs6.readFileSync(dotGit, "utf8"))?.[1]?.trim();
10048
+ if (!target)
10049
+ return null;
10050
+ gitDir = path9.resolve(dir, target);
10051
+ const common = path9.join(gitDir, "commondir");
10052
+ if (fs6.existsSync(common))
10053
+ gitDir = path9.resolve(gitDir, fs6.readFileSync(common, "utf8").trim());
10054
+ }
10055
+ return path9.join(gitDir, "workit");
10056
+ }
10057
+ if (path9.dirname(dir) === dir)
10247
10058
  return null;
10248
- const set = body.slice(index + 1, end).replace(/^!/u, "^").replace(/\\/gu, "\\\\");
10249
- source += `[${set}]`;
10250
- index = end;
10251
- } else if (char === "\\" && index + 1 < body.length) {
10252
- source += body[index + 1].replace(/[.*+?^${}()|[\]\\/]/gu, "\\$&");
10253
- index += 1;
10254
- } else
10255
- source += char.replace(/[.*+?^${}()|[\]\\/]/gu, "\\$&");
10059
+ }
10060
+ } catch {
10061
+ return null;
10256
10062
  }
10257
- const prefix = dir ? `${dir.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&")}/` : "";
10063
+ }, standingRevFile = (store) => path9.join(store, "standing", "rev"), standingSeenFile = (store, session, agentId) => path9.join(store, "standing", "seen", `${createHash8("sha256").update(`${session}\x00${agentId ?? ""}`).digest("hex").slice(0, 32)}.json`), readStandingRev = (store) => {
10258
10064
  try {
10259
- return new RegExp(anchored ? `^${prefix}${source}$` : `^${prefix}(?:.*/)?${source}$`, "u");
10065
+ return fs6.readFileSync(standingRevFile(store), "utf8").trim() || null;
10260
10066
  } catch {
10261
10067
  return null;
10262
10068
  }
10069
+ }, readStandingSeen = (file) => {
10070
+ try {
10071
+ const value = JSON.parse(fs6.readFileSync(file, "utf8"));
10072
+ return {
10073
+ rev: typeof value.rev === "string" ? value.rev : null,
10074
+ ids: Array.isArray(value.ids) ? value.ids.filter((id) => typeof id === "string") : []
10075
+ };
10076
+ } catch {
10077
+ return { rev: null, ids: [] };
10078
+ }
10079
+ }, writeStandingSeen = (file, seen) => {
10080
+ try {
10081
+ fs6.mkdirSync(path9.dirname(file), { recursive: true });
10082
+ fs6.writeFileSync(file, JSON.stringify(seen));
10083
+ } catch {}
10084
+ };
10085
+ var init_standing_signal = () => {};
10086
+
10087
+ // packages/workit-core/src/ledger.ts
10088
+ import { spawnSync as spawnSync8 } from "node:child_process";
10089
+ import { createHash as createHash9, randomBytes as randomBytes2 } from "node:crypto";
10090
+ import fs7 from "node:fs";
10091
+ import path10 from "node:path";
10092
+ function resolveCommit(cwd, ref) {
10093
+ if (!safeRef2(ref))
10094
+ return null;
10095
+ return git3(cwd, ["rev-parse", "--verify", "-q", `${ref}^{commit}`]);
10263
10096
  }
10264
- function rulesOf(dir, text) {
10265
- const out = [];
10266
- for (const raw of text.split(`
10097
+ function branchHead(cwd, branch) {
10098
+ if (!safeRef2(branch))
10099
+ return null;
10100
+ return resolveCommit(cwd, `refs/heads/${branch}`) ?? resolveCommit(cwd, `refs/remotes/origin/${branch}`);
10101
+ }
10102
+ function diffHash(cwd, base, head) {
10103
+ if (!safeRef2(base) || !safeRef2(head))
10104
+ return null;
10105
+ const diff = gitRun2(cwd, [
10106
+ "diff",
10107
+ "-U3",
10108
+ "--diff-algorithm=myers",
10109
+ "--indent-heuristic",
10110
+ "--no-color",
10111
+ "--no-ext-diff",
10112
+ "--no-textconv",
10113
+ "--no-renames",
10114
+ "--full-index",
10115
+ "--binary",
10116
+ "--src-prefix=a/",
10117
+ "--dst-prefix=b/",
10118
+ `${base}...${head}`,
10119
+ "--"
10120
+ ], GIT_TIMEOUTS.worktree);
10121
+ if (!diff.ok || !diff.stdout)
10122
+ return null;
10123
+ const hash = createHash9("sha256");
10124
+ for (const line of diff.stdout.split(`
10267
10125
  `)) {
10268
- const line = raw.trim();
10269
- if (!line || line.startsWith("#") || line.startsWith("[attr]"))
10126
+ if (line.startsWith("index "))
10270
10127
  continue;
10271
- const [pattern, ...attributes] = line.split(/\s+/u);
10272
- let generated = null;
10273
- for (const attribute of attributes) {
10274
- if (attribute === "linguist-generated" || attribute === "linguist-generated=true")
10275
- generated = true;
10276
- else if (attribute === "-linguist-generated" || attribute === "!linguist-generated" || attribute.startsWith("linguist-generated="))
10277
- generated = false;
10128
+ hash.update(line.startsWith("@@ ") ? "@@" : line);
10129
+ hash.update(`
10130
+ `);
10131
+ }
10132
+ return `sha256:${hash.digest("hex")}`;
10133
+ }
10134
+ function storeRoot(cwd) {
10135
+ const location = resolveStore(path10.resolve(cwd));
10136
+ if (location instanceof Error)
10137
+ return err("unavailable", location.message.replace(/; fix the repository.*$/u, ""), "fix the repository (git status should work here) or run from a non-git directory");
10138
+ return { ok: true, value: { root: location.dir, shared: location.shared } };
10139
+ }
10140
+ function ledgerPath(cwd) {
10141
+ const root = storeRoot(cwd);
10142
+ return root.ok ? { ok: true, value: path10.join(root.value.root, "ledger", "ledger.jsonl") } : root;
10143
+ }
10144
+ function withLedgerLock(file, fn, waitMs = hostAwareLockTimeout(LOCK_WAIT_MS)) {
10145
+ const lock = `${file}.lock`;
10146
+ const deadline = Date.now() + waitMs;
10147
+ let token = null;
10148
+ for (;; ) {
10149
+ token = ledgerLock.acquire(lock);
10150
+ if (token)
10151
+ break;
10152
+ const observed = lockToken(lock);
10153
+ const age = lockAgeMs(lock);
10154
+ if (age !== null && age > LOCK_STALE_MS && ledgerLock.reap(lock, observed))
10155
+ continue;
10156
+ if (Date.now() >= deadline)
10157
+ return null;
10158
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 25);
10159
+ }
10160
+ try {
10161
+ return fn();
10162
+ } finally {
10163
+ ledgerLock.release(lock, token);
10164
+ }
10165
+ }
10166
+ function appendLine(file, build, options) {
10167
+ const write = () => {
10168
+ const fd = fs7.openSync(file, "a+", 384);
10169
+ try {
10170
+ if (process.platform !== "win32")
10171
+ tighten(fd);
10172
+ const size = fs7.fstatSync(fd).size;
10173
+ let fence = "";
10174
+ if (size > 0) {
10175
+ const last = Buffer.alloc(1);
10176
+ fs7.readSync(fd, last, 0, 1, size - 1);
10177
+ if (last[0] !== 10)
10178
+ fence = `
10179
+ `;
10180
+ }
10181
+ const line = build(lastLine(fd, size));
10182
+ if (!line.ok)
10183
+ return line;
10184
+ const bytes = Buffer.from(fence + line.value);
10185
+ const written = fs7.writeSync(fd, bytes);
10186
+ if (written !== bytes.length)
10187
+ return err("unavailable", `short write to ${file}: ${written} of ${bytes.length} bytes; the row is torn and will be skipped`);
10188
+ return { ok: true, value: undefined };
10189
+ } finally {
10190
+ fs7.closeSync(fd);
10278
10191
  }
10279
- if (generated === null)
10280
- continue;
10281
- const regex = patternRegex(dir, pattern);
10282
- if (regex)
10283
- out.push({ regex, generated });
10192
+ };
10193
+ try {
10194
+ fs7.mkdirSync(path10.dirname(file), { recursive: true });
10195
+ if (!options.forceLock && !onNetworkFs(path10.dirname(file)))
10196
+ return write();
10197
+ const locked = withLedgerLock(file, write);
10198
+ return locked ?? err("busy", `ledger lock ${file}.lock is held`, "retry; remove the .lock directory if no workit process is running");
10199
+ } catch (error) {
10200
+ return err("unavailable", `cannot append to ${file}: ${error.message}`);
10284
10201
  }
10285
- return out;
10286
10202
  }
10287
- function generatedByAttributes(files, paths) {
10288
- const rules = [...files].map(([file, text]) => {
10289
- const dir = file.includes("/") ? file.slice(0, file.lastIndexOf("/")) : "";
10290
- return {
10291
- depth: dir ? dir.split("/").length : 0,
10292
- rules: rulesOf(dir, text)
10293
- };
10294
- }).toSorted((left, right) => left.depth - right.depth).flatMap((entry) => entry.rules);
10295
- const out = new Set;
10296
- for (const file of paths) {
10297
- const last = rules.findLast((rule) => rule.regex.test(file));
10298
- if (last?.generated)
10299
- out.add(file);
10300
- }
10301
- return out;
10203
+ function appendRaw(cwd, row, options) {
10204
+ const file = ledgerPath(cwd);
10205
+ if (!file.ok)
10206
+ return file;
10207
+ const id = newId2();
10208
+ const at = (options.now ?? new Date).toISOString();
10209
+ let full = null;
10210
+ const written = appendLine(file.value, (previous) => {
10211
+ const next = sealed(row, id, at, previous);
10212
+ const bytes = Buffer.byteLength(next.line);
10213
+ if (bytes > MAX_LINE_BYTES)
10214
+ return err("invalid_input", `ledger row is ${bytes} bytes; the limit is ${MAX_LINE_BYTES}. Shorten the text and pass details by --ref <path|url>`);
10215
+ full = next.full;
10216
+ return { ok: true, value: next.line };
10217
+ }, options);
10218
+ return written.ok ? { ok: true, value: full } : written;
10302
10219
  }
10303
-
10304
- // packages/workit-core/src/core/policy/substance.ts
10305
- function substanceOf(file, text) {
10306
- const syntax = syntaxOf(file);
10307
- if (!syntax)
10308
- return plainSubstance(text);
10309
- const markup = syntax.block.some(([open]) => open === "<!--");
10310
- if (markup && (/<(?:script|style)\b|<!\[CDATA\[|<\?/iu.test(text) || /[^>\s][ \t]*<!--/u.test(text)))
10311
- return plainSubstance(text);
10312
- return lex(syntax, text);
10220
+ function observedRowBytes(row) {
10221
+ const sample = sealed({ ...row, observer: "workit_cli" }, newId2(), new Date().toISOString(), "");
10222
+ return Buffer.byteLength(sample.line);
10313
10223
  }
10314
- function plainSubstance(text) {
10315
- return text.split(`
10316
- `).map((line) => line.replace(/[ \t\r\f\v]+$/u, "")).filter(Boolean).join(`
10317
- `);
10224
+ function appendObserved(cwd, row, options = {}) {
10225
+ return appendRaw(cwd, { ...row, observer: "workit_cli" }, options);
10318
10226
  }
10319
- function lex(syntax, text) {
10320
- const collapse = !syntax.indented;
10321
- let out = "";
10322
- let lineStart = 0;
10323
- let kept = 0;
10324
- let pendingSpace = false;
10325
- const frames = [{ kind: "code", braces: 0 }];
10326
- let index = 0;
10327
- if (text.startsWith("#!")) {
10328
- const end = text.indexOf(`
10329
- `);
10330
- out = end < 0 ? text : text.slice(0, end);
10331
- kept = out.length;
10332
- index = end < 0 ? text.length : end;
10227
+ function appendHookObserved(cwd, row) {
10228
+ return appendRaw(cwd, { ...row, observer: "host_hook" }, {});
10229
+ }
10230
+ function supersedeAllowed(by, target, onBranch = (row) => (by.branch ?? null) === (row.branch ?? null)) {
10231
+ if (by.type !== target.type)
10232
+ return `a ${by.type} row cannot supersede a ${target.type} row`;
10233
+ if (!by.actor.session || by.actor.session !== target.actor.session)
10234
+ return "only the session that wrote a row can supersede it";
10235
+ if (!onBranch(target))
10236
+ return `the row is on another branch (${String(target.branch ?? "none")}); a row supersedes only one on its own branch`;
10237
+ if (by.type === "verdict" && (by.kind ?? "review") !== (target.kind ?? "review"))
10238
+ return `a ${String(by.kind ?? "review")} verdict cannot supersede a ${String(target.kind ?? "review")} verdict; record it with --kind ${String(target.kind ?? "review")}`;
10239
+ if (by.type === "verdict" && by.self === true && target.self !== true)
10240
+ return "a self verdict cannot replace an independent verdict";
10241
+ return null;
10242
+ }
10243
+ function readLedger(cwd, options = {}) {
10244
+ const file = ledgerPath(cwd);
10245
+ if (!file.ok)
10246
+ return file;
10247
+ let text = "";
10248
+ let stat = null;
10249
+ try {
10250
+ stat = fs7.statSync(file.value);
10251
+ text = fs7.readFileSync(file.value, "utf8");
10252
+ } catch (error) {
10253
+ if (error.code === "ENOENT")
10254
+ return {
10255
+ ok: true,
10256
+ value: {
10257
+ path: file.value,
10258
+ rows: [],
10259
+ skipped: 0,
10260
+ integrity: options.integrity ? { chained: 0, legacy: 0, unverified: [] } : null
10261
+ }
10262
+ };
10263
+ return err("unavailable", `cannot read ${file.value}: ${error.message}`);
10333
10264
  }
10334
- const emitCode = (chunk) => {
10335
- if (collapse && pendingSpace && out.length > lineStart) {
10336
- const before = out[out.length - 1];
10337
- if (!NO_SPACE_AFTER.includes(before) && !NO_SPACE_BEFORE.includes(chunk[0]))
10338
- out += " ";
10339
- }
10340
- pendingSpace = false;
10341
- out += chunk;
10342
- };
10343
- const emitVerbatim = (chunk) => {
10344
- emitCode(chunk);
10345
- kept = out.length;
10346
- };
10347
- const newline = () => {
10348
- if (!collapse) {
10349
- let end = out.length;
10350
- while (end > Math.max(lineStart, kept) && SPACE.test(out[end - 1]))
10351
- end -= 1;
10352
- out = out.slice(0, end);
10265
+ const rows = [];
10266
+ let skipped = 0;
10267
+ const integrity = { chained: 0, legacy: 0, unverified: [] };
10268
+ const earlier = new Set([null]);
10269
+ let chainStarted = false;
10270
+ const ids = new Set;
10271
+ for (const raw of text.split(`
10272
+ `)) {
10273
+ const trimmed = raw.trim();
10274
+ if (!trimmed)
10275
+ continue;
10276
+ const linked = (prevHash) => earlier.has(prevHash);
10277
+ if (options.integrity)
10278
+ earlier.add(sha2562(trimmed));
10279
+ let parsed;
10280
+ try {
10281
+ parsed = JSON.parse(trimmed);
10282
+ } catch {
10283
+ skipped += 1;
10284
+ continue;
10353
10285
  }
10354
- pendingSpace = false;
10355
- if (out.length > lineStart) {
10356
- out += `
10357
- `;
10358
- lineStart = out.length;
10286
+ if (!isRecord2(parsed) || !str(parsed.id) || !str(parsed.type) || !str(parsed.at)) {
10287
+ skipped += 1;
10288
+ continue;
10359
10289
  }
10290
+ let rowIntegrity = options.integrity ? "verified" : undefined;
10291
+ let reason = null;
10292
+ if (!options.integrity) {} else if (typeof parsed.rowHash === "string") {
10293
+ chainStarted = true;
10294
+ const { rowHash, ...body } = parsed;
10295
+ const prevHash = str(parsed.prevHash);
10296
+ if (chainHash(prevHash, JSON.stringify(body)) !== rowHash)
10297
+ reason = "hash_mismatch";
10298
+ else if (!linked(prevHash))
10299
+ reason = "broken_link";
10300
+ } else if (chainStarted)
10301
+ reason = "unsigned";
10302
+ else
10303
+ rowIntegrity = "legacy";
10304
+ if (options.integrity && !reason && ids.has(parsed.id))
10305
+ reason = "duplicate_id";
10306
+ if (options.integrity)
10307
+ ids.add(parsed.id);
10308
+ if (reason)
10309
+ rowIntegrity = "unverified";
10310
+ const pr = typeof parsed.pr === "number" && Number.isInteger(parsed.pr) ? parsed.pr : undefined;
10311
+ const row = {
10312
+ ...parsed,
10313
+ v: typeof parsed.v === "number" ? parsed.v : LEDGER_VERSION,
10314
+ id: parsed.id,
10315
+ at: parsed.at,
10316
+ type: parsed.type,
10317
+ actor: normalizeActor(parsed.actor),
10318
+ branch: str(parsed.branch),
10319
+ head: str(parsed.head),
10320
+ base: str(parsed.base),
10321
+ baseSha: str(parsed.baseSha),
10322
+ patchId: str(parsed.patchId),
10323
+ diffHash: str(parsed.diffHash),
10324
+ tree: str(parsed.tree),
10325
+ dirty: typeof parsed.dirty === "boolean" ? parsed.dirty : null,
10326
+ ...pr === undefined ? {} : { pr },
10327
+ ...str(parsed.supersedes) ? { supersedes: parsed.supersedes } : {},
10328
+ seq: rows.length + 1,
10329
+ superseded: false,
10330
+ supersedeIgnored: false,
10331
+ ...rowIntegrity ? { integrity: rowIntegrity } : {},
10332
+ ...reason ? { integrityReason: reason } : {}
10333
+ };
10334
+ rows.push(row);
10335
+ if (!options.integrity)
10336
+ continue;
10337
+ if (rowIntegrity === "verified")
10338
+ integrity.chained += 1;
10339
+ else if (rowIntegrity === "legacy")
10340
+ integrity.legacy += 1;
10341
+ else if (reason)
10342
+ integrity.unverified.push({
10343
+ seq: row.seq,
10344
+ id: row.id,
10345
+ type: row.type,
10346
+ branch: row.branch,
10347
+ reason
10348
+ });
10349
+ }
10350
+ const scopes = new Map;
10351
+ const scopeOf = (branch) => {
10352
+ let scope = scopes.get(branch);
10353
+ if (!scope)
10354
+ scopes.set(branch, scope = branchScope(cwd, branch));
10355
+ return scope;
10360
10356
  };
10361
- const regexAllowed = () => {
10362
- let end = out.length;
10363
- while (end > 0 && /\s/u.test(out[end - 1]))
10364
- end -= 1;
10365
- if (end === 0 || REGEX_AFTER.includes(out[end - 1]))
10366
- return true;
10367
- let start = end;
10368
- while (start > 0 && WORD.test(out[start - 1]))
10369
- start -= 1;
10370
- return REGEX_KEYWORDS.has(out.slice(start, end));
10357
+ const byId = new Map;
10358
+ for (const row of rows) {
10359
+ const target = row.supersedes ? byId.get(row.supersedes) : undefined;
10360
+ if (target && row.integrity !== "unverified" && supersedeAllowed(row, target, renameAwareBranch(scopeOf, row.branch)) === null)
10361
+ target.superseded = true;
10362
+ else if (row.supersedes)
10363
+ row.supersedeIgnored = true;
10364
+ if (!byId.has(row.id))
10365
+ byId.set(row.id, row);
10366
+ }
10367
+ if (options.integrity && stat && Buffer.byteLength(text) === stat.size)
10368
+ tamperCache.set(file.value, {
10369
+ size: stat.size,
10370
+ stamp: fileStamp(stat),
10371
+ breaks: integrity.unverified.filter((row) => TAMPER_REASONS.has(row.reason))
10372
+ });
10373
+ return {
10374
+ ok: true,
10375
+ value: { path: file.value, rows, skipped, integrity: options.integrity ? integrity : null }
10371
10376
  };
10372
- const scanString = (quote, escapes, multiline) => {
10373
- const prefix = text.slice(Math.max(0, index - 2), index).match(/[a-z]*$/iu)?.[0] ?? "";
10374
- const fstring = syntax.fstring === true && /f/iu.test(prefix);
10375
- let end = index + quote.length;
10376
- while (end < text.length) {
10377
- const char = text[end];
10378
- if (escapes && char === "\\") {
10379
- end += 2;
10377
+ }
10378
+ function ledgerTampering(cwd) {
10379
+ const file = ledgerPath(cwd);
10380
+ if (!file.ok)
10381
+ return [];
10382
+ try {
10383
+ const stat = fs7.statSync(file.value);
10384
+ const cached = tamperCache.get(file.value);
10385
+ if (cached && cached.size === stat.size && cached.stamp === fileStamp(stat))
10386
+ return cached.breaks;
10387
+ } catch {
10388
+ return [];
10389
+ }
10390
+ const read = readLedger(cwd, { integrity: true });
10391
+ return read.ok ? (read.value.integrity?.unverified ?? []).filter((row) => TAMPER_REASONS.has(row.reason)) : [];
10392
+ }
10393
+ function tamperedUnblock(cwd) {
10394
+ const file = ledgerPath(cwd);
10395
+ const where = file.ok ? file.value : "the ledger file";
10396
+ 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`;
10397
+ }
10398
+ function verdictBasis(verdict, current) {
10399
+ if (verdict.head && verdict.head === current.head)
10400
+ return "fresh";
10401
+ if (verdict.patchId && verdict.patchId === current.patchId && verdict.diffHash && verdict.diffHash === current.diffHash)
10402
+ return "carried";
10403
+ return "stale";
10404
+ }
10405
+ function trailerSessions(cwd, base, head) {
10406
+ if (!base || !head || !safeRef2(base) || !safeRef2(head))
10407
+ return [];
10408
+ const out = git3(cwd, [
10409
+ "log",
10410
+ "--format=%(trailers:key=Workit-Session,valueonly,separator=%x00)",
10411
+ `${base}..${head}`
10412
+ ]);
10413
+ return (out ?? "").split(/[\0\n]/u).map((value) => value.trim()).filter(Boolean);
10414
+ }
10415
+ function branchScope(cwd, branch) {
10416
+ const renamedAt = new Map;
10417
+ let since = Number.NEGATIVE_INFINITY;
10418
+ if (safeRef2(branch)) {
10419
+ const log = git3(cwd, [
10420
+ "log",
10421
+ "-g",
10422
+ "--date=unix",
10423
+ "--format=%gd%x09%gs",
10424
+ `refs/heads/${branch}`,
10425
+ "--"
10426
+ ]);
10427
+ for (const line of (log ?? "").split(`
10428
+ `)) {
10429
+ const [selector = "", subject = ""] = line.split("\t");
10430
+ const seconds = Number(/@\{(\d+)\}$/u.exec(selector)?.[1]);
10431
+ if (!Number.isFinite(seconds))
10380
10432
  continue;
10381
- }
10382
- if (text.startsWith(quote, end)) {
10383
- if (fstring && unbalanced(text.slice(index + quote.length, end)))
10384
- return false;
10385
- emitVerbatim(text.slice(index, end + quote.length));
10386
- index = end + quote.length;
10387
- return true;
10388
- }
10389
- if (char === `
10390
- ` && !multiline)
10391
- return false;
10392
- end += 1;
10433
+ since = seconds * 1000;
10434
+ const match = RENAMED.exec(subject);
10435
+ if (!match || match[1] === branch)
10436
+ continue;
10437
+ const until = (seconds + 1) * 1000;
10438
+ renamedAt.set(match[1], Math.max(renamedAt.get(match[1]) ?? 0, until));
10393
10439
  }
10394
- return false;
10440
+ }
10441
+ return (row) => {
10442
+ if (row.branch === branch)
10443
+ return true;
10444
+ const until = row.branch === null ? undefined : renamedAt.get(row.branch);
10445
+ if (until === undefined)
10446
+ return false;
10447
+ const at = Date.parse(row.at);
10448
+ return at >= since && at <= until;
10395
10449
  };
10396
- while (index < text.length) {
10397
- const frame = frames[frames.length - 1];
10398
- const char = text[index];
10399
- if (frame.kind === "template") {
10400
- if (char === "\\") {
10401
- emitVerbatim(text.slice(index, index + 2));
10402
- index += 2;
10403
- } else if (char === "`") {
10404
- emitVerbatim(char);
10405
- frames.pop();
10406
- index += 1;
10407
- } else if (text.startsWith("${", index)) {
10408
- emitVerbatim("${");
10409
- frames.push({ kind: "code", braces: 0 });
10410
- index += 2;
10411
- } else {
10412
- out += char;
10413
- kept = out.length;
10414
- index += 1;
10415
- }
10416
- continue;
10417
- }
10418
- if (char === `
10419
- `) {
10420
- newline();
10421
- index += 1;
10422
- continue;
10423
- }
10424
- if (SPACE.test(char)) {
10425
- if (collapse)
10426
- pendingSpace = true;
10427
- else
10428
- out += char;
10429
- index += 1;
10450
+ }
10451
+ function authorSessions(cwd, rows, branch, range = { base: null, head: null }, scope = branchScope(cwd, branch)) {
10452
+ const sessions = new Set;
10453
+ for (const row of rows) {
10454
+ if (!scope(row) || row.type !== "commit.recorded" && row.type !== "task.opened")
10430
10455
  continue;
10431
- }
10432
- const lineOpener = syntax.line.find((opener) => text.startsWith(opener, index));
10433
- if (lineOpener) {
10434
- const end = text.indexOf(`
10435
- `, index);
10436
- const comment = text.slice(index, end < 0 ? text.length : end);
10437
- if (DIRECTIVE.test(comment.slice(lineOpener.length)))
10438
- emitVerbatim(comment);
10439
- else
10440
- pendingSpace = true;
10441
- index = end < 0 ? text.length : end;
10456
+ const session = str(row.session) ?? row.actor.session;
10457
+ if (session)
10458
+ sessions.add(session);
10459
+ }
10460
+ for (const session of trailerSessions(cwd, range.base, range.head))
10461
+ sessions.add(session);
10462
+ return sessions;
10463
+ }
10464
+ function sessionTrust(cwd, row) {
10465
+ const session = row.actor.session;
10466
+ if (!session || row.self === true)
10467
+ return null;
10468
+ const source = typeof row.sessionSource === "string" ? row.sessionSource : "env";
10469
+ const host = row.actor.host;
10470
+ const issuedOnly = source === "flag" ? host !== "cli" : source === "env" && session.includes(":") && !SHELL_VOUCHED_HOSTS.has(host);
10471
+ if (!issuedOnly)
10472
+ return null;
10473
+ const binding = issuedBinding(cwd, session);
10474
+ if (binding === null)
10475
+ return "unissued_session";
10476
+ return binding === "token" && row.sessionBound !== true ? "unbound_session" : null;
10477
+ }
10478
+ function trustReasons(cwd, row) {
10479
+ if (row.integrity === "unverified")
10480
+ return [row.integrityReason ?? "unsigned"];
10481
+ if (row.integrity !== "verified")
10482
+ return ["unsigned"];
10483
+ const session = sessionTrust(cwd, row);
10484
+ return session ? [session] : [];
10485
+ }
10486
+ function withIntegrity(cwd, rows) {
10487
+ if (rows.every((row) => row.integrity !== undefined))
10488
+ return rows;
10489
+ const read = readLedger(cwd, { integrity: true });
10490
+ const bySeq = new Map(read.ok ? read.value.rows.map((row) => [row.seq, row]) : []);
10491
+ return rows.map((row) => {
10492
+ if (row.integrity !== undefined)
10493
+ return row;
10494
+ const fresh = bySeq.get(row.seq);
10495
+ return fresh && fresh.id === row.id && fresh.at === row.at ? fresh : row;
10496
+ });
10497
+ }
10498
+ function effectiveOf(rows, reasonsOf, sameCode, isCurrent) {
10499
+ let effective = null;
10500
+ for (const row of rows) {
10501
+ if (!effective) {
10502
+ effective = row;
10442
10503
  continue;
10443
10504
  }
10444
- const block = syntax.block.find(([open]) => text.startsWith(open, index));
10445
- if (block) {
10446
- const end = text.indexOf(block[1], index + block[0].length);
10447
- if (end < 0)
10448
- return null;
10449
- const comment = text.slice(index, end + block[1].length);
10450
- if (DIRECTIVE.test(comment.slice(block[0].length, -block[1].length)))
10451
- emitVerbatim(comment);
10452
- else
10453
- pendingSpace = true;
10454
- if (comment.includes(`
10455
- `))
10456
- newline();
10457
- index = end + block[1].length;
10505
+ if (untrusted(reasonsOf(row)) && !untrusted(reasonsOf(effective)))
10458
10506
  continue;
10459
- }
10460
- if (syntax.triple && (char === '"' || char === "'") && text.startsWith(char.repeat(3), index)) {
10461
- if (!scanString(char.repeat(3), !syntax.literal?.includes(char), true))
10462
- return null;
10507
+ const rowIndependent = reasonsOf(row).length === 0;
10508
+ const effIndependent = reasonsOf(effective).length === 0;
10509
+ if (effIndependent && !rowIndependent && isCurrent(effective))
10463
10510
  continue;
10464
- }
10465
- if (syntax.quotes.includes(char)) {
10466
- if (!scanString(char, true, Boolean(syntax.multiline)))
10467
- return null;
10511
+ if (effIndependent && FAILING.has(String(effective.result)) && row.actor.session === effective.actor.session && sameCode(effective, row))
10468
10512
  continue;
10513
+ effective = row;
10514
+ }
10515
+ return effective;
10516
+ }
10517
+ function restackCarries(exactIn, rows, branch, from, to) {
10518
+ if (!from || !to)
10519
+ return false;
10520
+ const exact = (row, fromHead, head) => {
10521
+ if (exactIn === null)
10522
+ return true;
10523
+ const cwd = exactIn;
10524
+ let known = restackExact.get(row);
10525
+ if (known === undefined) {
10526
+ known = typeof row.fromBase === "string" && typeof row.toBase === "string" && sameOwnChange(cwd, {
10527
+ from: fromHead,
10528
+ to: head,
10529
+ fromBase: row.fromBase,
10530
+ toBase: row.toBase
10531
+ });
10532
+ restackExact.set(row, known);
10469
10533
  }
10470
- if (syntax.literal?.includes(char)) {
10471
- if (!scanString(char, false, false))
10472
- return null;
10473
- continue;
10534
+ return known;
10535
+ };
10536
+ const edges = new Map;
10537
+ for (const row of rows)
10538
+ if (row.type === "stack.restacked" && row.observer === "workit_cli" && row.integrity === "verified" && row.branch === branch && row.patchEqual === true && typeof row.fromHead === "string" && row.head && exact(row, row.fromHead, row.head))
10539
+ edges.set(row.fromHead, [...edges.get(row.fromHead) ?? [], row.head]);
10540
+ const seen = new Set([from]);
10541
+ const queue = [from];
10542
+ while (queue.length) {
10543
+ const next = queue.shift();
10544
+ if (next === to)
10545
+ return true;
10546
+ for (const target of edges.get(next) ?? [])
10547
+ if (!seen.has(target)) {
10548
+ seen.add(target);
10549
+ queue.push(target);
10550
+ }
10551
+ }
10552
+ return false;
10553
+ }
10554
+ function checkVerdicts(cwd, branch, ledgerRows) {
10555
+ const rows = withIntegrity(cwd, ledgerRows);
10556
+ const head = branchHead(cwd, branch);
10557
+ const fallbackBase = defaultBase(cwd);
10558
+ const scope = branchScope(cwd, branch);
10559
+ const keyCache = new Map;
10560
+ const keysAt = (base, at) => {
10561
+ if (!base || !at)
10562
+ return { patchId: null, diffHash: null };
10563
+ const cacheKey = `${base}\x00${at}`;
10564
+ let key = keyCache.get(cacheKey);
10565
+ if (!key) {
10566
+ key = { patchId: patchId(cwd, base, at), diffHash: diffHash(cwd, base, at) };
10567
+ keyCache.set(cacheKey, key);
10474
10568
  }
10475
- if (syntax.rawBacktick && char === "`") {
10476
- if (!scanString("`", false, true))
10477
- return null;
10478
- continue;
10569
+ return key;
10570
+ };
10571
+ const keyFor = (base) => keysAt(base, head);
10572
+ const driftCache = new Map;
10573
+ const driftOf = (row) => {
10574
+ if (driftCache.has(row))
10575
+ return driftCache.get(row) ?? null;
10576
+ let drift = null;
10577
+ const toBase = row.base ?? fallbackBase;
10578
+ const toBaseSha = toBase ? resolveCommit(cwd, toBase) : null;
10579
+ const fromBase = row.baseSha ?? toBaseSha;
10580
+ if (row.head && head && row.head !== head && toBaseSha && fromBase && !untrusted(reasonsOf(row)))
10581
+ drift = verdictDrift(cwd, {
10582
+ from: row.head,
10583
+ to: head,
10584
+ fromBase,
10585
+ toBase: toBaseSha,
10586
+ kind: typeof row.kind === "string" ? row.kind : "review"
10587
+ });
10588
+ driftCache.set(row, drift);
10589
+ return drift;
10590
+ };
10591
+ const exactCache = new Map;
10592
+ const exactCarry = (row) => {
10593
+ if (FAILING.has(String(row.result)))
10594
+ return true;
10595
+ let exact = exactCache.get(row);
10596
+ if (exact === undefined) {
10597
+ const toBase = row.base ?? fallbackBase;
10598
+ const toBaseSha = toBase ? resolveCommit(cwd, toBase) : null;
10599
+ const fromBase = row.baseSha ?? toBaseSha;
10600
+ exact = Boolean(row.head && head && toBaseSha && fromBase && sameOwnChange(cwd, { from: row.head, to: head, fromBase, toBase: toBaseSha }));
10601
+ exactCache.set(row, exact);
10602
+ }
10603
+ return exact;
10604
+ };
10605
+ const keyBasis = (row) => {
10606
+ const basis = verdictBasis(row, { head, ...keyFor(row.base ?? fallbackBase) });
10607
+ return basis === "carried" && !exactCarry(row) ? "stale" : basis;
10608
+ };
10609
+ const restacked = (row) => restackCarries(FAILING.has(String(row.result)) ? null : cwd, rows, branch, row.head, head);
10610
+ const carryOf = (row) => {
10611
+ if (keyBasis(row) !== "stale")
10612
+ return null;
10613
+ if (restacked(row))
10614
+ return null;
10615
+ const drift = driftOf(row);
10616
+ return drift?.carries ? { rule: drift.rule, since: drift.since, files: drift.files } : null;
10617
+ };
10618
+ const basisOf = (row) => {
10619
+ const basis = keyBasis(row);
10620
+ if (basis !== "stale")
10621
+ return basis;
10622
+ return restacked(row) || carryOf(row) ? "carried" : basis;
10623
+ };
10624
+ const sameCode = (failed, row) => {
10625
+ if (failed.head && failed.head === row.head)
10626
+ return true;
10627
+ const base = failed.base ?? fallbackBase;
10628
+ const stored = (row.base ?? fallbackBase) === base && row.baseSha !== null && row.baseSha === failed.baseSha;
10629
+ const keys = stored ? { patchId: row.patchId, diffHash: row.diffHash } : keysAt(base, row.head);
10630
+ return verdictBasis(failed, { head: row.head, ...keys }) !== "stale" || restackCarries(null, rows, branch, failed.head, row.head);
10631
+ };
10632
+ const authors = authorSessions(cwd, rows, branch, { base: fallbackBase, head }, scope);
10633
+ const failureAuthorCache = new Map;
10634
+ const failureAuthors = (row) => {
10635
+ let set = failureAuthorCache.get(row.seq);
10636
+ if (!set) {
10637
+ set = authorSessions(cwd, rows.filter((other) => other.integrity === "verified" && other.seq < row.seq), branch, { base: null, head: null }, scope);
10638
+ failureAuthorCache.set(row.seq, set);
10479
10639
  }
10480
- if (syntax.js && char === "`") {
10481
- emitVerbatim(char);
10482
- frames.push({ kind: "template" });
10483
- index += 1;
10484
- continue;
10640
+ return set;
10641
+ };
10642
+ const reasonCache = new Map;
10643
+ const reasonsOf = (row) => {
10644
+ let reasons = reasonCache.get(row);
10645
+ if (!reasons) {
10646
+ const failing = FAILING.has(String(row.result));
10647
+ reasons = [
10648
+ ...trustReasons(cwd, row),
10649
+ ...independenceReasons(row, failing ? failureAuthors(row) : authors)
10650
+ ];
10651
+ reasonCache.set(row, reasons);
10485
10652
  }
10486
- if (syntax.cssUrl && /^url\(\s*[^"')\s]/iu.test(text.slice(index, index + 64)))
10487
- return null;
10488
- if (syntax.js && char === "/" && !regexAllowed() && out.trimEnd().endsWith(")")) {
10489
- const lineEnd = text.indexOf(`
10490
- `, index);
10491
- if (text.slice(index + 1, lineEnd < 0 ? text.length : lineEnd).includes("/"))
10492
- return null;
10653
+ return reasons;
10654
+ };
10655
+ const retired = new Set;
10656
+ for (const row of rows)
10657
+ if (row.type === "verdict" && row.supersedes && !row.supersedeIgnored && trustReasons(cwd, row).length === 0)
10658
+ retired.add(row.supersedes);
10659
+ const byKind = new Map;
10660
+ for (const row of rows)
10661
+ if (isLiveVerdict(row, retired) && scope(row)) {
10662
+ const kind = typeof row.kind === "string" ? row.kind : "review";
10663
+ byKind.set(kind, [...byKind.get(kind) ?? [], row]);
10493
10664
  }
10494
- if (syntax.js && char === "/" && regexAllowed()) {
10495
- let end = index + 1;
10496
- let inClass = false;
10497
- while (end < text.length && (inClass || text[end] !== "/")) {
10498
- if (text[end] === `
10499
- `)
10500
- return null;
10501
- if (text[end] === "\\")
10502
- end += 1;
10503
- else if (text[end] === "[")
10504
- inClass = true;
10505
- else if (text[end] === "]")
10506
- inClass = false;
10507
- end += 1;
10508
- }
10509
- if (end >= text.length)
10510
- return null;
10511
- emitVerbatim(text.slice(index, end + 1));
10512
- index = end + 1;
10665
+ const verdicts = [];
10666
+ const warnings = [];
10667
+ for (const [kind, list] of byKind) {
10668
+ const row = effectiveOf(list, reasonsOf, sameCode, (candidate) => basisOf(candidate) !== "stale");
10669
+ if (!row)
10513
10670
  continue;
10671
+ const basis = basisOf(row);
10672
+ const current = basis !== "stale";
10673
+ const independence = reasonsOf(row);
10674
+ const reasons = [
10675
+ ...current ? [] : ["stale"],
10676
+ ...PASSING.has(String(row.result)) ? [] : ["not_passing"],
10677
+ ...independence
10678
+ ];
10679
+ verdicts.push({
10680
+ kind,
10681
+ basis,
10682
+ current,
10683
+ independent: independence.length === 0,
10684
+ accepted: reasons.length === 0,
10685
+ reasons,
10686
+ carry: basis === "carried" ? carryOf(row) : null,
10687
+ verdict: row
10688
+ });
10689
+ if (reasons.length === 0) {
10690
+ for (const later of list)
10691
+ if (later.seq > row.seq && reasonsOf(later).length > 0 && !untrusted(reasonsOf(later)) && FAILING.has(String(later.result)) && basisOf(later) !== "stale")
10692
+ 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`);
10514
10693
  }
10515
- if (frames.length > 1 && frame.kind === "code") {
10516
- if (char === "{")
10517
- frame.braces += 1;
10518
- else if (char === "}") {
10519
- if (frame.braces === 0) {
10520
- emitVerbatim(char);
10521
- frames.pop();
10522
- index += 1;
10523
- continue;
10524
- }
10525
- frame.braces -= 1;
10526
- }
10527
- }
10528
- emitCode(char);
10529
- index += 1;
10530
- }
10531
- if (frames.length !== 1)
10532
- return null;
10533
- newline();
10534
- return out;
10535
- }
10536
- function unbalanced(body) {
10537
- let depth = 0;
10538
- for (const part of body.replaceAll("{{", "").replaceAll("}}", "")) {
10539
- if (part === "{")
10540
- depth += 1;
10541
- else if (part === "}")
10542
- depth = Math.max(0, depth - 1);
10543
- }
10544
- return depth > 0;
10545
- }
10546
- function hasOpaqueLineBreak(text) {
10547
- return /\r(?!\n)|[\u2028\u2029]/u.test(text);
10548
- }
10549
- function textOf(bytes) {
10550
- if (bytes.includes(0))
10551
- return null;
10552
- try {
10553
- return decoder.decode(bytes);
10554
- } catch {
10555
- return null;
10556
10694
  }
10557
- }
10558
- function cosmeticChange(file, before, after) {
10559
- const left = textOf(before);
10560
- const right = textOf(after);
10561
- if (left === null || right === null)
10562
- return false;
10563
- if (hasOpaqueLineBreak(left) || hasOpaqueLineBreak(right))
10564
- return false;
10565
- const a = substanceOf(file, left);
10566
- return a !== null && a === substanceOf(file, right);
10567
- }
10568
- var SLASH_BLOCK, C_LIKE, SYNTAXES, DIRECTIVE, extensionOf = (file) => {
10569
- const name = file.slice(file.lastIndexOf("/") + 1);
10570
- const dot = name.lastIndexOf(".");
10571
- return dot > 0 ? name.slice(dot + 1).toLowerCase() : "";
10572
- }, CI_PATH, syntaxOf = (file) => CI_PATH.test(file) ? null : SYNTAXES[extensionOf(file)] ?? null, SPACE, WORD, NO_SPACE_AFTER = "([{,;", NO_SPACE_BEFORE = ")]},;", REGEX_AFTER = "(,=:[!&|?{};+-*%<>~^", REGEX_KEYWORDS, decoder;
10573
- var init_substance = __esm(() => {
10574
- SLASH_BLOCK = [["/*", "*/"]];
10575
- C_LIKE = { line: ["//"], block: SLASH_BLOCK, quotes: `"'` };
10576
- SYNTAXES = Object.fromEntries([
10577
- ...["ts", "mts", "cts", "js", "mjs", "cjs"].map((ext) => [ext, { ...C_LIKE, js: true }]),
10578
- ...["java", "kt", "kts", "scala", "swift", "dart", "groovy"].map((ext) => [
10579
- ext,
10580
- { ...C_LIKE, triple: true }
10581
- ]),
10582
- ["go", { ...C_LIKE, rawBacktick: true }],
10583
- ...["css", "scss", "less"].map((ext) => [
10584
- ext,
10585
- { line: [], block: SLASH_BLOCK, quotes: `"'`, cssUrl: true }
10586
- ]),
10587
- ...["py", "pyi"].map((ext) => [
10588
- ext,
10589
- { line: ["#"], block: [], quotes: `"'`, triple: true, indented: true, fstring: true }
10590
- ]),
10591
- ["toml", { line: ["#"], block: [], quotes: `"`, literal: "'", triple: true }],
10592
- ...["html", "htm", "xml", "svg", "xhtml"].map((ext) => [
10593
- ext,
10594
- { line: [], block: [["<!--", "-->"]], quotes: "", indented: true }
10595
- ])
10596
- ]);
10597
- DIRECTIVE = /@ts-|eslint|prettier|biome|oxlint|istanbul|\bc8\b|\bv8\b|@jsx|@flow|@vite|webpack|__PURE__|__NO_SIDE_EFFECTS__|source(?:Mapping)?URL|^\/\s*<|go:|\+build|nolint|noqa|type:|pyright|mypy|pylint|pragma|coding[:=]|fmt:|NOSONAR|@license|@preserve|@refresh|environment|language=|cython|ruff|isort|deno-|@vitest|@jest|swiftlint|ktlint|checkstyle|suppress|nullable|#(?:end)?region/iu;
10598
- CI_PATH = /^\.github\/|(?:^|\/)(?:\.gitlab-ci\.ya?ml|Jenkinsfile|azure-pipelines\.ya?ml|\.pre-commit-config\.ya?ml|\.circleci\/.*)$/u;
10599
- SPACE = /[ \t\r\f\v]/u;
10600
- WORD = /[\w$]/u;
10601
- REGEX_KEYWORDS = new Set([
10602
- "return",
10603
- "typeof",
10604
- "instanceof",
10605
- "case",
10606
- "do",
10607
- "else",
10608
- "in",
10609
- "of",
10610
- "new",
10611
- "delete",
10612
- "void",
10613
- "throw",
10614
- "yield",
10615
- "await"
10616
- ]);
10617
- decoder = new TextDecoder("utf-8", { fatal: true });
10618
- });
10619
-
10620
- // packages/workit-core/src/core/policy/lane.ts
10621
- import { existsSync as existsSync2, readFileSync as readFileSync3 } from "node:fs";
10622
- import path10 from "node:path";
10623
- function kindOf(file) {
10624
- if (file.binary)
10625
- return "behavior";
10626
- if (file.generated)
10627
- return "generated";
10628
- if (LOCKFILES.has(basename(file.path)))
10629
- return "lockfile";
10630
- if (isDoc(file.path))
10631
- return "doc";
10632
- if (file.cosmetic)
10633
- return "cosmetic";
10634
- return TEST_PATH.test(file.path) ? "test" : "behavior";
10635
- }
10636
- function globMatch(pattern, file) {
10637
- let regex = globCache.get(pattern);
10638
- if (!regex) {
10639
- let source = "";
10640
- for (let index = 0;index < pattern.length; index += 1) {
10641
- const char = pattern[index];
10642
- if (char === "*" && pattern[index + 1] === "*") {
10643
- source += pattern[index + 2] === "/" ? "(?:[^/]*/)*" : ".*";
10644
- index += pattern[index + 2] === "/" ? 2 : 1;
10645
- } else if (char === "*")
10646
- source += "[^/]*";
10647
- else if (char === "?")
10648
- source += "[^/]";
10649
- else
10650
- source += char.replace(/[.+^${}()|[\]\\]/gu, "\\$&");
10695
+ for (const row of rows)
10696
+ if (row.integrity === "unverified" && scope(row))
10697
+ 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)`);
10698
+ const tampered = ledgerTampering(cwd);
10699
+ if (tampered.length) {
10700
+ warnings.push(tamperedWarning(cwd, tampered));
10701
+ for (const entry of verdicts) {
10702
+ entry.accepted = false;
10703
+ entry.reasons = [...entry.reasons, "ledger_tampered"];
10651
10704
  }
10652
- regex = new RegExp(`^${source}$`, "u");
10653
- globCache.set(pattern, regex);
10654
10705
  }
10655
- return regex.test(file) || file.startsWith(`${pattern.replace(/\/$/u, "")}/`);
10706
+ verdicts.sort((a, b) => a.verdict.seq - b.verdict.seq);
10707
+ const newestCurrent = verdicts.findLast((entry) => entry.current) ?? verdicts.at(-1) ?? null;
10708
+ const failing = verdicts.find((entry) => entry.current && entry.independent && FAILING.has(String(entry.verdict.result)));
10709
+ const newestAccepted = verdicts.findLast((entry) => entry.accepted && STRONG_RESULTS.has(String(entry.verdict.result))) ?? null;
10710
+ const accepted = tampered.length ? { accepted: false, verdict: null, reasons: ["ledger_tampered"] } : newestAccepted && !failing ? { accepted: true, verdict: newestAccepted.verdict, reasons: [] } : {
10711
+ accepted: false,
10712
+ verdict: failing?.verdict ?? null,
10713
+ reasons: failing ? ["failing_verdict"] : !newestCurrent ? ["no_verdict"] : newestCurrent.reasons.length ? newestCurrent.reasons : ["type_check_only"]
10714
+ };
10715
+ const latestSelf = failing ? undefined : verdicts.findLast((entry) => entry.current && !entry.independent && !untrusted(entry.reasons) && (STRONG_RESULTS.has(String(entry.verdict.result)) || FAILING.has(String(entry.verdict.result))));
10716
+ const selfEntry = latestSelf && STRONG_RESULTS.has(String(latestSelf.verdict.result)) ? latestSelf : undefined;
10717
+ const carryEntry = verdicts.find((entry) => entry.verdict === accepted.verdict && entry.accepted) ?? newestCurrent;
10718
+ const lastPass = accepted.accepted || failing || tampered.length ? undefined : verdicts.findLast((entry) => !entry.current && !untrusted(entry.reasons) && STRONG_RESULTS.has(String(entry.verdict.result)));
10719
+ const drift = lastPass ? driftOf(lastPass.verdict) : null;
10720
+ const reverify = drift && !drift.carries && drift.focused && drift.files ? { scope: "focused", since: drift.since, files: drift.files } : null;
10721
+ return {
10722
+ branch,
10723
+ head,
10724
+ review: accepted.accepted ? "verified" : selfEntry ? "self-reviewed" : "unreviewed",
10725
+ selfVerdict: accepted.accepted ? null : selfEntry?.verdict ?? null,
10726
+ base: newestCurrent?.verdict.base ?? fallbackBase,
10727
+ patchId: keyFor(newestCurrent?.verdict.base ?? fallbackBase).patchId,
10728
+ current: { basis: newestCurrent?.basis ?? "none", verdict: newestCurrent?.verdict ?? null },
10729
+ carry: carryEntry?.carry ?? null,
10730
+ reverify,
10731
+ accepted,
10732
+ verdicts,
10733
+ authors: [...authors].toSorted(),
10734
+ warnings
10735
+ };
10656
10736
  }
10657
- function isRiskPath(file, extra = []) {
10658
- if (extra.some((pattern) => globMatch(pattern, file)))
10659
- return true;
10660
- if (RISK_FILES.has(file) || RISK_NAMES.has(basename(file)))
10661
- return true;
10662
- if (TEST_PATH.test(file) || isDoc(file))
10663
- return false;
10664
- return file.split("/").map((segment, index, all) => index === all.length - 1 ? segment.replace(/\.[^.]+$/u, "") : segment).some((segment) => segment.toLowerCase().split(/[-_.]/u).some((token) => RISK_TOKENS.has(token)));
10737
+ function activeStanding(rows, fanout, leads = null) {
10738
+ const active = [];
10739
+ for (const row of rows) {
10740
+ if (row.fanout !== fanout || !fromLead(row, leads))
10741
+ continue;
10742
+ if (row.type === "standing" && typeof row.what === "string" && row.what.trim()) {
10743
+ if (!row.superseded)
10744
+ active.push(row);
10745
+ } else if (row.type === "standing.cleared") {
10746
+ const target = str(row.target);
10747
+ if (target === null)
10748
+ active.length = 0;
10749
+ else {
10750
+ const index = active.findIndex((order) => order.id === target);
10751
+ if (index >= 0)
10752
+ active.splice(index, 1);
10753
+ }
10754
+ }
10755
+ }
10756
+ return active;
10665
10757
  }
10666
- function computeLane(input) {
10667
- const riskFiles = input.files.filter((file) => isRiskPath(file.path, input.riskPaths)).map((file) => file.path);
10668
- const risky = new Set(riskFiles);
10669
- const kinds = input.files.map((file) => ({
10670
- file,
10671
- kind: risky.has(file.path) ? "behavior" : kindOf(file)
10672
- }));
10673
- const behavior = kinds.filter((entry) => entry.kind === "behavior" || entry.kind === "test" || entry.kind === "lockfile");
10674
- const lines = behavior.filter((entry) => entry.kind !== "lockfile").reduce((sum, entry) => sum + entry.file.added + entry.file.deleted, 0);
10675
- const opaque = behavior.filter((entry) => entry.kind === "lockfile" || entry.file.binary);
10676
- const result = (lane, reasons) => ({
10677
- lane,
10678
- reasons,
10679
- lines,
10680
- files: behavior.length,
10681
- riskFiles
10758
+ var LEDGER_VERSION = 1, MAX_LINE_BYTES = 4096, PASSING, FAILING, STRONG_RESULTS, PR_ROW_TYPES, err = (code, error, unblock) => ({
10759
+ ok: false,
10760
+ code,
10761
+ error,
10762
+ ...unblock ? { unblock } : {}
10763
+ }), gitRun2 = (cwd, args, timeoutMs = GIT_TIMEOUTS.local) => {
10764
+ const run = spawnSync8("git", args, {
10765
+ cwd,
10766
+ encoding: "utf8",
10767
+ timeout: timeoutMs,
10768
+ killSignal: "SIGKILL",
10769
+ windowsHide: true,
10770
+ maxBuffer: 256 * 1024 * 1024,
10771
+ env: {
10772
+ ...process.env,
10773
+ GIT_TERMINAL_PROMPT: "0",
10774
+ GIT_OPTIONAL_LOCKS: "0",
10775
+ LC_ALL: "C",
10776
+ LANGUAGE: "C"
10777
+ }
10682
10778
  });
10683
- const { selfMaxLines, selfMaxFiles, fullMinLines } = LANE_THRESHOLDS;
10684
- if (input.riskTier === "high")
10685
- return result("full", ["the task judgment records high risk"]);
10686
- if (riskFiles.length)
10687
- return result("full", [
10688
- `touches a risk path: ${riskFiles.slice(0, 3).join(", ")}${riskFiles.length > 3 ? ", …" : ""}`
10689
- ]);
10690
- if (!behavior.length)
10691
- return result("none", [
10692
- input.files.length ? "only docs, comments, formatting or generated copies changed" : "no changes"
10693
- ]);
10694
- if (lines >= fullMinLines)
10695
- return result("full", [`large diff: ${lines} lines (≥ ${fullMinLines})`]);
10696
- if (lines > selfMaxLines)
10697
- return result("focused", [`${lines} lines changed (> ${selfMaxLines})`]);
10698
- if (behavior.length > selfMaxFiles)
10699
- return result("focused", [`${behavior.length} behavior files changed (> ${selfMaxFiles})`]);
10700
- if (opaque.length)
10701
- return result("focused", [
10702
- `a lockfile or binary changed: ${opaque.slice(0, 3).map((entry) => entry.file.path).join(", ")}${opaque.length > 3 ? ", …" : ""}`
10703
- ]);
10704
- return result("self", [
10705
- `small change: ${lines} line${lines === 1 ? "" : "s"} in ${behavior.length} file${behavior.length === 1 ? "" : "s"}, no risk path`
10706
- ]);
10707
- }
10708
- function untrackedStat(cwd, file) {
10779
+ return {
10780
+ ok: run.status === 0,
10781
+ stdout: run.stdout ?? "",
10782
+ stderr: run.error ? String(run.error.message) : run.stderr ?? ""
10783
+ };
10784
+ }, git3 = (cwd, args) => {
10785
+ const run = gitRun2(cwd, args);
10786
+ const value = run.ok ? run.stdout.trim() : "";
10787
+ return value ? value : null;
10788
+ }, safeRef2 = (value) => value.length > 0 && !value.startsWith("-"), newId2 = () => `${Date.now().toString(36)}-${randomBytes2(6).toString("hex")}`, NETWORK_FS_TYPES, isNetworkFsType = (type) => NETWORK_FS_TYPES.has(type >>> 0), onNetworkFs = (dir) => {
10789
+ if (process.platform !== "linux")
10790
+ return false;
10709
10791
  try {
10710
- const bytes = readFileSync3(path10.join(cwd, file));
10711
- if (bytes.includes(0))
10712
- return { path: file, added: 0, deleted: 0, binary: true };
10713
- const text = bytes.toString("utf8");
10714
- if (hasOpaqueLineBreak(text))
10715
- return { path: file, added: 0, deleted: 0, binary: true };
10716
- const lines = text.split(`
10717
- `).length - (text.endsWith(`
10718
- `) ? 1 : 0);
10719
- return { path: file, added: lines, deleted: 0 };
10792
+ return isNetworkFsType(fs7.statfsSync(dir).type);
10720
10793
  } catch {
10721
- return { path: file, added: 0, deleted: 0, binary: true };
10794
+ return false;
10722
10795
  }
10723
- }
10724
- function cosmeticFiles(cwd, fork, head) {
10725
- const raw = diffRaw(cwd, fork, head);
10726
- if (!raw)
10727
- return null;
10728
- const candidates = raw.filter((entry) => REGULAR.has(entry.oldMode) && entry.oldMode === entry.newMode && !ABSENT.test(entry.oldBlob) && (head === null || !ABSENT.test(entry.newBlob)));
10729
- const blobs = readBlobs(cwd, candidates.flatMap((entry) => [entry.oldBlob, entry.newBlob]));
10730
- if (!blobs)
10796
+ }, LOCK_WAIT_MS = 5000, LOCK_STALE_MS = 30000, LOCK_OWNER = "owner", lockToken = (lock) => {
10797
+ try {
10798
+ return fs7.readFileSync(path10.join(lock, LOCK_OWNER), "utf8");
10799
+ } catch {
10731
10800
  return null;
10732
- const out = new Set;
10733
- const opaque = new Set;
10734
- for (const entry of candidates) {
10735
- const before = blobs.get(entry.oldBlob);
10736
- let after = blobs.get(entry.newBlob);
10737
- if (!after && head === null && ABSENT.test(entry.newBlob))
10738
- try {
10739
- after = readFileSync3(path10.join(cwd, entry.path));
10740
- } catch {
10741
- after = undefined;
10742
- }
10743
- if (before && after && cosmeticChange(entry.path, before, after))
10744
- out.add(entry.path);
10745
- else if ([before, after].some((bytes) => bytes && hasOpaqueLineBreak(bytes.toString("utf8"))))
10746
- opaque.add(entry.path);
10747
10801
  }
10748
- return { cosmetic: out, opaque };
10749
- }
10750
- function generatedAt(cwd, fork, paths) {
10751
- const files = treeFilesNamed(cwd, fork, ".gitattributes");
10752
- const blobs = files?.size ? readBlobs(cwd, [...files.values()]) : null;
10753
- if (!files || !blobs)
10754
- return new Set;
10755
- const texts = new Map;
10756
- for (const [file, id] of files) {
10757
- const bytes = blobs.get(id);
10758
- if (bytes)
10759
- texts.set(file, bytes.toString("utf8"));
10802
+ }, lockAgeMs = (lock) => {
10803
+ for (const target of [path10.join(lock, LOCK_OWNER), lock]) {
10804
+ try {
10805
+ return Date.now() - fs7.statSync(target).mtimeMs;
10806
+ } catch {}
10760
10807
  }
10761
- return generatedByAttributes(texts, paths);
10762
- }
10763
- function laneFiles(cwd, fork, head) {
10764
- const stats = diffNumstat(cwd, fork, head);
10765
- const cosmetic = stats ? cosmeticFiles(cwd, fork, head) : null;
10766
- if (!stats || !cosmetic)
10767
- return null;
10768
- const files = [
10769
- ...stats.map((entry) => ({
10770
- ...entry,
10771
- ...cosmetic.opaque.has(entry.path) ? { binary: true } : {},
10772
- cosmetic: !entry.binary && cosmetic.cosmetic.has(entry.path)
10773
- })),
10774
- ...head === null ? untrackedFiles(cwd).map((file) => untrackedStat(cwd, file)) : []
10775
- ];
10776
- const generated = generatedAt(cwd, fork, files.map((entry) => entry.path));
10777
- return files.map((entry) => ({
10778
- ...entry,
10779
- generated: generated.has(entry.path)
10780
- }));
10781
- }
10782
- function branchLane(cwd, options) {
10783
- const candidates = options.base ? [`origin/${options.base}`, options.base] : [];
10784
- const base = candidates.find((ref) => resolveRef(cwd, ref) !== null) ?? defaultBase(cwd) ?? null;
10785
- const head = options.workingTree ? null : options.head;
10786
- const fork = base && (options.workingTree || options.head) ? forkOf(cwd, base, head) : null;
10787
- const files = fork ? laneFiles(cwd, fork, head) : null;
10788
- const riskTier = options.judgment?.riskTier ?? null;
10789
- if (!fork || !files) {
10790
- const high = riskTier === "high";
10791
- return {
10792
- lane: high ? "full" : "focused",
10793
- reasons: [
10794
- `the diff of ${options.head?.slice(0, 12) ?? "(no head)"} against ${base ?? "(no base)"} cannot be read`,
10795
- ...high ? ["the task judgment records high risk"] : []
10796
- ],
10797
- lines: 0,
10798
- files: 0,
10799
- riskFiles: [],
10800
- base,
10801
- head: options.head
10802
- };
10808
+ return null;
10809
+ }, ledgerLock, sha2562 = (text) => createHash9("sha256").update(text).digest("hex"), chainHash = (prevHash, body) => sha2562(`${prevHash ?? ""}
10810
+ ${body}`), TAIL_BYTES, lastLine = (fd, size) => {
10811
+ for (let span = Math.min(size, TAIL_BYTES);span > 0; span = Math.min(size, span * 2)) {
10812
+ const buffer = Buffer.alloc(span);
10813
+ fs7.readSync(fd, buffer, 0, span, size - span);
10814
+ const end = buffer.lastIndexOf(10);
10815
+ if (end < 0) {
10816
+ if (span === size)
10817
+ return null;
10818
+ continue;
10819
+ }
10820
+ let stop = end;
10821
+ let text = "";
10822
+ while (stop >= 0) {
10823
+ const start = buffer.lastIndexOf(10, stop - 1);
10824
+ text = buffer.toString("utf8", start + 1, stop).trim();
10825
+ if (text) {
10826
+ if (start >= 0 || span === size)
10827
+ return text;
10828
+ break;
10829
+ }
10830
+ if (start < 0) {
10831
+ if (span === size)
10832
+ return null;
10833
+ break;
10834
+ }
10835
+ stop = start;
10836
+ }
10837
+ if (span === size)
10838
+ return text || null;
10803
10839
  }
10804
- const repoRisk = parseRiskPaths(fileAt(cwd, fork, RISK_PATHS_FILE) ?? "");
10840
+ return null;
10841
+ }, tighten = (fd) => {
10842
+ try {
10843
+ if ((fs7.fstatSync(fd).mode & 63) !== 0)
10844
+ fs7.fchmodSync(fd, 384);
10845
+ } catch {}
10846
+ }, sealed = (row, id, at, previous) => {
10847
+ const { prevHash: _prev, rowHash: _row, ...fields } = row;
10848
+ const prevHash = previous === null ? null : sha2562(previous);
10849
+ const body = { v: LEDGER_VERSION, id, at, ...fields, prevHash };
10850
+ const full = { ...body, rowHash: chainHash(prevHash, JSON.stringify(body)) };
10851
+ return { full, line: `${JSON.stringify(full)}
10852
+ ` };
10853
+ }, isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), str = (value) => typeof value === "string" ? value : null, normalizeActor = (value) => {
10854
+ const actor = isRecord2(value) ? value : {};
10805
10855
  return {
10806
- ...computeLane({
10807
- files,
10808
- riskTier,
10809
- riskPaths: [...repoRisk, ...options.riskPaths ?? []]
10810
- }),
10811
- base,
10812
- head: options.head
10856
+ host: str(actor.host) ?? "unknown",
10857
+ session: str(actor.session),
10858
+ agentId: str(actor.agentId)
10813
10859
  };
10814
- }
10815
- var LANE_THRESHOLDS, RISK_TOKENS, RISK_FILES, RISK_NAMES, RISK_PATHS_FILE = ".workit/risk-paths", LOCKFILES, DOC_EXT, DOC_NAMES, INSTRUCTIONS, TEST_PATH, basename = (file) => file.slice(file.lastIndexOf("/") + 1), isDoc = (file) => !INSTRUCTIONS.test(file) && (DOC_EXT.test(file) || DOC_NAMES.test(basename(file))), globCache, parseRiskPaths = (text) => text.split(`
10816
- `).map((line) => line.trim()).filter((line) => line && !line.startsWith("#")).map((line) => line.replace(/^\//u, "")), REGULAR, ABSENT, forkOf = (cwd, base, head) => mergeBase(cwd, base, head ?? "HEAD"), laneNeed = (lane) => lane === "none" ? "none" : lane === "self" ? "self" : "non-author";
10817
- var init_lane = __esm(() => {
10860
+ }, TAMPER_REASONS, tamperCache, fileStamp = (stat) => `${stat.ino}:${stat.mtimeMs}:${stat.ctimeMs}`, 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)}`, isLiveVerdict = (row, retired) => row.type === "verdict" && typeof row.result === "string" && !retired.has(row.id), RENAMED, renameAwareBranch = (scopeOf, branch) => {
10861
+ return (target) => {
10862
+ const own = typeof branch === "string" ? branch : null;
10863
+ const theirs = typeof target.branch === "string" ? target.branch : null;
10864
+ if (own === theirs)
10865
+ return true;
10866
+ if (own === null || theirs === null || typeof target.at !== "string")
10867
+ return false;
10868
+ return scopeOf(own)({ branch: theirs, at: target.at });
10869
+ };
10870
+ }, derivesFrom = (derived, bare) => !bare.includes(":") && derived.endsWith(`:${bare}`), authoredBy = (authors, session) => {
10871
+ if (authors.has(session))
10872
+ return true;
10873
+ for (const author of authors)
10874
+ if (derivesFrom(author, session) || derivesFrom(session, author))
10875
+ return true;
10876
+ return false;
10877
+ }, TRUST_REASONS, SHELL_VOUCHED_HOSTS, untrusted = (reasons) => reasons.some((reason) => TRUST_REASONS.has(reason)), independenceReasons = (row, authors) => {
10878
+ const reasons = [];
10879
+ if (!row.actor.session)
10880
+ reasons.push("no_session");
10881
+ else if (authoredBy(authors, row.actor.session))
10882
+ reasons.push("author_session");
10883
+ if (row.self === true && !reasons.includes("no_session"))
10884
+ reasons.push("self");
10885
+ return reasons;
10886
+ }, restackExact, fromLead = (row, leads) => leads === null || row.actor.session !== null && leads.includes(row.actor.session), OBSERVED_ONLY;
10887
+ var init_ledger = __esm(() => {
10888
+ init_store_lock();
10889
+ init_carry();
10890
+ init_default_base();
10818
10891
  init_rev();
10819
- init_ops();
10820
- init_ledger();
10821
- init_substance();
10822
- LANE_THRESHOLDS = {
10823
- selfMaxLines: 100,
10824
- selfMaxFiles: 5,
10825
- fullMinLines: 500
10892
+ init_host_session();
10893
+ init_standing_signal();
10894
+ init_paths();
10895
+ PASSING = new Set(["verified", "tests-verified", "type-check-only"]);
10896
+ FAILING = new Set(["failed", "blocked"]);
10897
+ STRONG_RESULTS = new Set(["verified", "tests-verified"]);
10898
+ PR_ROW_TYPES = new Set([
10899
+ "pr.created",
10900
+ "pr.status",
10901
+ "pr.merged",
10902
+ "pr.linked"
10903
+ ]);
10904
+ NETWORK_FS_TYPES = new Set([
10905
+ 26985,
10906
+ 20859,
10907
+ 4266872130,
10908
+ 4283649346,
10909
+ 1702057286,
10910
+ 1397113167,
10911
+ 12805120,
10912
+ 16914839,
10913
+ 1196443219,
10914
+ 198183888
10915
+ ]);
10916
+ ledgerLock = {
10917
+ acquire(lock) {
10918
+ try {
10919
+ fs7.mkdirSync(lock);
10920
+ } catch (error) {
10921
+ if (error.code === "EEXIST")
10922
+ return null;
10923
+ throw error;
10924
+ }
10925
+ const token = `${process.pid}-${randomBytes2(8).toString("hex")}`;
10926
+ fs7.writeFileSync(path10.join(lock, LOCK_OWNER), token);
10927
+ return token;
10928
+ },
10929
+ release(lock, token) {
10930
+ if (lockToken(lock) !== token)
10931
+ return false;
10932
+ const tomb = `${lock}.release-${randomBytes2(6).toString("hex")}`;
10933
+ try {
10934
+ fs7.renameSync(lock, tomb);
10935
+ } catch {
10936
+ return false;
10937
+ }
10938
+ if (lockToken(tomb) !== token) {
10939
+ try {
10940
+ fs7.renameSync(tomb, lock);
10941
+ } catch {}
10942
+ return false;
10943
+ }
10944
+ fs7.rmSync(tomb, { recursive: true, force: true });
10945
+ return true;
10946
+ },
10947
+ token: lockToken,
10948
+ reap(lock, observed) {
10949
+ const tomb = `${lock}.reap-${randomBytes2(6).toString("hex")}`;
10950
+ try {
10951
+ fs7.renameSync(lock, tomb);
10952
+ } catch {
10953
+ return false;
10954
+ }
10955
+ if (lockToken(tomb) === observed) {
10956
+ fs7.rmSync(tomb, { recursive: true, force: true });
10957
+ return true;
10958
+ }
10959
+ try {
10960
+ fs7.renameSync(tomb, lock);
10961
+ } catch {
10962
+ fs7.rmSync(tomb, { recursive: true, force: true });
10963
+ }
10964
+ return false;
10965
+ }
10826
10966
  };
10827
- RISK_TOKENS = new Set([
10828
- "auth",
10829
- "authn",
10830
- "authz",
10831
- "oauth",
10832
- "security",
10833
- "permission",
10834
- "permissions",
10835
- "secret",
10836
- "secrets",
10837
- "crypto",
10838
- "gate",
10839
- "gates",
10840
- "policy",
10841
- "policies",
10842
- "hook",
10843
- "hooks",
10844
- "husky",
10845
- "lefthook",
10846
- "release",
10847
- "releases",
10848
- "migration",
10849
- "migrations",
10850
- "migrate",
10851
- "workflows"
10967
+ TAIL_BYTES = 64 * 1024;
10968
+ TAMPER_REASONS = new Set(["hash_mismatch", "broken_link"]);
10969
+ tamperCache = new Map;
10970
+ RENAMED = /^branch: renamed refs\/heads\/(.+) to refs\/heads\/(.+)$/iu;
10971
+ TRUST_REASONS = new Set([
10972
+ "unsigned",
10973
+ "hash_mismatch",
10974
+ "broken_link",
10975
+ "duplicate_id",
10976
+ "unissued_session",
10977
+ "unbound_session"
10852
10978
  ]);
10853
- RISK_FILES = new Set([
10854
- ".gitlab-ci.yml",
10855
- "Jenkinsfile",
10856
- "azure-pipelines.yml",
10857
- ".pre-commit-config.yaml",
10858
- ".workit/risk-paths"
10979
+ SHELL_VOUCHED_HOSTS = new Set([
10980
+ "cli",
10981
+ "opencode",
10982
+ "pi",
10983
+ "codex_cli",
10984
+ "codex_desktop"
10859
10985
  ]);
10860
- RISK_NAMES = new Set([".gitattributes"]);
10861
- LOCKFILES = new Set([
10862
- "bun.lock",
10863
- "bun.lockb",
10864
- "package-lock.json",
10865
- "npm-shrinkwrap.json",
10866
- "yarn.lock",
10867
- "pnpm-lock.yaml",
10868
- "Cargo.lock",
10869
- "go.sum",
10870
- "poetry.lock",
10871
- "uv.lock",
10872
- "Pipfile.lock",
10873
- "Gemfile.lock",
10874
- "composer.lock"
10986
+ restackExact = new WeakMap;
10987
+ OBSERVED_ONLY = new Set([
10988
+ "check",
10989
+ "merge.unverified",
10990
+ "policy.judged",
10991
+ ...PR_ROW_TYPES
10875
10992
  ]);
10876
- DOC_EXT = /\.(?:md|mdx|markdown|rst|adoc|txt)$/iu;
10877
- DOC_NAMES = /^(?:license|licence|notice|authors|changelog|changes|contributors)$/iu;
10878
- INSTRUCTIONS = /(?:^|\/)(?:AGENTS|CLAUDE|GEMINI|SKILL)\.md$|(?:^|\/)(?:skills|agents|commands|rules|prompts)\/[^/]+/iu;
10879
- TEST_PATH = /(?:^|\/)(?:test|tests|__tests__|spec|specs|fixtures)\/|\.(?:test|spec)\.[^/]+$/iu;
10880
- globCache = new Map;
10881
- REGULAR = new Set(["100644", "100755"]);
10882
- ABSENT = /^0+$/u;
10883
10993
  });
10884
10994
 
10885
10995
  // packages/workit-core/src/core/policy/derive.ts
@@ -14625,7 +14735,7 @@ function splitCommand(command) {
14625
14735
  return argv.length ? argv : new Error("empty command");
14626
14736
  }
14627
14737
  function checkRoot(cwd) {
14628
- return path18.resolve(git5(cwd, ["rev-parse", "--show-toplevel"]) ?? cwd);
14738
+ return path18.resolve(git4(cwd, ["rev-parse", "--show-toplevel"]) ?? cwd);
14629
14739
  }
14630
14740
  function detectedChecks(root) {
14631
14741
  const read = (file) => {
@@ -14758,7 +14868,7 @@ function workitAvailable(env = process.env) {
14758
14868
  function checkCommand(args, version, onPath = workitAvailable()) {
14759
14869
  return onPath ? `workit check ${args}` : `npx -y @brainervirus/workit-cli@${version} check ${args}`;
14760
14870
  }
14761
- var SCRIPT_CHECKS, CHECKS_FILE = ".workit/checks.json", LEGACY_CHECKS_FILE = "workit.checks.json", isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), SHELL_META, git5 = (cwd, args) => {
14871
+ var SCRIPT_CHECKS, CHECKS_FILE = ".workit/checks.json", LEGACY_CHECKS_FILE = "workit.checks.json", isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), SHELL_META, git4 = (cwd, args) => {
14762
14872
  const run = spawnSync9("git", args, {
14763
14873
  cwd,
14764
14874
  encoding: "utf8",
@@ -15554,7 +15664,7 @@ Autonomy contract:
15554
15664
  CLI first: if a step has one right answer, use the \`workit\` verb instead of
15555
15665
  hand-running git, gh or glab (\`workit git|pr|ci|check|help\`;
15556
15666
  else \`npx -y @brainervirus/workit-cli\`).
15557
- Other checkout: \`workit --cwd <dir> <verb>\` (Codex: \`<verb> --cwd <dir>\`); one workit
15667
+ Other checkout: \`workit <verb> --cwd <dir> …\` (verb first: host rules match it); one workit
15558
15668
  verb per shell call, never \`cd <dir> && workit\`. zsh-safe: no \`echo =====\`
15559
15669
  (\`printf '%s\\n' ---\`), quote globs and URLs, no variable named \`status\` or \`path\`.
15560
15670
  Add Co-Authored-By via \`workit git commit --trailer "K: v" -- <paths>\`, never
@@ -15566,7 +15676,7 @@ authorizes an effect. Judge work once with
15566
15676
  \`workit policy assess --judge risk= behavior= product-choice= plan=\`.
15567
15677
  Across repositories, bind items to checkout, branch and endpoint, resolve
15568
15678
  competing targets before a mutation, and reconcile an uncertain
15569
- external effect before a retry.
15679
+ effect before a retry.
15570
15680
 
15571
15681
  Mid-task: never silently drop or resume an objective (workit-continue).
15572
15682
 
@@ -17911,7 +18021,17 @@ var ShellAnalysisError, MAX_NESTING = 64, OPEN = "", COMMA = "", CLOSE = "
17911
18021
  }
17912
18022
  }
17913
18023
  return found;
17914
- }, PLAIN_POSIX, PLAIN_POWERSHELL, arithmeticEnd = (command, start) => {
18024
+ }, PLAIN_POSIX, PLAIN_POWERSHELL, expandsInQuotes = (body, dialect) => {
18025
+ const escape = dialect === "posix" ? "\\" : "`";
18026
+ for (let index = 0;index < body.length; index++) {
18027
+ const char = body[index];
18028
+ if (char === escape)
18029
+ index++;
18030
+ else if (char === "$" || dialect === "posix" && char === "`")
18031
+ return true;
18032
+ }
18033
+ return false;
18034
+ }, EXPANDING_RUN, arithmeticEnd = (command, start) => {
17915
18035
  const end = substitutionEnd(command, start);
17916
18036
  return command[end - 1] === ")" && end > start + 2 ? end : 0;
17917
18037
  }, segmentsOf = (command, dialect = "posix") => {
@@ -17927,6 +18047,7 @@ var ShellAnalysisError, MAX_NESTING = 64, OPEN = "", COMMA = "", CLOSE = "
17927
18047
  let current = { words: [], redirects: [] };
17928
18048
  let word = "";
17929
18049
  let inWord = false;
18050
+ let expanding = false;
17930
18051
  let pending = null;
17931
18052
  const heredocs = [];
17932
18053
  const escape = posix ? "\\" : "`";
@@ -17941,11 +18062,16 @@ var ShellAnalysisError, MAX_NESTING = 64, OPEN = "", COMMA = "", CLOSE = "
17941
18062
  heredocs.push({ delimiter: text.replace(/^-/, ""), owner: current });
17942
18063
  else if (pending === "herestring")
17943
18064
  (current.stdin ??= []).push(text);
17944
- else if (pending !== "input")
18065
+ else if (pending !== "input") {
18066
+ const at = current.words.length;
17945
18067
  current.words.push(...MARKERS.test(word) ? expandBraces(word) : [word]);
18068
+ if (expanding || !posix && word.startsWith("@"))
18069
+ (current.expands ??= []).push(...current.words.slice(at).map((_, i) => at + i));
18070
+ }
17946
18071
  pending = null;
17947
18072
  word = "";
17948
18073
  inWord = false;
18074
+ expanding = false;
17949
18075
  };
17950
18076
  const flushSegment = () => {
17951
18077
  flushWord();
@@ -17960,6 +18086,7 @@ var ShellAnalysisError, MAX_NESTING = 64, OPEN = "", COMMA = "", CLOSE = "
17960
18086
  };
17961
18087
  const substitution = (inner, text) => {
17962
18088
  (current.subs ??= []).push(inner);
18089
+ expanding = true;
17963
18090
  word += text;
17964
18091
  inWord = true;
17965
18092
  };
@@ -17970,6 +18097,8 @@ var ShellAnalysisError, MAX_NESTING = 64, OPEN = "", COMMA = "", CLOSE = "
17970
18097
  if (run) {
17971
18098
  word += run[0];
17972
18099
  inWord = true;
18100
+ if (EXPANDING_RUN.test(run[0]))
18101
+ expanding = true;
17973
18102
  index += run[0].length - 1;
17974
18103
  continue;
17975
18104
  }
@@ -17998,6 +18127,8 @@ var ShellAnalysisError, MAX_NESTING = 64, OPEN = "", COMMA = "", CLOSE = "
17998
18127
  const body = command.slice(index + 1, end < 0 ? undefined : end);
17999
18128
  if (posix && char === '"' && /\$\(|`/.test(body))
18000
18129
  (current.subs ??= []).push(...substitutions(body));
18130
+ if (char === '"' && expandsInQuotes(body, dialect))
18131
+ expanding = true;
18001
18132
  word += unquote(body, char, dialect);
18002
18133
  inWord = true;
18003
18134
  index = end < 0 ? command.length : end;
@@ -18043,6 +18174,8 @@ var ShellAnalysisError, MAX_NESTING = 64, OPEN = "", COMMA = "", CLOSE = "
18043
18174
  if (char === "(" && inWord && word.endsWith("$")) {
18044
18175
  word = word.slice(0, -1);
18045
18176
  inWord = word !== "";
18177
+ if (!inWord)
18178
+ expanding = false;
18046
18179
  }
18047
18180
  group(char === "(" ? "open" : "close");
18048
18181
  } else if (!posix && (char === "{" || char === "}")) {
@@ -18096,6 +18229,8 @@ var ShellAnalysisError, MAX_NESTING = 64, OPEN = "", COMMA = "", CLOSE = "
18096
18229
  else if (posix && char === "}" && word.includes(OPEN))
18097
18230
  word += CLOSE;
18098
18231
  else {
18232
+ if (char === "$")
18233
+ expanding = true;
18099
18234
  word += char;
18100
18235
  inWord = true;
18101
18236
  }
@@ -18121,6 +18256,7 @@ var init_shell_words = __esm(() => {
18121
18256
  };
18122
18257
  PLAIN_POSIX = /[^\s'"\\`$(){},<>|;&#]+/uy;
18123
18258
  PLAIN_POWERSHELL = /[^\s'"`(){}<>|;&#]+/uy;
18259
+ EXPANDING_RUN = /[*?[]/u;
18124
18260
  });
18125
18261
 
18126
18262
  // packages/workit-core/src/hooks/raw-git.ts
@@ -18189,8 +18325,17 @@ function walk2(command, dialect, depth, seen) {
18189
18325
  visit(next, undefined, hops + 1);
18190
18326
  };
18191
18327
  const wrapped = seen ? null : workitCheckTail(words);
18192
- if (wrapped)
18193
- return "script" in wrapped ? nested(wrapped.script, "posix") : runs([wrapped.argv]);
18328
+ if (wrapped) {
18329
+ const outer = dir;
18330
+ if (wrapped.cwd !== null)
18331
+ dir = joinDir(dir, wrapped.cwd);
18332
+ if ("script" in wrapped)
18333
+ nested(wrapped.script, "posix");
18334
+ else
18335
+ runs([wrapped.argv]);
18336
+ dir = outer;
18337
+ return;
18338
+ }
18194
18339
  if (CD.has(name)) {
18195
18340
  const target = cdTarget(words);
18196
18341
  dir = target === null ? os5.homedir() : joinDir(dir, target);
@@ -18275,14 +18420,14 @@ function workitCheckTail(words) {
18275
18420
  return null;
18276
18421
  } else if (!/^workit(?:\.exe|\.cmd)?$/.test(commandName(words[0])))
18277
18422
  return null;
18278
- const args = words.slice(at + 1);
18423
+ const { rest: args, cwd } = skipWorkitGlobals(words.slice(at + 1));
18279
18424
  if (args[0] !== "check" && !(args[0] === "lock" && args[1] === "run"))
18280
18425
  return null;
18281
18426
  const split = args.indexOf("--");
18282
18427
  const tail = split < 0 ? [] : args.slice(split + 1);
18283
18428
  if (!tail.length)
18284
18429
  return null;
18285
- return args.slice(0, split).includes("--shell") ? { script: tail.join(" ") } : { argv: tail };
18430
+ return args.slice(0, split).includes("--shell") ? { script: tail.join(" "), cwd } : { argv: tail, cwd };
18286
18431
  }
18287
18432
  function simpleCommand(command, dialect = "posix", depth = 0) {
18288
18433
  let segments;
@@ -18342,7 +18487,21 @@ function skipWorkitGlobals(args) {
18342
18487
  } else if (args[at] !== "--json")
18343
18488
  break;
18344
18489
  }
18345
- return { rest: args.slice(at), cwd };
18490
+ const rest = [];
18491
+ for (;at < args.length; at++) {
18492
+ const arg = args[at];
18493
+ if (arg === "--") {
18494
+ rest.push(...args.slice(at));
18495
+ break;
18496
+ }
18497
+ if (arg.startsWith("--cwd="))
18498
+ cwd = arg.slice("--cwd=".length);
18499
+ else if (arg === "--cwd" && args[at + 1] !== undefined)
18500
+ cwd = args[++at];
18501
+ else
18502
+ rest.push(arg);
18503
+ }
18504
+ return { rest, cwd };
18346
18505
  }
18347
18506
  function rawAction(invocation) {
18348
18507
  const args = invocation.args;
@@ -18454,7 +18613,7 @@ function settlePendingCommit(input) {
18454
18613
  settle2(input, repo, file);
18455
18614
  } catch {}
18456
18615
  }
18457
- var NONE2, SHELLS, POWERSHELLS, EVALS, WRAPPERS, WRAPPER_VALUES, KEYWORDS, DECLARATIONS, SCRIPT_FLAG, CD, PUSHD, POPD, GIT_VALUE_OPTIONS, GIT_CONFIG_OPTIONS, GIT_CONFIG_ENV, ASSIGNMENT2, MAX_DEPTH = 4, MAX_EXPANSIONS = 16, isAbsolute = (value) => path22.isAbsolute(value) || /^[A-Za-z]:[\\/]/.test(value) || value.startsWith("\\\\"), HERE, joinDir = (base, raw) => {
18616
+ var NONE2, SHELLS, POWERSHELLS, EVALS, WRAPPERS, WRAPPER_VALUES, KEYWORDS, DECLARATIONS, SCRIPT_FLAG, CD, PUSHD, POPD, GIT_VALUE_OPTIONS, GIT_CONFIG_OPTIONS, GIT_CONFIG_ENV, ASSIGNMENT, MAX_DEPTH = 4, MAX_EXPANSIONS = 16, isAbsolute = (value) => path22.isAbsolute(value) || /^[A-Za-z]:[\\/]/.test(value) || value.startsWith("\\\\"), HERE, joinDir = (base, raw) => {
18458
18617
  const next = raw.replace(HERE, ".");
18459
18618
  if (base === undefined || /[$`*?]/.test(next) || next === "-")
18460
18619
  return;
@@ -18473,7 +18632,7 @@ var NONE2, SHELLS, POWERSHELLS, EVALS, WRAPPERS, WRAPPER_VALUES, KEYWORDS, DECLA
18473
18632
  const first = words[0];
18474
18633
  if (first === undefined)
18475
18634
  return { words, env };
18476
- const assignment = ASSIGNMENT2.exec(first);
18635
+ const assignment = ASSIGNMENT.exec(first);
18477
18636
  if (assignment) {
18478
18637
  if (GIT_CONFIG_ENV.test(assignment[1]))
18479
18638
  env[assignment[1]] = assignment[2];
@@ -18527,10 +18686,10 @@ var NONE2, SHELLS, POWERSHELLS, EVALS, WRAPPERS, WRAPPER_VALUES, KEYWORDS, DECLA
18527
18686
  while (rest[0]?.startsWith("-"))
18528
18687
  rest = rest.slice(1);
18529
18688
  }
18530
- if (rest.length === 0 || !rest.every((word) => ASSIGNMENT2.test(word)))
18689
+ if (rest.length === 0 || !rest.every((word) => ASSIGNMENT.test(word)))
18531
18690
  return null;
18532
18691
  return rest.map((word) => {
18533
- const [, name, value] = ASSIGNMENT2.exec(word);
18692
+ const [, name, value] = ASSIGNMENT.exec(word);
18534
18693
  return [name, expandWord(value, vars)];
18535
18694
  });
18536
18695
  }, literalOutput = (segment) => {
@@ -18695,7 +18854,7 @@ var NONE2, SHELLS, POWERSHELLS, EVALS, WRAPPERS, WRAPPER_VALUES, KEYWORDS, DECLA
18695
18854
  return line.slice(index).join(" ").slice(15);
18696
18855
  if (/^-S./.test(word))
18697
18856
  return [word.slice(2), ...line.slice(index + 1)].join(" ");
18698
- if (!word.startsWith("-") && !ASSIGNMENT2.test(word))
18857
+ if (!word.startsWith("-") && !ASSIGNMENT.test(word))
18699
18858
  return null;
18700
18859
  }
18701
18860
  return null;
@@ -19067,7 +19226,7 @@ var NONE2, SHELLS, POWERSHELLS, EVALS, WRAPPERS, WRAPPER_VALUES, KEYWORDS, DECLA
19067
19226
  return best.base + url.slice(best.prefix.length);
19068
19227
  }
19069
19228
  return url;
19070
- }, pushUrl2 = (config, remote, dir) => {
19229
+ }, pushUrl = (config, remote, dir) => {
19071
19230
  const configured = configValue(config, `remote.${remote}.pushurl`) ?? configValue(config, `remote.${remote}.url`);
19072
19231
  return normalizedUrl(rewrittenUrl(configured ?? remote, config), dir);
19073
19232
  }, pushRemoteOf = (config, branch) => {
@@ -19093,7 +19252,7 @@ var NONE2, SHELLS, POWERSHELLS, EVALS, WRAPPERS, WRAPPER_VALUES, KEYWORDS, DECLA
19093
19252
  const pushRemote = pushRemoteOf(config, repo.branch);
19094
19253
  if (pushRemote === null || pushRemote === remote)
19095
19254
  return true;
19096
- return pushUrl2(config, remote, repo.dir) === pushUrl2(config, pushRemote, repo.dir);
19255
+ return pushUrl(config, remote, repo.dir) === pushUrl(config, pushRemote, repo.dir);
19097
19256
  }, quote = (text) => `\`${text}\``, refusal = (action, branch, dir) => {
19098
19257
  if (action.kind === "merge")
19099
19258
  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")}).`;
@@ -19405,7 +19564,7 @@ var init_raw_git = __esm(() => {
19405
19564
  ]);
19406
19565
  GIT_CONFIG_OPTIONS = new Set(["-c", "--config-env", "--git-dir"]);
19407
19566
  GIT_CONFIG_ENV = /^GIT_(?:CONFIG\w*|DIR|COMMON_DIR|WORK_TREE)$/;
19408
- ASSIGNMENT2 = /^([A-Za-z_]\w*)=([\s\S]*)$/;
19567
+ ASSIGNMENT = /^([A-Za-z_]\w*)=([\s\S]*)$/;
19409
19568
  HERE = /^(?:\$PWD|\$\{PWD\}|\$\(\s*pwd\s*\)|`pwd`|\$\(\s*git\s+rev-parse\s+--show-toplevel\s*\))(?=$|[\\/])/;
19410
19569
  VARIABLE = /\$(?:\{(\w+)\}|(\w+))/g;
19411
19570
  XARGS_VALUES = new Set(["-n", "-L", "-l", "-P", "-d", "-E", "-e", "-s", "-a", "--arg-file", "--delimiter"].concat([
@@ -19677,6 +19836,53 @@ var init_policy = __esm(() => {
19677
19836
  init_shell_words();
19678
19837
  });
19679
19838
 
19839
+ // packages/workit-core/src/hooks/delivery-verbs.ts
19840
+ var WORKIT_ALLOWED_VERBS, WORKIT_BYPASS_FLAGS, ALLOWED, isBypass = (word) => WORKIT_BYPASS_FLAGS.some((flag) => word === flag || word.startsWith(`${flag}=`)), isWorkitWord = (word) => /^(?:.*[\\/])?workit(?:-cli)?$/u.test(word), bypassReason = (flag) => `Workit: \`${flag}\` overrides a Workit gate, so the user decides this call (no allow rule covers it).`, EXPANSION_REASON = "Workit: an expansion before `--` could become a bypass flag, so the user decides this call (no allow rule covers it).", workitBypassFlag = (command, dialect = "posix") => {
19841
+ for (const segment of segmentsOf(command, dialect)) {
19842
+ if (segment.unanalyzable) {
19843
+ const text = command.replaceAll(/['"\\`]/gu, "");
19844
+ if (!/\bworkit\b/u.test(text))
19845
+ return null;
19846
+ const found = WORKIT_BYPASS_FLAGS.find((flag) => text.includes(flag));
19847
+ return found ? bypassReason(found) : null;
19848
+ }
19849
+ const start = segment.words.findIndex(isWorkitWord);
19850
+ if (start < 0)
19851
+ continue;
19852
+ const words = segment.words.slice(start + 1);
19853
+ const end = words.indexOf("--");
19854
+ const flag = (end < 0 ? words : words.slice(0, end)).find(isBypass);
19855
+ if (flag)
19856
+ return bypassReason(flag.split("=")[0] ?? flag);
19857
+ const limit = start + 1 + (end < 0 ? words.length : end);
19858
+ if (segment.expands?.some((index) => index > start && index < limit))
19859
+ return EXPANSION_REASON;
19860
+ }
19861
+ return null;
19862
+ };
19863
+ var init_delivery_verbs = __esm(() => {
19864
+ init_shell_words();
19865
+ WORKIT_ALLOWED_VERBS = [
19866
+ "git",
19867
+ "pr",
19868
+ "ci",
19869
+ "stack",
19870
+ "release",
19871
+ "task",
19872
+ "ledger",
19873
+ "evidence",
19874
+ "finding",
19875
+ "decision",
19876
+ "worker",
19877
+ "verify-delivery",
19878
+ "policy",
19879
+ "doctor",
19880
+ "help"
19881
+ ];
19882
+ WORKIT_BYPASS_FLAGS = ["--unverified", "--overwrite-unintegrated"];
19883
+ ALLOWED = new Set(WORKIT_ALLOWED_VERBS);
19884
+ });
19885
+
19680
19886
  // packages/workit-core/src/core/heavy-rules.ts
19681
19887
  import fs18 from "node:fs";
19682
19888
  import os6 from "node:os";
@@ -21038,7 +21244,7 @@ var NONE3, CASE_INSENSITIVE, canonical2 = (file) => {
21038
21244
  }
21039
21245
  const inner = depth < 4 ? wrappedScript(words) ?? (strict ? shellScript2(words) : null) : null;
21040
21246
  if (inner !== null && dir !== undefined)
21041
- targets.push(...segmentTargets(dir, inner, "posix", depth + 1, strict));
21247
+ targets.push(...segmentTargets(workitRunDir(dir, words), inner, "posix", depth + 1, strict));
21042
21248
  if (strict && dir !== undefined) {
21043
21249
  const runIn = dir;
21044
21250
  for (const sub of depth < 4 ? segment.subs ?? [] : [])
@@ -21077,6 +21283,13 @@ var NONE3, CASE_INSENSITIVE, canonical2 = (file) => {
21077
21283
  }
21078
21284
  }
21079
21285
  return targets;
21286
+ }, workitRunDir = (dir, words) => {
21287
+ for (const invocation of workitInvocations(words.map(quoteWord2).join(" "), "posix")) {
21288
+ const { at, args } = workitCwd(dir, invocation);
21289
+ if (args[0] === "check" || args[0] === "lock" && args[1] === "run")
21290
+ return at;
21291
+ }
21292
+ return dir;
21080
21293
  }, workitCwd = (dir, args) => {
21081
21294
  const { rest, cwd } = skipWorkitGlobals(args);
21082
21295
  return { at: cwd === null ? dir : path28.resolve(dir, homeExpanded(cwd)), args: rest };
@@ -21824,7 +22037,7 @@ var init_standing = __esm(() => {
21824
22037
  });
21825
22038
 
21826
22039
  // packages/workit-core/src/hooks/cwd-normalize.ts
21827
- var COMPOUND, FILTERS, stdinOnly = (words) => {
22040
+ var DIR, COMPOUND, GLOBALS_FIRST, VERB_FIRST, VERBS, FILTERS, clean = (s) => !s.unanalyzable && !s.group && !s.subs?.length && !s.stdin?.length && !s.redirects.length, stdinOnly = (words) => {
21828
22041
  const [name, ...args] = words;
21829
22042
  const operands = args.filter((w) => !w.startsWith("-"));
21830
22043
  if (name === "grep")
@@ -21844,18 +22057,36 @@ var COMPOUND, FILTERS, stdinOnly = (words) => {
21844
22057
  return null;
21845
22058
  if (head.words.some((w) => w === "--cwd" || w.startsWith("--cwd=")))
21846
22059
  return null;
21847
- const clean = (s) => !s.unanalyzable && !s.group && !s.subs?.length && !s.stdin?.length && !s.redirects.length;
21848
22060
  if (!segments.every(clean))
21849
22061
  return null;
21850
22062
  if (!segments.slice(0, -1).every((s) => s.pipe))
21851
22063
  return null;
21852
22064
  if (!filters.every((s) => FILTERS.has(s.words[0] ?? "") && stdinOnly(s.words)))
21853
22065
  return null;
21854
- return `workit --cwd ${dir} ${rest.slice("workit".length).trimStart()}`.trimEnd();
22066
+ const args = rest.slice("workit".length).trimStart();
22067
+ const verb = VERB_FIRST.exec(args);
22068
+ if (!verb?.[2] || !VERBS.has(verb[2]))
22069
+ return `workit --cwd ${dir} ${args}`.trimEnd();
22070
+ const json = verb[1] ? " --json" : "";
22071
+ return `workit ${verb[2]} --cwd ${dir}${json}${args.slice(verb[0].length)}`.trimEnd();
22072
+ }, verbFirst = (command) => {
22073
+ const match = GLOBALS_FIRST.exec(command);
22074
+ const [whole, lead, globals, verb] = match ?? [];
22075
+ if (!whole || !verb || !VERBS.has(verb) || !globals?.includes("--cwd"))
22076
+ return null;
22077
+ const segments = segmentsOf(command);
22078
+ if (segments.length !== 1 || !segments.every(clean))
22079
+ return null;
22080
+ return `${lead} ${verb}${globals}${command.slice(whole.length)}`;
21855
22081
  };
21856
22082
  var init_cwd_normalize = __esm(() => {
22083
+ init_delivery_verbs();
21857
22084
  init_shell_words();
21858
- COMPOUND = /^\s*cd\s+([A-Za-z0-9_./@%+:,=-]+|'[^'$`\\\n]*'|"[^"$`\\!\n]*")\s*&&\s*(workit(?:\s[\s\S]*)?)$/u;
22085
+ DIR = String.raw`[A-Za-z0-9_./@%+:,=-]+|'[^'$${"`"}\\\n]*'|"[^"$${"`"}\\!\n]*"`;
22086
+ COMPOUND = new RegExp(String.raw`^\s*cd\s+(${DIR})\s*&&\s*(workit(?:\s[\s\S]*)?)$`, "u");
22087
+ GLOBALS_FIRST = new RegExp(String.raw`^(\s*workit)((?:\s+(?:--json|--cwd(?:=|\s+)(?:${DIR})))+)\s+([a-z][a-z-]*)(?=\s|$)`, "u");
22088
+ VERB_FIRST = /^((?:--json\s+)*)([a-z][a-z-]*)(?=\s|$)/u;
22089
+ VERBS = new Set(WORKIT_ALLOWED_VERBS);
21859
22090
  FILTERS = new Set(["tail", "head", "grep"]);
21860
22091
  });
21861
22092
 
@@ -21995,14 +22226,14 @@ var FANOUT_TOKEN_FILE = "workit-fanout-token", NONE5, CASE_INSENSITIVE2, canonic
21995
22226
  return false;
21996
22227
  }
21997
22228
  }, shown = (target) => path31.basename(target) === ".workit-live-target" ? path31.dirname(target) : target, unboundDeny = (input, target) => {
21998
- const unblock = "Work only in the worktree your brief names, which the lead made with `workit fanout worktree create <slice>`: edit files under it and run one verb per call against it (`workit --cwd <worktree> git commit -m … -- <paths>`, `workit --cwd <worktree> check <name>`). If your brief names no worktree, stop and report that to the lead.";
22229
+ const unblock = "Work only in the worktree your brief names, which the lead made with `workit fanout worktree create <slice>`: edit files under it and run one verb per call against it (`workit git --cwd <worktree> commit -m … -- <paths>`, `workit check --cwd <worktree> <name>`). If your brief names no worktree, stop and report that to the lead.";
21999
22230
  return {
22000
22231
  kind: "deny",
22001
22232
  reason: `workit worktree confinement: implementer ${input.session.agentId} is not working in a Workit worktree yet, and ${shown(target)} is outside every worktree \`workit fanout worktree create\` made, so it would change the lead's checkout. ${unblock}`,
22002
22233
  unblock
22003
22234
  };
22004
22235
  }, outsideDeny = (input, worktree, target) => {
22005
- const unblock = `Edit only under ${worktree} and run git there, one verb per call: \`workit --cwd ${worktree} git commit -m … -- <paths>\`, \`workit --cwd ${worktree} check <name>\` (or \`git -C ${worktree} …\`); a command whose writes Workit cannot see is judged by the directory it runs in. Anything outside it is a follow-up for your report.`;
22236
+ const unblock = `Edit only under ${worktree} and run git there, one verb per call: \`workit git --cwd ${worktree} commit -m … -- <paths>\`, \`workit check --cwd ${worktree} <name>\` (or \`git -C ${worktree} …\`); a command whose writes Workit cannot see is judged by the directory it runs in. Anything outside it is a follow-up for your report.`;
22006
22237
  return {
22007
22238
  kind: "deny",
22008
22239
  reason: `workit worktree confinement: implementer ${input.session.agentId} is bound to the worktree ${worktree}, and ${shown(target)} is outside it. ${unblock}`,
@@ -22010,7 +22241,7 @@ var FANOUT_TOKEN_FILE = "workit-fanout-token", NONE5, CASE_INSENSITIVE2, canonic
22010
22241
  };
22011
22242
  }, unknownDirDeny = (input, worktree) => {
22012
22243
  const wt = worktree ?? "<worktree>";
22013
- const unblock = `Workit cannot resolve \`cd -\`, \`cd "$X"\` or \`popd\`: write by absolute path, \`git -C ${wt}\` or \`workit --cwd ${wt}\`.`;
22244
+ const unblock = `Workit cannot resolve \`cd -\`, \`cd "$X"\` or \`popd\`: write by absolute path, \`git -C ${wt}\` or \`workit <verb> --cwd ${wt}\`.`;
22014
22245
  return {
22015
22246
  kind: "deny",
22016
22247
  reason: `workit worktree confinement: implementer ${input.session.agentId} writes after an unknown directory change. ${unblock}`,
@@ -22108,7 +22339,7 @@ var init_worktree_confine = __esm(() => {
22108
22339
  // packages/workit-core/src/hooks/handle.ts
22109
22340
  function handleHook(input, deps) {
22110
22341
  const decision = decide(input, deps);
22111
- if (deps.descriptor.transport !== "hook-process" || !PRE_TOOL.has(input.event.kind) || decision.kind === "deny")
22342
+ if (deps.descriptor.transport !== "hook-process" || !PRE_TOOL.has(input.event.kind) || decision.kind === "deny" || decision.kind === "ask")
22112
22343
  return decision;
22113
22344
  const delta = standingDelta(input);
22114
22345
  if (!delta)
@@ -22175,9 +22406,12 @@ function decide(input, deps) {
22175
22406
  if (plan.kind === "rewrite" && !rewrites && canDeny)
22176
22407
  return heavyDenial(event.command, plan);
22177
22408
  const lease = plan.kind === "rewrite" && rewrites ? plan : null;
22178
- const cwdCommand = !lease && rewrites && input.host === "claude_code" ? cwdRewrite(event.command) : null;
22409
+ const cwdCommand = !lease && rewrites && input.host === "claude_code" ? cwdRewrite(event.command) ?? verbFirst(event.command) : null;
22179
22410
  const aside = plan.kind === "passthrough" ? plan.note : null;
22180
22411
  noteRawCommit(input, event.command, postTool ? callKey(event.toolUseId, event.command) : NEXT_COMMAND);
22412
+ const bypass = !lease && input.host === "claude_code" ? workitBypassFlag(event.command, event.dialect) : null;
22413
+ if (bypass)
22414
+ return { kind: "ask", reason: bypass };
22181
22415
  const rewrittenTo = lease?.command ?? cwdCommand;
22182
22416
  const rewritten = (text) => rewrittenTo ? {
22183
22417
  kind: "rewrite",
@@ -22249,7 +22483,7 @@ var usable2 = (value) => value === "native" || value === "partial", NONE6, resen
22249
22483
  return `${isolatedGit(input.cwd)}
22250
22484
  ${subagentStartText({ ...input, cwd: "" }, descriptor, event)}`;
22251
22485
  if (role === "implementer")
22252
- return `Workit observed ${who} as an implementer: it edits and commits only in its own git worktree, the one its brief names (the lead makes it with \`workit fanout worktree create <slice>\` before spawning), never in the lead's checkout, within its brief's scope. Workit binds it to the first Workit worktree it changes: from then on an edit or a mutating git command outside that worktree is denied, and before then so is one outside every Workit worktree. Run Workit against the worktree one verb per call, never \`cd … &&\` chains or \`$VAR\` paths: \`workit --cwd <worktree> git commit -m "<message>" -- <paths>\`, \`workit --cwd <worktree> check <name>\`. The worktree is on the brief's branch once the lead made it; otherwise switch with \`workit --cwd <worktree> git branch <branch> --base <base>\` (or \`--kind <kind> --slug <slug>\`, which picks the base); branch policy hooks still deny protected or non-compliant branches, raw git included.${guide ? ` ${guide}` : " (e.g. feature/<slug>)"} Never record a verdict on your own work. Never push, open a PR, or merge unless the brief asks for it.`;
22486
+ return `Workit observed ${who} as an implementer: it edits and commits only in its own git worktree, the one its brief names (the lead makes it with \`workit fanout worktree create <slice>\` before spawning), never in the lead's checkout, within its brief's scope. Workit binds it to the first Workit worktree it changes: from then on an edit or a mutating git command outside that worktree is denied, and before then so is one outside every Workit worktree. Run Workit against the worktree one verb per call, never \`cd … &&\` chains or \`$VAR\` paths: \`workit git --cwd <worktree> commit -m "<message>" -- <paths>\`, \`workit check --cwd <worktree> <name>\` (verb first, so host allow rules match). The worktree is on the brief's branch once the lead made it; otherwise switch with \`workit git --cwd <worktree> branch <branch> --base <base>\` (or \`--kind <kind> --slug <slug>\`, which picks the base); branch policy hooks still deny protected or non-compliant branches, raw git included.${guide ? ` ${guide}` : " (e.g. feature/<slug>)"} Never record a verdict on your own work. Never push, open a PR, or merge unless the brief asks for it.`;
22253
22487
  const readOnly = `Workit observed ${who} as read-only/agent-guided.`;
22254
22488
  if (role !== "judge" || !event.agentId)
22255
22489
  return readOnly;
@@ -22296,6 +22530,7 @@ var init_handle = __esm(() => {
22296
22530
  init_workspaces();
22297
22531
  init_host_session();
22298
22532
  init_cwd_normalize();
22533
+ init_delivery_verbs();
22299
22534
  init_heavy_gate();
22300
22535
  init_policy();
22301
22536
  init_raw_git();
@@ -22534,6 +22769,17 @@ var CLAUDE_CODE_DESCRIPTOR, EVENTS, SOURCES, SHELL_TOOLS, dialectOf2 = (value) =
22534
22769
  },
22535
22770
  exitCode: 0
22536
22771
  };
22772
+ if (decision.kind === "ask" && native === "PreToolUse")
22773
+ return {
22774
+ json: {
22775
+ hookSpecificOutput: {
22776
+ hookEventName: "PreToolUse",
22777
+ permissionDecision: "ask",
22778
+ permissionDecisionReason: decision.reason
22779
+ }
22780
+ },
22781
+ exitCode: 0
22782
+ };
22537
22783
  if (decision.kind === "context" && native !== null && CONTEXT_EVENTS.has(native))
22538
22784
  return {
22539
22785
  json: { hookSpecificOutput: { hookEventName: native, additionalContext: decision.text } },
@@ -23315,6 +23561,7 @@ var init_pi = () => {};
23315
23561
  var init_hooks = __esm(async () => {
23316
23562
  init_context();
23317
23563
  init_policy();
23564
+ init_delivery_verbs();
23318
23565
  init_heavy_gate();
23319
23566
  init_write_gate();
23320
23567
  init_live_session();