@brainervirus/workit-pi 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.
- package/dist/worker.js +526 -153
- package/dist/workit.js +2016 -1635
- package/package.json +1 -1
- package/skills/workit-fanout/SKILL.md +4 -4
- package/skills/workit-fanout/references/brief.md +4 -4
- package/skills/workit-fanout/references/orchestration.md +26 -17
- package/skills/workit-implement/SKILL.md +1 -1
- package/skills/workit-review/SKILL.md +4 -1
- package/skills/workit-ship/SKILL.md +1 -1
package/dist/workit.js
CHANGED
|
@@ -8407,6 +8407,7 @@ var workspaceConfigSchema = object({
|
|
|
8407
8407
|
trailers: boolean2().optional(),
|
|
8408
8408
|
liveSessionGuard: boolean2().optional(),
|
|
8409
8409
|
fanout: object({ tierModels: unknown().optional() }).passthrough().optional(),
|
|
8410
|
+
models: object({ roles: unknown().optional() }).passthrough().optional(),
|
|
8410
8411
|
autonomy: object(Object.fromEntries(["push", "pr", "merge", "release", "rerun"].map((kind) => [
|
|
8411
8412
|
kind,
|
|
8412
8413
|
union([boolean2(), literal("verified")]).optional()
|
|
@@ -8433,7 +8434,10 @@ var workspaceConfigSchema = object({
|
|
|
8433
8434
|
});
|
|
8434
8435
|
}
|
|
8435
8436
|
});
|
|
8436
|
-
var workspacesFileSchema = object({
|
|
8437
|
+
var workspacesFileSchema = object({
|
|
8438
|
+
workspaces: array(workspaceConfigSchema).optional(),
|
|
8439
|
+
models: object({ roles: unknown().optional() }).passthrough().optional()
|
|
8440
|
+
}).passthrough().superRefine((file, context) => {
|
|
8437
8441
|
const names = new Set;
|
|
8438
8442
|
for (const [index, workspace] of (file.workspaces ?? []).entries()) {
|
|
8439
8443
|
if (names.has(workspace.name)) {
|
|
@@ -8745,6 +8749,34 @@ var workspaceTrailers = (cwd, dir) => {
|
|
|
8745
8749
|
};
|
|
8746
8750
|
};
|
|
8747
8751
|
var workspaceLiveSessionGuard = (cwd, dir) => inheritedSetting(cwd, dir, (entry) => typeof entry.liveSessionGuard === "boolean")?.source?.liveSessionGuard !== false;
|
|
8752
|
+
var modelSources = (cwd, dir) => {
|
|
8753
|
+
const out = [];
|
|
8754
|
+
try {
|
|
8755
|
+
const matched = matchRuntimeWorkspace(cwd, dir);
|
|
8756
|
+
if (matched) {
|
|
8757
|
+
const enclosing = matched.matches.filter((entry) => entry !== matched.selected).toSorted((a, b) => globSpecificity(b.glob) - globSpecificity(a.glob));
|
|
8758
|
+
for (const entry of [matched.selected, ...enclosing])
|
|
8759
|
+
out.push({
|
|
8760
|
+
name: entry.name,
|
|
8761
|
+
global: false,
|
|
8762
|
+
models: entry.models,
|
|
8763
|
+
tierModels: entry.fanout?.tierModels
|
|
8764
|
+
});
|
|
8765
|
+
}
|
|
8766
|
+
} catch {}
|
|
8767
|
+
try {
|
|
8768
|
+
const document = JSON.parse(readFileSync2(path4.join(dir ?? configDir(), "workspaces.json"), "utf8"));
|
|
8769
|
+
if (document && typeof document === "object" && !Array.isArray(document))
|
|
8770
|
+
out.push({
|
|
8771
|
+
name: "global",
|
|
8772
|
+
global: true,
|
|
8773
|
+
models: document.models,
|
|
8774
|
+
tierModels: undefined
|
|
8775
|
+
});
|
|
8776
|
+
} catch {}
|
|
8777
|
+
return out;
|
|
8778
|
+
};
|
|
8779
|
+
var hostFamily = (host) => host.replace(/_(?:cli|desktop)$/u, "");
|
|
8748
8780
|
var TIER_KEYS = new Set(["mundane", "standard", "hard"]);
|
|
8749
8781
|
var TIER_HOSTS = new Set([
|
|
8750
8782
|
"claude_code",
|
|
@@ -8965,6 +8997,26 @@ function resolveRef(cwd, ref) {
|
|
|
8965
8997
|
return null;
|
|
8966
8998
|
return line(git(cwd, ["rev-parse", "--verify", "-q", `${ref}^{commit}`]));
|
|
8967
8999
|
}
|
|
9000
|
+
function originHeadRef(cwd) {
|
|
9001
|
+
return line(git(cwd, ["symbolic-ref", "-q", "--short", "refs/remotes/origin/HEAD"]));
|
|
9002
|
+
}
|
|
9003
|
+
function ownChangeDrift(cwd, range) {
|
|
9004
|
+
const before = diffRaw(cwd, range.fromFork, range.from);
|
|
9005
|
+
const after = diffRaw(cwd, range.toFork, range.to);
|
|
9006
|
+
if (!before || !after)
|
|
9007
|
+
return null;
|
|
9008
|
+
const judged = new Map(before.map((entry) => [entry.path, entry]));
|
|
9009
|
+
const current = new Map(after.map((entry) => [entry.path, entry]));
|
|
9010
|
+
const same = (a, b) => a !== undefined && b !== undefined && a.oldMode === b.oldMode && a.oldBlob === b.oldBlob && a.newMode === b.newMode && a.newBlob === b.newBlob;
|
|
9011
|
+
return [...new Set([...judged.keys(), ...current.keys()])].filter((file) => !same(judged.get(file), current.get(file))).toSorted();
|
|
9012
|
+
}
|
|
9013
|
+
function sameOwnChange(cwd, range) {
|
|
9014
|
+
const fromFork = mergeBase(cwd, range.fromBase, range.from);
|
|
9015
|
+
const toFork = mergeBase(cwd, range.toBase, range.to);
|
|
9016
|
+
if (!fromFork || !toFork)
|
|
9017
|
+
return false;
|
|
9018
|
+
return ownChangeDrift(cwd, { from: range.from, to: range.to, fromFork, toFork })?.length === 0;
|
|
9019
|
+
}
|
|
8968
9020
|
var LANE_DIFF = [
|
|
8969
9021
|
"-c",
|
|
8970
9022
|
"core.quotePath=false",
|
|
@@ -12860,9 +12912,9 @@ var workerBlocksTransition = (transition, state) => transition === "pause" ? fal
|
|
|
12860
12912
|
|
|
12861
12913
|
// packages/workit-core/src/core/task-evaluation.ts
|
|
12862
12914
|
import { createHash as createHash12 } from "node:crypto";
|
|
12863
|
-
import * as
|
|
12864
|
-
import { spawnSync as
|
|
12865
|
-
import
|
|
12915
|
+
import * as fs14 from "node:fs";
|
|
12916
|
+
import { spawnSync as spawnSync10 } from "node:child_process";
|
|
12917
|
+
import path18 from "node:path";
|
|
12866
12918
|
|
|
12867
12919
|
// packages/workit-core/src/check-config.ts
|
|
12868
12920
|
import { spawnSync as spawnSync8 } from "node:child_process";
|
|
@@ -13093,125 +13145,20 @@ function checkCommand(args, version, onPath = workitAvailable()) {
|
|
|
13093
13145
|
|
|
13094
13146
|
// packages/workit-core/src/core/policy/derive.ts
|
|
13095
13147
|
import { existsSync as existsSync5 } from "node:fs";
|
|
13096
|
-
import
|
|
13148
|
+
import path17 from "node:path";
|
|
13097
13149
|
|
|
13098
13150
|
// packages/workit-core/src/ledger.ts
|
|
13099
13151
|
import { spawnSync as spawnSync9 } from "node:child_process";
|
|
13100
13152
|
import { createHash as createHash11, randomBytes as randomBytes3 } from "node:crypto";
|
|
13101
13153
|
import fs13 from "node:fs";
|
|
13102
|
-
import
|
|
13154
|
+
import path16 from "node:path";
|
|
13103
13155
|
|
|
13104
|
-
// packages/workit-core/src/
|
|
13105
|
-
import {
|
|
13106
|
-
import fs12 from "node:fs";
|
|
13156
|
+
// packages/workit-core/src/core/policy/lane.ts
|
|
13157
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4 } from "node:fs";
|
|
13107
13158
|
import path14 from "node:path";
|
|
13108
|
-
var gitStoreDir = (cwd) => {
|
|
13109
|
-
try {
|
|
13110
|
-
for (let dir = path14.resolve(cwd);; dir = path14.dirname(dir)) {
|
|
13111
|
-
const dotGit = path14.join(dir, ".git");
|
|
13112
|
-
if (fs12.existsSync(dotGit)) {
|
|
13113
|
-
let gitDir = dotGit;
|
|
13114
|
-
if (fs12.statSync(dotGit).isFile()) {
|
|
13115
|
-
const target = /^gitdir:\s*(.+)$/mu.exec(fs12.readFileSync(dotGit, "utf8"))?.[1]?.trim();
|
|
13116
|
-
if (!target)
|
|
13117
|
-
return null;
|
|
13118
|
-
gitDir = path14.resolve(dir, target);
|
|
13119
|
-
const common = path14.join(gitDir, "commondir");
|
|
13120
|
-
if (fs12.existsSync(common))
|
|
13121
|
-
gitDir = path14.resolve(gitDir, fs12.readFileSync(common, "utf8").trim());
|
|
13122
|
-
}
|
|
13123
|
-
return path14.join(gitDir, "workit");
|
|
13124
|
-
}
|
|
13125
|
-
if (path14.dirname(dir) === dir)
|
|
13126
|
-
return null;
|
|
13127
|
-
}
|
|
13128
|
-
} catch {
|
|
13129
|
-
return null;
|
|
13130
|
-
}
|
|
13131
|
-
};
|
|
13132
|
-
var standingRevFile = (store) => path14.join(store, "standing", "rev");
|
|
13133
|
-
var standingSeenFile = (store, session, agentId) => path14.join(store, "standing", "seen", `${createHash10("sha256").update(`${session}\x00${agentId ?? ""}`).digest("hex").slice(0, 32)}.json`);
|
|
13134
|
-
var readStandingRev = (store) => {
|
|
13135
|
-
try {
|
|
13136
|
-
return fs12.readFileSync(standingRevFile(store), "utf8").trim() || null;
|
|
13137
|
-
} catch {
|
|
13138
|
-
return null;
|
|
13139
|
-
}
|
|
13140
|
-
};
|
|
13141
|
-
var readStandingSeen = (file) => {
|
|
13142
|
-
try {
|
|
13143
|
-
const value = JSON.parse(fs12.readFileSync(file, "utf8"));
|
|
13144
|
-
return {
|
|
13145
|
-
rev: typeof value.rev === "string" ? value.rev : null,
|
|
13146
|
-
ids: Array.isArray(value.ids) ? value.ids.filter((id) => typeof id === "string") : []
|
|
13147
|
-
};
|
|
13148
|
-
} catch {
|
|
13149
|
-
return { rev: null, ids: [] };
|
|
13150
|
-
}
|
|
13151
|
-
};
|
|
13152
|
-
var writeStandingSeen = (file, seen) => {
|
|
13153
|
-
try {
|
|
13154
|
-
fs12.mkdirSync(path14.dirname(file), { recursive: true });
|
|
13155
|
-
fs12.writeFileSync(file, JSON.stringify(seen));
|
|
13156
|
-
} catch {}
|
|
13157
|
-
};
|
|
13158
13159
|
|
|
13159
|
-
// packages/workit-core/src/
|
|
13160
|
-
var LEDGER_VERSION = 1;
|
|
13161
|
-
var MAX_LINE_BYTES = 4096;
|
|
13162
|
-
var PASSING = new Set(["verified", "tests-verified", "type-check-only"]);
|
|
13163
|
-
var FAILING = new Set(["failed", "blocked"]);
|
|
13164
|
-
var STRONG_RESULTS = new Set(["verified", "tests-verified"]);
|
|
13165
|
-
var PR_ROW_TYPES = new Set([
|
|
13166
|
-
"pr.created",
|
|
13167
|
-
"pr.status",
|
|
13168
|
-
"pr.merged",
|
|
13169
|
-
"pr.linked"
|
|
13170
|
-
]);
|
|
13171
|
-
var err = (code, error, unblock) => ({
|
|
13172
|
-
ok: false,
|
|
13173
|
-
code,
|
|
13174
|
-
error,
|
|
13175
|
-
...unblock ? { unblock } : {}
|
|
13176
|
-
});
|
|
13177
|
-
var gitRun2 = (cwd, args, timeoutMs = GIT_TIMEOUTS.local) => {
|
|
13178
|
-
const run = spawnSync9("git", args, {
|
|
13179
|
-
cwd,
|
|
13180
|
-
encoding: "utf8",
|
|
13181
|
-
timeout: timeoutMs,
|
|
13182
|
-
killSignal: "SIGKILL",
|
|
13183
|
-
windowsHide: true,
|
|
13184
|
-
maxBuffer: 256 * 1024 * 1024,
|
|
13185
|
-
env: {
|
|
13186
|
-
...process.env,
|
|
13187
|
-
GIT_TERMINAL_PROMPT: "0",
|
|
13188
|
-
GIT_OPTIONAL_LOCKS: "0",
|
|
13189
|
-
LC_ALL: "C",
|
|
13190
|
-
LANGUAGE: "C"
|
|
13191
|
-
}
|
|
13192
|
-
});
|
|
13193
|
-
return {
|
|
13194
|
-
ok: run.status === 0,
|
|
13195
|
-
stdout: run.stdout ?? "",
|
|
13196
|
-
stderr: run.error ? String(run.error.message) : run.stderr ?? ""
|
|
13197
|
-
};
|
|
13198
|
-
};
|
|
13199
|
-
var git4 = (cwd, args) => {
|
|
13200
|
-
const run = gitRun2(cwd, args);
|
|
13201
|
-
const value = run.ok ? run.stdout.trim() : "";
|
|
13202
|
-
return value ? value : null;
|
|
13203
|
-
};
|
|
13160
|
+
// packages/workit-core/src/git/default-base.ts
|
|
13204
13161
|
var safeRef = (value) => value.length > 0 && !value.startsWith("-");
|
|
13205
|
-
function resolveCommit(cwd, ref) {
|
|
13206
|
-
if (!safeRef(ref))
|
|
13207
|
-
return null;
|
|
13208
|
-
return git4(cwd, ["rev-parse", "--verify", "-q", `${ref}^{commit}`]);
|
|
13209
|
-
}
|
|
13210
|
-
function branchHead(cwd, branch) {
|
|
13211
|
-
if (!safeRef(branch))
|
|
13212
|
-
return null;
|
|
13213
|
-
return resolveCommit(cwd, `refs/heads/${branch}`) ?? resolveCommit(cwd, `refs/remotes/origin/${branch}`);
|
|
13214
|
-
}
|
|
13215
13162
|
var trackTrunk = (cwd) => {
|
|
13216
13163
|
try {
|
|
13217
13164
|
const resolved = vcsConfig("resolve", cwd);
|
|
@@ -13227,11 +13174,11 @@ function defaultBase(cwd) {
|
|
|
13227
13174
|
const trunk = trackTrunk(cwd);
|
|
13228
13175
|
if (trunk) {
|
|
13229
13176
|
for (const candidate of [`origin/${trunk}`, trunk])
|
|
13230
|
-
if (
|
|
13177
|
+
if (resolveRef(cwd, candidate))
|
|
13231
13178
|
return candidate;
|
|
13232
13179
|
}
|
|
13233
|
-
const originHead =
|
|
13234
|
-
if (originHead &&
|
|
13180
|
+
const originHead = originHeadRef(cwd);
|
|
13181
|
+
if (originHead && resolveRef(cwd, originHead))
|
|
13235
13182
|
return originHead;
|
|
13236
13183
|
for (const candidate of [
|
|
13237
13184
|
"origin/main",
|
|
@@ -13241,1514 +13188,1691 @@ function defaultBase(cwd) {
|
|
|
13241
13188
|
"origin/develop",
|
|
13242
13189
|
"develop"
|
|
13243
13190
|
])
|
|
13244
|
-
if (
|
|
13191
|
+
if (resolveRef(cwd, candidate))
|
|
13245
13192
|
return candidate;
|
|
13246
13193
|
return null;
|
|
13247
13194
|
}
|
|
13248
|
-
|
|
13249
|
-
|
|
13250
|
-
|
|
13251
|
-
|
|
13252
|
-
"diff",
|
|
13253
|
-
"-U3",
|
|
13254
|
-
"--diff-algorithm=myers",
|
|
13255
|
-
"--indent-heuristic",
|
|
13256
|
-
"--no-color",
|
|
13257
|
-
"--no-ext-diff",
|
|
13258
|
-
"--no-textconv",
|
|
13259
|
-
"--no-renames",
|
|
13260
|
-
"--full-index",
|
|
13261
|
-
"--binary",
|
|
13262
|
-
"--src-prefix=a/",
|
|
13263
|
-
"--dst-prefix=b/",
|
|
13264
|
-
`${base}...${head}`,
|
|
13265
|
-
"--"
|
|
13266
|
-
], GIT_TIMEOUTS.worktree);
|
|
13267
|
-
if (!diff.ok || !diff.stdout)
|
|
13195
|
+
|
|
13196
|
+
// packages/workit-core/src/core/policy/attributes.ts
|
|
13197
|
+
function patternRegex(dir, pattern) {
|
|
13198
|
+
if (!pattern || pattern.endsWith("/") || pattern.startsWith('"') || pattern.startsWith("!"))
|
|
13268
13199
|
return null;
|
|
13269
|
-
const
|
|
13270
|
-
|
|
13271
|
-
|
|
13272
|
-
|
|
13273
|
-
|
|
13274
|
-
|
|
13275
|
-
|
|
13276
|
-
|
|
13277
|
-
|
|
13278
|
-
|
|
13279
|
-
|
|
13280
|
-
|
|
13281
|
-
|
|
13282
|
-
|
|
13283
|
-
|
|
13284
|
-
|
|
13285
|
-
|
|
13286
|
-
|
|
13287
|
-
|
|
13288
|
-
|
|
13289
|
-
}
|
|
13290
|
-
|
|
13291
|
-
|
|
13292
|
-
|
|
13293
|
-
|
|
13294
|
-
4266872130,
|
|
13295
|
-
4283649346,
|
|
13296
|
-
1702057286,
|
|
13297
|
-
1397113167,
|
|
13298
|
-
12805120,
|
|
13299
|
-
16914839,
|
|
13300
|
-
1196443219,
|
|
13301
|
-
198183888
|
|
13302
|
-
]);
|
|
13303
|
-
var isNetworkFsType = (type) => NETWORK_FS_TYPES.has(type >>> 0);
|
|
13304
|
-
var onNetworkFs = (dir) => {
|
|
13305
|
-
if (process.platform !== "linux")
|
|
13306
|
-
return false;
|
|
13307
|
-
try {
|
|
13308
|
-
return isNetworkFsType(fs13.statfsSync(dir).type);
|
|
13309
|
-
} catch {
|
|
13310
|
-
return false;
|
|
13200
|
+
const anchored = pattern.includes("/");
|
|
13201
|
+
const body = pattern.replace(/^\//u, "");
|
|
13202
|
+
let source = "";
|
|
13203
|
+
for (let index = 0;index < body.length; index += 1) {
|
|
13204
|
+
const char = body[index];
|
|
13205
|
+
if (char === "*" && body[index + 1] === "*") {
|
|
13206
|
+
const slash = body[index + 2] === "/";
|
|
13207
|
+
source += slash ? "(?:.*/)?" : ".*";
|
|
13208
|
+
index += slash ? 2 : 1;
|
|
13209
|
+
} else if (char === "*")
|
|
13210
|
+
source += "[^/]*";
|
|
13211
|
+
else if (char === "?")
|
|
13212
|
+
source += "[^/]";
|
|
13213
|
+
else if (char === "[") {
|
|
13214
|
+
const end = body.indexOf("]", index + 2);
|
|
13215
|
+
if (end < 0)
|
|
13216
|
+
return null;
|
|
13217
|
+
const set = body.slice(index + 1, end).replace(/^!/u, "^").replace(/\\/gu, "\\\\");
|
|
13218
|
+
source += `[${set}]`;
|
|
13219
|
+
index = end;
|
|
13220
|
+
} else if (char === "\\" && index + 1 < body.length) {
|
|
13221
|
+
source += body[index + 1].replace(/[.*+?^${}()|[\]\\/]/gu, "\\$&");
|
|
13222
|
+
index += 1;
|
|
13223
|
+
} else
|
|
13224
|
+
source += char.replace(/[.*+?^${}()|[\]\\/]/gu, "\\$&");
|
|
13311
13225
|
}
|
|
13312
|
-
};
|
|
13313
|
-
var LOCK_WAIT_MS = 5000;
|
|
13314
|
-
var LOCK_STALE_MS = 30000;
|
|
13315
|
-
var LOCK_OWNER = "owner";
|
|
13316
|
-
var lockToken = (lock) => {
|
|
13226
|
+
const prefix = dir ? `${dir.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&")}/` : "";
|
|
13317
13227
|
try {
|
|
13318
|
-
return
|
|
13228
|
+
return new RegExp(anchored ? `^${prefix}${source}$` : `^${prefix}(?:.*/)?${source}$`, "u");
|
|
13319
13229
|
} catch {
|
|
13320
13230
|
return null;
|
|
13321
13231
|
}
|
|
13322
|
-
}
|
|
13323
|
-
|
|
13324
|
-
|
|
13325
|
-
|
|
13326
|
-
|
|
13327
|
-
|
|
13328
|
-
|
|
13329
|
-
|
|
13330
|
-
|
|
13331
|
-
|
|
13332
|
-
|
|
13333
|
-
|
|
13334
|
-
|
|
13335
|
-
|
|
13336
|
-
|
|
13337
|
-
return null;
|
|
13338
|
-
throw error;
|
|
13232
|
+
}
|
|
13233
|
+
function rulesOf(dir, text) {
|
|
13234
|
+
const out = [];
|
|
13235
|
+
for (const raw of text.split(`
|
|
13236
|
+
`)) {
|
|
13237
|
+
const line = raw.trim();
|
|
13238
|
+
if (!line || line.startsWith("#") || line.startsWith("[attr]"))
|
|
13239
|
+
continue;
|
|
13240
|
+
const [pattern, ...attributes] = line.split(/\s+/u);
|
|
13241
|
+
let generated = null;
|
|
13242
|
+
for (const attribute of attributes) {
|
|
13243
|
+
if (attribute === "linguist-generated" || attribute === "linguist-generated=true")
|
|
13244
|
+
generated = true;
|
|
13245
|
+
else if (attribute === "-linguist-generated" || attribute === "!linguist-generated" || attribute.startsWith("linguist-generated="))
|
|
13246
|
+
generated = false;
|
|
13339
13247
|
}
|
|
13340
|
-
|
|
13341
|
-
fs13.writeFileSync(path15.join(lock, LOCK_OWNER), token);
|
|
13342
|
-
return token;
|
|
13343
|
-
},
|
|
13344
|
-
release(lock, token) {
|
|
13345
|
-
if (lockToken(lock) !== token)
|
|
13346
|
-
return false;
|
|
13347
|
-
const tomb = `${lock}.release-${randomBytes3(6).toString("hex")}`;
|
|
13348
|
-
try {
|
|
13349
|
-
fs13.renameSync(lock, tomb);
|
|
13350
|
-
} catch {
|
|
13351
|
-
return false;
|
|
13352
|
-
}
|
|
13353
|
-
if (lockToken(tomb) !== token) {
|
|
13354
|
-
try {
|
|
13355
|
-
fs13.renameSync(tomb, lock);
|
|
13356
|
-
} catch {}
|
|
13357
|
-
return false;
|
|
13358
|
-
}
|
|
13359
|
-
fs13.rmSync(tomb, { recursive: true, force: true });
|
|
13360
|
-
return true;
|
|
13361
|
-
},
|
|
13362
|
-
token: lockToken,
|
|
13363
|
-
reap(lock, observed) {
|
|
13364
|
-
const tomb = `${lock}.reap-${randomBytes3(6).toString("hex")}`;
|
|
13365
|
-
try {
|
|
13366
|
-
fs13.renameSync(lock, tomb);
|
|
13367
|
-
} catch {
|
|
13368
|
-
return false;
|
|
13369
|
-
}
|
|
13370
|
-
if (lockToken(tomb) === observed) {
|
|
13371
|
-
fs13.rmSync(tomb, { recursive: true, force: true });
|
|
13372
|
-
return true;
|
|
13373
|
-
}
|
|
13374
|
-
try {
|
|
13375
|
-
fs13.renameSync(tomb, lock);
|
|
13376
|
-
} catch {
|
|
13377
|
-
fs13.rmSync(tomb, { recursive: true, force: true });
|
|
13378
|
-
}
|
|
13379
|
-
return false;
|
|
13380
|
-
}
|
|
13381
|
-
};
|
|
13382
|
-
function withLedgerLock(file, fn, waitMs = hostAwareLockTimeout(LOCK_WAIT_MS)) {
|
|
13383
|
-
const lock = `${file}.lock`;
|
|
13384
|
-
const deadline = Date.now() + waitMs;
|
|
13385
|
-
let token = null;
|
|
13386
|
-
for (;; ) {
|
|
13387
|
-
token = ledgerLock.acquire(lock);
|
|
13388
|
-
if (token)
|
|
13389
|
-
break;
|
|
13390
|
-
const observed = lockToken(lock);
|
|
13391
|
-
const age = lockAgeMs(lock);
|
|
13392
|
-
if (age !== null && age > LOCK_STALE_MS && ledgerLock.reap(lock, observed))
|
|
13248
|
+
if (generated === null)
|
|
13393
13249
|
continue;
|
|
13394
|
-
|
|
13395
|
-
|
|
13396
|
-
|
|
13250
|
+
const regex = patternRegex(dir, pattern);
|
|
13251
|
+
if (regex)
|
|
13252
|
+
out.push({ regex, generated });
|
|
13397
13253
|
}
|
|
13398
|
-
|
|
13399
|
-
|
|
13400
|
-
|
|
13401
|
-
|
|
13254
|
+
return out;
|
|
13255
|
+
}
|
|
13256
|
+
function generatedByAttributes(files, paths) {
|
|
13257
|
+
const rules = [...files].map(([file, text]) => {
|
|
13258
|
+
const dir = file.includes("/") ? file.slice(0, file.lastIndexOf("/")) : "";
|
|
13259
|
+
return {
|
|
13260
|
+
depth: dir ? dir.split("/").length : 0,
|
|
13261
|
+
rules: rulesOf(dir, text)
|
|
13262
|
+
};
|
|
13263
|
+
}).toSorted((left, right) => left.depth - right.depth).flatMap((entry) => entry.rules);
|
|
13264
|
+
const out = new Set;
|
|
13265
|
+
for (const file of paths) {
|
|
13266
|
+
const last = rules.findLast((rule) => rule.regex.test(file));
|
|
13267
|
+
if (last?.generated)
|
|
13268
|
+
out.add(file);
|
|
13402
13269
|
}
|
|
13270
|
+
return out;
|
|
13403
13271
|
}
|
|
13404
|
-
|
|
13405
|
-
|
|
13406
|
-
|
|
13407
|
-
var
|
|
13408
|
-
var
|
|
13409
|
-
|
|
13410
|
-
|
|
13411
|
-
|
|
13412
|
-
|
|
13413
|
-
|
|
13414
|
-
|
|
13415
|
-
|
|
13416
|
-
|
|
13272
|
+
|
|
13273
|
+
// packages/workit-core/src/core/policy/substance.ts
|
|
13274
|
+
var SLASH_BLOCK = [["/*", "*/"]];
|
|
13275
|
+
var C_LIKE = { line: ["//"], block: SLASH_BLOCK, quotes: `"'` };
|
|
13276
|
+
var SYNTAXES = Object.fromEntries([
|
|
13277
|
+
...["ts", "mts", "cts", "js", "mjs", "cjs"].map((ext) => [ext, { ...C_LIKE, js: true }]),
|
|
13278
|
+
...["java", "kt", "kts", "scala", "swift", "dart", "groovy"].map((ext) => [
|
|
13279
|
+
ext,
|
|
13280
|
+
{ ...C_LIKE, triple: true }
|
|
13281
|
+
]),
|
|
13282
|
+
["go", { ...C_LIKE, rawBacktick: true }],
|
|
13283
|
+
...["css", "scss", "less"].map((ext) => [
|
|
13284
|
+
ext,
|
|
13285
|
+
{ line: [], block: SLASH_BLOCK, quotes: `"'`, cssUrl: true }
|
|
13286
|
+
]),
|
|
13287
|
+
...["py", "pyi"].map((ext) => [
|
|
13288
|
+
ext,
|
|
13289
|
+
{ line: ["#"], block: [], quotes: `"'`, triple: true, indented: true, fstring: true }
|
|
13290
|
+
]),
|
|
13291
|
+
["toml", { line: ["#"], block: [], quotes: `"`, literal: "'", triple: true }],
|
|
13292
|
+
...["html", "htm", "xml", "svg", "xhtml"].map((ext) => [
|
|
13293
|
+
ext,
|
|
13294
|
+
{ line: [], block: [["<!--", "-->"]], quotes: "", indented: true }
|
|
13295
|
+
])
|
|
13296
|
+
]);
|
|
13297
|
+
var 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;
|
|
13298
|
+
var extensionOf = (file) => {
|
|
13299
|
+
const name = file.slice(file.lastIndexOf("/") + 1);
|
|
13300
|
+
const dot = name.lastIndexOf(".");
|
|
13301
|
+
return dot > 0 ? name.slice(dot + 1).toLowerCase() : "";
|
|
13302
|
+
};
|
|
13303
|
+
var CI_PATH = /^\.github\/|(?:^|\/)(?:\.gitlab-ci\.ya?ml|Jenkinsfile|azure-pipelines\.ya?ml|\.pre-commit-config\.ya?ml|\.circleci\/.*)$/u;
|
|
13304
|
+
var syntaxOf = (file) => CI_PATH.test(file) ? null : SYNTAXES[extensionOf(file)] ?? null;
|
|
13305
|
+
var SPACE = /[ \t\r\f\v]/u;
|
|
13306
|
+
var WORD = /[\w$]/u;
|
|
13307
|
+
var NO_SPACE_AFTER = "([{,;";
|
|
13308
|
+
var NO_SPACE_BEFORE = ")]},;";
|
|
13309
|
+
var REGEX_AFTER = "(,=:[!&|?{};+-*%<>~^";
|
|
13310
|
+
var REGEX_KEYWORDS = new Set([
|
|
13311
|
+
"return",
|
|
13312
|
+
"typeof",
|
|
13313
|
+
"instanceof",
|
|
13314
|
+
"case",
|
|
13315
|
+
"do",
|
|
13316
|
+
"else",
|
|
13317
|
+
"in",
|
|
13318
|
+
"of",
|
|
13319
|
+
"new",
|
|
13320
|
+
"delete",
|
|
13321
|
+
"void",
|
|
13322
|
+
"throw",
|
|
13323
|
+
"yield",
|
|
13324
|
+
"await"
|
|
13325
|
+
]);
|
|
13326
|
+
function substanceOf(file, text) {
|
|
13327
|
+
const syntax = syntaxOf(file);
|
|
13328
|
+
if (!syntax)
|
|
13329
|
+
return plainSubstance(text);
|
|
13330
|
+
const markup = syntax.block.some(([open]) => open === "<!--");
|
|
13331
|
+
if (markup && (/<(?:script|style)\b|<!\[CDATA\[|<\?/iu.test(text) || /[^>\s][ \t]*<!--/u.test(text)))
|
|
13332
|
+
return plainSubstance(text);
|
|
13333
|
+
return lex(syntax, text);
|
|
13334
|
+
}
|
|
13335
|
+
function plainSubstance(text) {
|
|
13336
|
+
return text.split(`
|
|
13337
|
+
`).map((line) => line.replace(/[ \t\r\f\v]+$/u, "")).filter(Boolean).join(`
|
|
13338
|
+
`);
|
|
13339
|
+
}
|
|
13340
|
+
function lex(syntax, text) {
|
|
13341
|
+
const collapse = !syntax.indented;
|
|
13342
|
+
let out = "";
|
|
13343
|
+
let lineStart = 0;
|
|
13344
|
+
let kept = 0;
|
|
13345
|
+
let pendingSpace = false;
|
|
13346
|
+
const frames = [{ kind: "code", braces: 0 }];
|
|
13347
|
+
let index = 0;
|
|
13348
|
+
if (text.startsWith("#!")) {
|
|
13349
|
+
const end = text.indexOf(`
|
|
13350
|
+
`);
|
|
13351
|
+
out = end < 0 ? text : text.slice(0, end);
|
|
13352
|
+
kept = out.length;
|
|
13353
|
+
index = end < 0 ? text.length : end;
|
|
13354
|
+
}
|
|
13355
|
+
const emitCode = (chunk) => {
|
|
13356
|
+
if (collapse && pendingSpace && out.length > lineStart) {
|
|
13357
|
+
const before = out[out.length - 1];
|
|
13358
|
+
if (!NO_SPACE_AFTER.includes(before) && !NO_SPACE_BEFORE.includes(chunk[0]))
|
|
13359
|
+
out += " ";
|
|
13417
13360
|
}
|
|
13418
|
-
|
|
13419
|
-
|
|
13420
|
-
|
|
13421
|
-
|
|
13422
|
-
|
|
13423
|
-
|
|
13424
|
-
|
|
13425
|
-
|
|
13426
|
-
|
|
13427
|
-
|
|
13428
|
-
|
|
13429
|
-
|
|
13430
|
-
|
|
13431
|
-
break;
|
|
13432
|
-
}
|
|
13433
|
-
stop = start;
|
|
13361
|
+
pendingSpace = false;
|
|
13362
|
+
out += chunk;
|
|
13363
|
+
};
|
|
13364
|
+
const emitVerbatim = (chunk) => {
|
|
13365
|
+
emitCode(chunk);
|
|
13366
|
+
kept = out.length;
|
|
13367
|
+
};
|
|
13368
|
+
const newline = () => {
|
|
13369
|
+
if (!collapse) {
|
|
13370
|
+
let end = out.length;
|
|
13371
|
+
while (end > Math.max(lineStart, kept) && SPACE.test(out[end - 1]))
|
|
13372
|
+
end -= 1;
|
|
13373
|
+
out = out.slice(0, end);
|
|
13434
13374
|
}
|
|
13435
|
-
|
|
13436
|
-
|
|
13437
|
-
|
|
13438
|
-
return null;
|
|
13439
|
-
};
|
|
13440
|
-
var tighten = (fd) => {
|
|
13441
|
-
try {
|
|
13442
|
-
if ((fs13.fstatSync(fd).mode & 63) !== 0)
|
|
13443
|
-
fs13.fchmodSync(fd, 384);
|
|
13444
|
-
} catch {}
|
|
13445
|
-
};
|
|
13446
|
-
function appendLine(file, build, options) {
|
|
13447
|
-
const write = () => {
|
|
13448
|
-
const fd = fs13.openSync(file, "a+", 384);
|
|
13449
|
-
try {
|
|
13450
|
-
if (process.platform !== "win32")
|
|
13451
|
-
tighten(fd);
|
|
13452
|
-
const size = fs13.fstatSync(fd).size;
|
|
13453
|
-
let fence = "";
|
|
13454
|
-
if (size > 0) {
|
|
13455
|
-
const last = Buffer.alloc(1);
|
|
13456
|
-
fs13.readSync(fd, last, 0, 1, size - 1);
|
|
13457
|
-
if (last[0] !== 10)
|
|
13458
|
-
fence = `
|
|
13375
|
+
pendingSpace = false;
|
|
13376
|
+
if (out.length > lineStart) {
|
|
13377
|
+
out += `
|
|
13459
13378
|
`;
|
|
13460
|
-
|
|
13461
|
-
const line = build(lastLine(fd, size));
|
|
13462
|
-
if (!line.ok)
|
|
13463
|
-
return line;
|
|
13464
|
-
const bytes = Buffer.from(fence + line.value);
|
|
13465
|
-
const written = fs13.writeSync(fd, bytes);
|
|
13466
|
-
if (written !== bytes.length)
|
|
13467
|
-
return err("unavailable", `short write to ${file}: ${written} of ${bytes.length} bytes; the row is torn and will be skipped`);
|
|
13468
|
-
return { ok: true, value: undefined };
|
|
13469
|
-
} finally {
|
|
13470
|
-
fs13.closeSync(fd);
|
|
13379
|
+
lineStart = out.length;
|
|
13471
13380
|
}
|
|
13472
13381
|
};
|
|
13473
|
-
|
|
13474
|
-
|
|
13475
|
-
|
|
13476
|
-
|
|
13477
|
-
|
|
13478
|
-
|
|
13479
|
-
|
|
13480
|
-
|
|
13481
|
-
|
|
13482
|
-
|
|
13483
|
-
var sealed = (row, id, at, previous) => {
|
|
13484
|
-
const { prevHash: _prev, rowHash: _row, ...fields } = row;
|
|
13485
|
-
const prevHash = previous === null ? null : sha2562(previous);
|
|
13486
|
-
const body = { v: LEDGER_VERSION, id, at, ...fields, prevHash };
|
|
13487
|
-
const full = { ...body, rowHash: chainHash(prevHash, JSON.stringify(body)) };
|
|
13488
|
-
return { full, line: `${JSON.stringify(full)}
|
|
13489
|
-
` };
|
|
13490
|
-
};
|
|
13491
|
-
function appendRaw(cwd, row, options) {
|
|
13492
|
-
const file = ledgerPath(cwd);
|
|
13493
|
-
if (!file.ok)
|
|
13494
|
-
return file;
|
|
13495
|
-
const id = newId2();
|
|
13496
|
-
const at = (options.now ?? new Date).toISOString();
|
|
13497
|
-
let full = null;
|
|
13498
|
-
const written = appendLine(file.value, (previous) => {
|
|
13499
|
-
const next = sealed(row, id, at, previous);
|
|
13500
|
-
const bytes = Buffer.byteLength(next.line);
|
|
13501
|
-
if (bytes > MAX_LINE_BYTES)
|
|
13502
|
-
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>`);
|
|
13503
|
-
full = next.full;
|
|
13504
|
-
return { ok: true, value: next.line };
|
|
13505
|
-
}, options);
|
|
13506
|
-
return written.ok ? { ok: true, value: full } : written;
|
|
13507
|
-
}
|
|
13508
|
-
function observedRowBytes(row) {
|
|
13509
|
-
const sample = sealed({ ...row, observer: "workit_cli" }, newId2(), new Date().toISOString(), "");
|
|
13510
|
-
return Buffer.byteLength(sample.line);
|
|
13511
|
-
}
|
|
13512
|
-
function appendObserved(cwd, row, options = {}) {
|
|
13513
|
-
return appendRaw(cwd, { ...row, observer: "workit_cli" }, options);
|
|
13514
|
-
}
|
|
13515
|
-
function appendHookObserved(cwd, row) {
|
|
13516
|
-
return appendRaw(cwd, { ...row, observer: "host_hook" }, {});
|
|
13517
|
-
}
|
|
13518
|
-
var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
13519
|
-
var str2 = (value) => typeof value === "string" ? value : null;
|
|
13520
|
-
var normalizeActor = (value) => {
|
|
13521
|
-
const actor = isRecord3(value) ? value : {};
|
|
13522
|
-
return {
|
|
13523
|
-
host: str2(actor.host) ?? "unknown",
|
|
13524
|
-
session: str2(actor.session),
|
|
13525
|
-
agentId: str2(actor.agentId)
|
|
13382
|
+
const regexAllowed = () => {
|
|
13383
|
+
let end = out.length;
|
|
13384
|
+
while (end > 0 && /\s/u.test(out[end - 1]))
|
|
13385
|
+
end -= 1;
|
|
13386
|
+
if (end === 0 || REGEX_AFTER.includes(out[end - 1]))
|
|
13387
|
+
return true;
|
|
13388
|
+
let start = end;
|
|
13389
|
+
while (start > 0 && WORD.test(out[start - 1]))
|
|
13390
|
+
start -= 1;
|
|
13391
|
+
return REGEX_KEYWORDS.has(out.slice(start, end));
|
|
13526
13392
|
};
|
|
13527
|
-
|
|
13528
|
-
|
|
13529
|
-
|
|
13530
|
-
|
|
13531
|
-
|
|
13532
|
-
|
|
13533
|
-
|
|
13534
|
-
|
|
13535
|
-
|
|
13536
|
-
|
|
13537
|
-
|
|
13538
|
-
|
|
13539
|
-
|
|
13540
|
-
|
|
13541
|
-
|
|
13542
|
-
|
|
13543
|
-
|
|
13544
|
-
|
|
13545
|
-
|
|
13546
|
-
|
|
13547
|
-
|
|
13548
|
-
stat = fs13.statSync(file.value);
|
|
13549
|
-
text = fs13.readFileSync(file.value, "utf8");
|
|
13550
|
-
} catch (error) {
|
|
13551
|
-
if (error.code === "ENOENT")
|
|
13552
|
-
return {
|
|
13553
|
-
ok: true,
|
|
13554
|
-
value: {
|
|
13555
|
-
path: file.value,
|
|
13556
|
-
rows: [],
|
|
13557
|
-
skipped: 0,
|
|
13558
|
-
integrity: options.integrity ? { chained: 0, legacy: 0, unverified: [] } : null
|
|
13559
|
-
}
|
|
13560
|
-
};
|
|
13561
|
-
return err("unavailable", `cannot read ${file.value}: ${error.message}`);
|
|
13562
|
-
}
|
|
13563
|
-
const rows = [];
|
|
13564
|
-
let skipped = 0;
|
|
13565
|
-
const integrity = { chained: 0, legacy: 0, unverified: [] };
|
|
13566
|
-
const earlier = new Set([null]);
|
|
13567
|
-
let chainStarted = false;
|
|
13568
|
-
const ids = new Set;
|
|
13569
|
-
for (const raw of text.split(`
|
|
13570
|
-
`)) {
|
|
13571
|
-
const trimmed = raw.trim();
|
|
13572
|
-
if (!trimmed)
|
|
13573
|
-
continue;
|
|
13574
|
-
const linked = (prevHash) => earlier.has(prevHash);
|
|
13575
|
-
if (options.integrity)
|
|
13576
|
-
earlier.add(sha2562(trimmed));
|
|
13577
|
-
let parsed;
|
|
13578
|
-
try {
|
|
13579
|
-
parsed = JSON.parse(trimmed);
|
|
13580
|
-
} catch {
|
|
13581
|
-
skipped += 1;
|
|
13582
|
-
continue;
|
|
13393
|
+
const scanString = (quote, escapes, multiline) => {
|
|
13394
|
+
const prefix = text.slice(Math.max(0, index - 2), index).match(/[a-z]*$/iu)?.[0] ?? "";
|
|
13395
|
+
const fstring = syntax.fstring === true && /f/iu.test(prefix);
|
|
13396
|
+
let end = index + quote.length;
|
|
13397
|
+
while (end < text.length) {
|
|
13398
|
+
const char = text[end];
|
|
13399
|
+
if (escapes && char === "\\") {
|
|
13400
|
+
end += 2;
|
|
13401
|
+
continue;
|
|
13402
|
+
}
|
|
13403
|
+
if (text.startsWith(quote, end)) {
|
|
13404
|
+
if (fstring && unbalanced(text.slice(index + quote.length, end)))
|
|
13405
|
+
return false;
|
|
13406
|
+
emitVerbatim(text.slice(index, end + quote.length));
|
|
13407
|
+
index = end + quote.length;
|
|
13408
|
+
return true;
|
|
13409
|
+
}
|
|
13410
|
+
if (char === `
|
|
13411
|
+
` && !multiline)
|
|
13412
|
+
return false;
|
|
13413
|
+
end += 1;
|
|
13583
13414
|
}
|
|
13584
|
-
|
|
13585
|
-
|
|
13415
|
+
return false;
|
|
13416
|
+
};
|
|
13417
|
+
while (index < text.length) {
|
|
13418
|
+
const frame = frames[frames.length - 1];
|
|
13419
|
+
const char = text[index];
|
|
13420
|
+
if (frame.kind === "template") {
|
|
13421
|
+
if (char === "\\") {
|
|
13422
|
+
emitVerbatim(text.slice(index, index + 2));
|
|
13423
|
+
index += 2;
|
|
13424
|
+
} else if (char === "`") {
|
|
13425
|
+
emitVerbatim(char);
|
|
13426
|
+
frames.pop();
|
|
13427
|
+
index += 1;
|
|
13428
|
+
} else if (text.startsWith("${", index)) {
|
|
13429
|
+
emitVerbatim("${");
|
|
13430
|
+
frames.push({ kind: "code", braces: 0 });
|
|
13431
|
+
index += 2;
|
|
13432
|
+
} else {
|
|
13433
|
+
out += char;
|
|
13434
|
+
kept = out.length;
|
|
13435
|
+
index += 1;
|
|
13436
|
+
}
|
|
13586
13437
|
continue;
|
|
13587
13438
|
}
|
|
13588
|
-
|
|
13589
|
-
|
|
13590
|
-
|
|
13591
|
-
|
|
13592
|
-
const { rowHash, ...body } = parsed;
|
|
13593
|
-
const prevHash = str2(parsed.prevHash);
|
|
13594
|
-
if (chainHash(prevHash, JSON.stringify(body)) !== rowHash)
|
|
13595
|
-
reason = "hash_mismatch";
|
|
13596
|
-
else if (!linked(prevHash))
|
|
13597
|
-
reason = "broken_link";
|
|
13598
|
-
} else if (chainStarted)
|
|
13599
|
-
reason = "unsigned";
|
|
13600
|
-
else
|
|
13601
|
-
rowIntegrity = "legacy";
|
|
13602
|
-
if (options.integrity && !reason && ids.has(parsed.id))
|
|
13603
|
-
reason = "duplicate_id";
|
|
13604
|
-
if (options.integrity)
|
|
13605
|
-
ids.add(parsed.id);
|
|
13606
|
-
if (reason)
|
|
13607
|
-
rowIntegrity = "unverified";
|
|
13608
|
-
const pr = typeof parsed.pr === "number" && Number.isInteger(parsed.pr) ? parsed.pr : undefined;
|
|
13609
|
-
const row = {
|
|
13610
|
-
...parsed,
|
|
13611
|
-
v: typeof parsed.v === "number" ? parsed.v : LEDGER_VERSION,
|
|
13612
|
-
id: parsed.id,
|
|
13613
|
-
at: parsed.at,
|
|
13614
|
-
type: parsed.type,
|
|
13615
|
-
actor: normalizeActor(parsed.actor),
|
|
13616
|
-
branch: str2(parsed.branch),
|
|
13617
|
-
head: str2(parsed.head),
|
|
13618
|
-
base: str2(parsed.base),
|
|
13619
|
-
baseSha: str2(parsed.baseSha),
|
|
13620
|
-
patchId: str2(parsed.patchId),
|
|
13621
|
-
diffHash: str2(parsed.diffHash),
|
|
13622
|
-
tree: str2(parsed.tree),
|
|
13623
|
-
dirty: typeof parsed.dirty === "boolean" ? parsed.dirty : null,
|
|
13624
|
-
...pr === undefined ? {} : { pr },
|
|
13625
|
-
...str2(parsed.supersedes) ? { supersedes: parsed.supersedes } : {},
|
|
13626
|
-
seq: rows.length + 1,
|
|
13627
|
-
superseded: false,
|
|
13628
|
-
supersedeIgnored: false,
|
|
13629
|
-
...rowIntegrity ? { integrity: rowIntegrity } : {},
|
|
13630
|
-
...reason ? { integrityReason: reason } : {}
|
|
13631
|
-
};
|
|
13632
|
-
rows.push(row);
|
|
13633
|
-
if (!options.integrity)
|
|
13439
|
+
if (char === `
|
|
13440
|
+
`) {
|
|
13441
|
+
newline();
|
|
13442
|
+
index += 1;
|
|
13634
13443
|
continue;
|
|
13635
|
-
if (rowIntegrity === "verified")
|
|
13636
|
-
integrity.chained += 1;
|
|
13637
|
-
else if (rowIntegrity === "legacy")
|
|
13638
|
-
integrity.legacy += 1;
|
|
13639
|
-
else if (reason)
|
|
13640
|
-
integrity.unverified.push({
|
|
13641
|
-
seq: row.seq,
|
|
13642
|
-
id: row.id,
|
|
13643
|
-
type: row.type,
|
|
13644
|
-
branch: row.branch,
|
|
13645
|
-
reason
|
|
13646
|
-
});
|
|
13647
|
-
}
|
|
13648
|
-
const scopes = new Map;
|
|
13649
|
-
const scopeOf = (branch) => {
|
|
13650
|
-
let scope = scopes.get(branch);
|
|
13651
|
-
if (!scope)
|
|
13652
|
-
scopes.set(branch, scope = branchScope(cwd, branch));
|
|
13653
|
-
return scope;
|
|
13654
|
-
};
|
|
13655
|
-
const byId = new Map;
|
|
13656
|
-
for (const row of rows) {
|
|
13657
|
-
const target = row.supersedes ? byId.get(row.supersedes) : undefined;
|
|
13658
|
-
if (target && row.integrity !== "unverified" && supersedeAllowed(row, target, renameAwareBranch(scopeOf, row.branch)) === null)
|
|
13659
|
-
target.superseded = true;
|
|
13660
|
-
else if (row.supersedes)
|
|
13661
|
-
row.supersedeIgnored = true;
|
|
13662
|
-
if (!byId.has(row.id))
|
|
13663
|
-
byId.set(row.id, row);
|
|
13664
|
-
}
|
|
13665
|
-
if (options.integrity && stat && Buffer.byteLength(text) === stat.size)
|
|
13666
|
-
tamperCache.set(file.value, {
|
|
13667
|
-
size: stat.size,
|
|
13668
|
-
stamp: fileStamp(stat),
|
|
13669
|
-
breaks: integrity.unverified.filter((row) => TAMPER_REASONS.has(row.reason))
|
|
13670
|
-
});
|
|
13671
|
-
return {
|
|
13672
|
-
ok: true,
|
|
13673
|
-
value: { path: file.value, rows, skipped, integrity: options.integrity ? integrity : null }
|
|
13674
|
-
};
|
|
13675
|
-
}
|
|
13676
|
-
var TAMPER_REASONS = new Set(["hash_mismatch", "broken_link"]);
|
|
13677
|
-
var tamperCache = new Map;
|
|
13678
|
-
var fileStamp = (stat) => `${stat.ino}:${stat.mtimeMs}:${stat.ctimeMs}`;
|
|
13679
|
-
function ledgerTampering(cwd) {
|
|
13680
|
-
const file = ledgerPath(cwd);
|
|
13681
|
-
if (!file.ok)
|
|
13682
|
-
return [];
|
|
13683
|
-
try {
|
|
13684
|
-
const stat = fs13.statSync(file.value);
|
|
13685
|
-
const cached = tamperCache.get(file.value);
|
|
13686
|
-
if (cached && cached.size === stat.size && cached.stamp === fileStamp(stat))
|
|
13687
|
-
return cached.breaks;
|
|
13688
|
-
} catch {
|
|
13689
|
-
return [];
|
|
13690
|
-
}
|
|
13691
|
-
const read = readLedger(cwd, { integrity: true });
|
|
13692
|
-
return read.ok ? (read.value.integrity?.unverified ?? []).filter((row) => TAMPER_REASONS.has(row.reason)) : [];
|
|
13693
|
-
}
|
|
13694
|
-
function tamperedUnblock(cwd) {
|
|
13695
|
-
const file = ledgerPath(cwd);
|
|
13696
|
-
const where = file.ok ? file.value : "the ledger file";
|
|
13697
|
-
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`;
|
|
13698
|
-
}
|
|
13699
|
-
var tamperedWarning = (cwd, breaks) => `the ledger was edited, or a row was removed or moved (${breaks.slice(0, 3).map((row) => `${row.reason} at row ${row.seq}`).join(", ")}${breaks.length > 3 ? ", …" : ""}): no verdict is accepted until it is repaired. ${tamperedUnblock(cwd)}`;
|
|
13700
|
-
function verdictBasis(verdict, current) {
|
|
13701
|
-
if (verdict.head && verdict.head === current.head)
|
|
13702
|
-
return "fresh";
|
|
13703
|
-
if (verdict.patchId && verdict.patchId === current.patchId && verdict.diffHash && verdict.diffHash === current.diffHash)
|
|
13704
|
-
return "carried";
|
|
13705
|
-
return "stale";
|
|
13706
|
-
}
|
|
13707
|
-
var isLiveVerdict = (row, retired) => row.type === "verdict" && typeof row.result === "string" && !retired.has(row.id);
|
|
13708
|
-
function trailerSessions(cwd, base, head) {
|
|
13709
|
-
if (!base || !head || !safeRef(base) || !safeRef(head))
|
|
13710
|
-
return [];
|
|
13711
|
-
const out = git4(cwd, [
|
|
13712
|
-
"log",
|
|
13713
|
-
"--format=%(trailers:key=Workit-Session,valueonly,separator=%x00)",
|
|
13714
|
-
`${base}..${head}`
|
|
13715
|
-
]);
|
|
13716
|
-
return (out ?? "").split(/[\0\n]/u).map((value) => value.trim()).filter(Boolean);
|
|
13717
|
-
}
|
|
13718
|
-
var RENAMED = /^branch: renamed refs\/heads\/(.+) to refs\/heads\/(.+)$/iu;
|
|
13719
|
-
function branchScope(cwd, branch) {
|
|
13720
|
-
const renamedAt = new Map;
|
|
13721
|
-
let since = Number.NEGATIVE_INFINITY;
|
|
13722
|
-
if (safeRef(branch)) {
|
|
13723
|
-
const log = git4(cwd, [
|
|
13724
|
-
"log",
|
|
13725
|
-
"-g",
|
|
13726
|
-
"--date=unix",
|
|
13727
|
-
"--format=%gd%x09%gs",
|
|
13728
|
-
`refs/heads/${branch}`,
|
|
13729
|
-
"--"
|
|
13730
|
-
]);
|
|
13731
|
-
for (const line of (log ?? "").split(`
|
|
13732
|
-
`)) {
|
|
13733
|
-
const [selector = "", subject = ""] = line.split("\t");
|
|
13734
|
-
const seconds = Number(/@\{(\d+)\}$/u.exec(selector)?.[1]);
|
|
13735
|
-
if (!Number.isFinite(seconds))
|
|
13736
|
-
continue;
|
|
13737
|
-
since = seconds * 1000;
|
|
13738
|
-
const match = RENAMED.exec(subject);
|
|
13739
|
-
if (!match || match[1] === branch)
|
|
13740
|
-
continue;
|
|
13741
|
-
const until = (seconds + 1) * 1000;
|
|
13742
|
-
renamedAt.set(match[1], Math.max(renamedAt.get(match[1]) ?? 0, until));
|
|
13743
13444
|
}
|
|
13744
|
-
|
|
13745
|
-
|
|
13746
|
-
|
|
13747
|
-
|
|
13748
|
-
|
|
13749
|
-
|
|
13750
|
-
return false;
|
|
13751
|
-
const at = Date.parse(row.at);
|
|
13752
|
-
return at >= since && at <= until;
|
|
13753
|
-
};
|
|
13754
|
-
}
|
|
13755
|
-
var renameAwareBranch = (scopeOf, branch) => {
|
|
13756
|
-
return (target) => {
|
|
13757
|
-
const own = typeof branch === "string" ? branch : null;
|
|
13758
|
-
const theirs = typeof target.branch === "string" ? target.branch : null;
|
|
13759
|
-
if (own === theirs)
|
|
13760
|
-
return true;
|
|
13761
|
-
if (own === null || theirs === null || typeof target.at !== "string")
|
|
13762
|
-
return false;
|
|
13763
|
-
return scopeOf(own)({ branch: theirs, at: target.at });
|
|
13764
|
-
};
|
|
13765
|
-
};
|
|
13766
|
-
function authorSessions(cwd, rows, branch, range = { base: null, head: null }, scope = branchScope(cwd, branch)) {
|
|
13767
|
-
const sessions = new Set;
|
|
13768
|
-
for (const row of rows) {
|
|
13769
|
-
if (!scope(row) || row.type !== "commit.recorded" && row.type !== "task.opened")
|
|
13770
|
-
continue;
|
|
13771
|
-
const session = str2(row.session) ?? row.actor.session;
|
|
13772
|
-
if (session)
|
|
13773
|
-
sessions.add(session);
|
|
13774
|
-
}
|
|
13775
|
-
for (const session of trailerSessions(cwd, range.base, range.head))
|
|
13776
|
-
sessions.add(session);
|
|
13777
|
-
return sessions;
|
|
13778
|
-
}
|
|
13779
|
-
var derivesFrom = (derived, bare) => !bare.includes(":") && derived.endsWith(`:${bare}`);
|
|
13780
|
-
var authoredBy = (authors, session) => {
|
|
13781
|
-
if (authors.has(session))
|
|
13782
|
-
return true;
|
|
13783
|
-
for (const author of authors)
|
|
13784
|
-
if (derivesFrom(author, session) || derivesFrom(session, author))
|
|
13785
|
-
return true;
|
|
13786
|
-
return false;
|
|
13787
|
-
};
|
|
13788
|
-
var TRUST_REASONS = new Set([
|
|
13789
|
-
"unsigned",
|
|
13790
|
-
"hash_mismatch",
|
|
13791
|
-
"broken_link",
|
|
13792
|
-
"duplicate_id",
|
|
13793
|
-
"unissued_session",
|
|
13794
|
-
"unbound_session"
|
|
13795
|
-
]);
|
|
13796
|
-
var SHELL_VOUCHED_HOSTS = new Set([
|
|
13797
|
-
"cli",
|
|
13798
|
-
"opencode",
|
|
13799
|
-
"pi",
|
|
13800
|
-
"codex_cli",
|
|
13801
|
-
"codex_desktop"
|
|
13802
|
-
]);
|
|
13803
|
-
function sessionTrust(cwd, row) {
|
|
13804
|
-
const session = row.actor.session;
|
|
13805
|
-
if (!session || row.self === true)
|
|
13806
|
-
return null;
|
|
13807
|
-
const source = typeof row.sessionSource === "string" ? row.sessionSource : "env";
|
|
13808
|
-
const host = row.actor.host;
|
|
13809
|
-
const issuedOnly = source === "flag" ? host !== "cli" : source === "env" && session.includes(":") && !SHELL_VOUCHED_HOSTS.has(host);
|
|
13810
|
-
if (!issuedOnly)
|
|
13811
|
-
return null;
|
|
13812
|
-
const binding = issuedBinding(cwd, session);
|
|
13813
|
-
if (binding === null)
|
|
13814
|
-
return "unissued_session";
|
|
13815
|
-
return binding === "token" && row.sessionBound !== true ? "unbound_session" : null;
|
|
13816
|
-
}
|
|
13817
|
-
function trustReasons(cwd, row) {
|
|
13818
|
-
if (row.integrity === "unverified")
|
|
13819
|
-
return [row.integrityReason ?? "unsigned"];
|
|
13820
|
-
if (row.integrity !== "verified")
|
|
13821
|
-
return ["unsigned"];
|
|
13822
|
-
const session = sessionTrust(cwd, row);
|
|
13823
|
-
return session ? [session] : [];
|
|
13824
|
-
}
|
|
13825
|
-
function withIntegrity(cwd, rows) {
|
|
13826
|
-
if (rows.every((row) => row.integrity !== undefined))
|
|
13827
|
-
return rows;
|
|
13828
|
-
const read = readLedger(cwd, { integrity: true });
|
|
13829
|
-
const bySeq = new Map(read.ok ? read.value.rows.map((row) => [row.seq, row]) : []);
|
|
13830
|
-
return rows.map((row) => {
|
|
13831
|
-
if (row.integrity !== undefined)
|
|
13832
|
-
return row;
|
|
13833
|
-
const fresh = bySeq.get(row.seq);
|
|
13834
|
-
return fresh && fresh.id === row.id && fresh.at === row.at ? fresh : row;
|
|
13835
|
-
});
|
|
13836
|
-
}
|
|
13837
|
-
var untrusted = (reasons) => reasons.some((reason) => TRUST_REASONS.has(reason));
|
|
13838
|
-
var independenceReasons = (row, authors) => {
|
|
13839
|
-
const reasons = [];
|
|
13840
|
-
if (!row.actor.session)
|
|
13841
|
-
reasons.push("no_session");
|
|
13842
|
-
else if (authoredBy(authors, row.actor.session))
|
|
13843
|
-
reasons.push("author_session");
|
|
13844
|
-
if (row.self === true && !reasons.includes("no_session"))
|
|
13845
|
-
reasons.push("self");
|
|
13846
|
-
return reasons;
|
|
13847
|
-
};
|
|
13848
|
-
function effectiveOf(rows, reasonsOf, sameCode, isCurrent) {
|
|
13849
|
-
let effective = null;
|
|
13850
|
-
for (const row of rows) {
|
|
13851
|
-
if (!effective) {
|
|
13852
|
-
effective = row;
|
|
13445
|
+
if (SPACE.test(char)) {
|
|
13446
|
+
if (collapse)
|
|
13447
|
+
pendingSpace = true;
|
|
13448
|
+
else
|
|
13449
|
+
out += char;
|
|
13450
|
+
index += 1;
|
|
13853
13451
|
continue;
|
|
13854
13452
|
}
|
|
13855
|
-
|
|
13453
|
+
const lineOpener = syntax.line.find((opener) => text.startsWith(opener, index));
|
|
13454
|
+
if (lineOpener) {
|
|
13455
|
+
const end = text.indexOf(`
|
|
13456
|
+
`, index);
|
|
13457
|
+
const comment = text.slice(index, end < 0 ? text.length : end);
|
|
13458
|
+
if (DIRECTIVE.test(comment.slice(lineOpener.length)))
|
|
13459
|
+
emitVerbatim(comment);
|
|
13460
|
+
else
|
|
13461
|
+
pendingSpace = true;
|
|
13462
|
+
index = end < 0 ? text.length : end;
|
|
13856
13463
|
continue;
|
|
13857
|
-
|
|
13858
|
-
const
|
|
13859
|
-
if (
|
|
13464
|
+
}
|
|
13465
|
+
const block = syntax.block.find(([open]) => text.startsWith(open, index));
|
|
13466
|
+
if (block) {
|
|
13467
|
+
const end = text.indexOf(block[1], index + block[0].length);
|
|
13468
|
+
if (end < 0)
|
|
13469
|
+
return null;
|
|
13470
|
+
const comment = text.slice(index, end + block[1].length);
|
|
13471
|
+
if (DIRECTIVE.test(comment.slice(block[0].length, -block[1].length)))
|
|
13472
|
+
emitVerbatim(comment);
|
|
13473
|
+
else
|
|
13474
|
+
pendingSpace = true;
|
|
13475
|
+
if (comment.includes(`
|
|
13476
|
+
`))
|
|
13477
|
+
newline();
|
|
13478
|
+
index = end + block[1].length;
|
|
13860
13479
|
continue;
|
|
13861
|
-
|
|
13480
|
+
}
|
|
13481
|
+
if (syntax.triple && (char === '"' || char === "'") && text.startsWith(char.repeat(3), index)) {
|
|
13482
|
+
if (!scanString(char.repeat(3), !syntax.literal?.includes(char), true))
|
|
13483
|
+
return null;
|
|
13862
13484
|
continue;
|
|
13863
|
-
effective = row;
|
|
13864
|
-
}
|
|
13865
|
-
return effective;
|
|
13866
|
-
}
|
|
13867
|
-
function restackCarries(rows, branch, from, to) {
|
|
13868
|
-
if (!from || !to)
|
|
13869
|
-
return false;
|
|
13870
|
-
const edges = new Map;
|
|
13871
|
-
for (const row of rows)
|
|
13872
|
-
if (row.type === "stack.restacked" && row.observer === "workit_cli" && row.integrity === "verified" && row.branch === branch && row.patchEqual === true && typeof row.fromHead === "string" && row.head)
|
|
13873
|
-
edges.set(row.fromHead, [...edges.get(row.fromHead) ?? [], row.head]);
|
|
13874
|
-
const seen = new Set([from]);
|
|
13875
|
-
const queue = [from];
|
|
13876
|
-
while (queue.length) {
|
|
13877
|
-
const next = queue.shift();
|
|
13878
|
-
if (next === to)
|
|
13879
|
-
return true;
|
|
13880
|
-
for (const target of edges.get(next) ?? [])
|
|
13881
|
-
if (!seen.has(target)) {
|
|
13882
|
-
seen.add(target);
|
|
13883
|
-
queue.push(target);
|
|
13884
|
-
}
|
|
13885
|
-
}
|
|
13886
|
-
return false;
|
|
13887
|
-
}
|
|
13888
|
-
function checkVerdicts(cwd, branch, ledgerRows) {
|
|
13889
|
-
const rows = withIntegrity(cwd, ledgerRows);
|
|
13890
|
-
const head = branchHead(cwd, branch);
|
|
13891
|
-
const fallbackBase = defaultBase(cwd);
|
|
13892
|
-
const scope = branchScope(cwd, branch);
|
|
13893
|
-
const keyCache = new Map;
|
|
13894
|
-
const keysAt = (base, at) => {
|
|
13895
|
-
if (!base || !at)
|
|
13896
|
-
return { patchId: null, diffHash: null };
|
|
13897
|
-
const cacheKey = `${base}\x00${at}`;
|
|
13898
|
-
let key = keyCache.get(cacheKey);
|
|
13899
|
-
if (!key) {
|
|
13900
|
-
key = { patchId: patchId(cwd, base, at), diffHash: diffHash(cwd, base, at) };
|
|
13901
|
-
keyCache.set(cacheKey, key);
|
|
13902
13485
|
}
|
|
13903
|
-
|
|
13904
|
-
|
|
13905
|
-
|
|
13906
|
-
|
|
13907
|
-
const basis = verdictBasis(row, { head, ...keyFor(row.base ?? fallbackBase) });
|
|
13908
|
-
return basis === "stale" && restackCarries(rows, branch, row.head, head) ? "carried" : basis;
|
|
13909
|
-
};
|
|
13910
|
-
const sameCode = (failed, row) => {
|
|
13911
|
-
if (failed.head && failed.head === row.head)
|
|
13912
|
-
return true;
|
|
13913
|
-
const base = failed.base ?? fallbackBase;
|
|
13914
|
-
const stored = (row.base ?? fallbackBase) === base && row.baseSha !== null && row.baseSha === failed.baseSha;
|
|
13915
|
-
const keys = stored ? { patchId: row.patchId, diffHash: row.diffHash } : keysAt(base, row.head);
|
|
13916
|
-
return verdictBasis(failed, { head: row.head, ...keys }) !== "stale" || restackCarries(rows, branch, failed.head, row.head);
|
|
13917
|
-
};
|
|
13918
|
-
const authors = authorSessions(cwd, rows, branch, { base: fallbackBase, head }, scope);
|
|
13919
|
-
const failureAuthorCache = new Map;
|
|
13920
|
-
const failureAuthors = (row) => {
|
|
13921
|
-
let set = failureAuthorCache.get(row.seq);
|
|
13922
|
-
if (!set) {
|
|
13923
|
-
set = authorSessions(cwd, rows.filter((other) => other.integrity === "verified" && other.seq < row.seq), branch, { base: null, head: null }, scope);
|
|
13924
|
-
failureAuthorCache.set(row.seq, set);
|
|
13486
|
+
if (syntax.quotes.includes(char)) {
|
|
13487
|
+
if (!scanString(char, true, Boolean(syntax.multiline)))
|
|
13488
|
+
return null;
|
|
13489
|
+
continue;
|
|
13925
13490
|
}
|
|
13926
|
-
|
|
13927
|
-
|
|
13928
|
-
|
|
13929
|
-
|
|
13930
|
-
let reasons = reasonCache.get(row);
|
|
13931
|
-
if (!reasons) {
|
|
13932
|
-
const failing = FAILING.has(String(row.result));
|
|
13933
|
-
reasons = [
|
|
13934
|
-
...trustReasons(cwd, row),
|
|
13935
|
-
...independenceReasons(row, failing ? failureAuthors(row) : authors)
|
|
13936
|
-
];
|
|
13937
|
-
reasonCache.set(row, reasons);
|
|
13491
|
+
if (syntax.literal?.includes(char)) {
|
|
13492
|
+
if (!scanString(char, false, false))
|
|
13493
|
+
return null;
|
|
13494
|
+
continue;
|
|
13938
13495
|
}
|
|
13939
|
-
|
|
13940
|
-
|
|
13941
|
-
|
|
13942
|
-
|
|
13943
|
-
if (row.type === "verdict" && row.supersedes && !row.supersedeIgnored && trustReasons(cwd, row).length === 0)
|
|
13944
|
-
retired.add(row.supersedes);
|
|
13945
|
-
const byKind = new Map;
|
|
13946
|
-
for (const row of rows)
|
|
13947
|
-
if (isLiveVerdict(row, retired) && scope(row)) {
|
|
13948
|
-
const kind = typeof row.kind === "string" ? row.kind : "review";
|
|
13949
|
-
byKind.set(kind, [...byKind.get(kind) ?? [], row]);
|
|
13496
|
+
if (syntax.rawBacktick && char === "`") {
|
|
13497
|
+
if (!scanString("`", false, true))
|
|
13498
|
+
return null;
|
|
13499
|
+
continue;
|
|
13950
13500
|
}
|
|
13951
|
-
|
|
13952
|
-
|
|
13953
|
-
|
|
13954
|
-
|
|
13955
|
-
if (!row)
|
|
13501
|
+
if (syntax.js && char === "`") {
|
|
13502
|
+
emitVerbatim(char);
|
|
13503
|
+
frames.push({ kind: "template" });
|
|
13504
|
+
index += 1;
|
|
13956
13505
|
continue;
|
|
13957
|
-
const basis = basisOf(row);
|
|
13958
|
-
const current = basis !== "stale";
|
|
13959
|
-
const independence = reasonsOf(row);
|
|
13960
|
-
const reasons = [
|
|
13961
|
-
...current ? [] : ["stale"],
|
|
13962
|
-
...PASSING.has(String(row.result)) ? [] : ["not_passing"],
|
|
13963
|
-
...independence
|
|
13964
|
-
];
|
|
13965
|
-
verdicts.push({
|
|
13966
|
-
kind,
|
|
13967
|
-
basis,
|
|
13968
|
-
current,
|
|
13969
|
-
independent: independence.length === 0,
|
|
13970
|
-
accepted: reasons.length === 0,
|
|
13971
|
-
reasons,
|
|
13972
|
-
verdict: row
|
|
13973
|
-
});
|
|
13974
|
-
if (reasons.length === 0) {
|
|
13975
|
-
for (const later of list)
|
|
13976
|
-
if (later.seq > row.seq && reasonsOf(later).length > 0 && !untrusted(reasonsOf(later)) && FAILING.has(String(later.result)) && basisOf(later) !== "stale")
|
|
13977
|
-
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`);
|
|
13978
13506
|
}
|
|
13979
|
-
|
|
13980
|
-
|
|
13981
|
-
if (
|
|
13982
|
-
|
|
13983
|
-
|
|
13984
|
-
|
|
13985
|
-
|
|
13986
|
-
|
|
13987
|
-
|
|
13988
|
-
|
|
13507
|
+
if (syntax.cssUrl && /^url\(\s*[^"')\s]/iu.test(text.slice(index, index + 64)))
|
|
13508
|
+
return null;
|
|
13509
|
+
if (syntax.js && char === "/" && !regexAllowed() && out.trimEnd().endsWith(")")) {
|
|
13510
|
+
const lineEnd = text.indexOf(`
|
|
13511
|
+
`, index);
|
|
13512
|
+
if (text.slice(index + 1, lineEnd < 0 ? text.length : lineEnd).includes("/"))
|
|
13513
|
+
return null;
|
|
13514
|
+
}
|
|
13515
|
+
if (syntax.js && char === "/" && regexAllowed()) {
|
|
13516
|
+
let end = index + 1;
|
|
13517
|
+
let inClass = false;
|
|
13518
|
+
while (end < text.length && (inClass || text[end] !== "/")) {
|
|
13519
|
+
if (text[end] === `
|
|
13520
|
+
`)
|
|
13521
|
+
return null;
|
|
13522
|
+
if (text[end] === "\\")
|
|
13523
|
+
end += 1;
|
|
13524
|
+
else if (text[end] === "[")
|
|
13525
|
+
inClass = true;
|
|
13526
|
+
else if (text[end] === "]")
|
|
13527
|
+
inClass = false;
|
|
13528
|
+
end += 1;
|
|
13529
|
+
}
|
|
13530
|
+
if (end >= text.length)
|
|
13531
|
+
return null;
|
|
13532
|
+
emitVerbatim(text.slice(index, end + 1));
|
|
13533
|
+
index = end + 1;
|
|
13534
|
+
continue;
|
|
13535
|
+
}
|
|
13536
|
+
if (frames.length > 1 && frame.kind === "code") {
|
|
13537
|
+
if (char === "{")
|
|
13538
|
+
frame.braces += 1;
|
|
13539
|
+
else if (char === "}") {
|
|
13540
|
+
if (frame.braces === 0) {
|
|
13541
|
+
emitVerbatim(char);
|
|
13542
|
+
frames.pop();
|
|
13543
|
+
index += 1;
|
|
13544
|
+
continue;
|
|
13545
|
+
}
|
|
13546
|
+
frame.braces -= 1;
|
|
13547
|
+
}
|
|
13989
13548
|
}
|
|
13549
|
+
emitCode(char);
|
|
13550
|
+
index += 1;
|
|
13990
13551
|
}
|
|
13991
|
-
|
|
13992
|
-
|
|
13993
|
-
|
|
13994
|
-
|
|
13995
|
-
|
|
13996
|
-
|
|
13997
|
-
|
|
13998
|
-
|
|
13999
|
-
|
|
14000
|
-
|
|
14001
|
-
|
|
14002
|
-
|
|
14003
|
-
|
|
14004
|
-
|
|
14005
|
-
|
|
14006
|
-
|
|
14007
|
-
|
|
14008
|
-
|
|
14009
|
-
|
|
14010
|
-
|
|
14011
|
-
|
|
14012
|
-
|
|
14013
|
-
|
|
14014
|
-
|
|
13552
|
+
if (frames.length !== 1)
|
|
13553
|
+
return null;
|
|
13554
|
+
newline();
|
|
13555
|
+
return out;
|
|
13556
|
+
}
|
|
13557
|
+
function unbalanced(body) {
|
|
13558
|
+
let depth = 0;
|
|
13559
|
+
for (const part of body.replaceAll("{{", "").replaceAll("}}", "")) {
|
|
13560
|
+
if (part === "{")
|
|
13561
|
+
depth += 1;
|
|
13562
|
+
else if (part === "}")
|
|
13563
|
+
depth = Math.max(0, depth - 1);
|
|
13564
|
+
}
|
|
13565
|
+
return depth > 0;
|
|
13566
|
+
}
|
|
13567
|
+
function hasOpaqueLineBreak(text) {
|
|
13568
|
+
return /\r(?!\n)|[\u2028\u2029]/u.test(text);
|
|
13569
|
+
}
|
|
13570
|
+
var decoder = new TextDecoder("utf-8", { fatal: true });
|
|
13571
|
+
function textOf(bytes) {
|
|
13572
|
+
if (bytes.includes(0))
|
|
13573
|
+
return null;
|
|
13574
|
+
try {
|
|
13575
|
+
return decoder.decode(bytes);
|
|
13576
|
+
} catch {
|
|
13577
|
+
return null;
|
|
13578
|
+
}
|
|
13579
|
+
}
|
|
13580
|
+
function cosmeticChange(file, before, after) {
|
|
13581
|
+
const left = textOf(before);
|
|
13582
|
+
const right = textOf(after);
|
|
13583
|
+
if (left === null || right === null)
|
|
13584
|
+
return false;
|
|
13585
|
+
if (hasOpaqueLineBreak(left) || hasOpaqueLineBreak(right))
|
|
13586
|
+
return false;
|
|
13587
|
+
const a = substanceOf(file, left);
|
|
13588
|
+
return a !== null && a === substanceOf(file, right);
|
|
13589
|
+
}
|
|
13590
|
+
|
|
13591
|
+
// packages/workit-core/src/core/policy/lane.ts
|
|
13592
|
+
var LANE_THRESHOLDS = {
|
|
13593
|
+
selfMaxLines: 100,
|
|
13594
|
+
selfMaxFiles: 5,
|
|
13595
|
+
fullMinLines: 500
|
|
13596
|
+
};
|
|
13597
|
+
var RISK_TOKENS = new Set([
|
|
13598
|
+
"auth",
|
|
13599
|
+
"authn",
|
|
13600
|
+
"authz",
|
|
13601
|
+
"oauth",
|
|
13602
|
+
"security",
|
|
13603
|
+
"permission",
|
|
13604
|
+
"permissions",
|
|
13605
|
+
"secret",
|
|
13606
|
+
"secrets",
|
|
13607
|
+
"crypto",
|
|
13608
|
+
"gate",
|
|
13609
|
+
"gates",
|
|
13610
|
+
"policy",
|
|
13611
|
+
"policies",
|
|
13612
|
+
"hook",
|
|
13613
|
+
"hooks",
|
|
13614
|
+
"husky",
|
|
13615
|
+
"lefthook",
|
|
13616
|
+
"release",
|
|
13617
|
+
"releases",
|
|
13618
|
+
"migration",
|
|
13619
|
+
"migrations",
|
|
13620
|
+
"migrate",
|
|
13621
|
+
"workflows"
|
|
13622
|
+
]);
|
|
13623
|
+
var RISK_FILES = new Set([
|
|
13624
|
+
".gitlab-ci.yml",
|
|
13625
|
+
"Jenkinsfile",
|
|
13626
|
+
"azure-pipelines.yml",
|
|
13627
|
+
".pre-commit-config.yaml",
|
|
13628
|
+
".workit/risk-paths"
|
|
13629
|
+
]);
|
|
13630
|
+
var RISK_NAMES = new Set([".gitattributes"]);
|
|
13631
|
+
var RISK_PATHS_FILE = ".workit/risk-paths";
|
|
13632
|
+
var LOCKFILES = new Set([
|
|
13633
|
+
"bun.lock",
|
|
13634
|
+
"bun.lockb",
|
|
13635
|
+
"package-lock.json",
|
|
13636
|
+
"npm-shrinkwrap.json",
|
|
13637
|
+
"yarn.lock",
|
|
13638
|
+
"pnpm-lock.yaml",
|
|
13639
|
+
"Cargo.lock",
|
|
13640
|
+
"go.sum",
|
|
13641
|
+
"poetry.lock",
|
|
13642
|
+
"uv.lock",
|
|
13643
|
+
"Pipfile.lock",
|
|
13644
|
+
"Gemfile.lock",
|
|
13645
|
+
"composer.lock"
|
|
13646
|
+
]);
|
|
13647
|
+
var DOC_EXT = /\.(?:md|mdx|markdown|rst|adoc)$/iu;
|
|
13648
|
+
var DOC_NAMES = /^(?:license|licence|notice|authors|changelog|changes|contributors)$/iu;
|
|
13649
|
+
var INSTRUCTIONS = /(?:^|\/)(?:AGENTS|CLAUDE|GEMINI|SKILL)\.md$|(?:^|\/)(?:skills|agents|commands|rules|prompts)\/[^/]+/iu;
|
|
13650
|
+
var TEST_PATH = /(?:^|\/)(?:test|tests|__tests__)\/|\.(?:test|spec)\.[^/]+$|_test\.[^/]+$/iu;
|
|
13651
|
+
var basename = (file) => file.slice(file.lastIndexOf("/") + 1);
|
|
13652
|
+
var isDoc = (file) => !INSTRUCTIONS.test(file) && (DOC_EXT.test(file) || DOC_NAMES.test(basename(file)));
|
|
13653
|
+
function kindOf(file) {
|
|
13654
|
+
if (file.binary)
|
|
13655
|
+
return "behavior";
|
|
13656
|
+
if (file.generated)
|
|
13657
|
+
return "generated";
|
|
13658
|
+
if (LOCKFILES.has(basename(file.path)))
|
|
13659
|
+
return "lockfile";
|
|
13660
|
+
if (isDoc(file.path))
|
|
13661
|
+
return "doc";
|
|
13662
|
+
if (file.cosmetic)
|
|
13663
|
+
return "cosmetic";
|
|
13664
|
+
return TEST_PATH.test(file.path) ? "test" : "behavior";
|
|
14015
13665
|
}
|
|
14016
|
-
var
|
|
14017
|
-
function
|
|
14018
|
-
|
|
14019
|
-
|
|
14020
|
-
|
|
14021
|
-
|
|
14022
|
-
|
|
14023
|
-
if (
|
|
14024
|
-
|
|
14025
|
-
|
|
14026
|
-
|
|
14027
|
-
|
|
14028
|
-
|
|
14029
|
-
|
|
14030
|
-
|
|
14031
|
-
|
|
14032
|
-
active.splice(index, 1);
|
|
14033
|
-
}
|
|
13666
|
+
var globCache = new Map;
|
|
13667
|
+
function globMatch(pattern, file) {
|
|
13668
|
+
let regex = globCache.get(pattern);
|
|
13669
|
+
if (!regex) {
|
|
13670
|
+
let source = "";
|
|
13671
|
+
for (let index = 0;index < pattern.length; index += 1) {
|
|
13672
|
+
const char = pattern[index];
|
|
13673
|
+
if (char === "*" && pattern[index + 1] === "*") {
|
|
13674
|
+
source += pattern[index + 2] === "/" ? "(?:[^/]*/)*" : ".*";
|
|
13675
|
+
index += pattern[index + 2] === "/" ? 2 : 1;
|
|
13676
|
+
} else if (char === "*")
|
|
13677
|
+
source += "[^/]*";
|
|
13678
|
+
else if (char === "?")
|
|
13679
|
+
source += "[^/]";
|
|
13680
|
+
else
|
|
13681
|
+
source += char.replace(/[.+^${}()|[\]\\]/gu, "\\$&");
|
|
14034
13682
|
}
|
|
13683
|
+
regex = new RegExp(`^${source}$`, "u");
|
|
13684
|
+
globCache.set(pattern, regex);
|
|
14035
13685
|
}
|
|
14036
|
-
return
|
|
13686
|
+
return regex.test(file) || file.startsWith(`${pattern.replace(/\/$/u, "")}/`);
|
|
14037
13687
|
}
|
|
14038
|
-
|
|
14039
|
-
|
|
14040
|
-
|
|
14041
|
-
|
|
14042
|
-
|
|
14043
|
-
|
|
14044
|
-
|
|
14045
|
-
|
|
14046
|
-
import { existsSync as existsSync4, readFileSync as readFileSync4 } from "node:fs";
|
|
14047
|
-
import path17 from "node:path";
|
|
14048
|
-
|
|
14049
|
-
// packages/workit-core/src/core/pr-create.ts
|
|
14050
|
-
import fs14 from "node:fs";
|
|
14051
|
-
import path16 from "node:path";
|
|
14052
|
-
import { spawnSync as spawnSync10 } from "node:child_process";
|
|
14053
|
-
// packages/workit-core/src/forge/pr-body.ts
|
|
14054
|
-
function parseGhIssue(value) {
|
|
14055
|
-
const m = /issues\/(\d+)/.exec(value);
|
|
14056
|
-
return m ? m[1] : String(value).trim().replace(/^#/, "");
|
|
13688
|
+
function isRiskPath(file, extra = []) {
|
|
13689
|
+
if (extra.some((pattern) => globMatch(pattern, file)))
|
|
13690
|
+
return true;
|
|
13691
|
+
if (RISK_FILES.has(file) || RISK_NAMES.has(basename(file)))
|
|
13692
|
+
return true;
|
|
13693
|
+
if (TEST_PATH.test(file) || isDoc(file))
|
|
13694
|
+
return false;
|
|
13695
|
+
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)));
|
|
14057
13696
|
}
|
|
14058
|
-
|
|
14059
|
-
|
|
14060
|
-
function
|
|
14061
|
-
const
|
|
14062
|
-
|
|
14063
|
-
|
|
14064
|
-
|
|
14065
|
-
|
|
14066
|
-
|
|
13697
|
+
var parseRiskPaths = (text) => text.split(`
|
|
13698
|
+
`).map((line) => line.trim()).filter((line) => line && !line.startsWith("#")).map((line) => line.replace(/^\//u, ""));
|
|
13699
|
+
function computeLane(input) {
|
|
13700
|
+
const riskFiles = input.files.filter((file) => isRiskPath(file.path, input.riskPaths)).map((file) => file.path);
|
|
13701
|
+
const risky = new Set(riskFiles);
|
|
13702
|
+
const kinds = input.files.map((file) => ({
|
|
13703
|
+
file,
|
|
13704
|
+
kind: risky.has(file.path) ? "behavior" : kindOf(file)
|
|
13705
|
+
}));
|
|
13706
|
+
const behavior = kinds.filter((entry) => entry.kind === "behavior" || entry.kind === "test" || entry.kind === "lockfile");
|
|
13707
|
+
const lines = behavior.filter((entry) => entry.kind !== "lockfile").reduce((sum, entry) => sum + entry.file.added + entry.file.deleted, 0);
|
|
13708
|
+
const opaque = behavior.filter((entry) => entry.kind === "lockfile" || entry.file.binary);
|
|
13709
|
+
const result = (lane, reasons) => ({
|
|
13710
|
+
lane,
|
|
13711
|
+
reasons,
|
|
13712
|
+
lines,
|
|
13713
|
+
files: behavior.length,
|
|
13714
|
+
riskFiles
|
|
13715
|
+
});
|
|
13716
|
+
const { selfMaxLines, selfMaxFiles, fullMinLines } = LANE_THRESHOLDS;
|
|
13717
|
+
if (input.riskTier === "high")
|
|
13718
|
+
return result("full", ["the task judgment records high risk"]);
|
|
13719
|
+
if (riskFiles.length)
|
|
13720
|
+
return result("full", [
|
|
13721
|
+
`touches a risk path: ${riskFiles.slice(0, 3).join(", ")}${riskFiles.length > 3 ? ", …" : ""}`
|
|
13722
|
+
]);
|
|
13723
|
+
if (!behavior.length)
|
|
13724
|
+
return result("none", [
|
|
13725
|
+
input.files.length ? "only docs, comments, formatting or generated copies changed" : "no changes"
|
|
13726
|
+
]);
|
|
13727
|
+
if (lines >= fullMinLines)
|
|
13728
|
+
return result("full", [`large diff: ${lines} lines (≥ ${fullMinLines})`]);
|
|
13729
|
+
if (lines > selfMaxLines)
|
|
13730
|
+
return result("focused", [`${lines} lines changed (> ${selfMaxLines})`]);
|
|
13731
|
+
if (behavior.length > selfMaxFiles)
|
|
13732
|
+
return result("focused", [`${behavior.length} behavior files changed (> ${selfMaxFiles})`]);
|
|
13733
|
+
if (opaque.length)
|
|
13734
|
+
return result("focused", [
|
|
13735
|
+
`a lockfile or binary changed: ${opaque.slice(0, 3).map((entry) => entry.file.path).join(", ")}${opaque.length > 3 ? ", …" : ""}`
|
|
13736
|
+
]);
|
|
13737
|
+
return result("self", [
|
|
13738
|
+
`small change: ${lines} line${lines === 1 ? "" : "s"} in ${behavior.length} file${behavior.length === 1 ? "" : "s"}, no risk path`
|
|
13739
|
+
]);
|
|
13740
|
+
}
|
|
13741
|
+
function untrackedStat(cwd, file) {
|
|
13742
|
+
try {
|
|
13743
|
+
const bytes = readFileSync4(path14.join(cwd, file));
|
|
13744
|
+
if (bytes.includes(0))
|
|
13745
|
+
return { path: file, added: 0, deleted: 0, binary: true };
|
|
13746
|
+
const text = bytes.toString("utf8");
|
|
13747
|
+
if (hasOpaqueLineBreak(text))
|
|
13748
|
+
return { path: file, added: 0, deleted: 0, binary: true };
|
|
13749
|
+
const lines = text.split(`
|
|
13750
|
+
`).length - (text.endsWith(`
|
|
13751
|
+
`) ? 1 : 0);
|
|
13752
|
+
return { path: file, added: lines, deleted: 0 };
|
|
13753
|
+
} catch {
|
|
13754
|
+
return { path: file, added: 0, deleted: 0, binary: true };
|
|
13755
|
+
}
|
|
13756
|
+
}
|
|
13757
|
+
var REGULAR = new Set(["100644", "100755"]);
|
|
13758
|
+
var ABSENT = /^0+$/u;
|
|
13759
|
+
function cosmeticFiles(cwd, fork, head) {
|
|
13760
|
+
const raw = diffRaw(cwd, fork, head);
|
|
13761
|
+
if (!raw)
|
|
13762
|
+
return null;
|
|
13763
|
+
const candidates = raw.filter((entry) => REGULAR.has(entry.oldMode) && entry.oldMode === entry.newMode && !ABSENT.test(entry.oldBlob) && (head === null || !ABSENT.test(entry.newBlob)));
|
|
13764
|
+
const blobs = readBlobs(cwd, candidates.flatMap((entry) => [entry.oldBlob, entry.newBlob]));
|
|
13765
|
+
if (!blobs)
|
|
13766
|
+
return null;
|
|
13767
|
+
const out = new Set;
|
|
13768
|
+
const opaque = new Set;
|
|
13769
|
+
for (const entry of candidates) {
|
|
13770
|
+
const before = blobs.get(entry.oldBlob);
|
|
13771
|
+
let after = blobs.get(entry.newBlob);
|
|
13772
|
+
if (!after && head === null && ABSENT.test(entry.newBlob))
|
|
14067
13773
|
try {
|
|
14068
|
-
|
|
14069
|
-
|
|
14070
|
-
|
|
14071
|
-
|
|
13774
|
+
after = readFileSync4(path14.join(cwd, entry.path));
|
|
13775
|
+
} catch {
|
|
13776
|
+
after = undefined;
|
|
13777
|
+
}
|
|
13778
|
+
if (before && after && cosmeticChange(entry.path, before, after))
|
|
13779
|
+
out.add(entry.path);
|
|
13780
|
+
else if ([before, after].some((bytes) => bytes && hasOpaqueLineBreak(bytes.toString("utf8"))))
|
|
13781
|
+
opaque.add(entry.path);
|
|
14072
13782
|
}
|
|
14073
|
-
return
|
|
13783
|
+
return { cosmetic: out, opaque };
|
|
14074
13784
|
}
|
|
14075
|
-
|
|
14076
|
-
|
|
14077
|
-
const
|
|
14078
|
-
|
|
14079
|
-
|
|
14080
|
-
|
|
14081
|
-
|
|
13785
|
+
function generatedAt(cwd, fork, paths) {
|
|
13786
|
+
const files = treeFilesNamed(cwd, fork, ".gitattributes");
|
|
13787
|
+
const blobs = files?.size ? readBlobs(cwd, [...files.values()]) : null;
|
|
13788
|
+
if (!files || !blobs)
|
|
13789
|
+
return new Set;
|
|
13790
|
+
const texts = new Map;
|
|
13791
|
+
for (const [file, id] of files) {
|
|
13792
|
+
const bytes = blobs.get(id);
|
|
13793
|
+
if (bytes)
|
|
13794
|
+
texts.set(file, bytes.toString("utf8"));
|
|
13795
|
+
}
|
|
13796
|
+
return generatedByAttributes(texts, paths);
|
|
13797
|
+
}
|
|
13798
|
+
var forkOf = (cwd, base, head) => mergeBase(cwd, base, head ?? "HEAD");
|
|
13799
|
+
function laneFiles(cwd, fork, head, attributesAt = fork) {
|
|
13800
|
+
const stats = diffNumstat(cwd, fork, head);
|
|
13801
|
+
const cosmetic = stats ? cosmeticFiles(cwd, fork, head) : null;
|
|
13802
|
+
if (!stats || !cosmetic)
|
|
14082
13803
|
return null;
|
|
14083
|
-
const
|
|
14084
|
-
|
|
14085
|
-
|
|
14086
|
-
|
|
14087
|
-
|
|
14088
|
-
|
|
14089
|
-
|
|
14090
|
-
|
|
14091
|
-
|
|
13804
|
+
const files = [
|
|
13805
|
+
...stats.map((entry) => ({
|
|
13806
|
+
...entry,
|
|
13807
|
+
...cosmetic.opaque.has(entry.path) ? { binary: true } : {},
|
|
13808
|
+
cosmetic: !entry.binary && cosmetic.cosmetic.has(entry.path)
|
|
13809
|
+
})),
|
|
13810
|
+
...head === null ? untrackedFiles(cwd).map((file) => untrackedStat(cwd, file)) : []
|
|
13811
|
+
];
|
|
13812
|
+
const generated = generatedAt(cwd, attributesAt, files.map((entry) => entry.path));
|
|
13813
|
+
return files.map((entry) => ({
|
|
13814
|
+
...entry,
|
|
13815
|
+
generated: generated.has(entry.path)
|
|
13816
|
+
}));
|
|
13817
|
+
}
|
|
13818
|
+
function driftFiles(cwd, range) {
|
|
13819
|
+
if (range.fromFork === range.toFork)
|
|
13820
|
+
return laneFiles(cwd, range.from, range.to, range.toFork);
|
|
13821
|
+
const drifted = ownChangeDrift(cwd, range);
|
|
13822
|
+
const stats = drifted ? diffNumstat(cwd, range.from, range.to) : null;
|
|
13823
|
+
if (!drifted || !stats)
|
|
13824
|
+
return null;
|
|
13825
|
+
const byPath = new Map(stats.map((entry) => [entry.path, entry]));
|
|
13826
|
+
const generated = generatedAt(cwd, range.toFork, drifted);
|
|
13827
|
+
return drifted.map((file) => ({
|
|
13828
|
+
path: file,
|
|
13829
|
+
added: byPath.get(file)?.added ?? 0,
|
|
13830
|
+
deleted: byPath.get(file)?.deleted ?? 0,
|
|
13831
|
+
...byPath.get(file)?.binary ? { binary: true } : {},
|
|
13832
|
+
cosmetic: false,
|
|
13833
|
+
generated: generated.has(file)
|
|
13834
|
+
}));
|
|
13835
|
+
}
|
|
13836
|
+
function branchLane(cwd, options) {
|
|
13837
|
+
const candidates = options.base ? [`origin/${options.base}`, options.base] : [];
|
|
13838
|
+
const base = candidates.find((ref) => resolveRef(cwd, ref) !== null) ?? defaultBase(cwd) ?? null;
|
|
13839
|
+
const head = options.workingTree ? null : options.head;
|
|
13840
|
+
const fork = base && (options.workingTree || options.head) ? forkOf(cwd, base, head) : null;
|
|
13841
|
+
const files = fork ? laneFiles(cwd, fork, head) : null;
|
|
13842
|
+
const riskTier = options.judgment?.riskTier ?? null;
|
|
13843
|
+
if (!fork || !files) {
|
|
13844
|
+
const high = riskTier === "high";
|
|
13845
|
+
return {
|
|
13846
|
+
lane: high ? "full" : "focused",
|
|
13847
|
+
reasons: [
|
|
13848
|
+
`the diff of ${options.head?.slice(0, 12) ?? "(no head)"} against ${base ?? "(no base)"} cannot be read`,
|
|
13849
|
+
...high ? ["the task judgment records high risk"] : []
|
|
13850
|
+
],
|
|
13851
|
+
lines: 0,
|
|
13852
|
+
files: 0,
|
|
13853
|
+
riskFiles: [],
|
|
13854
|
+
base,
|
|
13855
|
+
head: options.head
|
|
13856
|
+
};
|
|
13857
|
+
}
|
|
13858
|
+
const repoRisk = parseRiskPaths(fileAt(cwd, fork, RISK_PATHS_FILE) ?? "");
|
|
13859
|
+
return {
|
|
13860
|
+
...computeLane({
|
|
13861
|
+
files,
|
|
13862
|
+
riskTier,
|
|
13863
|
+
riskPaths: [...repoRisk, ...options.riskPaths ?? []]
|
|
13864
|
+
}),
|
|
13865
|
+
base,
|
|
13866
|
+
head: options.head
|
|
13867
|
+
};
|
|
13868
|
+
}
|
|
13869
|
+
var laneNeed = (lane) => lane === "none" ? "none" : lane === "self" ? "self" : "non-author";
|
|
14092
13870
|
|
|
14093
|
-
// packages/workit-core/src/
|
|
14094
|
-
var
|
|
13871
|
+
// packages/workit-core/src/core/policy/carry.ts
|
|
13872
|
+
var TEST_ONLY_KINDS = new Set(["doc", "cosmetic", "generated", "test"]);
|
|
13873
|
+
function verdictDrift(cwd, input) {
|
|
13874
|
+
const since = input.from;
|
|
13875
|
+
const stale = (reason, files = null, focused = false) => ({
|
|
13876
|
+
carries: false,
|
|
13877
|
+
since,
|
|
13878
|
+
files,
|
|
13879
|
+
focused,
|
|
13880
|
+
reason
|
|
13881
|
+
});
|
|
13882
|
+
const fromFork = mergeBase(cwd, input.fromBase, input.from);
|
|
13883
|
+
const toFork = mergeBase(cwd, input.toBase, input.to);
|
|
13884
|
+
const files = fromFork && toFork ? driftFiles(cwd, { from: input.from, to: input.to, fromFork, toFork }) : null;
|
|
13885
|
+
if (!fromFork || !toFork || !files)
|
|
13886
|
+
return stale(`the change since ${since.slice(0, 12)} cannot be read`);
|
|
13887
|
+
const riskPaths = [fromFork, toFork].flatMap((fork) => parseRiskPaths(fileAt(cwd, fork, RISK_PATHS_FILE) ?? ""));
|
|
13888
|
+
const lane = computeLane({ files, riskPaths });
|
|
13889
|
+
const paths = files.map((file) => file.path);
|
|
13890
|
+
if (lane.lane === "none")
|
|
13891
|
+
return { carries: true, rule: "lane-none", since, files: paths };
|
|
13892
|
+
if (input.kind === "unit" && lane.lane === "self" && files.every((file) => TEST_ONLY_KINDS.has(kindOf(file))))
|
|
13893
|
+
return { carries: true, rule: "tests-only", since, files: paths };
|
|
13894
|
+
return stale(lane.reasons[0] ?? `lane ${lane.lane}`, paths, lane.lane === "self");
|
|
13895
|
+
}
|
|
14095
13896
|
|
|
14096
|
-
// packages/workit-core/src/
|
|
14097
|
-
|
|
14098
|
-
|
|
14099
|
-
|
|
14100
|
-
|
|
14101
|
-
|
|
14102
|
-
|
|
14103
|
-
|
|
14104
|
-
|
|
14105
|
-
|
|
14106
|
-
|
|
14107
|
-
|
|
14108
|
-
|
|
14109
|
-
|
|
14110
|
-
|
|
14111
|
-
|
|
14112
|
-
|
|
14113
|
-
|
|
14114
|
-
|
|
14115
|
-
|
|
13897
|
+
// packages/workit-core/src/hooks/standing-signal.ts
|
|
13898
|
+
import { createHash as createHash10 } from "node:crypto";
|
|
13899
|
+
import fs12 from "node:fs";
|
|
13900
|
+
import path15 from "node:path";
|
|
13901
|
+
var gitStoreDir = (cwd) => {
|
|
13902
|
+
try {
|
|
13903
|
+
for (let dir = path15.resolve(cwd);; dir = path15.dirname(dir)) {
|
|
13904
|
+
const dotGit = path15.join(dir, ".git");
|
|
13905
|
+
if (fs12.existsSync(dotGit)) {
|
|
13906
|
+
let gitDir = dotGit;
|
|
13907
|
+
if (fs12.statSync(dotGit).isFile()) {
|
|
13908
|
+
const target = /^gitdir:\s*(.+)$/mu.exec(fs12.readFileSync(dotGit, "utf8"))?.[1]?.trim();
|
|
13909
|
+
if (!target)
|
|
13910
|
+
return null;
|
|
13911
|
+
gitDir = path15.resolve(dir, target);
|
|
13912
|
+
const common = path15.join(gitDir, "commondir");
|
|
13913
|
+
if (fs12.existsSync(common))
|
|
13914
|
+
gitDir = path15.resolve(gitDir, fs12.readFileSync(common, "utf8").trim());
|
|
13915
|
+
}
|
|
13916
|
+
return path15.join(gitDir, "workit");
|
|
13917
|
+
}
|
|
13918
|
+
if (path15.dirname(dir) === dir)
|
|
14116
13919
|
return null;
|
|
14117
|
-
|
|
14118
|
-
|
|
14119
|
-
|
|
14120
|
-
} else if (char === "\\" && index + 1 < body.length) {
|
|
14121
|
-
source += body[index + 1].replace(/[.*+?^${}()|[\]\\/]/gu, "\\$&");
|
|
14122
|
-
index += 1;
|
|
14123
|
-
} else
|
|
14124
|
-
source += char.replace(/[.*+?^${}()|[\]\\/]/gu, "\\$&");
|
|
13920
|
+
}
|
|
13921
|
+
} catch {
|
|
13922
|
+
return null;
|
|
14125
13923
|
}
|
|
14126
|
-
|
|
13924
|
+
};
|
|
13925
|
+
var standingRevFile = (store) => path15.join(store, "standing", "rev");
|
|
13926
|
+
var standingSeenFile = (store, session, agentId) => path15.join(store, "standing", "seen", `${createHash10("sha256").update(`${session}\x00${agentId ?? ""}`).digest("hex").slice(0, 32)}.json`);
|
|
13927
|
+
var readStandingRev = (store) => {
|
|
14127
13928
|
try {
|
|
14128
|
-
return
|
|
13929
|
+
return fs12.readFileSync(standingRevFile(store), "utf8").trim() || null;
|
|
14129
13930
|
} catch {
|
|
14130
13931
|
return null;
|
|
14131
13932
|
}
|
|
14132
|
-
}
|
|
14133
|
-
|
|
14134
|
-
|
|
14135
|
-
|
|
14136
|
-
`)) {
|
|
14137
|
-
const line = raw.trim();
|
|
14138
|
-
if (!line || line.startsWith("#") || line.startsWith("[attr]"))
|
|
14139
|
-
continue;
|
|
14140
|
-
const [pattern, ...attributes] = line.split(/\s+/u);
|
|
14141
|
-
let generated = null;
|
|
14142
|
-
for (const attribute of attributes) {
|
|
14143
|
-
if (attribute === "linguist-generated" || attribute === "linguist-generated=true")
|
|
14144
|
-
generated = true;
|
|
14145
|
-
else if (attribute === "-linguist-generated" || attribute === "!linguist-generated" || attribute.startsWith("linguist-generated="))
|
|
14146
|
-
generated = false;
|
|
14147
|
-
}
|
|
14148
|
-
if (generated === null)
|
|
14149
|
-
continue;
|
|
14150
|
-
const regex = patternRegex(dir, pattern);
|
|
14151
|
-
if (regex)
|
|
14152
|
-
out.push({ regex, generated });
|
|
14153
|
-
}
|
|
14154
|
-
return out;
|
|
14155
|
-
}
|
|
14156
|
-
function generatedByAttributes(files, paths) {
|
|
14157
|
-
const rules = [...files].map(([file, text]) => {
|
|
14158
|
-
const dir = file.includes("/") ? file.slice(0, file.lastIndexOf("/")) : "";
|
|
13933
|
+
};
|
|
13934
|
+
var readStandingSeen = (file) => {
|
|
13935
|
+
try {
|
|
13936
|
+
const value = JSON.parse(fs12.readFileSync(file, "utf8"));
|
|
14159
13937
|
return {
|
|
14160
|
-
|
|
14161
|
-
|
|
13938
|
+
rev: typeof value.rev === "string" ? value.rev : null,
|
|
13939
|
+
ids: Array.isArray(value.ids) ? value.ids.filter((id) => typeof id === "string") : []
|
|
14162
13940
|
};
|
|
14163
|
-
}
|
|
14164
|
-
|
|
14165
|
-
for (const file of paths) {
|
|
14166
|
-
const last = rules.findLast((rule) => rule.regex.test(file));
|
|
14167
|
-
if (last?.generated)
|
|
14168
|
-
out.add(file);
|
|
13941
|
+
} catch {
|
|
13942
|
+
return { rev: null, ids: [] };
|
|
14169
13943
|
}
|
|
14170
|
-
|
|
14171
|
-
|
|
13944
|
+
};
|
|
13945
|
+
var writeStandingSeen = (file, seen) => {
|
|
13946
|
+
try {
|
|
13947
|
+
fs12.mkdirSync(path15.dirname(file), { recursive: true });
|
|
13948
|
+
fs12.writeFileSync(file, JSON.stringify(seen));
|
|
13949
|
+
} catch {}
|
|
13950
|
+
};
|
|
14172
13951
|
|
|
14173
|
-
// packages/workit-core/src/
|
|
14174
|
-
var
|
|
14175
|
-
var
|
|
14176
|
-
var
|
|
14177
|
-
|
|
14178
|
-
|
|
14179
|
-
|
|
14180
|
-
|
|
14181
|
-
|
|
14182
|
-
|
|
14183
|
-
|
|
14184
|
-
ext,
|
|
14185
|
-
{ line: [], block: SLASH_BLOCK, quotes: `"'`, cssUrl: true }
|
|
14186
|
-
]),
|
|
14187
|
-
...["py", "pyi"].map((ext) => [
|
|
14188
|
-
ext,
|
|
14189
|
-
{ line: ["#"], block: [], quotes: `"'`, triple: true, indented: true, fstring: true }
|
|
14190
|
-
]),
|
|
14191
|
-
["toml", { line: ["#"], block: [], quotes: `"`, literal: "'", triple: true }],
|
|
14192
|
-
...["html", "htm", "xml", "svg", "xhtml"].map((ext) => [
|
|
14193
|
-
ext,
|
|
14194
|
-
{ line: [], block: [["<!--", "-->"]], quotes: "", indented: true }
|
|
14195
|
-
])
|
|
13952
|
+
// packages/workit-core/src/ledger.ts
|
|
13953
|
+
var LEDGER_VERSION = 1;
|
|
13954
|
+
var MAX_LINE_BYTES = 4096;
|
|
13955
|
+
var PASSING = new Set(["verified", "tests-verified", "type-check-only"]);
|
|
13956
|
+
var FAILING = new Set(["failed", "blocked"]);
|
|
13957
|
+
var STRONG_RESULTS = new Set(["verified", "tests-verified"]);
|
|
13958
|
+
var PR_ROW_TYPES = new Set([
|
|
13959
|
+
"pr.created",
|
|
13960
|
+
"pr.status",
|
|
13961
|
+
"pr.merged",
|
|
13962
|
+
"pr.linked"
|
|
14196
13963
|
]);
|
|
14197
|
-
var
|
|
14198
|
-
|
|
14199
|
-
|
|
14200
|
-
|
|
14201
|
-
|
|
13964
|
+
var err = (code, error, unblock) => ({
|
|
13965
|
+
ok: false,
|
|
13966
|
+
code,
|
|
13967
|
+
error,
|
|
13968
|
+
...unblock ? { unblock } : {}
|
|
13969
|
+
});
|
|
13970
|
+
var gitRun2 = (cwd, args, timeoutMs = GIT_TIMEOUTS.local) => {
|
|
13971
|
+
const run = spawnSync9("git", args, {
|
|
13972
|
+
cwd,
|
|
13973
|
+
encoding: "utf8",
|
|
13974
|
+
timeout: timeoutMs,
|
|
13975
|
+
killSignal: "SIGKILL",
|
|
13976
|
+
windowsHide: true,
|
|
13977
|
+
maxBuffer: 256 * 1024 * 1024,
|
|
13978
|
+
env: {
|
|
13979
|
+
...process.env,
|
|
13980
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
13981
|
+
GIT_OPTIONAL_LOCKS: "0",
|
|
13982
|
+
LC_ALL: "C",
|
|
13983
|
+
LANGUAGE: "C"
|
|
13984
|
+
}
|
|
13985
|
+
});
|
|
13986
|
+
return {
|
|
13987
|
+
ok: run.status === 0,
|
|
13988
|
+
stdout: run.stdout ?? "",
|
|
13989
|
+
stderr: run.error ? String(run.error.message) : run.stderr ?? ""
|
|
13990
|
+
};
|
|
14202
13991
|
};
|
|
14203
|
-
var
|
|
14204
|
-
|
|
14205
|
-
|
|
14206
|
-
|
|
14207
|
-
|
|
14208
|
-
var
|
|
14209
|
-
|
|
14210
|
-
|
|
14211
|
-
|
|
14212
|
-
"
|
|
14213
|
-
"instanceof",
|
|
14214
|
-
"case",
|
|
14215
|
-
"do",
|
|
14216
|
-
"else",
|
|
14217
|
-
"in",
|
|
14218
|
-
"of",
|
|
14219
|
-
"new",
|
|
14220
|
-
"delete",
|
|
14221
|
-
"void",
|
|
14222
|
-
"throw",
|
|
14223
|
-
"yield",
|
|
14224
|
-
"await"
|
|
14225
|
-
]);
|
|
14226
|
-
function substanceOf(file, text) {
|
|
14227
|
-
const syntax = syntaxOf(file);
|
|
14228
|
-
if (!syntax)
|
|
14229
|
-
return plainSubstance(text);
|
|
14230
|
-
const markup = syntax.block.some(([open]) => open === "<!--");
|
|
14231
|
-
if (markup && (/<(?:script|style)\b|<!\[CDATA\[|<\?/iu.test(text) || /[^>\s][ \t]*<!--/u.test(text)))
|
|
14232
|
-
return plainSubstance(text);
|
|
14233
|
-
return lex(syntax, text);
|
|
13992
|
+
var git4 = (cwd, args) => {
|
|
13993
|
+
const run = gitRun2(cwd, args);
|
|
13994
|
+
const value = run.ok ? run.stdout.trim() : "";
|
|
13995
|
+
return value ? value : null;
|
|
13996
|
+
};
|
|
13997
|
+
var safeRef2 = (value) => value.length > 0 && !value.startsWith("-");
|
|
13998
|
+
function resolveCommit(cwd, ref) {
|
|
13999
|
+
if (!safeRef2(ref))
|
|
14000
|
+
return null;
|
|
14001
|
+
return git4(cwd, ["rev-parse", "--verify", "-q", `${ref}^{commit}`]);
|
|
14234
14002
|
}
|
|
14235
|
-
function
|
|
14236
|
-
|
|
14237
|
-
|
|
14238
|
-
`);
|
|
14003
|
+
function branchHead(cwd, branch) {
|
|
14004
|
+
if (!safeRef2(branch))
|
|
14005
|
+
return null;
|
|
14006
|
+
return resolveCommit(cwd, `refs/heads/${branch}`) ?? resolveCommit(cwd, `refs/remotes/origin/${branch}`);
|
|
14239
14007
|
}
|
|
14240
|
-
function
|
|
14241
|
-
|
|
14242
|
-
|
|
14243
|
-
|
|
14244
|
-
|
|
14245
|
-
|
|
14246
|
-
|
|
14247
|
-
|
|
14248
|
-
|
|
14249
|
-
|
|
14008
|
+
function diffHash(cwd, base, head) {
|
|
14009
|
+
if (!safeRef2(base) || !safeRef2(head))
|
|
14010
|
+
return null;
|
|
14011
|
+
const diff = gitRun2(cwd, [
|
|
14012
|
+
"diff",
|
|
14013
|
+
"-U3",
|
|
14014
|
+
"--diff-algorithm=myers",
|
|
14015
|
+
"--indent-heuristic",
|
|
14016
|
+
"--no-color",
|
|
14017
|
+
"--no-ext-diff",
|
|
14018
|
+
"--no-textconv",
|
|
14019
|
+
"--no-renames",
|
|
14020
|
+
"--full-index",
|
|
14021
|
+
"--binary",
|
|
14022
|
+
"--src-prefix=a/",
|
|
14023
|
+
"--dst-prefix=b/",
|
|
14024
|
+
`${base}...${head}`,
|
|
14025
|
+
"--"
|
|
14026
|
+
], GIT_TIMEOUTS.worktree);
|
|
14027
|
+
if (!diff.ok || !diff.stdout)
|
|
14028
|
+
return null;
|
|
14029
|
+
const hash = createHash11("sha256");
|
|
14030
|
+
for (const line of diff.stdout.split(`
|
|
14031
|
+
`)) {
|
|
14032
|
+
if (line.startsWith("index "))
|
|
14033
|
+
continue;
|
|
14034
|
+
hash.update(line.startsWith("@@ ") ? "@@" : line);
|
|
14035
|
+
hash.update(`
|
|
14250
14036
|
`);
|
|
14251
|
-
out = end < 0 ? text : text.slice(0, end);
|
|
14252
|
-
kept = out.length;
|
|
14253
|
-
index = end < 0 ? text.length : end;
|
|
14254
14037
|
}
|
|
14255
|
-
|
|
14256
|
-
|
|
14257
|
-
|
|
14258
|
-
|
|
14259
|
-
|
|
14260
|
-
|
|
14261
|
-
|
|
14262
|
-
|
|
14263
|
-
|
|
14264
|
-
const
|
|
14265
|
-
|
|
14266
|
-
|
|
14267
|
-
|
|
14268
|
-
|
|
14269
|
-
|
|
14270
|
-
|
|
14271
|
-
|
|
14272
|
-
|
|
14273
|
-
|
|
14038
|
+
return `sha256:${hash.digest("hex")}`;
|
|
14039
|
+
}
|
|
14040
|
+
function storeRoot(cwd) {
|
|
14041
|
+
const location = resolveStore(path16.resolve(cwd));
|
|
14042
|
+
if (location instanceof Error)
|
|
14043
|
+
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");
|
|
14044
|
+
return { ok: true, value: { root: location.dir, shared: location.shared } };
|
|
14045
|
+
}
|
|
14046
|
+
function ledgerPath(cwd) {
|
|
14047
|
+
const root = storeRoot(cwd);
|
|
14048
|
+
return root.ok ? { ok: true, value: path16.join(root.value.root, "ledger", "ledger.jsonl") } : root;
|
|
14049
|
+
}
|
|
14050
|
+
var newId2 = () => `${Date.now().toString(36)}-${randomBytes3(6).toString("hex")}`;
|
|
14051
|
+
var NETWORK_FS_TYPES = new Set([
|
|
14052
|
+
26985,
|
|
14053
|
+
20859,
|
|
14054
|
+
4266872130,
|
|
14055
|
+
4283649346,
|
|
14056
|
+
1702057286,
|
|
14057
|
+
1397113167,
|
|
14058
|
+
12805120,
|
|
14059
|
+
16914839,
|
|
14060
|
+
1196443219,
|
|
14061
|
+
198183888
|
|
14062
|
+
]);
|
|
14063
|
+
var isNetworkFsType = (type) => NETWORK_FS_TYPES.has(type >>> 0);
|
|
14064
|
+
var onNetworkFs = (dir) => {
|
|
14065
|
+
if (process.platform !== "linux")
|
|
14066
|
+
return false;
|
|
14067
|
+
try {
|
|
14068
|
+
return isNetworkFsType(fs13.statfsSync(dir).type);
|
|
14069
|
+
} catch {
|
|
14070
|
+
return false;
|
|
14071
|
+
}
|
|
14072
|
+
};
|
|
14073
|
+
var LOCK_WAIT_MS = 5000;
|
|
14074
|
+
var LOCK_STALE_MS = 30000;
|
|
14075
|
+
var LOCK_OWNER = "owner";
|
|
14076
|
+
var lockToken = (lock) => {
|
|
14077
|
+
try {
|
|
14078
|
+
return fs13.readFileSync(path16.join(lock, LOCK_OWNER), "utf8");
|
|
14079
|
+
} catch {
|
|
14080
|
+
return null;
|
|
14081
|
+
}
|
|
14082
|
+
};
|
|
14083
|
+
var lockAgeMs = (lock) => {
|
|
14084
|
+
for (const target of [path16.join(lock, LOCK_OWNER), lock]) {
|
|
14085
|
+
try {
|
|
14086
|
+
return Date.now() - fs13.statSync(target).mtimeMs;
|
|
14087
|
+
} catch {}
|
|
14088
|
+
}
|
|
14089
|
+
return null;
|
|
14090
|
+
};
|
|
14091
|
+
var ledgerLock = {
|
|
14092
|
+
acquire(lock) {
|
|
14093
|
+
try {
|
|
14094
|
+
fs13.mkdirSync(lock);
|
|
14095
|
+
} catch (error) {
|
|
14096
|
+
if (error.code === "EEXIST")
|
|
14097
|
+
return null;
|
|
14098
|
+
throw error;
|
|
14274
14099
|
}
|
|
14275
|
-
|
|
14276
|
-
|
|
14277
|
-
|
|
14278
|
-
|
|
14279
|
-
|
|
14100
|
+
const token = `${process.pid}-${randomBytes3(8).toString("hex")}`;
|
|
14101
|
+
fs13.writeFileSync(path16.join(lock, LOCK_OWNER), token);
|
|
14102
|
+
return token;
|
|
14103
|
+
},
|
|
14104
|
+
release(lock, token) {
|
|
14105
|
+
if (lockToken(lock) !== token)
|
|
14106
|
+
return false;
|
|
14107
|
+
const tomb = `${lock}.release-${randomBytes3(6).toString("hex")}`;
|
|
14108
|
+
try {
|
|
14109
|
+
fs13.renameSync(lock, tomb);
|
|
14110
|
+
} catch {
|
|
14111
|
+
return false;
|
|
14280
14112
|
}
|
|
14281
|
-
|
|
14282
|
-
|
|
14283
|
-
|
|
14284
|
-
|
|
14285
|
-
|
|
14286
|
-
if (end === 0 || REGEX_AFTER.includes(out[end - 1]))
|
|
14287
|
-
return true;
|
|
14288
|
-
let start = end;
|
|
14289
|
-
while (start > 0 && WORD.test(out[start - 1]))
|
|
14290
|
-
start -= 1;
|
|
14291
|
-
return REGEX_KEYWORDS.has(out.slice(start, end));
|
|
14292
|
-
};
|
|
14293
|
-
const scanString = (quote, escapes, multiline) => {
|
|
14294
|
-
const prefix = text.slice(Math.max(0, index - 2), index).match(/[a-z]*$/iu)?.[0] ?? "";
|
|
14295
|
-
const fstring = syntax.fstring === true && /f/iu.test(prefix);
|
|
14296
|
-
let end = index + quote.length;
|
|
14297
|
-
while (end < text.length) {
|
|
14298
|
-
const char = text[end];
|
|
14299
|
-
if (escapes && char === "\\") {
|
|
14300
|
-
end += 2;
|
|
14301
|
-
continue;
|
|
14302
|
-
}
|
|
14303
|
-
if (text.startsWith(quote, end)) {
|
|
14304
|
-
if (fstring && unbalanced(text.slice(index + quote.length, end)))
|
|
14305
|
-
return false;
|
|
14306
|
-
emitVerbatim(text.slice(index, end + quote.length));
|
|
14307
|
-
index = end + quote.length;
|
|
14308
|
-
return true;
|
|
14309
|
-
}
|
|
14310
|
-
if (char === `
|
|
14311
|
-
` && !multiline)
|
|
14312
|
-
return false;
|
|
14313
|
-
end += 1;
|
|
14113
|
+
if (lockToken(tomb) !== token) {
|
|
14114
|
+
try {
|
|
14115
|
+
fs13.renameSync(tomb, lock);
|
|
14116
|
+
} catch {}
|
|
14117
|
+
return false;
|
|
14314
14118
|
}
|
|
14315
|
-
|
|
14316
|
-
|
|
14317
|
-
|
|
14318
|
-
|
|
14319
|
-
|
|
14320
|
-
|
|
14321
|
-
|
|
14322
|
-
|
|
14323
|
-
|
|
14324
|
-
|
|
14325
|
-
emitVerbatim(char);
|
|
14326
|
-
frames.pop();
|
|
14327
|
-
index += 1;
|
|
14328
|
-
} else if (text.startsWith("${", index)) {
|
|
14329
|
-
emitVerbatim("${");
|
|
14330
|
-
frames.push({ kind: "code", braces: 0 });
|
|
14331
|
-
index += 2;
|
|
14332
|
-
} else {
|
|
14333
|
-
out += char;
|
|
14334
|
-
kept = out.length;
|
|
14335
|
-
index += 1;
|
|
14336
|
-
}
|
|
14337
|
-
continue;
|
|
14119
|
+
fs13.rmSync(tomb, { recursive: true, force: true });
|
|
14120
|
+
return true;
|
|
14121
|
+
},
|
|
14122
|
+
token: lockToken,
|
|
14123
|
+
reap(lock, observed) {
|
|
14124
|
+
const tomb = `${lock}.reap-${randomBytes3(6).toString("hex")}`;
|
|
14125
|
+
try {
|
|
14126
|
+
fs13.renameSync(lock, tomb);
|
|
14127
|
+
} catch {
|
|
14128
|
+
return false;
|
|
14338
14129
|
}
|
|
14339
|
-
if (
|
|
14340
|
-
|
|
14341
|
-
|
|
14342
|
-
index += 1;
|
|
14343
|
-
continue;
|
|
14130
|
+
if (lockToken(tomb) === observed) {
|
|
14131
|
+
fs13.rmSync(tomb, { recursive: true, force: true });
|
|
14132
|
+
return true;
|
|
14344
14133
|
}
|
|
14345
|
-
|
|
14346
|
-
|
|
14347
|
-
|
|
14348
|
-
|
|
14349
|
-
out += char;
|
|
14350
|
-
index += 1;
|
|
14351
|
-
continue;
|
|
14134
|
+
try {
|
|
14135
|
+
fs13.renameSync(tomb, lock);
|
|
14136
|
+
} catch {
|
|
14137
|
+
fs13.rmSync(tomb, { recursive: true, force: true });
|
|
14352
14138
|
}
|
|
14353
|
-
|
|
14354
|
-
|
|
14355
|
-
|
|
14356
|
-
|
|
14357
|
-
|
|
14358
|
-
|
|
14359
|
-
|
|
14360
|
-
|
|
14361
|
-
|
|
14362
|
-
|
|
14139
|
+
return false;
|
|
14140
|
+
}
|
|
14141
|
+
};
|
|
14142
|
+
function withLedgerLock(file, fn, waitMs = hostAwareLockTimeout(LOCK_WAIT_MS)) {
|
|
14143
|
+
const lock = `${file}.lock`;
|
|
14144
|
+
const deadline = Date.now() + waitMs;
|
|
14145
|
+
let token = null;
|
|
14146
|
+
for (;; ) {
|
|
14147
|
+
token = ledgerLock.acquire(lock);
|
|
14148
|
+
if (token)
|
|
14149
|
+
break;
|
|
14150
|
+
const observed = lockToken(lock);
|
|
14151
|
+
const age = lockAgeMs(lock);
|
|
14152
|
+
if (age !== null && age > LOCK_STALE_MS && ledgerLock.reap(lock, observed))
|
|
14363
14153
|
continue;
|
|
14364
|
-
|
|
14365
|
-
|
|
14366
|
-
|
|
14367
|
-
|
|
14368
|
-
|
|
14154
|
+
if (Date.now() >= deadline)
|
|
14155
|
+
return null;
|
|
14156
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 25);
|
|
14157
|
+
}
|
|
14158
|
+
try {
|
|
14159
|
+
return fn();
|
|
14160
|
+
} finally {
|
|
14161
|
+
ledgerLock.release(lock, token);
|
|
14162
|
+
}
|
|
14163
|
+
}
|
|
14164
|
+
var sha2562 = (text) => createHash11("sha256").update(text).digest("hex");
|
|
14165
|
+
var chainHash = (prevHash, body) => sha2562(`${prevHash ?? ""}
|
|
14166
|
+
${body}`);
|
|
14167
|
+
var TAIL_BYTES = 64 * 1024;
|
|
14168
|
+
var lastLine = (fd, size) => {
|
|
14169
|
+
for (let span = Math.min(size, TAIL_BYTES);span > 0; span = Math.min(size, span * 2)) {
|
|
14170
|
+
const buffer = Buffer.alloc(span);
|
|
14171
|
+
fs13.readSync(fd, buffer, 0, span, size - span);
|
|
14172
|
+
const end = buffer.lastIndexOf(10);
|
|
14173
|
+
if (end < 0) {
|
|
14174
|
+
if (span === size)
|
|
14369
14175
|
return null;
|
|
14370
|
-
const comment = text.slice(index, end + block[1].length);
|
|
14371
|
-
if (DIRECTIVE.test(comment.slice(block[0].length, -block[1].length)))
|
|
14372
|
-
emitVerbatim(comment);
|
|
14373
|
-
else
|
|
14374
|
-
pendingSpace = true;
|
|
14375
|
-
if (comment.includes(`
|
|
14376
|
-
`))
|
|
14377
|
-
newline();
|
|
14378
|
-
index = end + block[1].length;
|
|
14379
14176
|
continue;
|
|
14380
14177
|
}
|
|
14381
|
-
|
|
14382
|
-
|
|
14383
|
-
|
|
14384
|
-
|
|
14178
|
+
let stop = end;
|
|
14179
|
+
let text = "";
|
|
14180
|
+
while (stop >= 0) {
|
|
14181
|
+
const start = buffer.lastIndexOf(10, stop - 1);
|
|
14182
|
+
text = buffer.toString("utf8", start + 1, stop).trim();
|
|
14183
|
+
if (text) {
|
|
14184
|
+
if (start >= 0 || span === size)
|
|
14185
|
+
return text;
|
|
14186
|
+
break;
|
|
14187
|
+
}
|
|
14188
|
+
if (start < 0) {
|
|
14189
|
+
if (span === size)
|
|
14190
|
+
return null;
|
|
14191
|
+
break;
|
|
14192
|
+
}
|
|
14193
|
+
stop = start;
|
|
14385
14194
|
}
|
|
14386
|
-
if (
|
|
14387
|
-
|
|
14388
|
-
|
|
14389
|
-
|
|
14195
|
+
if (span === size)
|
|
14196
|
+
return text || null;
|
|
14197
|
+
}
|
|
14198
|
+
return null;
|
|
14199
|
+
};
|
|
14200
|
+
var tighten = (fd) => {
|
|
14201
|
+
try {
|
|
14202
|
+
if ((fs13.fstatSync(fd).mode & 63) !== 0)
|
|
14203
|
+
fs13.fchmodSync(fd, 384);
|
|
14204
|
+
} catch {}
|
|
14205
|
+
};
|
|
14206
|
+
function appendLine(file, build, options) {
|
|
14207
|
+
const write = () => {
|
|
14208
|
+
const fd = fs13.openSync(file, "a+", 384);
|
|
14209
|
+
try {
|
|
14210
|
+
if (process.platform !== "win32")
|
|
14211
|
+
tighten(fd);
|
|
14212
|
+
const size = fs13.fstatSync(fd).size;
|
|
14213
|
+
let fence = "";
|
|
14214
|
+
if (size > 0) {
|
|
14215
|
+
const last = Buffer.alloc(1);
|
|
14216
|
+
fs13.readSync(fd, last, 0, 1, size - 1);
|
|
14217
|
+
if (last[0] !== 10)
|
|
14218
|
+
fence = `
|
|
14219
|
+
`;
|
|
14220
|
+
}
|
|
14221
|
+
const line = build(lastLine(fd, size));
|
|
14222
|
+
if (!line.ok)
|
|
14223
|
+
return line;
|
|
14224
|
+
const bytes = Buffer.from(fence + line.value);
|
|
14225
|
+
const written = fs13.writeSync(fd, bytes);
|
|
14226
|
+
if (written !== bytes.length)
|
|
14227
|
+
return err("unavailable", `short write to ${file}: ${written} of ${bytes.length} bytes; the row is torn and will be skipped`);
|
|
14228
|
+
return { ok: true, value: undefined };
|
|
14229
|
+
} finally {
|
|
14230
|
+
fs13.closeSync(fd);
|
|
14390
14231
|
}
|
|
14391
|
-
|
|
14392
|
-
|
|
14393
|
-
|
|
14232
|
+
};
|
|
14233
|
+
try {
|
|
14234
|
+
fs13.mkdirSync(path16.dirname(file), { recursive: true });
|
|
14235
|
+
if (!options.forceLock && !onNetworkFs(path16.dirname(file)))
|
|
14236
|
+
return write();
|
|
14237
|
+
const locked = withLedgerLock(file, write);
|
|
14238
|
+
return locked ?? err("busy", `ledger lock ${file}.lock is held`, "retry; remove the .lock directory if no workit process is running");
|
|
14239
|
+
} catch (error) {
|
|
14240
|
+
return err("unavailable", `cannot append to ${file}: ${error.message}`);
|
|
14241
|
+
}
|
|
14242
|
+
}
|
|
14243
|
+
var sealed = (row, id, at, previous) => {
|
|
14244
|
+
const { prevHash: _prev, rowHash: _row, ...fields } = row;
|
|
14245
|
+
const prevHash = previous === null ? null : sha2562(previous);
|
|
14246
|
+
const body = { v: LEDGER_VERSION, id, at, ...fields, prevHash };
|
|
14247
|
+
const full = { ...body, rowHash: chainHash(prevHash, JSON.stringify(body)) };
|
|
14248
|
+
return { full, line: `${JSON.stringify(full)}
|
|
14249
|
+
` };
|
|
14250
|
+
};
|
|
14251
|
+
function appendRaw(cwd, row, options) {
|
|
14252
|
+
const file = ledgerPath(cwd);
|
|
14253
|
+
if (!file.ok)
|
|
14254
|
+
return file;
|
|
14255
|
+
const id = newId2();
|
|
14256
|
+
const at = (options.now ?? new Date).toISOString();
|
|
14257
|
+
let full = null;
|
|
14258
|
+
const written = appendLine(file.value, (previous) => {
|
|
14259
|
+
const next = sealed(row, id, at, previous);
|
|
14260
|
+
const bytes = Buffer.byteLength(next.line);
|
|
14261
|
+
if (bytes > MAX_LINE_BYTES)
|
|
14262
|
+
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>`);
|
|
14263
|
+
full = next.full;
|
|
14264
|
+
return { ok: true, value: next.line };
|
|
14265
|
+
}, options);
|
|
14266
|
+
return written.ok ? { ok: true, value: full } : written;
|
|
14267
|
+
}
|
|
14268
|
+
function observedRowBytes(row) {
|
|
14269
|
+
const sample = sealed({ ...row, observer: "workit_cli" }, newId2(), new Date().toISOString(), "");
|
|
14270
|
+
return Buffer.byteLength(sample.line);
|
|
14271
|
+
}
|
|
14272
|
+
function appendObserved(cwd, row, options = {}) {
|
|
14273
|
+
return appendRaw(cwd, { ...row, observer: "workit_cli" }, options);
|
|
14274
|
+
}
|
|
14275
|
+
function appendHookObserved(cwd, row) {
|
|
14276
|
+
return appendRaw(cwd, { ...row, observer: "host_hook" }, {});
|
|
14277
|
+
}
|
|
14278
|
+
var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
14279
|
+
var str2 = (value) => typeof value === "string" ? value : null;
|
|
14280
|
+
var normalizeActor = (value) => {
|
|
14281
|
+
const actor = isRecord3(value) ? value : {};
|
|
14282
|
+
return {
|
|
14283
|
+
host: str2(actor.host) ?? "unknown",
|
|
14284
|
+
session: str2(actor.session),
|
|
14285
|
+
agentId: str2(actor.agentId)
|
|
14286
|
+
};
|
|
14287
|
+
};
|
|
14288
|
+
function supersedeAllowed(by, target, onBranch = (row) => (by.branch ?? null) === (row.branch ?? null)) {
|
|
14289
|
+
if (by.type !== target.type)
|
|
14290
|
+
return `a ${by.type} row cannot supersede a ${target.type} row`;
|
|
14291
|
+
if (!by.actor.session || by.actor.session !== target.actor.session)
|
|
14292
|
+
return "only the session that wrote a row can supersede it";
|
|
14293
|
+
if (!onBranch(target))
|
|
14294
|
+
return `the row is on another branch (${String(target.branch ?? "none")}); a row supersedes only one on its own branch`;
|
|
14295
|
+
if (by.type === "verdict" && (by.kind ?? "review") !== (target.kind ?? "review"))
|
|
14296
|
+
return `a ${String(by.kind ?? "review")} verdict cannot supersede a ${String(target.kind ?? "review")} verdict; record it with --kind ${String(target.kind ?? "review")}`;
|
|
14297
|
+
if (by.type === "verdict" && by.self === true && target.self !== true)
|
|
14298
|
+
return "a self verdict cannot replace an independent verdict";
|
|
14299
|
+
return null;
|
|
14300
|
+
}
|
|
14301
|
+
function readLedger(cwd, options = {}) {
|
|
14302
|
+
const file = ledgerPath(cwd);
|
|
14303
|
+
if (!file.ok)
|
|
14304
|
+
return file;
|
|
14305
|
+
let text = "";
|
|
14306
|
+
let stat = null;
|
|
14307
|
+
try {
|
|
14308
|
+
stat = fs13.statSync(file.value);
|
|
14309
|
+
text = fs13.readFileSync(file.value, "utf8");
|
|
14310
|
+
} catch (error) {
|
|
14311
|
+
if (error.code === "ENOENT")
|
|
14312
|
+
return {
|
|
14313
|
+
ok: true,
|
|
14314
|
+
value: {
|
|
14315
|
+
path: file.value,
|
|
14316
|
+
rows: [],
|
|
14317
|
+
skipped: 0,
|
|
14318
|
+
integrity: options.integrity ? { chained: 0, legacy: 0, unverified: [] } : null
|
|
14319
|
+
}
|
|
14320
|
+
};
|
|
14321
|
+
return err("unavailable", `cannot read ${file.value}: ${error.message}`);
|
|
14322
|
+
}
|
|
14323
|
+
const rows = [];
|
|
14324
|
+
let skipped = 0;
|
|
14325
|
+
const integrity = { chained: 0, legacy: 0, unverified: [] };
|
|
14326
|
+
const earlier = new Set([null]);
|
|
14327
|
+
let chainStarted = false;
|
|
14328
|
+
const ids = new Set;
|
|
14329
|
+
for (const raw of text.split(`
|
|
14330
|
+
`)) {
|
|
14331
|
+
const trimmed = raw.trim();
|
|
14332
|
+
if (!trimmed)
|
|
14394
14333
|
continue;
|
|
14395
|
-
|
|
14396
|
-
if (
|
|
14397
|
-
|
|
14398
|
-
|
|
14334
|
+
const linked = (prevHash) => earlier.has(prevHash);
|
|
14335
|
+
if (options.integrity)
|
|
14336
|
+
earlier.add(sha2562(trimmed));
|
|
14337
|
+
let parsed;
|
|
14338
|
+
try {
|
|
14339
|
+
parsed = JSON.parse(trimmed);
|
|
14340
|
+
} catch {
|
|
14341
|
+
skipped += 1;
|
|
14399
14342
|
continue;
|
|
14400
14343
|
}
|
|
14401
|
-
if (
|
|
14402
|
-
|
|
14403
|
-
frames.push({ kind: "template" });
|
|
14404
|
-
index += 1;
|
|
14344
|
+
if (!isRecord3(parsed) || !str2(parsed.id) || !str2(parsed.type) || !str2(parsed.at)) {
|
|
14345
|
+
skipped += 1;
|
|
14405
14346
|
continue;
|
|
14406
14347
|
}
|
|
14407
|
-
|
|
14408
|
-
|
|
14409
|
-
if (
|
|
14410
|
-
|
|
14411
|
-
|
|
14412
|
-
|
|
14413
|
-
|
|
14414
|
-
|
|
14415
|
-
|
|
14416
|
-
|
|
14417
|
-
|
|
14418
|
-
|
|
14419
|
-
|
|
14420
|
-
|
|
14421
|
-
|
|
14422
|
-
|
|
14423
|
-
|
|
14424
|
-
|
|
14425
|
-
|
|
14426
|
-
|
|
14427
|
-
|
|
14428
|
-
|
|
14429
|
-
|
|
14430
|
-
|
|
14431
|
-
|
|
14432
|
-
|
|
14433
|
-
|
|
14348
|
+
let rowIntegrity = options.integrity ? "verified" : undefined;
|
|
14349
|
+
let reason = null;
|
|
14350
|
+
if (!options.integrity) {} else if (typeof parsed.rowHash === "string") {
|
|
14351
|
+
chainStarted = true;
|
|
14352
|
+
const { rowHash, ...body } = parsed;
|
|
14353
|
+
const prevHash = str2(parsed.prevHash);
|
|
14354
|
+
if (chainHash(prevHash, JSON.stringify(body)) !== rowHash)
|
|
14355
|
+
reason = "hash_mismatch";
|
|
14356
|
+
else if (!linked(prevHash))
|
|
14357
|
+
reason = "broken_link";
|
|
14358
|
+
} else if (chainStarted)
|
|
14359
|
+
reason = "unsigned";
|
|
14360
|
+
else
|
|
14361
|
+
rowIntegrity = "legacy";
|
|
14362
|
+
if (options.integrity && !reason && ids.has(parsed.id))
|
|
14363
|
+
reason = "duplicate_id";
|
|
14364
|
+
if (options.integrity)
|
|
14365
|
+
ids.add(parsed.id);
|
|
14366
|
+
if (reason)
|
|
14367
|
+
rowIntegrity = "unverified";
|
|
14368
|
+
const pr = typeof parsed.pr === "number" && Number.isInteger(parsed.pr) ? parsed.pr : undefined;
|
|
14369
|
+
const row = {
|
|
14370
|
+
...parsed,
|
|
14371
|
+
v: typeof parsed.v === "number" ? parsed.v : LEDGER_VERSION,
|
|
14372
|
+
id: parsed.id,
|
|
14373
|
+
at: parsed.at,
|
|
14374
|
+
type: parsed.type,
|
|
14375
|
+
actor: normalizeActor(parsed.actor),
|
|
14376
|
+
branch: str2(parsed.branch),
|
|
14377
|
+
head: str2(parsed.head),
|
|
14378
|
+
base: str2(parsed.base),
|
|
14379
|
+
baseSha: str2(parsed.baseSha),
|
|
14380
|
+
patchId: str2(parsed.patchId),
|
|
14381
|
+
diffHash: str2(parsed.diffHash),
|
|
14382
|
+
tree: str2(parsed.tree),
|
|
14383
|
+
dirty: typeof parsed.dirty === "boolean" ? parsed.dirty : null,
|
|
14384
|
+
...pr === undefined ? {} : { pr },
|
|
14385
|
+
...str2(parsed.supersedes) ? { supersedes: parsed.supersedes } : {},
|
|
14386
|
+
seq: rows.length + 1,
|
|
14387
|
+
superseded: false,
|
|
14388
|
+
supersedeIgnored: false,
|
|
14389
|
+
...rowIntegrity ? { integrity: rowIntegrity } : {},
|
|
14390
|
+
...reason ? { integrityReason: reason } : {}
|
|
14391
|
+
};
|
|
14392
|
+
rows.push(row);
|
|
14393
|
+
if (!options.integrity)
|
|
14434
14394
|
continue;
|
|
14435
|
-
|
|
14436
|
-
|
|
14437
|
-
|
|
14438
|
-
|
|
14439
|
-
|
|
14440
|
-
|
|
14441
|
-
|
|
14442
|
-
|
|
14443
|
-
|
|
14444
|
-
|
|
14445
|
-
|
|
14446
|
-
|
|
14447
|
-
}
|
|
14448
|
-
}
|
|
14449
|
-
emitCode(char);
|
|
14450
|
-
index += 1;
|
|
14395
|
+
if (rowIntegrity === "verified")
|
|
14396
|
+
integrity.chained += 1;
|
|
14397
|
+
else if (rowIntegrity === "legacy")
|
|
14398
|
+
integrity.legacy += 1;
|
|
14399
|
+
else if (reason)
|
|
14400
|
+
integrity.unverified.push({
|
|
14401
|
+
seq: row.seq,
|
|
14402
|
+
id: row.id,
|
|
14403
|
+
type: row.type,
|
|
14404
|
+
branch: row.branch,
|
|
14405
|
+
reason
|
|
14406
|
+
});
|
|
14451
14407
|
}
|
|
14452
|
-
|
|
14453
|
-
|
|
14454
|
-
|
|
14455
|
-
|
|
14456
|
-
|
|
14457
|
-
|
|
14458
|
-
|
|
14459
|
-
|
|
14460
|
-
|
|
14461
|
-
|
|
14462
|
-
|
|
14463
|
-
|
|
14408
|
+
const scopes = new Map;
|
|
14409
|
+
const scopeOf = (branch) => {
|
|
14410
|
+
let scope = scopes.get(branch);
|
|
14411
|
+
if (!scope)
|
|
14412
|
+
scopes.set(branch, scope = branchScope(cwd, branch));
|
|
14413
|
+
return scope;
|
|
14414
|
+
};
|
|
14415
|
+
const byId = new Map;
|
|
14416
|
+
for (const row of rows) {
|
|
14417
|
+
const target = row.supersedes ? byId.get(row.supersedes) : undefined;
|
|
14418
|
+
if (target && row.integrity !== "unverified" && supersedeAllowed(row, target, renameAwareBranch(scopeOf, row.branch)) === null)
|
|
14419
|
+
target.superseded = true;
|
|
14420
|
+
else if (row.supersedes)
|
|
14421
|
+
row.supersedeIgnored = true;
|
|
14422
|
+
if (!byId.has(row.id))
|
|
14423
|
+
byId.set(row.id, row);
|
|
14464
14424
|
}
|
|
14465
|
-
|
|
14466
|
-
|
|
14467
|
-
|
|
14468
|
-
|
|
14425
|
+
if (options.integrity && stat && Buffer.byteLength(text) === stat.size)
|
|
14426
|
+
tamperCache.set(file.value, {
|
|
14427
|
+
size: stat.size,
|
|
14428
|
+
stamp: fileStamp(stat),
|
|
14429
|
+
breaks: integrity.unverified.filter((row) => TAMPER_REASONS.has(row.reason))
|
|
14430
|
+
});
|
|
14431
|
+
return {
|
|
14432
|
+
ok: true,
|
|
14433
|
+
value: { path: file.value, rows, skipped, integrity: options.integrity ? integrity : null }
|
|
14434
|
+
};
|
|
14469
14435
|
}
|
|
14470
|
-
var
|
|
14471
|
-
|
|
14472
|
-
|
|
14473
|
-
|
|
14436
|
+
var TAMPER_REASONS = new Set(["hash_mismatch", "broken_link"]);
|
|
14437
|
+
var tamperCache = new Map;
|
|
14438
|
+
var fileStamp = (stat) => `${stat.ino}:${stat.mtimeMs}:${stat.ctimeMs}`;
|
|
14439
|
+
function ledgerTampering(cwd) {
|
|
14440
|
+
const file = ledgerPath(cwd);
|
|
14441
|
+
if (!file.ok)
|
|
14442
|
+
return [];
|
|
14474
14443
|
try {
|
|
14475
|
-
|
|
14444
|
+
const stat = fs13.statSync(file.value);
|
|
14445
|
+
const cached = tamperCache.get(file.value);
|
|
14446
|
+
if (cached && cached.size === stat.size && cached.stamp === fileStamp(stat))
|
|
14447
|
+
return cached.breaks;
|
|
14476
14448
|
} catch {
|
|
14477
|
-
return
|
|
14449
|
+
return [];
|
|
14478
14450
|
}
|
|
14451
|
+
const read = readLedger(cwd, { integrity: true });
|
|
14452
|
+
return read.ok ? (read.value.integrity?.unverified ?? []).filter((row) => TAMPER_REASONS.has(row.reason)) : [];
|
|
14479
14453
|
}
|
|
14480
|
-
function
|
|
14481
|
-
const
|
|
14482
|
-
const
|
|
14483
|
-
if (
|
|
14484
|
-
return false;
|
|
14485
|
-
if (hasOpaqueLineBreak(left) || hasOpaqueLineBreak(right))
|
|
14486
|
-
return false;
|
|
14487
|
-
const a = substanceOf(file, left);
|
|
14488
|
-
return a !== null && a === substanceOf(file, right);
|
|
14454
|
+
function tamperedUnblock(cwd) {
|
|
14455
|
+
const file = ledgerPath(cwd);
|
|
14456
|
+
const where = file.ok ? file.value : "the ledger file";
|
|
14457
|
+
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`;
|
|
14489
14458
|
}
|
|
14490
|
-
|
|
14491
|
-
|
|
14492
|
-
|
|
14493
|
-
|
|
14494
|
-
|
|
14495
|
-
|
|
14496
|
-
|
|
14497
|
-
var RISK_TOKENS = new Set([
|
|
14498
|
-
"auth",
|
|
14499
|
-
"authn",
|
|
14500
|
-
"authz",
|
|
14501
|
-
"oauth",
|
|
14502
|
-
"security",
|
|
14503
|
-
"permission",
|
|
14504
|
-
"permissions",
|
|
14505
|
-
"secret",
|
|
14506
|
-
"secrets",
|
|
14507
|
-
"crypto",
|
|
14508
|
-
"gate",
|
|
14509
|
-
"gates",
|
|
14510
|
-
"policy",
|
|
14511
|
-
"policies",
|
|
14512
|
-
"hook",
|
|
14513
|
-
"hooks",
|
|
14514
|
-
"husky",
|
|
14515
|
-
"lefthook",
|
|
14516
|
-
"release",
|
|
14517
|
-
"releases",
|
|
14518
|
-
"migration",
|
|
14519
|
-
"migrations",
|
|
14520
|
-
"migrate",
|
|
14521
|
-
"workflows"
|
|
14522
|
-
]);
|
|
14523
|
-
var RISK_FILES = new Set([
|
|
14524
|
-
".gitlab-ci.yml",
|
|
14525
|
-
"Jenkinsfile",
|
|
14526
|
-
"azure-pipelines.yml",
|
|
14527
|
-
".pre-commit-config.yaml",
|
|
14528
|
-
".workit/risk-paths"
|
|
14529
|
-
]);
|
|
14530
|
-
var RISK_NAMES = new Set([".gitattributes"]);
|
|
14531
|
-
var RISK_PATHS_FILE = ".workit/risk-paths";
|
|
14532
|
-
var LOCKFILES = new Set([
|
|
14533
|
-
"bun.lock",
|
|
14534
|
-
"bun.lockb",
|
|
14535
|
-
"package-lock.json",
|
|
14536
|
-
"npm-shrinkwrap.json",
|
|
14537
|
-
"yarn.lock",
|
|
14538
|
-
"pnpm-lock.yaml",
|
|
14539
|
-
"Cargo.lock",
|
|
14540
|
-
"go.sum",
|
|
14541
|
-
"poetry.lock",
|
|
14542
|
-
"uv.lock",
|
|
14543
|
-
"Pipfile.lock",
|
|
14544
|
-
"Gemfile.lock",
|
|
14545
|
-
"composer.lock"
|
|
14546
|
-
]);
|
|
14547
|
-
var DOC_EXT = /\.(?:md|mdx|markdown|rst|adoc|txt)$/iu;
|
|
14548
|
-
var DOC_NAMES = /^(?:license|licence|notice|authors|changelog|changes|contributors)$/iu;
|
|
14549
|
-
var INSTRUCTIONS = /(?:^|\/)(?:AGENTS|CLAUDE|GEMINI|SKILL)\.md$|(?:^|\/)(?:skills|agents|commands|rules|prompts)\/[^/]+/iu;
|
|
14550
|
-
var TEST_PATH = /(?:^|\/)(?:test|tests|__tests__|spec|specs|fixtures)\/|\.(?:test|spec)\.[^/]+$/iu;
|
|
14551
|
-
var basename = (file) => file.slice(file.lastIndexOf("/") + 1);
|
|
14552
|
-
var isDoc = (file) => !INSTRUCTIONS.test(file) && (DOC_EXT.test(file) || DOC_NAMES.test(basename(file)));
|
|
14553
|
-
function kindOf(file) {
|
|
14554
|
-
if (file.binary)
|
|
14555
|
-
return "behavior";
|
|
14556
|
-
if (file.generated)
|
|
14557
|
-
return "generated";
|
|
14558
|
-
if (LOCKFILES.has(basename(file.path)))
|
|
14559
|
-
return "lockfile";
|
|
14560
|
-
if (isDoc(file.path))
|
|
14561
|
-
return "doc";
|
|
14562
|
-
if (file.cosmetic)
|
|
14563
|
-
return "cosmetic";
|
|
14564
|
-
return TEST_PATH.test(file.path) ? "test" : "behavior";
|
|
14459
|
+
var tamperedWarning = (cwd, breaks) => `the ledger was edited, or a row was removed or moved (${breaks.slice(0, 3).map((row) => `${row.reason} at row ${row.seq}`).join(", ")}${breaks.length > 3 ? ", …" : ""}): no verdict is accepted until it is repaired. ${tamperedUnblock(cwd)}`;
|
|
14460
|
+
function verdictBasis(verdict, current) {
|
|
14461
|
+
if (verdict.head && verdict.head === current.head)
|
|
14462
|
+
return "fresh";
|
|
14463
|
+
if (verdict.patchId && verdict.patchId === current.patchId && verdict.diffHash && verdict.diffHash === current.diffHash)
|
|
14464
|
+
return "carried";
|
|
14465
|
+
return "stale";
|
|
14565
14466
|
}
|
|
14566
|
-
var
|
|
14567
|
-
function
|
|
14568
|
-
|
|
14569
|
-
|
|
14570
|
-
|
|
14571
|
-
|
|
14572
|
-
|
|
14573
|
-
|
|
14574
|
-
|
|
14575
|
-
|
|
14576
|
-
|
|
14577
|
-
|
|
14578
|
-
|
|
14579
|
-
|
|
14580
|
-
|
|
14581
|
-
|
|
14467
|
+
var isLiveVerdict = (row, retired) => row.type === "verdict" && typeof row.result === "string" && !retired.has(row.id);
|
|
14468
|
+
function trailerSessions(cwd, base, head) {
|
|
14469
|
+
if (!base || !head || !safeRef2(base) || !safeRef2(head))
|
|
14470
|
+
return [];
|
|
14471
|
+
const out = git4(cwd, [
|
|
14472
|
+
"log",
|
|
14473
|
+
"--format=%(trailers:key=Workit-Session,valueonly,separator=%x00)",
|
|
14474
|
+
`${base}..${head}`
|
|
14475
|
+
]);
|
|
14476
|
+
return (out ?? "").split(/[\0\n]/u).map((value) => value.trim()).filter(Boolean);
|
|
14477
|
+
}
|
|
14478
|
+
var RENAMED = /^branch: renamed refs\/heads\/(.+) to refs\/heads\/(.+)$/iu;
|
|
14479
|
+
function branchScope(cwd, branch) {
|
|
14480
|
+
const renamedAt = new Map;
|
|
14481
|
+
let since = Number.NEGATIVE_INFINITY;
|
|
14482
|
+
if (safeRef2(branch)) {
|
|
14483
|
+
const log = git4(cwd, [
|
|
14484
|
+
"log",
|
|
14485
|
+
"-g",
|
|
14486
|
+
"--date=unix",
|
|
14487
|
+
"--format=%gd%x09%gs",
|
|
14488
|
+
`refs/heads/${branch}`,
|
|
14489
|
+
"--"
|
|
14490
|
+
]);
|
|
14491
|
+
for (const line of (log ?? "").split(`
|
|
14492
|
+
`)) {
|
|
14493
|
+
const [selector = "", subject = ""] = line.split("\t");
|
|
14494
|
+
const seconds = Number(/@\{(\d+)\}$/u.exec(selector)?.[1]);
|
|
14495
|
+
if (!Number.isFinite(seconds))
|
|
14496
|
+
continue;
|
|
14497
|
+
since = seconds * 1000;
|
|
14498
|
+
const match = RENAMED.exec(subject);
|
|
14499
|
+
if (!match || match[1] === branch)
|
|
14500
|
+
continue;
|
|
14501
|
+
const until = (seconds + 1) * 1000;
|
|
14502
|
+
renamedAt.set(match[1], Math.max(renamedAt.get(match[1]) ?? 0, until));
|
|
14582
14503
|
}
|
|
14583
|
-
regex = new RegExp(`^${source}$`, "u");
|
|
14584
|
-
globCache.set(pattern, regex);
|
|
14585
14504
|
}
|
|
14586
|
-
return
|
|
14505
|
+
return (row) => {
|
|
14506
|
+
if (row.branch === branch)
|
|
14507
|
+
return true;
|
|
14508
|
+
const until = row.branch === null ? undefined : renamedAt.get(row.branch);
|
|
14509
|
+
if (until === undefined)
|
|
14510
|
+
return false;
|
|
14511
|
+
const at = Date.parse(row.at);
|
|
14512
|
+
return at >= since && at <= until;
|
|
14513
|
+
};
|
|
14587
14514
|
}
|
|
14588
|
-
|
|
14589
|
-
|
|
14590
|
-
|
|
14591
|
-
|
|
14515
|
+
var renameAwareBranch = (scopeOf, branch) => {
|
|
14516
|
+
return (target) => {
|
|
14517
|
+
const own = typeof branch === "string" ? branch : null;
|
|
14518
|
+
const theirs = typeof target.branch === "string" ? target.branch : null;
|
|
14519
|
+
if (own === theirs)
|
|
14520
|
+
return true;
|
|
14521
|
+
if (own === null || theirs === null || typeof target.at !== "string")
|
|
14522
|
+
return false;
|
|
14523
|
+
return scopeOf(own)({ branch: theirs, at: target.at });
|
|
14524
|
+
};
|
|
14525
|
+
};
|
|
14526
|
+
function authorSessions(cwd, rows, branch, range = { base: null, head: null }, scope = branchScope(cwd, branch)) {
|
|
14527
|
+
const sessions = new Set;
|
|
14528
|
+
for (const row of rows) {
|
|
14529
|
+
if (!scope(row) || row.type !== "commit.recorded" && row.type !== "task.opened")
|
|
14530
|
+
continue;
|
|
14531
|
+
const session = str2(row.session) ?? row.actor.session;
|
|
14532
|
+
if (session)
|
|
14533
|
+
sessions.add(session);
|
|
14534
|
+
}
|
|
14535
|
+
for (const session of trailerSessions(cwd, range.base, range.head))
|
|
14536
|
+
sessions.add(session);
|
|
14537
|
+
return sessions;
|
|
14538
|
+
}
|
|
14539
|
+
var derivesFrom = (derived, bare) => !bare.includes(":") && derived.endsWith(`:${bare}`);
|
|
14540
|
+
var authoredBy = (authors, session) => {
|
|
14541
|
+
if (authors.has(session))
|
|
14592
14542
|
return true;
|
|
14593
|
-
|
|
14594
|
-
|
|
14595
|
-
|
|
14543
|
+
for (const author of authors)
|
|
14544
|
+
if (derivesFrom(author, session) || derivesFrom(session, author))
|
|
14545
|
+
return true;
|
|
14546
|
+
return false;
|
|
14547
|
+
};
|
|
14548
|
+
var TRUST_REASONS = new Set([
|
|
14549
|
+
"unsigned",
|
|
14550
|
+
"hash_mismatch",
|
|
14551
|
+
"broken_link",
|
|
14552
|
+
"duplicate_id",
|
|
14553
|
+
"unissued_session",
|
|
14554
|
+
"unbound_session"
|
|
14555
|
+
]);
|
|
14556
|
+
var SHELL_VOUCHED_HOSTS = new Set([
|
|
14557
|
+
"cli",
|
|
14558
|
+
"opencode",
|
|
14559
|
+
"pi",
|
|
14560
|
+
"codex_cli",
|
|
14561
|
+
"codex_desktop"
|
|
14562
|
+
]);
|
|
14563
|
+
function sessionTrust(cwd, row) {
|
|
14564
|
+
const session = row.actor.session;
|
|
14565
|
+
if (!session || row.self === true)
|
|
14566
|
+
return null;
|
|
14567
|
+
const source = typeof row.sessionSource === "string" ? row.sessionSource : "env";
|
|
14568
|
+
const host = row.actor.host;
|
|
14569
|
+
const issuedOnly = source === "flag" ? host !== "cli" : source === "env" && session.includes(":") && !SHELL_VOUCHED_HOSTS.has(host);
|
|
14570
|
+
if (!issuedOnly)
|
|
14571
|
+
return null;
|
|
14572
|
+
const binding = issuedBinding(cwd, session);
|
|
14573
|
+
if (binding === null)
|
|
14574
|
+
return "unissued_session";
|
|
14575
|
+
return binding === "token" && row.sessionBound !== true ? "unbound_session" : null;
|
|
14596
14576
|
}
|
|
14597
|
-
|
|
14598
|
-
|
|
14599
|
-
|
|
14600
|
-
|
|
14601
|
-
|
|
14602
|
-
const
|
|
14603
|
-
|
|
14604
|
-
|
|
14605
|
-
|
|
14606
|
-
|
|
14607
|
-
|
|
14608
|
-
const
|
|
14609
|
-
const
|
|
14610
|
-
|
|
14611
|
-
|
|
14612
|
-
|
|
14613
|
-
|
|
14614
|
-
|
|
14577
|
+
function trustReasons(cwd, row) {
|
|
14578
|
+
if (row.integrity === "unverified")
|
|
14579
|
+
return [row.integrityReason ?? "unsigned"];
|
|
14580
|
+
if (row.integrity !== "verified")
|
|
14581
|
+
return ["unsigned"];
|
|
14582
|
+
const session = sessionTrust(cwd, row);
|
|
14583
|
+
return session ? [session] : [];
|
|
14584
|
+
}
|
|
14585
|
+
function withIntegrity(cwd, rows) {
|
|
14586
|
+
if (rows.every((row) => row.integrity !== undefined))
|
|
14587
|
+
return rows;
|
|
14588
|
+
const read = readLedger(cwd, { integrity: true });
|
|
14589
|
+
const bySeq = new Map(read.ok ? read.value.rows.map((row) => [row.seq, row]) : []);
|
|
14590
|
+
return rows.map((row) => {
|
|
14591
|
+
if (row.integrity !== undefined)
|
|
14592
|
+
return row;
|
|
14593
|
+
const fresh = bySeq.get(row.seq);
|
|
14594
|
+
return fresh && fresh.id === row.id && fresh.at === row.at ? fresh : row;
|
|
14615
14595
|
});
|
|
14616
|
-
const { selfMaxLines, selfMaxFiles, fullMinLines } = LANE_THRESHOLDS;
|
|
14617
|
-
if (input.riskTier === "high")
|
|
14618
|
-
return result("full", ["the task judgment records high risk"]);
|
|
14619
|
-
if (riskFiles.length)
|
|
14620
|
-
return result("full", [
|
|
14621
|
-
`touches a risk path: ${riskFiles.slice(0, 3).join(", ")}${riskFiles.length > 3 ? ", …" : ""}`
|
|
14622
|
-
]);
|
|
14623
|
-
if (!behavior.length)
|
|
14624
|
-
return result("none", [
|
|
14625
|
-
input.files.length ? "only docs, comments, formatting or generated copies changed" : "no changes"
|
|
14626
|
-
]);
|
|
14627
|
-
if (lines >= fullMinLines)
|
|
14628
|
-
return result("full", [`large diff: ${lines} lines (≥ ${fullMinLines})`]);
|
|
14629
|
-
if (lines > selfMaxLines)
|
|
14630
|
-
return result("focused", [`${lines} lines changed (> ${selfMaxLines})`]);
|
|
14631
|
-
if (behavior.length > selfMaxFiles)
|
|
14632
|
-
return result("focused", [`${behavior.length} behavior files changed (> ${selfMaxFiles})`]);
|
|
14633
|
-
if (opaque.length)
|
|
14634
|
-
return result("focused", [
|
|
14635
|
-
`a lockfile or binary changed: ${opaque.slice(0, 3).map((entry) => entry.file.path).join(", ")}${opaque.length > 3 ? ", …" : ""}`
|
|
14636
|
-
]);
|
|
14637
|
-
return result("self", [
|
|
14638
|
-
`small change: ${lines} line${lines === 1 ? "" : "s"} in ${behavior.length} file${behavior.length === 1 ? "" : "s"}, no risk path`
|
|
14639
|
-
]);
|
|
14640
14596
|
}
|
|
14641
|
-
|
|
14642
|
-
|
|
14643
|
-
|
|
14644
|
-
|
|
14645
|
-
|
|
14646
|
-
|
|
14647
|
-
|
|
14648
|
-
|
|
14649
|
-
|
|
14650
|
-
|
|
14651
|
-
|
|
14652
|
-
|
|
14653
|
-
|
|
14654
|
-
|
|
14597
|
+
var untrusted = (reasons) => reasons.some((reason) => TRUST_REASONS.has(reason));
|
|
14598
|
+
var independenceReasons = (row, authors) => {
|
|
14599
|
+
const reasons = [];
|
|
14600
|
+
if (!row.actor.session)
|
|
14601
|
+
reasons.push("no_session");
|
|
14602
|
+
else if (authoredBy(authors, row.actor.session))
|
|
14603
|
+
reasons.push("author_session");
|
|
14604
|
+
if (row.self === true && !reasons.includes("no_session"))
|
|
14605
|
+
reasons.push("self");
|
|
14606
|
+
return reasons;
|
|
14607
|
+
};
|
|
14608
|
+
function effectiveOf(rows, reasonsOf, sameCode, isCurrent) {
|
|
14609
|
+
let effective = null;
|
|
14610
|
+
for (const row of rows) {
|
|
14611
|
+
if (!effective) {
|
|
14612
|
+
effective = row;
|
|
14613
|
+
continue;
|
|
14614
|
+
}
|
|
14615
|
+
if (untrusted(reasonsOf(row)) && !untrusted(reasonsOf(effective)))
|
|
14616
|
+
continue;
|
|
14617
|
+
const rowIndependent = reasonsOf(row).length === 0;
|
|
14618
|
+
const effIndependent = reasonsOf(effective).length === 0;
|
|
14619
|
+
if (effIndependent && !rowIndependent && isCurrent(effective))
|
|
14620
|
+
continue;
|
|
14621
|
+
if (effIndependent && FAILING.has(String(effective.result)) && row.actor.session === effective.actor.session && sameCode(effective, row))
|
|
14622
|
+
continue;
|
|
14623
|
+
effective = row;
|
|
14655
14624
|
}
|
|
14625
|
+
return effective;
|
|
14656
14626
|
}
|
|
14657
|
-
var
|
|
14658
|
-
|
|
14659
|
-
|
|
14660
|
-
|
|
14661
|
-
|
|
14662
|
-
|
|
14663
|
-
|
|
14664
|
-
|
|
14665
|
-
|
|
14666
|
-
|
|
14667
|
-
|
|
14668
|
-
|
|
14669
|
-
|
|
14670
|
-
|
|
14671
|
-
|
|
14672
|
-
|
|
14673
|
-
|
|
14674
|
-
|
|
14675
|
-
|
|
14676
|
-
|
|
14627
|
+
var restackExact = new WeakMap;
|
|
14628
|
+
function restackCarries(exactIn, rows, branch, from, to) {
|
|
14629
|
+
if (!from || !to)
|
|
14630
|
+
return false;
|
|
14631
|
+
const exact = (row, fromHead, head) => {
|
|
14632
|
+
if (exactIn === null)
|
|
14633
|
+
return true;
|
|
14634
|
+
const cwd = exactIn;
|
|
14635
|
+
let known = restackExact.get(row);
|
|
14636
|
+
if (known === undefined) {
|
|
14637
|
+
known = typeof row.fromBase === "string" && typeof row.toBase === "string" && sameOwnChange(cwd, {
|
|
14638
|
+
from: fromHead,
|
|
14639
|
+
to: head,
|
|
14640
|
+
fromBase: row.fromBase,
|
|
14641
|
+
toBase: row.toBase
|
|
14642
|
+
});
|
|
14643
|
+
restackExact.set(row, known);
|
|
14644
|
+
}
|
|
14645
|
+
return known;
|
|
14646
|
+
};
|
|
14647
|
+
const edges = new Map;
|
|
14648
|
+
for (const row of rows)
|
|
14649
|
+
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))
|
|
14650
|
+
edges.set(row.fromHead, [...edges.get(row.fromHead) ?? [], row.head]);
|
|
14651
|
+
const seen = new Set([from]);
|
|
14652
|
+
const queue = [from];
|
|
14653
|
+
while (queue.length) {
|
|
14654
|
+
const next = queue.shift();
|
|
14655
|
+
if (next === to)
|
|
14656
|
+
return true;
|
|
14657
|
+
for (const target of edges.get(next) ?? [])
|
|
14658
|
+
if (!seen.has(target)) {
|
|
14659
|
+
seen.add(target);
|
|
14660
|
+
queue.push(target);
|
|
14677
14661
|
}
|
|
14678
|
-
if (before && after && cosmeticChange(entry.path, before, after))
|
|
14679
|
-
out.add(entry.path);
|
|
14680
|
-
else if ([before, after].some((bytes) => bytes && hasOpaqueLineBreak(bytes.toString("utf8"))))
|
|
14681
|
-
opaque.add(entry.path);
|
|
14682
14662
|
}
|
|
14683
|
-
return
|
|
14663
|
+
return false;
|
|
14684
14664
|
}
|
|
14685
|
-
function
|
|
14686
|
-
const
|
|
14687
|
-
const
|
|
14688
|
-
|
|
14689
|
-
|
|
14690
|
-
const
|
|
14691
|
-
|
|
14692
|
-
|
|
14693
|
-
|
|
14694
|
-
|
|
14665
|
+
function checkVerdicts(cwd, branch, ledgerRows) {
|
|
14666
|
+
const rows = withIntegrity(cwd, ledgerRows);
|
|
14667
|
+
const head = branchHead(cwd, branch);
|
|
14668
|
+
const fallbackBase = defaultBase(cwd);
|
|
14669
|
+
const scope = branchScope(cwd, branch);
|
|
14670
|
+
const keyCache = new Map;
|
|
14671
|
+
const keysAt = (base, at) => {
|
|
14672
|
+
if (!base || !at)
|
|
14673
|
+
return { patchId: null, diffHash: null };
|
|
14674
|
+
const cacheKey = `${base}\x00${at}`;
|
|
14675
|
+
let key = keyCache.get(cacheKey);
|
|
14676
|
+
if (!key) {
|
|
14677
|
+
key = { patchId: patchId(cwd, base, at), diffHash: diffHash(cwd, base, at) };
|
|
14678
|
+
keyCache.set(cacheKey, key);
|
|
14679
|
+
}
|
|
14680
|
+
return key;
|
|
14681
|
+
};
|
|
14682
|
+
const keyFor = (base) => keysAt(base, head);
|
|
14683
|
+
const driftCache = new Map;
|
|
14684
|
+
const driftOf = (row) => {
|
|
14685
|
+
if (driftCache.has(row))
|
|
14686
|
+
return driftCache.get(row) ?? null;
|
|
14687
|
+
let drift = null;
|
|
14688
|
+
const toBase = row.base ?? fallbackBase;
|
|
14689
|
+
const toBaseSha = toBase ? resolveCommit(cwd, toBase) : null;
|
|
14690
|
+
const fromBase = row.baseSha ?? toBaseSha;
|
|
14691
|
+
if (row.head && head && row.head !== head && toBaseSha && fromBase && !untrusted(reasonsOf(row)))
|
|
14692
|
+
drift = verdictDrift(cwd, {
|
|
14693
|
+
from: row.head,
|
|
14694
|
+
to: head,
|
|
14695
|
+
fromBase,
|
|
14696
|
+
toBase: toBaseSha,
|
|
14697
|
+
kind: typeof row.kind === "string" ? row.kind : "review"
|
|
14698
|
+
});
|
|
14699
|
+
driftCache.set(row, drift);
|
|
14700
|
+
return drift;
|
|
14701
|
+
};
|
|
14702
|
+
const exactCache = new Map;
|
|
14703
|
+
const exactCarry = (row) => {
|
|
14704
|
+
if (FAILING.has(String(row.result)))
|
|
14705
|
+
return true;
|
|
14706
|
+
let exact = exactCache.get(row);
|
|
14707
|
+
if (exact === undefined) {
|
|
14708
|
+
const toBase = row.base ?? fallbackBase;
|
|
14709
|
+
const toBaseSha = toBase ? resolveCommit(cwd, toBase) : null;
|
|
14710
|
+
const fromBase = row.baseSha ?? toBaseSha;
|
|
14711
|
+
exact = Boolean(row.head && head && toBaseSha && fromBase && sameOwnChange(cwd, { from: row.head, to: head, fromBase, toBase: toBaseSha }));
|
|
14712
|
+
exactCache.set(row, exact);
|
|
14713
|
+
}
|
|
14714
|
+
return exact;
|
|
14715
|
+
};
|
|
14716
|
+
const keyBasis = (row) => {
|
|
14717
|
+
const basis = verdictBasis(row, { head, ...keyFor(row.base ?? fallbackBase) });
|
|
14718
|
+
return basis === "carried" && !exactCarry(row) ? "stale" : basis;
|
|
14719
|
+
};
|
|
14720
|
+
const restacked = (row) => restackCarries(FAILING.has(String(row.result)) ? null : cwd, rows, branch, row.head, head);
|
|
14721
|
+
const carryOf = (row) => {
|
|
14722
|
+
if (keyBasis(row) !== "stale")
|
|
14723
|
+
return null;
|
|
14724
|
+
if (restacked(row))
|
|
14725
|
+
return null;
|
|
14726
|
+
const drift = driftOf(row);
|
|
14727
|
+
return drift?.carries ? { rule: drift.rule, since: drift.since, files: drift.files } : null;
|
|
14728
|
+
};
|
|
14729
|
+
const basisOf = (row) => {
|
|
14730
|
+
const basis = keyBasis(row);
|
|
14731
|
+
if (basis !== "stale")
|
|
14732
|
+
return basis;
|
|
14733
|
+
return restacked(row) || carryOf(row) ? "carried" : basis;
|
|
14734
|
+
};
|
|
14735
|
+
const sameCode = (failed, row) => {
|
|
14736
|
+
if (failed.head && failed.head === row.head)
|
|
14737
|
+
return true;
|
|
14738
|
+
const base = failed.base ?? fallbackBase;
|
|
14739
|
+
const stored = (row.base ?? fallbackBase) === base && row.baseSha !== null && row.baseSha === failed.baseSha;
|
|
14740
|
+
const keys = stored ? { patchId: row.patchId, diffHash: row.diffHash } : keysAt(base, row.head);
|
|
14741
|
+
return verdictBasis(failed, { head: row.head, ...keys }) !== "stale" || restackCarries(null, rows, branch, failed.head, row.head);
|
|
14742
|
+
};
|
|
14743
|
+
const authors = authorSessions(cwd, rows, branch, { base: fallbackBase, head }, scope);
|
|
14744
|
+
const failureAuthorCache = new Map;
|
|
14745
|
+
const failureAuthors = (row) => {
|
|
14746
|
+
let set = failureAuthorCache.get(row.seq);
|
|
14747
|
+
if (!set) {
|
|
14748
|
+
set = authorSessions(cwd, rows.filter((other) => other.integrity === "verified" && other.seq < row.seq), branch, { base: null, head: null }, scope);
|
|
14749
|
+
failureAuthorCache.set(row.seq, set);
|
|
14750
|
+
}
|
|
14751
|
+
return set;
|
|
14752
|
+
};
|
|
14753
|
+
const reasonCache = new Map;
|
|
14754
|
+
const reasonsOf = (row) => {
|
|
14755
|
+
let reasons = reasonCache.get(row);
|
|
14756
|
+
if (!reasons) {
|
|
14757
|
+
const failing = FAILING.has(String(row.result));
|
|
14758
|
+
reasons = [
|
|
14759
|
+
...trustReasons(cwd, row),
|
|
14760
|
+
...independenceReasons(row, failing ? failureAuthors(row) : authors)
|
|
14761
|
+
];
|
|
14762
|
+
reasonCache.set(row, reasons);
|
|
14763
|
+
}
|
|
14764
|
+
return reasons;
|
|
14765
|
+
};
|
|
14766
|
+
const retired = new Set;
|
|
14767
|
+
for (const row of rows)
|
|
14768
|
+
if (row.type === "verdict" && row.supersedes && !row.supersedeIgnored && trustReasons(cwd, row).length === 0)
|
|
14769
|
+
retired.add(row.supersedes);
|
|
14770
|
+
const byKind = new Map;
|
|
14771
|
+
for (const row of rows)
|
|
14772
|
+
if (isLiveVerdict(row, retired) && scope(row)) {
|
|
14773
|
+
const kind = typeof row.kind === "string" ? row.kind : "review";
|
|
14774
|
+
byKind.set(kind, [...byKind.get(kind) ?? [], row]);
|
|
14775
|
+
}
|
|
14776
|
+
const verdicts = [];
|
|
14777
|
+
const warnings = [];
|
|
14778
|
+
for (const [kind, list] of byKind) {
|
|
14779
|
+
const row = effectiveOf(list, reasonsOf, sameCode, (candidate) => basisOf(candidate) !== "stale");
|
|
14780
|
+
if (!row)
|
|
14781
|
+
continue;
|
|
14782
|
+
const basis = basisOf(row);
|
|
14783
|
+
const current = basis !== "stale";
|
|
14784
|
+
const independence = reasonsOf(row);
|
|
14785
|
+
const reasons = [
|
|
14786
|
+
...current ? [] : ["stale"],
|
|
14787
|
+
...PASSING.has(String(row.result)) ? [] : ["not_passing"],
|
|
14788
|
+
...independence
|
|
14789
|
+
];
|
|
14790
|
+
verdicts.push({
|
|
14791
|
+
kind,
|
|
14792
|
+
basis,
|
|
14793
|
+
current,
|
|
14794
|
+
independent: independence.length === 0,
|
|
14795
|
+
accepted: reasons.length === 0,
|
|
14796
|
+
reasons,
|
|
14797
|
+
carry: basis === "carried" ? carryOf(row) : null,
|
|
14798
|
+
verdict: row
|
|
14799
|
+
});
|
|
14800
|
+
if (reasons.length === 0) {
|
|
14801
|
+
for (const later of list)
|
|
14802
|
+
if (later.seq > row.seq && reasonsOf(later).length > 0 && !untrusted(reasonsOf(later)) && FAILING.has(String(later.result)) && basisOf(later) !== "stale")
|
|
14803
|
+
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`);
|
|
14804
|
+
}
|
|
14695
14805
|
}
|
|
14696
|
-
|
|
14697
|
-
|
|
14698
|
-
|
|
14699
|
-
|
|
14700
|
-
|
|
14701
|
-
|
|
14702
|
-
|
|
14703
|
-
|
|
14704
|
-
|
|
14705
|
-
|
|
14706
|
-
...entry,
|
|
14707
|
-
...cosmetic.opaque.has(entry.path) ? { binary: true } : {},
|
|
14708
|
-
cosmetic: !entry.binary && cosmetic.cosmetic.has(entry.path)
|
|
14709
|
-
})),
|
|
14710
|
-
...head === null ? untrackedFiles(cwd).map((file) => untrackedStat(cwd, file)) : []
|
|
14711
|
-
];
|
|
14712
|
-
const generated = generatedAt(cwd, fork, files.map((entry) => entry.path));
|
|
14713
|
-
return files.map((entry) => ({
|
|
14714
|
-
...entry,
|
|
14715
|
-
generated: generated.has(entry.path)
|
|
14716
|
-
}));
|
|
14717
|
-
}
|
|
14718
|
-
function branchLane(cwd, options) {
|
|
14719
|
-
const candidates = options.base ? [`origin/${options.base}`, options.base] : [];
|
|
14720
|
-
const base = candidates.find((ref) => resolveRef(cwd, ref) !== null) ?? defaultBase(cwd) ?? null;
|
|
14721
|
-
const head = options.workingTree ? null : options.head;
|
|
14722
|
-
const fork = base && (options.workingTree || options.head) ? forkOf(cwd, base, head) : null;
|
|
14723
|
-
const files = fork ? laneFiles(cwd, fork, head) : null;
|
|
14724
|
-
const riskTier = options.judgment?.riskTier ?? null;
|
|
14725
|
-
if (!fork || !files) {
|
|
14726
|
-
const high = riskTier === "high";
|
|
14727
|
-
return {
|
|
14728
|
-
lane: high ? "full" : "focused",
|
|
14729
|
-
reasons: [
|
|
14730
|
-
`the diff of ${options.head?.slice(0, 12) ?? "(no head)"} against ${base ?? "(no base)"} cannot be read`,
|
|
14731
|
-
...high ? ["the task judgment records high risk"] : []
|
|
14732
|
-
],
|
|
14733
|
-
lines: 0,
|
|
14734
|
-
files: 0,
|
|
14735
|
-
riskFiles: [],
|
|
14736
|
-
base,
|
|
14737
|
-
head: options.head
|
|
14738
|
-
};
|
|
14806
|
+
for (const row of rows)
|
|
14807
|
+
if (row.integrity === "unverified" && scope(row))
|
|
14808
|
+
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)`);
|
|
14809
|
+
const tampered = ledgerTampering(cwd);
|
|
14810
|
+
if (tampered.length) {
|
|
14811
|
+
warnings.push(tamperedWarning(cwd, tampered));
|
|
14812
|
+
for (const entry of verdicts) {
|
|
14813
|
+
entry.accepted = false;
|
|
14814
|
+
entry.reasons = [...entry.reasons, "ledger_tampered"];
|
|
14815
|
+
}
|
|
14739
14816
|
}
|
|
14740
|
-
|
|
14817
|
+
verdicts.sort((a, b) => a.verdict.seq - b.verdict.seq);
|
|
14818
|
+
const newestCurrent = verdicts.findLast((entry) => entry.current) ?? verdicts.at(-1) ?? null;
|
|
14819
|
+
const failing = verdicts.find((entry) => entry.current && entry.independent && FAILING.has(String(entry.verdict.result)));
|
|
14820
|
+
const newestAccepted = verdicts.findLast((entry) => entry.accepted && STRONG_RESULTS.has(String(entry.verdict.result))) ?? null;
|
|
14821
|
+
const accepted = tampered.length ? { accepted: false, verdict: null, reasons: ["ledger_tampered"] } : newestAccepted && !failing ? { accepted: true, verdict: newestAccepted.verdict, reasons: [] } : {
|
|
14822
|
+
accepted: false,
|
|
14823
|
+
verdict: failing?.verdict ?? null,
|
|
14824
|
+
reasons: failing ? ["failing_verdict"] : !newestCurrent ? ["no_verdict"] : newestCurrent.reasons.length ? newestCurrent.reasons : ["type_check_only"]
|
|
14825
|
+
};
|
|
14826
|
+
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))));
|
|
14827
|
+
const selfEntry = latestSelf && STRONG_RESULTS.has(String(latestSelf.verdict.result)) ? latestSelf : undefined;
|
|
14828
|
+
const carryEntry = verdicts.find((entry) => entry.verdict === accepted.verdict && entry.accepted) ?? newestCurrent;
|
|
14829
|
+
const lastPass = accepted.accepted || failing || tampered.length ? undefined : verdicts.findLast((entry) => !entry.current && !untrusted(entry.reasons) && STRONG_RESULTS.has(String(entry.verdict.result)));
|
|
14830
|
+
const drift = lastPass ? driftOf(lastPass.verdict) : null;
|
|
14831
|
+
const reverify = drift && !drift.carries && drift.focused && drift.files ? { scope: "focused", since: drift.since, files: drift.files } : null;
|
|
14741
14832
|
return {
|
|
14742
|
-
|
|
14743
|
-
|
|
14744
|
-
|
|
14745
|
-
|
|
14746
|
-
|
|
14747
|
-
base,
|
|
14748
|
-
|
|
14833
|
+
branch,
|
|
14834
|
+
head,
|
|
14835
|
+
review: accepted.accepted ? "verified" : selfEntry ? "self-reviewed" : "unreviewed",
|
|
14836
|
+
selfVerdict: accepted.accepted ? null : selfEntry?.verdict ?? null,
|
|
14837
|
+
base: newestCurrent?.verdict.base ?? fallbackBase,
|
|
14838
|
+
patchId: keyFor(newestCurrent?.verdict.base ?? fallbackBase).patchId,
|
|
14839
|
+
current: { basis: newestCurrent?.basis ?? "none", verdict: newestCurrent?.verdict ?? null },
|
|
14840
|
+
carry: carryEntry?.carry ?? null,
|
|
14841
|
+
reverify,
|
|
14842
|
+
accepted,
|
|
14843
|
+
verdicts,
|
|
14844
|
+
authors: [...authors].toSorted(),
|
|
14845
|
+
warnings
|
|
14749
14846
|
};
|
|
14750
14847
|
}
|
|
14751
|
-
var
|
|
14848
|
+
var fromLead = (row, leads) => leads === null || row.actor.session !== null && leads.includes(row.actor.session);
|
|
14849
|
+
function activeStanding(rows, fanout, leads = null) {
|
|
14850
|
+
const active = [];
|
|
14851
|
+
for (const row of rows) {
|
|
14852
|
+
if (row.fanout !== fanout || !fromLead(row, leads))
|
|
14853
|
+
continue;
|
|
14854
|
+
if (row.type === "standing" && typeof row.what === "string" && row.what.trim()) {
|
|
14855
|
+
if (!row.superseded)
|
|
14856
|
+
active.push(row);
|
|
14857
|
+
} else if (row.type === "standing.cleared") {
|
|
14858
|
+
const target = str2(row.target);
|
|
14859
|
+
if (target === null)
|
|
14860
|
+
active.length = 0;
|
|
14861
|
+
else {
|
|
14862
|
+
const index = active.findIndex((order) => order.id === target);
|
|
14863
|
+
if (index >= 0)
|
|
14864
|
+
active.splice(index, 1);
|
|
14865
|
+
}
|
|
14866
|
+
}
|
|
14867
|
+
}
|
|
14868
|
+
return active;
|
|
14869
|
+
}
|
|
14870
|
+
var OBSERVED_ONLY = new Set([
|
|
14871
|
+
"check",
|
|
14872
|
+
"merge.unverified",
|
|
14873
|
+
"policy.judged",
|
|
14874
|
+
...PR_ROW_TYPES
|
|
14875
|
+
]);
|
|
14752
14876
|
|
|
14753
14877
|
// packages/workit-core/src/core/policy/derive.ts
|
|
14754
14878
|
var RULES = {
|
|
@@ -14903,7 +15027,7 @@ var productDecision = (task, ledger) => {
|
|
|
14903
15027
|
};
|
|
14904
15028
|
};
|
|
14905
15029
|
var planRecorded = (task, root) => {
|
|
14906
|
-
const cited = (latestJudgment(task)?.refs ?? []).find((ref) => ref.kind === "external" || ref.kind === "file" && existsSync5(
|
|
15030
|
+
const cited = (latestJudgment(task)?.refs ?? []).find((ref) => ref.kind === "external" || ref.kind === "file" && existsSync5(path17.resolve(root, ref.path)));
|
|
14907
15031
|
if (cited)
|
|
14908
15032
|
return {
|
|
14909
15033
|
met: true,
|
|
@@ -14981,14 +15105,14 @@ function writeBlockers(task, root) {
|
|
|
14981
15105
|
|
|
14982
15106
|
// packages/workit-core/src/core/task-evaluation.ts
|
|
14983
15107
|
var digestBytes = (value) => createHash12("sha256").update(value).digest("hex");
|
|
14984
|
-
var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${
|
|
14985
|
-
var relative = (root, candidate) =>
|
|
15108
|
+
var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path18.sep}`);
|
|
15109
|
+
var relative = (root, candidate) => path18.relative(root, candidate).split(path18.sep).join("/") || ".";
|
|
14986
15110
|
var canonicalPath = (value) => {
|
|
14987
15111
|
if (typeof value !== "string" || !value)
|
|
14988
15112
|
return null;
|
|
14989
15113
|
if (value.includes("\\") || value.split("/").includes(".."))
|
|
14990
15114
|
return null;
|
|
14991
|
-
const normalized =
|
|
15115
|
+
const normalized = path18.posix.normalize(value);
|
|
14992
15116
|
if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
|
|
14993
15117
|
return null;
|
|
14994
15118
|
return normalized === "." ? "." : normalized.replace(/\/$/, "");
|
|
@@ -15024,7 +15148,7 @@ var compareCodeUnits2 = (left, right) => {
|
|
|
15024
15148
|
return left.length - right.length;
|
|
15025
15149
|
};
|
|
15026
15150
|
var gitPaths = (root) => {
|
|
15027
|
-
const result =
|
|
15151
|
+
const result = spawnSync10("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
|
|
15028
15152
|
cwd: root,
|
|
15029
15153
|
encoding: "buffer"
|
|
15030
15154
|
});
|
|
@@ -15036,21 +15160,21 @@ var gitPaths = (root) => {
|
|
|
15036
15160
|
git: !/not a git repository/i.test(stderr)
|
|
15037
15161
|
};
|
|
15038
15162
|
}
|
|
15039
|
-
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(
|
|
15040
|
-
const stagedDeleted =
|
|
15163
|
+
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path18.sep).join("/")) : [];
|
|
15164
|
+
const stagedDeleted = spawnSync10("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
|
|
15041
15165
|
if (stagedDeleted.status !== 0) {
|
|
15042
15166
|
const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
|
|
15043
15167
|
if (!/not a git repository/i.test(stderr))
|
|
15044
15168
|
return { paths, uncertain: true, git: true };
|
|
15045
15169
|
} else if (stagedDeleted.stdout) {
|
|
15046
|
-
paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(
|
|
15170
|
+
paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path18.sep).join("/")));
|
|
15047
15171
|
}
|
|
15048
15172
|
return { paths: [...new Set(paths)], uncertain: false, git: true };
|
|
15049
15173
|
};
|
|
15050
15174
|
var walk = (root, directory, output) => {
|
|
15051
15175
|
let names;
|
|
15052
15176
|
try {
|
|
15053
|
-
names =
|
|
15177
|
+
names = fs14.readdirSync(directory);
|
|
15054
15178
|
} catch {
|
|
15055
15179
|
return true;
|
|
15056
15180
|
}
|
|
@@ -15058,10 +15182,10 @@ var walk = (root, directory, output) => {
|
|
|
15058
15182
|
for (const name of names) {
|
|
15059
15183
|
if (name === ".git" || name === ".workit")
|
|
15060
15184
|
continue;
|
|
15061
|
-
const target =
|
|
15185
|
+
const target = path18.join(directory, name);
|
|
15062
15186
|
let stat;
|
|
15063
15187
|
try {
|
|
15064
|
-
stat =
|
|
15188
|
+
stat = fs14.lstatSync(target);
|
|
15065
15189
|
} catch {
|
|
15066
15190
|
uncertain = true;
|
|
15067
15191
|
continue;
|
|
@@ -15076,14 +15200,14 @@ var walk = (root, directory, output) => {
|
|
|
15076
15200
|
var scopeRoots = (root, scope) => {
|
|
15077
15201
|
const roots = [];
|
|
15078
15202
|
for (const item of scope.paths.length ? scope.paths : ["."]) {
|
|
15079
|
-
const target =
|
|
15203
|
+
const target = path18.resolve(root, item);
|
|
15080
15204
|
if (!inside(root, target))
|
|
15081
15205
|
return failure2("invalid_input", `candidate scope escapes checkout: ${item} — keep one task per repository and coordinate a separate linked task in the other checkout`, { fields: [{ path: "scope.paths", reason: `${item} is outside the checkout` }] });
|
|
15082
15206
|
let ancestor = target;
|
|
15083
|
-
while (!
|
|
15084
|
-
ancestor =
|
|
15207
|
+
while (!fs14.existsSync(ancestor) && ancestor !== root)
|
|
15208
|
+
ancestor = path18.dirname(ancestor);
|
|
15085
15209
|
try {
|
|
15086
|
-
if (!inside(root,
|
|
15210
|
+
if (!inside(root, fs14.realpathSync(ancestor)))
|
|
15087
15211
|
return failure2("invalid_input", `candidate scope escapes checkout through a symlink: ${item} — keep one task per repository and coordinate a separate linked task in the other checkout`, { fields: [{ path: "scope.paths", reason: `${item} resolves outside the checkout` }] });
|
|
15088
15212
|
} catch {
|
|
15089
15213
|
return failure2("invalid_input", `candidate scope cannot be inspected: ${item}`);
|
|
@@ -15095,7 +15219,7 @@ var scopeRoots = (root, scope) => {
|
|
|
15095
15219
|
function captureCandidate(root, scope, environment = []) {
|
|
15096
15220
|
let checkout;
|
|
15097
15221
|
try {
|
|
15098
|
-
checkout =
|
|
15222
|
+
checkout = fs14.realpathSync(root);
|
|
15099
15223
|
} catch {
|
|
15100
15224
|
return failure2("invalid_input", "candidate root does not exist");
|
|
15101
15225
|
}
|
|
@@ -15113,11 +15237,11 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
15113
15237
|
if (item === ".git" || item === ".workit")
|
|
15114
15238
|
continue;
|
|
15115
15239
|
names.add(item);
|
|
15116
|
-
if (!
|
|
15240
|
+
if (!fs14.existsSync(target))
|
|
15117
15241
|
continue;
|
|
15118
15242
|
let stat;
|
|
15119
15243
|
try {
|
|
15120
|
-
stat =
|
|
15244
|
+
stat = fs14.lstatSync(target);
|
|
15121
15245
|
} catch {
|
|
15122
15246
|
uncertain = true;
|
|
15123
15247
|
continue;
|
|
@@ -15133,17 +15257,17 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
15133
15257
|
for (const item of [...names].toSorted()) {
|
|
15134
15258
|
if (!scopeMatches(item, normalizedScope))
|
|
15135
15259
|
continue;
|
|
15136
|
-
const target =
|
|
15260
|
+
const target = path18.join(checkout, item);
|
|
15137
15261
|
try {
|
|
15138
|
-
const stat =
|
|
15262
|
+
const stat = fs14.lstatSync(target);
|
|
15139
15263
|
if (stat.isSymbolicLink()) {
|
|
15140
|
-
const link =
|
|
15264
|
+
const link = fs14.readlinkSync(target);
|
|
15141
15265
|
files.push({ path: item, kind: "symlink", digest: digestBytes(link), executable: null });
|
|
15142
15266
|
} else if (stat.isFile()) {
|
|
15143
15267
|
files.push({
|
|
15144
15268
|
path: item,
|
|
15145
15269
|
kind: "file",
|
|
15146
|
-
digest: digestBytes(
|
|
15270
|
+
digest: digestBytes(fs14.readFileSync(target)),
|
|
15147
15271
|
executable: (stat.mode & 73) !== 0
|
|
15148
15272
|
});
|
|
15149
15273
|
} else if (!stat.isDirectory()) {
|
|
@@ -15168,7 +15292,7 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
15168
15292
|
const values = supplied.toSorted((left, right) => compareCodeUnits2(left.name, right.name)).map(({ name, value }) => ({ name, value, refs: [] }));
|
|
15169
15293
|
if (values.some(({ value }) => value === null))
|
|
15170
15294
|
completeness = "uncertain";
|
|
15171
|
-
const headResult =
|
|
15295
|
+
const headResult = spawnSync10("git", ["rev-parse", "HEAD"], { cwd: checkout, encoding: "utf8" });
|
|
15172
15296
|
if (headResult.status !== 0 && !/not a git repository/i.test(headResult.stderr ?? ""))
|
|
15173
15297
|
completeness = "uncertain";
|
|
15174
15298
|
const head = headResult.status === 0 ? headResult.stdout.trim() : null;
|
|
@@ -15308,7 +15432,7 @@ var findingVerificationPasses = (findingCandidateId, evidence) => evidence.statu
|
|
|
15308
15432
|
var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
15309
15433
|
let root;
|
|
15310
15434
|
try {
|
|
15311
|
-
root =
|
|
15435
|
+
root = fs14.realpathSync(checkoutRoot);
|
|
15312
15436
|
} catch {
|
|
15313
15437
|
return failure2("permission_denied", "checkout root is unavailable");
|
|
15314
15438
|
}
|
|
@@ -15317,23 +15441,23 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
|
15317
15441
|
continue;
|
|
15318
15442
|
if (!reference.digest)
|
|
15319
15443
|
return failure2("invalid_input", "document references require a byte digest");
|
|
15320
|
-
const target =
|
|
15321
|
-
if (target !== root && !target.startsWith(`${root}${
|
|
15444
|
+
const target = path18.resolve(root, reference.path);
|
|
15445
|
+
if (target !== root && !target.startsWith(`${root}${path18.sep}`))
|
|
15322
15446
|
return failure2("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
|
|
15323
15447
|
fields: [
|
|
15324
15448
|
{ path: "contentRefs.path", reason: `${reference.path} is outside the checkout` }
|
|
15325
15449
|
]
|
|
15326
15450
|
});
|
|
15327
15451
|
try {
|
|
15328
|
-
const below =
|
|
15452
|
+
const below = path18.relative(root, target);
|
|
15329
15453
|
let current = root;
|
|
15330
|
-
for (const segment of below.split(
|
|
15454
|
+
for (const segment of below.split(path18.sep)) {
|
|
15331
15455
|
if (!segment)
|
|
15332
15456
|
continue;
|
|
15333
|
-
current =
|
|
15334
|
-
const stat =
|
|
15335
|
-
const real =
|
|
15336
|
-
if (real !== root && !real.startsWith(`${root}${
|
|
15457
|
+
current = path18.join(current, segment);
|
|
15458
|
+
const stat = fs14.lstatSync(current);
|
|
15459
|
+
const real = fs14.realpathSync(current);
|
|
15460
|
+
if (real !== root && !real.startsWith(`${root}${path18.sep}`))
|
|
15337
15461
|
return failure2("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
|
|
15338
15462
|
fields: [
|
|
15339
15463
|
{
|
|
@@ -15349,7 +15473,7 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
|
15349
15473
|
if (current !== target && !stat.isDirectory())
|
|
15350
15474
|
return failure2("invalid_input", "document reference ancestor is not a directory");
|
|
15351
15475
|
}
|
|
15352
|
-
if (digestBytes(
|
|
15476
|
+
if (digestBytes(fs14.readFileSync(target)) !== reference.digest)
|
|
15353
15477
|
return failure2("permission_denied", "decision document bytes have changed");
|
|
15354
15478
|
} catch {
|
|
15355
15479
|
return failure2("invalid_input", "decision document is unavailable");
|
|
@@ -15780,7 +15904,7 @@ Autonomy contract:
|
|
|
15780
15904
|
CLI first: if a step has one right answer, use the \`workit\` verb instead of
|
|
15781
15905
|
hand-running git, gh or glab (\`workit git|pr|ci|check|help\`;
|
|
15782
15906
|
else \`npx -y @brainervirus/workit-cli\`).
|
|
15783
|
-
Other checkout: \`workit --cwd <dir>
|
|
15907
|
+
Other checkout: \`workit <verb> --cwd <dir> …\` (verb first: host rules match it); one workit
|
|
15784
15908
|
verb per shell call, never \`cd <dir> && workit\`. zsh-safe: no \`echo =====\`
|
|
15785
15909
|
(\`printf '%s\\n' ---\`), quote globs and URLs, no variable named \`status\` or \`path\`.
|
|
15786
15910
|
Add Co-Authored-By via \`workit git commit --trailer "K: v" -- <paths>\`, never
|
|
@@ -15792,7 +15916,7 @@ authorizes an effect. Judge work once with
|
|
|
15792
15916
|
\`workit policy assess --judge risk= behavior= product-choice= plan=\`.
|
|
15793
15917
|
Across repositories, bind items to checkout, branch and endpoint, resolve
|
|
15794
15918
|
competing targets before a mutation, and reconcile an uncertain
|
|
15795
|
-
|
|
15919
|
+
effect before a retry.
|
|
15796
15920
|
|
|
15797
15921
|
Mid-task: never silently drop or resume an objective (workit-continue).
|
|
15798
15922
|
|
|
@@ -15934,21 +16058,21 @@ var exportDigest = (bundle) => sha256({
|
|
|
15934
16058
|
task: bundle.task
|
|
15935
16059
|
});
|
|
15936
16060
|
// packages/workit-core/src/core/route-intent.ts
|
|
15937
|
-
import { spawnSync as
|
|
16061
|
+
import { spawnSync as spawnSync12 } from "node:child_process";
|
|
15938
16062
|
import { existsSync as existsSync7 } from "node:fs";
|
|
15939
|
-
import
|
|
16063
|
+
import path21 from "node:path";
|
|
15940
16064
|
|
|
15941
16065
|
// packages/workit-core/src/hooks/raw-git.ts
|
|
15942
|
-
import { spawnSync as
|
|
16066
|
+
import { spawnSync as spawnSync11 } from "node:child_process";
|
|
15943
16067
|
import { createHash as createHash14 } from "node:crypto";
|
|
15944
|
-
import
|
|
16068
|
+
import fs16 from "node:fs";
|
|
15945
16069
|
import os4 from "node:os";
|
|
15946
|
-
import
|
|
16070
|
+
import path20 from "node:path";
|
|
15947
16071
|
|
|
15948
16072
|
// packages/workit-core/src/hooks/session-marker.ts
|
|
15949
16073
|
import { createHash as createHash13 } from "node:crypto";
|
|
15950
|
-
import
|
|
15951
|
-
import
|
|
16074
|
+
import fs15 from "node:fs";
|
|
16075
|
+
import path19 from "node:path";
|
|
15952
16076
|
var sessionMarkerFile = (input, session, store) => {
|
|
15953
16077
|
let dir = store;
|
|
15954
16078
|
if (dir === undefined) {
|
|
@@ -15958,13 +16082,13 @@ var sessionMarkerFile = (input, session, store) => {
|
|
|
15958
16082
|
dir = location.dir;
|
|
15959
16083
|
}
|
|
15960
16084
|
const name = createHash13("sha256").update(`${input.host}\x00${session}`).digest("hex").slice(0, 32);
|
|
15961
|
-
return
|
|
16085
|
+
return path19.join(dir, "hooks", `skills-${name}.json`);
|
|
15962
16086
|
};
|
|
15963
16087
|
var list2 = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
15964
16088
|
var EMPTY = { nudged: [], loaded: [], raw: [], stops: [], judges: [] };
|
|
15965
16089
|
var readRaw = (file) => {
|
|
15966
16090
|
try {
|
|
15967
|
-
return
|
|
16091
|
+
return fs15.readFileSync(file, "utf8");
|
|
15968
16092
|
} catch {
|
|
15969
16093
|
return "";
|
|
15970
16094
|
}
|
|
@@ -15985,13 +16109,13 @@ var parseMarker = (text) => {
|
|
|
15985
16109
|
};
|
|
15986
16110
|
var readSessionMarker = (file) => parseMarker(readRaw(file));
|
|
15987
16111
|
var writeAtomic = (file, marker) => {
|
|
15988
|
-
|
|
16112
|
+
fs15.mkdirSync(path19.dirname(file), { recursive: true });
|
|
15989
16113
|
const temp = `${file}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
15990
16114
|
try {
|
|
15991
|
-
|
|
15992
|
-
|
|
16115
|
+
fs15.writeFileSync(temp, JSON.stringify(marker));
|
|
16116
|
+
fs15.renameSync(temp, file);
|
|
15993
16117
|
} catch (error) {
|
|
15994
|
-
|
|
16118
|
+
fs15.rmSync(temp, { force: true });
|
|
15995
16119
|
throw error;
|
|
15996
16120
|
}
|
|
15997
16121
|
};
|
|
@@ -16272,6 +16396,18 @@ var substitutions = (body) => {
|
|
|
16272
16396
|
};
|
|
16273
16397
|
var PLAIN_POSIX = /[^\s'"\\`$(){},<>|;&#]+/uy;
|
|
16274
16398
|
var PLAIN_POWERSHELL = /[^\s'"`(){}<>|;&#]+/uy;
|
|
16399
|
+
var expandsInQuotes = (body, dialect) => {
|
|
16400
|
+
const escape = dialect === "posix" ? "\\" : "`";
|
|
16401
|
+
for (let index = 0;index < body.length; index++) {
|
|
16402
|
+
const char = body[index];
|
|
16403
|
+
if (char === escape)
|
|
16404
|
+
index++;
|
|
16405
|
+
else if (char === "$" || dialect === "posix" && char === "`")
|
|
16406
|
+
return true;
|
|
16407
|
+
}
|
|
16408
|
+
return false;
|
|
16409
|
+
};
|
|
16410
|
+
var EXPANDING_RUN = /[*?[]/u;
|
|
16275
16411
|
var arithmeticEnd = (command, start) => {
|
|
16276
16412
|
const end = substitutionEnd(command, start);
|
|
16277
16413
|
return command[end - 1] === ")" && end > start + 2 ? end : 0;
|
|
@@ -16290,6 +16426,7 @@ var tokenize = (command, dialect) => {
|
|
|
16290
16426
|
let current = { words: [], redirects: [] };
|
|
16291
16427
|
let word = "";
|
|
16292
16428
|
let inWord = false;
|
|
16429
|
+
let expanding = false;
|
|
16293
16430
|
let pending = null;
|
|
16294
16431
|
const heredocs = [];
|
|
16295
16432
|
const escape = posix ? "\\" : "`";
|
|
@@ -16304,11 +16441,16 @@ var tokenize = (command, dialect) => {
|
|
|
16304
16441
|
heredocs.push({ delimiter: text.replace(/^-/, ""), owner: current });
|
|
16305
16442
|
else if (pending === "herestring")
|
|
16306
16443
|
(current.stdin ??= []).push(text);
|
|
16307
|
-
else if (pending !== "input")
|
|
16444
|
+
else if (pending !== "input") {
|
|
16445
|
+
const at = current.words.length;
|
|
16308
16446
|
current.words.push(...MARKERS.test(word) ? expandBraces(word) : [word]);
|
|
16447
|
+
if (expanding || !posix && word.startsWith("@"))
|
|
16448
|
+
(current.expands ??= []).push(...current.words.slice(at).map((_, i) => at + i));
|
|
16449
|
+
}
|
|
16309
16450
|
pending = null;
|
|
16310
16451
|
word = "";
|
|
16311
16452
|
inWord = false;
|
|
16453
|
+
expanding = false;
|
|
16312
16454
|
};
|
|
16313
16455
|
const flushSegment = () => {
|
|
16314
16456
|
flushWord();
|
|
@@ -16323,6 +16465,7 @@ var tokenize = (command, dialect) => {
|
|
|
16323
16465
|
};
|
|
16324
16466
|
const substitution = (inner, text) => {
|
|
16325
16467
|
(current.subs ??= []).push(inner);
|
|
16468
|
+
expanding = true;
|
|
16326
16469
|
word += text;
|
|
16327
16470
|
inWord = true;
|
|
16328
16471
|
};
|
|
@@ -16333,6 +16476,8 @@ var tokenize = (command, dialect) => {
|
|
|
16333
16476
|
if (run) {
|
|
16334
16477
|
word += run[0];
|
|
16335
16478
|
inWord = true;
|
|
16479
|
+
if (EXPANDING_RUN.test(run[0]))
|
|
16480
|
+
expanding = true;
|
|
16336
16481
|
index += run[0].length - 1;
|
|
16337
16482
|
continue;
|
|
16338
16483
|
}
|
|
@@ -16361,6 +16506,8 @@ var tokenize = (command, dialect) => {
|
|
|
16361
16506
|
const body = command.slice(index + 1, end < 0 ? undefined : end);
|
|
16362
16507
|
if (posix && char === '"' && /\$\(|`/.test(body))
|
|
16363
16508
|
(current.subs ??= []).push(...substitutions(body));
|
|
16509
|
+
if (char === '"' && expandsInQuotes(body, dialect))
|
|
16510
|
+
expanding = true;
|
|
16364
16511
|
word += unquote(body, char, dialect);
|
|
16365
16512
|
inWord = true;
|
|
16366
16513
|
index = end < 0 ? command.length : end;
|
|
@@ -16406,6 +16553,8 @@ var tokenize = (command, dialect) => {
|
|
|
16406
16553
|
if (char === "(" && inWord && word.endsWith("$")) {
|
|
16407
16554
|
word = word.slice(0, -1);
|
|
16408
16555
|
inWord = word !== "";
|
|
16556
|
+
if (!inWord)
|
|
16557
|
+
expanding = false;
|
|
16409
16558
|
}
|
|
16410
16559
|
group(char === "(" ? "open" : "close");
|
|
16411
16560
|
} else if (!posix && (char === "{" || char === "}")) {
|
|
@@ -16459,6 +16608,8 @@ var tokenize = (command, dialect) => {
|
|
|
16459
16608
|
else if (posix && char === "}" && word.includes(OPEN))
|
|
16460
16609
|
word += CLOSE;
|
|
16461
16610
|
else {
|
|
16611
|
+
if (char === "$")
|
|
16612
|
+
expanding = true;
|
|
16462
16613
|
word += char;
|
|
16463
16614
|
inWord = true;
|
|
16464
16615
|
}
|
|
@@ -16513,17 +16664,17 @@ var GIT_VALUE_OPTIONS = new Set([
|
|
|
16513
16664
|
]);
|
|
16514
16665
|
var GIT_CONFIG_OPTIONS = new Set(["-c", "--config-env", "--git-dir"]);
|
|
16515
16666
|
var GIT_CONFIG_ENV = /^GIT_(?:CONFIG\w*|DIR|COMMON_DIR|WORK_TREE)$/;
|
|
16516
|
-
var
|
|
16667
|
+
var ASSIGNMENT = /^([A-Za-z_]\w*)=([\s\S]*)$/;
|
|
16517
16668
|
var MAX_DEPTH = 4;
|
|
16518
16669
|
var MAX_EXPANSIONS = 16;
|
|
16519
|
-
var isAbsolute = (value) =>
|
|
16670
|
+
var isAbsolute = (value) => path20.isAbsolute(value) || /^[A-Za-z]:[\\/]/.test(value) || value.startsWith("\\\\");
|
|
16520
16671
|
var HERE = /^(?:\$PWD|\$\{PWD\}|\$\(\s*pwd\s*\)|`pwd`|\$\(\s*git\s+rev-parse\s+--show-toplevel\s*\))(?=$|[\\/])/;
|
|
16521
16672
|
var joinDir = (base, raw) => {
|
|
16522
16673
|
const next = raw.replace(HERE, ".");
|
|
16523
16674
|
if (base === undefined || /[$`*?]/.test(next) || next === "-")
|
|
16524
16675
|
return;
|
|
16525
|
-
const expanded = next === "~" || next.startsWith("~/") ?
|
|
16526
|
-
return base === null || isAbsolute(expanded) ? expanded :
|
|
16676
|
+
const expanded = next === "~" || next.startsWith("~/") ? path20.join(os4.homedir(), next.slice(1)) : next;
|
|
16677
|
+
return base === null || isAbsolute(expanded) ? expanded : path20.join(base, expanded);
|
|
16527
16678
|
};
|
|
16528
16679
|
var rebase = (base, inner) => inner === null ? base : inner === undefined ? undefined : joinDir(base, inner);
|
|
16529
16680
|
var cdTarget = (words) => {
|
|
@@ -16540,7 +16691,7 @@ var stripPrefixes = (input) => {
|
|
|
16540
16691
|
const first = words[0];
|
|
16541
16692
|
if (first === undefined)
|
|
16542
16693
|
return { words, env };
|
|
16543
|
-
const assignment =
|
|
16694
|
+
const assignment = ASSIGNMENT.exec(first);
|
|
16544
16695
|
if (assignment) {
|
|
16545
16696
|
if (GIT_CONFIG_ENV.test(assignment[1]))
|
|
16546
16697
|
env[assignment[1]] = assignment[2];
|
|
@@ -16599,10 +16750,10 @@ var assignments = (words, vars) => {
|
|
|
16599
16750
|
while (rest[0]?.startsWith("-"))
|
|
16600
16751
|
rest = rest.slice(1);
|
|
16601
16752
|
}
|
|
16602
|
-
if (rest.length === 0 || !rest.every((word) =>
|
|
16753
|
+
if (rest.length === 0 || !rest.every((word) => ASSIGNMENT.test(word)))
|
|
16603
16754
|
return null;
|
|
16604
16755
|
return rest.map((word) => {
|
|
16605
|
-
const [, name, value] =
|
|
16756
|
+
const [, name, value] = ASSIGNMENT.exec(word);
|
|
16606
16757
|
return [name, expandWord(value, vars)];
|
|
16607
16758
|
});
|
|
16608
16759
|
};
|
|
@@ -16790,7 +16941,7 @@ var splitString = (line) => {
|
|
|
16790
16941
|
return line.slice(index).join(" ").slice(15);
|
|
16791
16942
|
if (/^-S./.test(word))
|
|
16792
16943
|
return [word.slice(2), ...line.slice(index + 1)].join(" ");
|
|
16793
|
-
if (!word.startsWith("-") && !
|
|
16944
|
+
if (!word.startsWith("-") && !ASSIGNMENT.test(word))
|
|
16794
16945
|
return null;
|
|
16795
16946
|
}
|
|
16796
16947
|
return null;
|
|
@@ -16903,8 +17054,17 @@ function walk2(command, dialect, depth, seen) {
|
|
|
16903
17054
|
visit(next, undefined, hops + 1);
|
|
16904
17055
|
};
|
|
16905
17056
|
const wrapped = seen ? null : workitCheckTail(words);
|
|
16906
|
-
if (wrapped)
|
|
16907
|
-
|
|
17057
|
+
if (wrapped) {
|
|
17058
|
+
const outer = dir;
|
|
17059
|
+
if (wrapped.cwd !== null)
|
|
17060
|
+
dir = joinDir(dir, wrapped.cwd);
|
|
17061
|
+
if ("script" in wrapped)
|
|
17062
|
+
nested(wrapped.script, "posix");
|
|
17063
|
+
else
|
|
17064
|
+
runs([wrapped.argv]);
|
|
17065
|
+
dir = outer;
|
|
17066
|
+
return;
|
|
17067
|
+
}
|
|
16908
17068
|
if (CD.has(name)) {
|
|
16909
17069
|
const target = cdTarget(words);
|
|
16910
17070
|
dir = target === null ? os4.homedir() : joinDir(dir, target);
|
|
@@ -17005,14 +17165,14 @@ function workitCheckTail(words) {
|
|
|
17005
17165
|
return null;
|
|
17006
17166
|
} else if (!/^workit(?:\.exe|\.cmd)?$/.test(commandName(words[0])))
|
|
17007
17167
|
return null;
|
|
17008
|
-
const args = words.slice(at + 1);
|
|
17168
|
+
const { rest: args, cwd } = skipWorkitGlobals(words.slice(at + 1));
|
|
17009
17169
|
if (args[0] !== "check" && !(args[0] === "lock" && args[1] === "run"))
|
|
17010
17170
|
return null;
|
|
17011
17171
|
const split = args.indexOf("--");
|
|
17012
17172
|
const tail = split < 0 ? [] : args.slice(split + 1);
|
|
17013
17173
|
if (!tail.length)
|
|
17014
17174
|
return null;
|
|
17015
|
-
return args.slice(0, split).includes("--shell") ? { script: tail.join(" ") } : { argv: tail };
|
|
17175
|
+
return args.slice(0, split).includes("--shell") ? { script: tail.join(" "), cwd } : { argv: tail, cwd };
|
|
17016
17176
|
}
|
|
17017
17177
|
function simpleCommand(command, dialect = "posix", depth = 0) {
|
|
17018
17178
|
let segments;
|
|
@@ -17073,7 +17233,21 @@ function skipWorkitGlobals(args) {
|
|
|
17073
17233
|
} else if (args[at] !== "--json")
|
|
17074
17234
|
break;
|
|
17075
17235
|
}
|
|
17076
|
-
|
|
17236
|
+
const rest = [];
|
|
17237
|
+
for (;at < args.length; at++) {
|
|
17238
|
+
const arg = args[at];
|
|
17239
|
+
if (arg === "--") {
|
|
17240
|
+
rest.push(...args.slice(at));
|
|
17241
|
+
break;
|
|
17242
|
+
}
|
|
17243
|
+
if (arg.startsWith("--cwd="))
|
|
17244
|
+
cwd = arg.slice("--cwd=".length);
|
|
17245
|
+
else if (arg === "--cwd" && args[at + 1] !== undefined)
|
|
17246
|
+
cwd = args[++at];
|
|
17247
|
+
else
|
|
17248
|
+
rest.push(arg);
|
|
17249
|
+
}
|
|
17250
|
+
return { rest, cwd };
|
|
17077
17251
|
}
|
|
17078
17252
|
var PUSH_VALUE_OPTIONS = new Set(["-o", "--push-option", "--receive-pack", "--exec", "--repo"]);
|
|
17079
17253
|
var CURRENT_BRANCH = /^\$\(\s*git\s+(?:branch\s+--show-current|rev-parse\s+--abbrev-ref\s+HEAD|symbolic-ref\s+--short\s+HEAD)\s*\)$/;
|
|
@@ -17353,7 +17527,7 @@ var repoAt = (dir) => {
|
|
|
17353
17527
|
return { location, dir, branch: resolveTaskKey(dir, location).branch };
|
|
17354
17528
|
};
|
|
17355
17529
|
var isWorkitWorkspace = (repo) => {
|
|
17356
|
-
if (
|
|
17530
|
+
if (fs16.existsSync(repo.location.dir))
|
|
17357
17531
|
return true;
|
|
17358
17532
|
try {
|
|
17359
17533
|
return resolveRuntimeWorkspaceVcs(repo.location.top ?? repo.dir) !== null;
|
|
@@ -17374,17 +17548,17 @@ var headSha2 = (repo) => {
|
|
|
17374
17548
|
if (!git || git.reftable)
|
|
17375
17549
|
return null;
|
|
17376
17550
|
try {
|
|
17377
|
-
const head =
|
|
17551
|
+
const head = fs16.readFileSync(path20.join(git.gitDir, "HEAD"), "utf8").trim();
|
|
17378
17552
|
if (/^[0-9a-f]{40,64}$/.test(head))
|
|
17379
17553
|
return head;
|
|
17380
17554
|
const ref = /^ref: (refs\/.+)$/.exec(head)?.[1];
|
|
17381
17555
|
if (!ref)
|
|
17382
17556
|
return null;
|
|
17383
|
-
const common =
|
|
17557
|
+
const common = path20.dirname(repo.location.dir);
|
|
17384
17558
|
try {
|
|
17385
|
-
return
|
|
17559
|
+
return fs16.readFileSync(path20.join(common, ref), "utf8").trim() || null;
|
|
17386
17560
|
} catch {}
|
|
17387
|
-
const packed =
|
|
17561
|
+
const packed = fs16.readFileSync(path20.join(common, "packed-refs"), "utf8");
|
|
17388
17562
|
return new RegExp(`^([0-9a-f]{40,64}) ${ref.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`, "m").exec(packed)?.[1] ?? null;
|
|
17389
17563
|
} catch {
|
|
17390
17564
|
return null;
|
|
@@ -17396,7 +17570,7 @@ var gitConfig = (dir, invocation, cache) => {
|
|
|
17396
17570
|
if (cached)
|
|
17397
17571
|
return cached;
|
|
17398
17572
|
const config = new Map;
|
|
17399
|
-
const listed =
|
|
17573
|
+
const listed = spawnSync11("git", [...invocation.config ?? [], "config", "-z", "--list"], {
|
|
17400
17574
|
cwd: dir,
|
|
17401
17575
|
encoding: "utf8",
|
|
17402
17576
|
timeout: 3000,
|
|
@@ -17472,8 +17646,8 @@ var expandGitAliases = (root, invocation, cache = new Map, depth = 0) => {
|
|
|
17472
17646
|
const sub = invocation.args[0];
|
|
17473
17647
|
if (invocation.tool !== "git" || invocation.dir === undefined || !sub || sub.startsWith("-") || GIT_COMMANDS.has(sub) || depth >= MAX_DEPTH)
|
|
17474
17648
|
return [invocation];
|
|
17475
|
-
const dir =
|
|
17476
|
-
if (!
|
|
17649
|
+
const dir = path20.resolve(root, invocation.dir ?? ".");
|
|
17650
|
+
if (!fs16.existsSync(dir))
|
|
17477
17651
|
return [invocation];
|
|
17478
17652
|
const config = gitConfig(dir, invocation, cache);
|
|
17479
17653
|
const alias = configValue(config, `alias.${sub.toLowerCase()}`);
|
|
@@ -17495,11 +17669,11 @@ var expandGitAliases = (root, invocation, cache = new Map, depth = 0) => {
|
|
|
17495
17669
|
var normalizedUrl = (url, dir) => {
|
|
17496
17670
|
const trimmed = url.trim();
|
|
17497
17671
|
if (/^file:\/\//i.test(trimmed))
|
|
17498
|
-
return
|
|
17672
|
+
return path20.resolve(dir, trimmed.slice(7)).replace(/\/+$/, "").replace(/\.git$/, "");
|
|
17499
17673
|
const scheme = /^[a-z][a-z0-9+.-]*:\/\//i.exec(trimmed);
|
|
17500
17674
|
const scp = !scheme && /^(?:[^@/\\]+@)?[^/\\:]{2,}:/.test(trimmed);
|
|
17501
17675
|
if (!scheme && !scp)
|
|
17502
|
-
return
|
|
17676
|
+
return path20.resolve(dir, trimmed).replace(/\/+$/, "").replace(/\.git$/, "");
|
|
17503
17677
|
const value = scheme ? trimmed.slice(scheme[0].length).replace(/^[^@/]*@/, "").replace(/^([^/:]+):\d+(?=\/)/, "$1") : trimmed.replace(/^[^@/]*@/, "").replace(":", "/");
|
|
17504
17678
|
return value.toLowerCase().replace(/\/{2,}/g, "/").replace(/^www\./, "").replace(/\/+$/, "").replace(/\.git$/, "");
|
|
17505
17679
|
};
|
|
@@ -17519,7 +17693,7 @@ var rewrittenUrl = (url, config) => {
|
|
|
17519
17693
|
}
|
|
17520
17694
|
return url;
|
|
17521
17695
|
};
|
|
17522
|
-
var
|
|
17696
|
+
var pushUrl = (config, remote, dir) => {
|
|
17523
17697
|
const configured = configValue(config, `remote.${remote}.pushurl`) ?? configValue(config, `remote.${remote}.url`);
|
|
17524
17698
|
return normalizedUrl(rewrittenUrl(configured ?? remote, config), dir);
|
|
17525
17699
|
};
|
|
@@ -17547,7 +17721,7 @@ var toPushRemote = (repo, remote, config) => {
|
|
|
17547
17721
|
const pushRemote = pushRemoteOf(config, repo.branch);
|
|
17548
17722
|
if (pushRemote === null || pushRemote === remote)
|
|
17549
17723
|
return true;
|
|
17550
|
-
return
|
|
17724
|
+
return pushUrl(config, remote, repo.dir) === pushUrl(config, pushRemote, repo.dir);
|
|
17551
17725
|
};
|
|
17552
17726
|
var quote = (text) => `\`${text}\``;
|
|
17553
17727
|
var refusal = (action, branch, dir) => {
|
|
@@ -17560,7 +17734,7 @@ var refusal = (action, branch, dir) => {
|
|
|
17560
17734
|
};
|
|
17561
17735
|
var refFacts = (repo, remote) => {
|
|
17562
17736
|
const facts = { heads: new Set, tags: new Set, remoteHeads: new Set };
|
|
17563
|
-
const listed =
|
|
17737
|
+
const listed = spawnSync11("git", ["for-each-ref", "--format=%(refname)", "refs/heads", "refs/tags", `refs/remotes/${remote}`], { cwd: repo.dir, encoding: "utf8", timeout: 5000 });
|
|
17564
17738
|
const remotePrefix = `refs/remotes/${remote}/`;
|
|
17565
17739
|
for (const ref of (listed.stdout ?? "").split(`
|
|
17566
17740
|
`)) {
|
|
@@ -17577,7 +17751,7 @@ var globBranches = (repo, glob) => {
|
|
|
17577
17751
|
const source = glob.source.startsWith("refs/") ? glob.source : `refs/heads/${glob.source}`;
|
|
17578
17752
|
const [sourcePrefix, sourceSuffix = ""] = source.split("*");
|
|
17579
17753
|
const [destinationPrefix, destinationSuffix = ""] = glob.destination.split("*");
|
|
17580
|
-
const listed =
|
|
17754
|
+
const listed = spawnSync11("git", [
|
|
17581
17755
|
"for-each-ref",
|
|
17582
17756
|
"--format=%(refname)",
|
|
17583
17757
|
sourcePrefix.slice(0, sourcePrefix.lastIndexOf("/") + 1)
|
|
@@ -17654,7 +17828,7 @@ var tagRefusal = (action, facts, dir) => {
|
|
|
17654
17828
|
return `workit: raw ${quote(`git push${action.allTags ? " --tags" : ""}`)} of ${tags.length ? `tag ${tags.join(", ")}` : "tags"} bypasses the release grant. Push a release tag with ${quote(`workit git push --release ${named}`)} (${shape} tags whose commit the production branch already has; a tag never moves), under the ${quote("release")} grant (if refused, ask the user to run ${quote("workit grant set <workspace> release=true")}).`;
|
|
17655
17829
|
};
|
|
17656
17830
|
var remoteHeads = (repo, remote) => {
|
|
17657
|
-
const listed =
|
|
17831
|
+
const listed = spawnSync11("git", ["ls-remote", "--heads", remote], {
|
|
17658
17832
|
cwd: repo.dir,
|
|
17659
17833
|
encoding: "utf8",
|
|
17660
17834
|
timeout: 3000,
|
|
@@ -17723,7 +17897,7 @@ var actionsIn = (cwd, command, dialect, cache = new Map) => {
|
|
|
17723
17897
|
const out = [];
|
|
17724
17898
|
const repos = new Map;
|
|
17725
17899
|
const repoFor = (relative) => {
|
|
17726
|
-
const dir =
|
|
17900
|
+
const dir = path20.resolve(cwd, relative ?? ".");
|
|
17727
17901
|
if (!repos.has(dir)) {
|
|
17728
17902
|
const repo = repoAt(dir);
|
|
17729
17903
|
repos.set(dir, repo && isWorkitWorkspace(repo) ? repo : null);
|
|
@@ -17809,7 +17983,7 @@ var analysisDenial = (error) => ({
|
|
|
17809
17983
|
});
|
|
17810
17984
|
var NEXT_COMMAND = "next";
|
|
17811
17985
|
var MARKER_TTL_SECONDS = 3600;
|
|
17812
|
-
var markerFile = (repo, session, key) =>
|
|
17986
|
+
var markerFile = (repo, session, key) => path20.join(repo.location.dir, "hooks", `commit-${createHash14("sha256").update(`${session}\x00${key}`).digest("hex").slice(0, 32)}.json`);
|
|
17813
17987
|
var callKey = (toolUseId, command) => toolUseId ?? `cmd:${createHash14("sha256").update(command).digest("hex")}`;
|
|
17814
17988
|
function noteRawCommit(input, command, key) {
|
|
17815
17989
|
try {
|
|
@@ -17820,14 +17994,14 @@ function noteRawCommit(input, command, key) {
|
|
|
17820
17994
|
if (action.kind !== "commit")
|
|
17821
17995
|
continue;
|
|
17822
17996
|
const file = markerFile(repo, input.session.id, key);
|
|
17823
|
-
|
|
17824
|
-
|
|
17997
|
+
fs16.mkdirSync(path20.dirname(file), { recursive: true });
|
|
17998
|
+
fs16.writeFileSync(file, `${JSON.stringify({ head: headSha2(repo), at })}
|
|
17825
17999
|
`);
|
|
17826
18000
|
}
|
|
17827
18001
|
} catch {}
|
|
17828
18002
|
}
|
|
17829
18003
|
var headCommit = (dir) => {
|
|
17830
|
-
const run =
|
|
18004
|
+
const run = spawnSync11("git", [
|
|
17831
18005
|
"log",
|
|
17832
18006
|
"-1",
|
|
17833
18007
|
"--format=%H%x1f%ct%x1f%s%x1f%(trailers:key=Workit-Session,valueonly,separator=%x2c)"
|
|
@@ -17844,11 +18018,11 @@ var headCommit = (dir) => {
|
|
|
17844
18018
|
var settle2 = (input, repo, file) => {
|
|
17845
18019
|
let marker;
|
|
17846
18020
|
try {
|
|
17847
|
-
marker = JSON.parse(
|
|
18021
|
+
marker = JSON.parse(fs16.readFileSync(file, "utf8"));
|
|
17848
18022
|
} catch {
|
|
17849
18023
|
return;
|
|
17850
18024
|
}
|
|
17851
|
-
|
|
18025
|
+
fs16.rmSync(file, { force: true });
|
|
17852
18026
|
const session = input.session.id;
|
|
17853
18027
|
const now = Date.now() / 1000;
|
|
17854
18028
|
if (typeof marker.at !== "number" || now - marker.at > MARKER_TTL_SECONDS)
|
|
@@ -17897,7 +18071,7 @@ function settlePendingCommit(input) {
|
|
|
17897
18071
|
if (!repo)
|
|
17898
18072
|
return;
|
|
17899
18073
|
const file = markerFile(repo, input.session.id, NEXT_COMMAND);
|
|
17900
|
-
if (
|
|
18074
|
+
if (fs16.existsSync(file))
|
|
17901
18075
|
settle2(input, repo, file);
|
|
17902
18076
|
} catch {}
|
|
17903
18077
|
}
|
|
@@ -18041,7 +18215,7 @@ var targetsOf = (invocations) => invocations.flatMap((invocation) => invocation.
|
|
|
18041
18215
|
branch,
|
|
18042
18216
|
dir: invocation.dir
|
|
18043
18217
|
})) : []);
|
|
18044
|
-
var localBranchExists = (dir, branch) =>
|
|
18218
|
+
var localBranchExists = (dir, branch) => spawnSync12("git", ["show-ref", "--verify", "--quiet", `refs/heads/${branch}`], {
|
|
18045
18219
|
cwd: dir,
|
|
18046
18220
|
stdio: "ignore",
|
|
18047
18221
|
timeout: 5000
|
|
@@ -18051,7 +18225,7 @@ var judgedDir = (root, dir) => {
|
|
|
18051
18225
|
return null;
|
|
18052
18226
|
if (dir === null)
|
|
18053
18227
|
return root;
|
|
18054
|
-
const resolved =
|
|
18228
|
+
const resolved = path21.resolve(root, dir);
|
|
18055
18229
|
return existsSync7(resolved) && inWorkitWorkspace(resolved) ? resolved : null;
|
|
18056
18230
|
};
|
|
18057
18231
|
var shellBranchPolicyViolation = (root, command, dialect = "posix") => {
|
|
@@ -18287,7 +18461,7 @@ var judgeInputSummary = (raw) => {
|
|
|
18287
18461
|
|
|
18288
18462
|
// packages/workit-core/src/autonomy.ts
|
|
18289
18463
|
import os5 from "node:os";
|
|
18290
|
-
import
|
|
18464
|
+
import path22 from "node:path";
|
|
18291
18465
|
var accountHome = () => {
|
|
18292
18466
|
try {
|
|
18293
18467
|
return os5.userInfo().homedir;
|
|
@@ -18296,10 +18470,10 @@ var accountHome = () => {
|
|
|
18296
18470
|
}
|
|
18297
18471
|
};
|
|
18298
18472
|
var grantsHome = { resolve: accountHome };
|
|
18299
|
-
var grantsDir = () =>
|
|
18473
|
+
var grantsDir = () => path22.join(grantsHome.resolve(), ".config", "workit");
|
|
18300
18474
|
var grantsOverride = () => {
|
|
18301
|
-
const configured =
|
|
18302
|
-
return configured ===
|
|
18475
|
+
const configured = path22.resolve(resolveConfigDir());
|
|
18476
|
+
return configured === path22.resolve(grantsDir()) ? null : `the config directory is redirected to ${configured} (WORKFLOW_TOOLKIT_CONFIG, WORKFLOW_TOOLKIT_CONFIG_DIR or XDG_CONFIG_HOME); grants are read only from ${path22.join(grantsDir(), "workspaces.json")}, so the D4 defaults apply`;
|
|
18303
18477
|
};
|
|
18304
18478
|
var GRANT_KINDS = ["push", "pr", "merge", "release", "rerun"];
|
|
18305
18479
|
var DEFAULT_GRANTS = Object.freeze({
|
|
@@ -20108,9 +20282,9 @@ class WorkitCore {
|
|
|
20108
20282
|
}
|
|
20109
20283
|
}
|
|
20110
20284
|
// packages/workit-core/src/core/repo-context.ts
|
|
20111
|
-
import { spawnSync as
|
|
20285
|
+
import { spawnSync as spawnSync13 } from "node:child_process";
|
|
20112
20286
|
import { existsSync as existsSync8, readFileSync as readFileSync6, readdirSync as readdirSync4 } from "node:fs";
|
|
20113
|
-
import
|
|
20287
|
+
import path23 from "node:path";
|
|
20114
20288
|
var isSafeContextRange = (value) => value.length <= 4096 && (() => {
|
|
20115
20289
|
try {
|
|
20116
20290
|
gitRevisionParts(value);
|
|
@@ -20137,7 +20311,7 @@ var invalidRange = (cwd) => ({
|
|
|
20137
20311
|
cwd
|
|
20138
20312
|
});
|
|
20139
20313
|
var runGit = (cwd, args) => {
|
|
20140
|
-
const result =
|
|
20314
|
+
const result = spawnSync13("git", args, { cwd, encoding: "utf8" });
|
|
20141
20315
|
return {
|
|
20142
20316
|
stdout: (result.stdout ?? "").trimEnd(),
|
|
20143
20317
|
stderr: (result.stderr ?? "").trimEnd(),
|
|
@@ -20244,7 +20418,7 @@ var findPrTemplate = (cwd) => {
|
|
|
20244
20418
|
]) {
|
|
20245
20419
|
const parts = rel.split("/");
|
|
20246
20420
|
const base = parts[parts.length - 1];
|
|
20247
|
-
const dir = parts.length > 1 ?
|
|
20421
|
+
const dir = parts.length > 1 ? path23.join(cwd, ...parts.slice(0, -1)) : cwd;
|
|
20248
20422
|
const actual = probeCaseInsensitive(dir, base);
|
|
20249
20423
|
if (actual)
|
|
20250
20424
|
return [...parts.slice(0, -1), actual].join("/");
|
|
@@ -20275,7 +20449,7 @@ var diffStatForRange = (cwd, range) => {
|
|
|
20275
20449
|
return runGit(cwd, ["diff", "--stat", "--"]).stdout;
|
|
20276
20450
|
};
|
|
20277
20451
|
var packageScripts = (cwd, keys) => {
|
|
20278
|
-
const pkgPath =
|
|
20452
|
+
const pkgPath = path23.join(cwd, "package.json");
|
|
20279
20453
|
if (!existsSync8(pkgPath))
|
|
20280
20454
|
return [];
|
|
20281
20455
|
try {
|
|
@@ -20301,9 +20475,9 @@ var documentationFiles = (cwd) => {
|
|
|
20301
20475
|
if (entry.isDirectory()) {
|
|
20302
20476
|
if (excluded.has(entry.name))
|
|
20303
20477
|
continue;
|
|
20304
|
-
walk(
|
|
20478
|
+
walk(path23.join(dir, entry.name), depth + 1);
|
|
20305
20479
|
} else if (entry.isFile() && (entry.name === "README.md" || entry.name.endsWith(".md"))) {
|
|
20306
|
-
matches.push("./" +
|
|
20480
|
+
matches.push("./" + path23.relative(cwd, path23.join(dir, entry.name)).replaceAll("\\", "/"));
|
|
20307
20481
|
}
|
|
20308
20482
|
}
|
|
20309
20483
|
};
|
|
@@ -20319,7 +20493,7 @@ var readTrimmed2 = (file, maxLines) => {
|
|
|
20319
20493
|
`);
|
|
20320
20494
|
};
|
|
20321
20495
|
function prReadyContext(root, range) {
|
|
20322
|
-
const cwd =
|
|
20496
|
+
const cwd = path23.resolve(repoRoot(root));
|
|
20323
20497
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
20324
20498
|
return invalidRange(cwd);
|
|
20325
20499
|
let stdout = "";
|
|
@@ -20377,7 +20551,7 @@ ${status.stderr}` : "");
|
|
|
20377
20551
|
stdout += `template_path: ${template}
|
|
20378
20552
|
|
|
20379
20553
|
`;
|
|
20380
|
-
stdout += readTrimmed2(
|
|
20554
|
+
stdout += readTrimmed2(path23.join(cwd, template), 220);
|
|
20381
20555
|
} else {
|
|
20382
20556
|
stdout += `template_path: none
|
|
20383
20557
|
|
|
@@ -20387,7 +20561,7 @@ ${status.stderr}` : "");
|
|
|
20387
20561
|
stdout += `
|
|
20388
20562
|
`;
|
|
20389
20563
|
stdout += printSection("Recent Validation Signals");
|
|
20390
|
-
if (existsSync8(
|
|
20564
|
+
if (existsSync8(path23.join(cwd, "package.json"))) {
|
|
20391
20565
|
stdout += `package.json detected. Common scripts:
|
|
20392
20566
|
`;
|
|
20393
20567
|
const found = packageScripts(cwd, ["lint", "format:check", "test", "build"]);
|
|
@@ -20396,7 +20570,7 @@ ${status.stderr}` : "");
|
|
|
20396
20570
|
`) + `
|
|
20397
20571
|
`;
|
|
20398
20572
|
}
|
|
20399
|
-
if (existsSync8(
|
|
20573
|
+
if (existsSync8(path23.join(cwd, "Cargo.toml")) || existsSync8(path23.join(cwd, "src-tauri/Cargo.toml"))) {
|
|
20400
20574
|
stdout += `Rust project detected.
|
|
20401
20575
|
`;
|
|
20402
20576
|
}
|
|
@@ -20429,7 +20603,7 @@ var CHANGELOG_RULES = `- Use an [Unreleased] section.
|
|
|
20429
20603
|
- Apply with the host-native editor, preserving the repository changelog conventions.
|
|
20430
20604
|
- If Unreleased already has duplicate category headings, normalize_only first.`;
|
|
20431
20605
|
function changelogContext(root, range) {
|
|
20432
|
-
const cwd =
|
|
20606
|
+
const cwd = path23.resolve(repoRoot(root));
|
|
20433
20607
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
20434
20608
|
return invalidRange(cwd);
|
|
20435
20609
|
const resolvedRange = rangeArgOrDefault(range, cwd);
|
|
@@ -20447,7 +20621,7 @@ function changelogContext(root, range) {
|
|
|
20447
20621
|
stdout += CHANGELOG_RULES + `
|
|
20448
20622
|
`;
|
|
20449
20623
|
stdout += printSection("Existing CHANGELOG.md");
|
|
20450
|
-
const changelogPath =
|
|
20624
|
+
const changelogPath = path23.join(cwd, "CHANGELOG.md");
|
|
20451
20625
|
if (existsSync8(changelogPath)) {
|
|
20452
20626
|
stdout += readTrimmed2(changelogPath, 260) + `
|
|
20453
20627
|
`;
|
|
@@ -20467,7 +20641,7 @@ function changelogContext(root, range) {
|
|
|
20467
20641
|
return { stdout, stderr: "", exitCode: 0, cwd };
|
|
20468
20642
|
}
|
|
20469
20643
|
function docsRefreshContext(root, range) {
|
|
20470
|
-
const cwd =
|
|
20644
|
+
const cwd = path23.resolve(repoRoot(root));
|
|
20471
20645
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
20472
20646
|
return invalidRange(cwd);
|
|
20473
20647
|
const resolvedRange = rangeArgOrDefault(range, cwd);
|
|
@@ -20489,7 +20663,7 @@ function docsRefreshContext(root, range) {
|
|
|
20489
20663
|
`) + `
|
|
20490
20664
|
`;
|
|
20491
20665
|
stdout += printSection("README Preview");
|
|
20492
|
-
const readme =
|
|
20666
|
+
const readme = path23.join(cwd, "README.md");
|
|
20493
20667
|
if (existsSync8(readme)) {
|
|
20494
20668
|
stdout += readTrimmed2(readme, 220) + `
|
|
20495
20669
|
`;
|
|
@@ -20498,7 +20672,7 @@ function docsRefreshContext(root, range) {
|
|
|
20498
20672
|
`;
|
|
20499
20673
|
}
|
|
20500
20674
|
stdout += printSection("Package Scripts");
|
|
20501
|
-
const pkgPath =
|
|
20675
|
+
const pkgPath = path23.join(cwd, "package.json");
|
|
20502
20676
|
if (existsSync8(pkgPath)) {
|
|
20503
20677
|
try {
|
|
20504
20678
|
const pkg = JSON.parse(readFileSync6(pkgPath, "utf8"));
|
|
@@ -20512,7 +20686,7 @@ function docsRefreshContext(root, range) {
|
|
|
20512
20686
|
return { stdout, stderr: "", exitCode: 0, cwd };
|
|
20513
20687
|
}
|
|
20514
20688
|
function releaseNotesContext(root, rangeOrTag) {
|
|
20515
|
-
const cwd =
|
|
20689
|
+
const cwd = path23.resolve(repoRoot(root));
|
|
20516
20690
|
if (!rangeOrTag) {
|
|
20517
20691
|
return { stdout: "", stderr: `ERROR: release tag or range required
|
|
20518
20692
|
`, exitCode: 1, cwd };
|
|
@@ -20550,7 +20724,7 @@ function releaseNotesContext(root, rangeOrTag) {
|
|
|
20550
20724
|
`;
|
|
20551
20725
|
stdout += printSection("Existing Release Files");
|
|
20552
20726
|
for (const rel of ["CHANGELOG.md", "RELEASE_NOTES.md", ".github/releases.md"]) {
|
|
20553
|
-
if (existsSync8(
|
|
20727
|
+
if (existsSync8(path23.join(cwd, rel)))
|
|
20554
20728
|
stdout += rel + `
|
|
20555
20729
|
`;
|
|
20556
20730
|
}
|
|
@@ -20579,6 +20753,46 @@ function releaseNotesContext(root, rangeOrTag) {
|
|
|
20579
20753
|
|
|
20580
20754
|
// packages/workit-core/src/core/tracker-issues.ts
|
|
20581
20755
|
import { spawnSync as spawnSync15 } from "node:child_process";
|
|
20756
|
+
|
|
20757
|
+
// packages/workit-core/src/core/pr-create.ts
|
|
20758
|
+
import fs17 from "node:fs";
|
|
20759
|
+
import path24 from "node:path";
|
|
20760
|
+
import { spawnSync as spawnSync14 } from "node:child_process";
|
|
20761
|
+
// packages/workit-core/src/forge/pr-body.ts
|
|
20762
|
+
function parseGhIssue(value) {
|
|
20763
|
+
const m = /issues\/(\d+)/.exec(value);
|
|
20764
|
+
return m ? m[1] : String(value).trim().replace(/^#/, "");
|
|
20765
|
+
}
|
|
20766
|
+
|
|
20767
|
+
// packages/workit-core/src/core/pr-create.ts
|
|
20768
|
+
function whichOnPath(tool) {
|
|
20769
|
+
const names = process.platform === "win32" ? [tool, `${tool}.exe`, `${tool}.cmd`] : [tool];
|
|
20770
|
+
for (const dir of (process.env.PATH ?? "").split(path24.delimiter)) {
|
|
20771
|
+
if (!dir)
|
|
20772
|
+
continue;
|
|
20773
|
+
for (const name of names) {
|
|
20774
|
+
const candidate = path24.join(dir, name);
|
|
20775
|
+
try {
|
|
20776
|
+
fs17.accessSync(candidate, fs17.constants.X_OK);
|
|
20777
|
+
return candidate;
|
|
20778
|
+
} catch {}
|
|
20779
|
+
}
|
|
20780
|
+
}
|
|
20781
|
+
return null;
|
|
20782
|
+
}
|
|
20783
|
+
var hostingCliAvailable = (provider) => whichOnPath(provider === "gitlab" ? "glab" : "gh") !== null;
|
|
20784
|
+
var pushRemote = (cwd) => {
|
|
20785
|
+
const result = spawnSync14("git", ["remote", "get-url", "--push", "--all", "origin"], {
|
|
20786
|
+
cwd,
|
|
20787
|
+
encoding: "utf8"
|
|
20788
|
+
});
|
|
20789
|
+
if (result.status !== 0)
|
|
20790
|
+
return null;
|
|
20791
|
+
const urls = (result.stdout ?? "").split(/\r?\n/u).map((url) => url.trim()).filter(Boolean);
|
|
20792
|
+
return urls.length === 1 ? urls[0] : null;
|
|
20793
|
+
};
|
|
20794
|
+
|
|
20795
|
+
// packages/workit-core/src/core/tracker-issues.ts
|
|
20582
20796
|
var repoPathFromRemote = (remote) => {
|
|
20583
20797
|
let rest = (remote || "").trim().replace(/\/+$/, "");
|
|
20584
20798
|
if (!rest)
|
|
@@ -21404,6 +21618,53 @@ var shellPolicy = (cwd, command, dialect = "posix") => {
|
|
|
21404
21618
|
}
|
|
21405
21619
|
return policy && !policy.ok ? { kind: "deny", reason: `branch_policy_denied: ${policy.error}`, unblock: null } : { kind: "none" };
|
|
21406
21620
|
};
|
|
21621
|
+
// packages/workit-core/src/hooks/delivery-verbs.ts
|
|
21622
|
+
var WORKIT_ALLOWED_VERBS = [
|
|
21623
|
+
"git",
|
|
21624
|
+
"pr",
|
|
21625
|
+
"ci",
|
|
21626
|
+
"stack",
|
|
21627
|
+
"release",
|
|
21628
|
+
"task",
|
|
21629
|
+
"ledger",
|
|
21630
|
+
"evidence",
|
|
21631
|
+
"finding",
|
|
21632
|
+
"decision",
|
|
21633
|
+
"worker",
|
|
21634
|
+
"verify-delivery",
|
|
21635
|
+
"policy",
|
|
21636
|
+
"doctor",
|
|
21637
|
+
"help"
|
|
21638
|
+
];
|
|
21639
|
+
var WORKIT_BYPASS_FLAGS = ["--unverified", "--overwrite-unintegrated"];
|
|
21640
|
+
var ALLOWED = new Set(WORKIT_ALLOWED_VERBS);
|
|
21641
|
+
var isBypass = (word) => WORKIT_BYPASS_FLAGS.some((flag) => word === flag || word.startsWith(`${flag}=`));
|
|
21642
|
+
var isWorkitWord = (word) => /^(?:.*[\\/])?workit(?:-cli)?$/u.test(word);
|
|
21643
|
+
var bypassReason = (flag) => `Workit: \`${flag}\` overrides a Workit gate, so the user decides this call (no allow rule covers it).`;
|
|
21644
|
+
var EXPANSION_REASON = "Workit: an expansion before `--` could become a bypass flag, so the user decides this call (no allow rule covers it).";
|
|
21645
|
+
var workitBypassFlag = (command, dialect = "posix") => {
|
|
21646
|
+
for (const segment of segmentsOf(command, dialect)) {
|
|
21647
|
+
if (segment.unanalyzable) {
|
|
21648
|
+
const text = command.replaceAll(/['"\\`]/gu, "");
|
|
21649
|
+
if (!/\bworkit\b/u.test(text))
|
|
21650
|
+
return null;
|
|
21651
|
+
const found = WORKIT_BYPASS_FLAGS.find((flag) => text.includes(flag));
|
|
21652
|
+
return found ? bypassReason(found) : null;
|
|
21653
|
+
}
|
|
21654
|
+
const start = segment.words.findIndex(isWorkitWord);
|
|
21655
|
+
if (start < 0)
|
|
21656
|
+
continue;
|
|
21657
|
+
const words = segment.words.slice(start + 1);
|
|
21658
|
+
const end = words.indexOf("--");
|
|
21659
|
+
const flag = (end < 0 ? words : words.slice(0, end)).find(isBypass);
|
|
21660
|
+
if (flag)
|
|
21661
|
+
return bypassReason(flag.split("=")[0] ?? flag);
|
|
21662
|
+
const limit = start + 1 + (end < 0 ? words.length : end);
|
|
21663
|
+
if (segment.expands?.some((index) => index > start && index < limit))
|
|
21664
|
+
return EXPANSION_REASON;
|
|
21665
|
+
}
|
|
21666
|
+
return null;
|
|
21667
|
+
};
|
|
21407
21668
|
// packages/workit-core/src/core/heavy-rules.ts
|
|
21408
21669
|
import fs20 from "node:fs";
|
|
21409
21670
|
import os6 from "node:os";
|
|
@@ -22788,7 +23049,7 @@ var segmentTargets = (cwd, command, dialect, depth = 0, strict = false) => {
|
|
|
22788
23049
|
}
|
|
22789
23050
|
const inner = depth < 4 ? wrappedScript(words) ?? (strict ? shellScript2(words) : null) : null;
|
|
22790
23051
|
if (inner !== null && dir !== undefined)
|
|
22791
|
-
targets.push(...segmentTargets(dir, inner, "posix", depth + 1, strict));
|
|
23052
|
+
targets.push(...segmentTargets(workitRunDir(dir, words), inner, "posix", depth + 1, strict));
|
|
22792
23053
|
if (strict && dir !== undefined) {
|
|
22793
23054
|
const runIn = dir;
|
|
22794
23055
|
for (const sub of depth < 4 ? segment.subs ?? [] : [])
|
|
@@ -22828,6 +23089,14 @@ var segmentTargets = (cwd, command, dialect, depth = 0, strict = false) => {
|
|
|
22828
23089
|
}
|
|
22829
23090
|
return targets;
|
|
22830
23091
|
};
|
|
23092
|
+
var workitRunDir = (dir, words) => {
|
|
23093
|
+
for (const invocation of workitInvocations(words.map(quoteWord2).join(" "), "posix")) {
|
|
23094
|
+
const { at, args } = workitCwd(dir, invocation);
|
|
23095
|
+
if (args[0] === "check" || args[0] === "lock" && args[1] === "run")
|
|
23096
|
+
return at;
|
|
23097
|
+
}
|
|
23098
|
+
return dir;
|
|
23099
|
+
};
|
|
22831
23100
|
var workitCwd = (dir, args) => {
|
|
22832
23101
|
const { rest, cwd } = skipWorkitGlobals(args);
|
|
22833
23102
|
return { at: cwd === null ? dir : path32.resolve(dir, homeExpanded(cwd)), args: rest };
|
|
@@ -23575,8 +23844,13 @@ var standingDelta = (input) => {
|
|
|
23575
23844
|
};
|
|
23576
23845
|
var sinceStart = new Map;
|
|
23577
23846
|
// packages/workit-core/src/hooks/cwd-normalize.ts
|
|
23578
|
-
var
|
|
23847
|
+
var DIR = String.raw`[A-Za-z0-9_./@%+:,=-]+|'[^'$${"`"}\\\n]*'|"[^"$${"`"}\\!\n]*"`;
|
|
23848
|
+
var COMPOUND = new RegExp(String.raw`^\s*cd\s+(${DIR})\s*&&\s*(workit(?:\s[\s\S]*)?)$`, "u");
|
|
23849
|
+
var GLOBALS_FIRST = new RegExp(String.raw`^(\s*workit)((?:\s+(?:--json|--cwd(?:=|\s+)(?:${DIR})))+)\s+([a-z][a-z-]*)(?=\s|$)`, "u");
|
|
23850
|
+
var VERB_FIRST = /^((?:--json\s+)*)([a-z][a-z-]*)(?=\s|$)/u;
|
|
23851
|
+
var VERBS = new Set(WORKIT_ALLOWED_VERBS);
|
|
23579
23852
|
var FILTERS = new Set(["tail", "head", "grep"]);
|
|
23853
|
+
var clean = (s) => !s.unanalyzable && !s.group && !s.subs?.length && !s.stdin?.length && !s.redirects.length;
|
|
23580
23854
|
var stdinOnly = (words) => {
|
|
23581
23855
|
const [name, ...args] = words;
|
|
23582
23856
|
const operands = args.filter((w) => !w.startsWith("-"));
|
|
@@ -23598,14 +23872,28 @@ var cwdRewrite = (command) => {
|
|
|
23598
23872
|
return null;
|
|
23599
23873
|
if (head.words.some((w) => w === "--cwd" || w.startsWith("--cwd=")))
|
|
23600
23874
|
return null;
|
|
23601
|
-
const clean = (s) => !s.unanalyzable && !s.group && !s.subs?.length && !s.stdin?.length && !s.redirects.length;
|
|
23602
23875
|
if (!segments.every(clean))
|
|
23603
23876
|
return null;
|
|
23604
23877
|
if (!segments.slice(0, -1).every((s) => s.pipe))
|
|
23605
23878
|
return null;
|
|
23606
23879
|
if (!filters.every((s) => FILTERS.has(s.words[0] ?? "") && stdinOnly(s.words)))
|
|
23607
23880
|
return null;
|
|
23608
|
-
|
|
23881
|
+
const args = rest.slice("workit".length).trimStart();
|
|
23882
|
+
const verb = VERB_FIRST.exec(args);
|
|
23883
|
+
if (!verb?.[2] || !VERBS.has(verb[2]))
|
|
23884
|
+
return `workit --cwd ${dir} ${args}`.trimEnd();
|
|
23885
|
+
const json = verb[1] ? " --json" : "";
|
|
23886
|
+
return `workit ${verb[2]} --cwd ${dir}${json}${args.slice(verb[0].length)}`.trimEnd();
|
|
23887
|
+
};
|
|
23888
|
+
var verbFirst = (command) => {
|
|
23889
|
+
const match = GLOBALS_FIRST.exec(command);
|
|
23890
|
+
const [whole, lead, globals, verb] = match ?? [];
|
|
23891
|
+
if (!whole || !verb || !VERBS.has(verb) || !globals?.includes("--cwd"))
|
|
23892
|
+
return null;
|
|
23893
|
+
const segments = segmentsOf(command);
|
|
23894
|
+
if (segments.length !== 1 || !segments.every(clean))
|
|
23895
|
+
return null;
|
|
23896
|
+
return `${lead} ${verb}${globals}${command.slice(whole.length)}`;
|
|
23609
23897
|
};
|
|
23610
23898
|
|
|
23611
23899
|
// packages/workit-core/src/hooks/worktree-confine.ts
|
|
@@ -23721,7 +24009,7 @@ var hostIsolated = (cwd, agentId) => {
|
|
|
23721
24009
|
};
|
|
23722
24010
|
var shown = (target) => path34.basename(target) === ".workit-live-target" ? path34.dirname(target) : target;
|
|
23723
24011
|
var unboundDeny = (input, target) => {
|
|
23724
|
-
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>
|
|
24012
|
+
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.";
|
|
23725
24013
|
return {
|
|
23726
24014
|
kind: "deny",
|
|
23727
24015
|
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}`,
|
|
@@ -23729,7 +24017,7 @@ var unboundDeny = (input, target) => {
|
|
|
23729
24017
|
};
|
|
23730
24018
|
};
|
|
23731
24019
|
var outsideDeny = (input, worktree, target) => {
|
|
23732
|
-
const unblock = `Edit only under ${worktree} and run git there, one verb per call: \`workit --cwd ${worktree}
|
|
24020
|
+
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.`;
|
|
23733
24021
|
return {
|
|
23734
24022
|
kind: "deny",
|
|
23735
24023
|
reason: `workit worktree confinement: implementer ${input.session.agentId} is bound to the worktree ${worktree}, and ${shown(target)} is outside it. ${unblock}`,
|
|
@@ -23738,7 +24026,7 @@ var outsideDeny = (input, worktree, target) => {
|
|
|
23738
24026
|
};
|
|
23739
24027
|
var unknownDirDeny = (input, worktree) => {
|
|
23740
24028
|
const wt = worktree ?? "<worktree>";
|
|
23741
|
-
const unblock = `Workit cannot resolve \`cd -\`, \`cd "$X"\` or \`popd\`: write by absolute path, \`git -C ${wt}\` or \`workit --cwd ${wt}\`.`;
|
|
24029
|
+
const unblock = `Workit cannot resolve \`cd -\`, \`cd "$X"\` or \`popd\`: write by absolute path, \`git -C ${wt}\` or \`workit <verb> --cwd ${wt}\`.`;
|
|
23742
24030
|
return {
|
|
23743
24031
|
kind: "deny",
|
|
23744
24032
|
reason: `workit worktree confinement: implementer ${input.session.agentId} writes after an unknown directory change. ${unblock}`,
|
|
@@ -23896,7 +24184,7 @@ var subagentStartText = (input, descriptor, event) => {
|
|
|
23896
24184
|
return `${isolatedGit(input.cwd)}
|
|
23897
24185
|
${subagentStartText({ ...input, cwd: "" }, descriptor, event)}`;
|
|
23898
24186
|
if (role === "implementer")
|
|
23899
|
-
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>
|
|
24187
|
+
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.`;
|
|
23900
24188
|
const readOnly = `Workit observed ${who} as read-only/agent-guided.`;
|
|
23901
24189
|
if (role !== "judge" || !event.agentId)
|
|
23902
24190
|
return readOnly;
|
|
@@ -23913,7 +24201,7 @@ ${subagentStartText({ ...input, cwd: "" }, descriptor, event)}`;
|
|
|
23913
24201
|
var PRE_TOOL = new Set(["shell.pre", "write.pre", "tool.pre"]);
|
|
23914
24202
|
function handleHook(input, deps) {
|
|
23915
24203
|
const decision = decide(input, deps);
|
|
23916
|
-
if (deps.descriptor.transport !== "hook-process" || !PRE_TOOL.has(input.event.kind) || decision.kind === "deny")
|
|
24204
|
+
if (deps.descriptor.transport !== "hook-process" || !PRE_TOOL.has(input.event.kind) || decision.kind === "deny" || decision.kind === "ask")
|
|
23917
24205
|
return decision;
|
|
23918
24206
|
const delta = standingDelta(input);
|
|
23919
24207
|
if (!delta)
|
|
@@ -23980,9 +24268,12 @@ function decide(input, deps) {
|
|
|
23980
24268
|
if (plan.kind === "rewrite" && !rewrites && canDeny)
|
|
23981
24269
|
return heavyDenial(event.command, plan);
|
|
23982
24270
|
const lease = plan.kind === "rewrite" && rewrites ? plan : null;
|
|
23983
|
-
const cwdCommand = !lease && rewrites && input.host === "claude_code" ? cwdRewrite(event.command) : null;
|
|
24271
|
+
const cwdCommand = !lease && rewrites && input.host === "claude_code" ? cwdRewrite(event.command) ?? verbFirst(event.command) : null;
|
|
23984
24272
|
const aside = plan.kind === "passthrough" ? plan.note : null;
|
|
23985
24273
|
noteRawCommit(input, event.command, postTool ? callKey(event.toolUseId, event.command) : NEXT_COMMAND);
|
|
24274
|
+
const bypass = !lease && input.host === "claude_code" ? workitBypassFlag(event.command, event.dialect) : null;
|
|
24275
|
+
if (bypass)
|
|
24276
|
+
return { kind: "ask", reason: bypass };
|
|
23986
24277
|
const rewrittenTo = lease?.command ?? cwdCommand;
|
|
23987
24278
|
const rewritten = (text) => rewrittenTo ? {
|
|
23988
24279
|
kind: "rewrite",
|
|
@@ -24410,6 +24701,92 @@ var cancelHint = (workerId) => `cancel it first: workit_worker {"action":"cancel
|
|
|
24410
24701
|
import { spawn as nodeSpawn } from "node:child_process";
|
|
24411
24702
|
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
24412
24703
|
|
|
24704
|
+
// packages/workit-core/src/core/role-models.ts
|
|
24705
|
+
var MODEL_ROLES = [
|
|
24706
|
+
"decider",
|
|
24707
|
+
"implementer",
|
|
24708
|
+
"verifier",
|
|
24709
|
+
"reviewer",
|
|
24710
|
+
"explore",
|
|
24711
|
+
"babysit"
|
|
24712
|
+
];
|
|
24713
|
+
var MODEL_TIERS = ["mundane", "standard", "hard"];
|
|
24714
|
+
var HOST_KEYS = new Set([
|
|
24715
|
+
"claude_code",
|
|
24716
|
+
"opencode",
|
|
24717
|
+
"codex",
|
|
24718
|
+
"codex_cli",
|
|
24719
|
+
"codex_desktop",
|
|
24720
|
+
"cursor",
|
|
24721
|
+
"pi",
|
|
24722
|
+
"default"
|
|
24723
|
+
]);
|
|
24724
|
+
var BUILT_IN_CLAUDE_CODE = {
|
|
24725
|
+
decider: { model: "fable", effort: "high" },
|
|
24726
|
+
"implementer.hard": { model: "opus" },
|
|
24727
|
+
"implementer.standard": { model: "sonnet" },
|
|
24728
|
+
"implementer.mundane": { model: "haiku" },
|
|
24729
|
+
verifier: { model: "sonnet" },
|
|
24730
|
+
reviewer: { model: "opus" },
|
|
24731
|
+
explore: { model: "haiku" },
|
|
24732
|
+
babysit: { model: "haiku" }
|
|
24733
|
+
};
|
|
24734
|
+
var isObject7 = (value) => !!value && typeof value === "object" && !Array.isArray(value);
|
|
24735
|
+
var name = (value) => typeof value === "string" && value.trim() ? value.trim() : null;
|
|
24736
|
+
var leaf = (value) => {
|
|
24737
|
+
const text = name(value);
|
|
24738
|
+
if (text)
|
|
24739
|
+
return { model: text, effort: null };
|
|
24740
|
+
if (!isObject7(value))
|
|
24741
|
+
return null;
|
|
24742
|
+
const model = name(value.model);
|
|
24743
|
+
return model ? { model, effort: name(value.effort) } : null;
|
|
24744
|
+
};
|
|
24745
|
+
var isFlat = (value) => typeof value === "string" || isObject7(value) && Object.hasOwn(value, "model");
|
|
24746
|
+
var forHost = (value, host) => {
|
|
24747
|
+
if (isFlat(value))
|
|
24748
|
+
return leaf(value);
|
|
24749
|
+
if (!isObject7(value))
|
|
24750
|
+
return null;
|
|
24751
|
+
const family = host ? hostFamily(host) : null;
|
|
24752
|
+
for (const key of [family, host, "default"])
|
|
24753
|
+
if (key && Object.hasOwn(value, key)) {
|
|
24754
|
+
const found = leaf(value[key]);
|
|
24755
|
+
if (found)
|
|
24756
|
+
return found;
|
|
24757
|
+
}
|
|
24758
|
+
return null;
|
|
24759
|
+
};
|
|
24760
|
+
var rolesOf = (models) => isObject7(models) && isObject7(models.roles) ? models.roles : null;
|
|
24761
|
+
var resolveRoleModel = (cwd, selector, host, dir) => {
|
|
24762
|
+
const { role, tier } = selector;
|
|
24763
|
+
for (const source of modelSources(cwd, dir)) {
|
|
24764
|
+
const where = source.global ? "the global default" : `workspace ${source.name}`;
|
|
24765
|
+
const roles = rolesOf(source.models);
|
|
24766
|
+
const configured = roles && Object.hasOwn(roles, role) ? roles[role] : undefined;
|
|
24767
|
+
const fromRoles = role === "implementer" ? tier && isObject7(configured) && Object.hasOwn(configured, tier) ? forHost(configured[tier], host) : null : forHost(configured, host);
|
|
24768
|
+
if (fromRoles)
|
|
24769
|
+
return { ...fromRoles, source: `models.roles in ${where}` };
|
|
24770
|
+
if (role === "implementer" && tier && isObject7(source.tierModels)) {
|
|
24771
|
+
const fromTiers = Object.hasOwn(source.tierModels, tier) ? forHost(source.tierModels[tier], host) : null;
|
|
24772
|
+
if (fromTiers)
|
|
24773
|
+
return { model: fromTiers.model, effort: null, source: `fanout.tierModels in ${where}` };
|
|
24774
|
+
}
|
|
24775
|
+
}
|
|
24776
|
+
if (host && hostFamily(host) === "claude_code") {
|
|
24777
|
+
const builtIn = BUILT_IN_CLAUDE_CODE[tier ? `${role}.${tier}` : role];
|
|
24778
|
+
if (builtIn)
|
|
24779
|
+
return { model: builtIn.model, effort: builtIn.effort ?? null, source: "built-in" };
|
|
24780
|
+
}
|
|
24781
|
+
return null;
|
|
24782
|
+
};
|
|
24783
|
+
var uniformImplementerModel = (cwd, host, dir) => {
|
|
24784
|
+
const tiers = MODEL_TIERS.map((tier) => resolveRoleModel(cwd, { role: "implementer", tier }, host, dir));
|
|
24785
|
+
const [first] = tiers;
|
|
24786
|
+
return first && tiers.every((t) => t?.model === first.model && t.effort === first.effort) ? first : null;
|
|
24787
|
+
};
|
|
24788
|
+
var ROLE_SELECTORS = MODEL_ROLES.flatMap((role) => role === "implementer" ? MODEL_TIERS.map((tier) => ({ role, tier })) : [{ role }]);
|
|
24789
|
+
|
|
24413
24790
|
// packages/workit-pi/src/worker-protocol.ts
|
|
24414
24791
|
var MAX_WORKER_LINE_BYTES = 64 * 1024;
|
|
24415
24792
|
var MAX_WORKER_STDERR_BYTES = 64 * 1024;
|
|
@@ -24481,7 +24858,9 @@ var roleTools = (role) => {
|
|
|
24481
24858
|
return [...readOnly, "write", "edit", ...familyTools];
|
|
24482
24859
|
return [...readOnly, ...familyTools];
|
|
24483
24860
|
};
|
|
24861
|
+
var roleModel = (cwd, role) => role === "implementer" ? uniformImplementerModel(cwd, "pi") : resolveRoleModel(cwd, { role: role === "investigator" ? "explore" : "reviewer" }, "pi");
|
|
24484
24862
|
function workerCommand2(runtime, assignment) {
|
|
24863
|
+
const configured = roleModel(runtime.root, assignment.role);
|
|
24485
24864
|
return {
|
|
24486
24865
|
command: runtime.node,
|
|
24487
24866
|
args: [
|
|
@@ -24492,6 +24871,8 @@ function workerCommand2(runtime, assignment) {
|
|
|
24492
24871
|
...runtime.sessionId ? ["--session-id", runtime.sessionId] : [],
|
|
24493
24872
|
"--approve",
|
|
24494
24873
|
"--offline",
|
|
24874
|
+
...configured ? ["--model", configured.model] : [],
|
|
24875
|
+
...configured?.effort ? ["--thinking", configured.effort] : [],
|
|
24495
24876
|
"--no-context-files",
|
|
24496
24877
|
"--tools",
|
|
24497
24878
|
roleTools(assignment.role).join(","),
|