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