@brainervirus/workit-pi 8.24.0 → 8.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/worker.js +99 -103
- package/dist/workit.js +1897 -1638
- package/package.json +1 -1
- package/skills/workit-fanout/SKILL.md +1 -1
- package/skills/workit-fanout/references/brief.md +4 -4
- package/skills/workit-review/SKILL.md +2 -0
- package/skills/workit-ship/SKILL.md +1 -1
package/dist/workit.js
CHANGED
|
@@ -8965,6 +8965,26 @@ function resolveRef(cwd, ref) {
|
|
|
8965
8965
|
return null;
|
|
8966
8966
|
return line(git(cwd, ["rev-parse", "--verify", "-q", `${ref}^{commit}`]));
|
|
8967
8967
|
}
|
|
8968
|
+
function originHeadRef(cwd) {
|
|
8969
|
+
return line(git(cwd, ["symbolic-ref", "-q", "--short", "refs/remotes/origin/HEAD"]));
|
|
8970
|
+
}
|
|
8971
|
+
function ownChangeDrift(cwd, range) {
|
|
8972
|
+
const before = diffRaw(cwd, range.fromFork, range.from);
|
|
8973
|
+
const after = diffRaw(cwd, range.toFork, range.to);
|
|
8974
|
+
if (!before || !after)
|
|
8975
|
+
return null;
|
|
8976
|
+
const judged = new Map(before.map((entry) => [entry.path, entry]));
|
|
8977
|
+
const current = new Map(after.map((entry) => [entry.path, entry]));
|
|
8978
|
+
const same = (a, b) => a !== undefined && b !== undefined && a.oldMode === b.oldMode && a.oldBlob === b.oldBlob && a.newMode === b.newMode && a.newBlob === b.newBlob;
|
|
8979
|
+
return [...new Set([...judged.keys(), ...current.keys()])].filter((file) => !same(judged.get(file), current.get(file))).toSorted();
|
|
8980
|
+
}
|
|
8981
|
+
function sameOwnChange(cwd, range) {
|
|
8982
|
+
const fromFork = mergeBase(cwd, range.fromBase, range.from);
|
|
8983
|
+
const toFork = mergeBase(cwd, range.toBase, range.to);
|
|
8984
|
+
if (!fromFork || !toFork)
|
|
8985
|
+
return false;
|
|
8986
|
+
return ownChangeDrift(cwd, { from: range.from, to: range.to, fromFork, toFork })?.length === 0;
|
|
8987
|
+
}
|
|
8968
8988
|
var LANE_DIFF = [
|
|
8969
8989
|
"-c",
|
|
8970
8990
|
"core.quotePath=false",
|
|
@@ -12860,9 +12880,9 @@ var workerBlocksTransition = (transition, state) => transition === "pause" ? fal
|
|
|
12860
12880
|
|
|
12861
12881
|
// packages/workit-core/src/core/task-evaluation.ts
|
|
12862
12882
|
import { createHash as createHash12 } from "node:crypto";
|
|
12863
|
-
import * as
|
|
12864
|
-
import { spawnSync as
|
|
12865
|
-
import
|
|
12883
|
+
import * as fs14 from "node:fs";
|
|
12884
|
+
import { spawnSync as spawnSync10 } from "node:child_process";
|
|
12885
|
+
import path18 from "node:path";
|
|
12866
12886
|
|
|
12867
12887
|
// packages/workit-core/src/check-config.ts
|
|
12868
12888
|
import { spawnSync as spawnSync8 } from "node:child_process";
|
|
@@ -13093,125 +13113,20 @@ function checkCommand(args, version, onPath = workitAvailable()) {
|
|
|
13093
13113
|
|
|
13094
13114
|
// packages/workit-core/src/core/policy/derive.ts
|
|
13095
13115
|
import { existsSync as existsSync5 } from "node:fs";
|
|
13096
|
-
import
|
|
13116
|
+
import path17 from "node:path";
|
|
13097
13117
|
|
|
13098
13118
|
// packages/workit-core/src/ledger.ts
|
|
13099
13119
|
import { spawnSync as spawnSync9 } from "node:child_process";
|
|
13100
13120
|
import { createHash as createHash11, randomBytes as randomBytes3 } from "node:crypto";
|
|
13101
13121
|
import fs13 from "node:fs";
|
|
13102
|
-
import
|
|
13122
|
+
import path16 from "node:path";
|
|
13103
13123
|
|
|
13104
|
-
// packages/workit-core/src/
|
|
13105
|
-
import {
|
|
13106
|
-
import fs12 from "node:fs";
|
|
13124
|
+
// packages/workit-core/src/core/policy/lane.ts
|
|
13125
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4 } from "node:fs";
|
|
13107
13126
|
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
13127
|
|
|
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
|
-
};
|
|
13128
|
+
// packages/workit-core/src/git/default-base.ts
|
|
13204
13129
|
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
13130
|
var trackTrunk = (cwd) => {
|
|
13216
13131
|
try {
|
|
13217
13132
|
const resolved = vcsConfig("resolve", cwd);
|
|
@@ -13227,11 +13142,11 @@ function defaultBase(cwd) {
|
|
|
13227
13142
|
const trunk = trackTrunk(cwd);
|
|
13228
13143
|
if (trunk) {
|
|
13229
13144
|
for (const candidate of [`origin/${trunk}`, trunk])
|
|
13230
|
-
if (
|
|
13145
|
+
if (resolveRef(cwd, candidate))
|
|
13231
13146
|
return candidate;
|
|
13232
13147
|
}
|
|
13233
|
-
const originHead =
|
|
13234
|
-
if (originHead &&
|
|
13148
|
+
const originHead = originHeadRef(cwd);
|
|
13149
|
+
if (originHead && resolveRef(cwd, originHead))
|
|
13235
13150
|
return originHead;
|
|
13236
13151
|
for (const candidate of [
|
|
13237
13152
|
"origin/main",
|
|
@@ -13241,1514 +13156,1691 @@ function defaultBase(cwd) {
|
|
|
13241
13156
|
"origin/develop",
|
|
13242
13157
|
"develop"
|
|
13243
13158
|
])
|
|
13244
|
-
if (
|
|
13159
|
+
if (resolveRef(cwd, candidate))
|
|
13245
13160
|
return candidate;
|
|
13246
13161
|
return null;
|
|
13247
13162
|
}
|
|
13248
|
-
|
|
13249
|
-
|
|
13163
|
+
|
|
13164
|
+
// packages/workit-core/src/core/policy/attributes.ts
|
|
13165
|
+
function patternRegex(dir, pattern) {
|
|
13166
|
+
if (!pattern || pattern.endsWith("/") || pattern.startsWith('"') || pattern.startsWith("!"))
|
|
13250
13167
|
return null;
|
|
13251
|
-
const
|
|
13252
|
-
|
|
13253
|
-
|
|
13254
|
-
|
|
13255
|
-
|
|
13256
|
-
"
|
|
13257
|
-
|
|
13258
|
-
|
|
13259
|
-
|
|
13260
|
-
"
|
|
13261
|
-
|
|
13262
|
-
"
|
|
13263
|
-
|
|
13264
|
-
|
|
13265
|
-
|
|
13266
|
-
|
|
13267
|
-
|
|
13168
|
+
const anchored = pattern.includes("/");
|
|
13169
|
+
const body = pattern.replace(/^\//u, "");
|
|
13170
|
+
let source = "";
|
|
13171
|
+
for (let index = 0;index < body.length; index += 1) {
|
|
13172
|
+
const char = body[index];
|
|
13173
|
+
if (char === "*" && body[index + 1] === "*") {
|
|
13174
|
+
const slash = body[index + 2] === "/";
|
|
13175
|
+
source += slash ? "(?:.*/)?" : ".*";
|
|
13176
|
+
index += slash ? 2 : 1;
|
|
13177
|
+
} else if (char === "*")
|
|
13178
|
+
source += "[^/]*";
|
|
13179
|
+
else if (char === "?")
|
|
13180
|
+
source += "[^/]";
|
|
13181
|
+
else if (char === "[") {
|
|
13182
|
+
const end = body.indexOf("]", index + 2);
|
|
13183
|
+
if (end < 0)
|
|
13184
|
+
return null;
|
|
13185
|
+
const set = body.slice(index + 1, end).replace(/^!/u, "^").replace(/\\/gu, "\\\\");
|
|
13186
|
+
source += `[${set}]`;
|
|
13187
|
+
index = end;
|
|
13188
|
+
} else if (char === "\\" && index + 1 < body.length) {
|
|
13189
|
+
source += body[index + 1].replace(/[.*+?^${}()|[\]\\/]/gu, "\\$&");
|
|
13190
|
+
index += 1;
|
|
13191
|
+
} else
|
|
13192
|
+
source += char.replace(/[.*+?^${}()|[\]\\/]/gu, "\\$&");
|
|
13193
|
+
}
|
|
13194
|
+
const prefix = dir ? `${dir.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&")}/` : "";
|
|
13195
|
+
try {
|
|
13196
|
+
return new RegExp(anchored ? `^${prefix}${source}$` : `^${prefix}(?:.*/)?${source}$`, "u");
|
|
13197
|
+
} catch {
|
|
13268
13198
|
return null;
|
|
13269
|
-
|
|
13270
|
-
|
|
13199
|
+
}
|
|
13200
|
+
}
|
|
13201
|
+
function rulesOf(dir, text) {
|
|
13202
|
+
const out = [];
|
|
13203
|
+
for (const raw of text.split(`
|
|
13271
13204
|
`)) {
|
|
13272
|
-
|
|
13205
|
+
const line = raw.trim();
|
|
13206
|
+
if (!line || line.startsWith("#") || line.startsWith("[attr]"))
|
|
13273
13207
|
continue;
|
|
13274
|
-
|
|
13275
|
-
|
|
13276
|
-
|
|
13208
|
+
const [pattern, ...attributes] = line.split(/\s+/u);
|
|
13209
|
+
let generated = null;
|
|
13210
|
+
for (const attribute of attributes) {
|
|
13211
|
+
if (attribute === "linguist-generated" || attribute === "linguist-generated=true")
|
|
13212
|
+
generated = true;
|
|
13213
|
+
else if (attribute === "-linguist-generated" || attribute === "!linguist-generated" || attribute.startsWith("linguist-generated="))
|
|
13214
|
+
generated = false;
|
|
13215
|
+
}
|
|
13216
|
+
if (generated === null)
|
|
13217
|
+
continue;
|
|
13218
|
+
const regex = patternRegex(dir, pattern);
|
|
13219
|
+
if (regex)
|
|
13220
|
+
out.push({ regex, generated });
|
|
13277
13221
|
}
|
|
13278
|
-
return
|
|
13279
|
-
}
|
|
13280
|
-
function storeRoot(cwd) {
|
|
13281
|
-
const location = resolveStore(path15.resolve(cwd));
|
|
13282
|
-
if (location instanceof Error)
|
|
13283
|
-
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");
|
|
13284
|
-
return { ok: true, value: { root: location.dir, shared: location.shared } };
|
|
13222
|
+
return out;
|
|
13285
13223
|
}
|
|
13286
|
-
function
|
|
13287
|
-
const
|
|
13288
|
-
|
|
13224
|
+
function generatedByAttributes(files, paths) {
|
|
13225
|
+
const rules = [...files].map(([file, text]) => {
|
|
13226
|
+
const dir = file.includes("/") ? file.slice(0, file.lastIndexOf("/")) : "";
|
|
13227
|
+
return {
|
|
13228
|
+
depth: dir ? dir.split("/").length : 0,
|
|
13229
|
+
rules: rulesOf(dir, text)
|
|
13230
|
+
};
|
|
13231
|
+
}).toSorted((left, right) => left.depth - right.depth).flatMap((entry) => entry.rules);
|
|
13232
|
+
const out = new Set;
|
|
13233
|
+
for (const file of paths) {
|
|
13234
|
+
const last = rules.findLast((rule) => rule.regex.test(file));
|
|
13235
|
+
if (last?.generated)
|
|
13236
|
+
out.add(file);
|
|
13237
|
+
}
|
|
13238
|
+
return out;
|
|
13289
13239
|
}
|
|
13290
|
-
|
|
13291
|
-
|
|
13292
|
-
|
|
13293
|
-
|
|
13294
|
-
|
|
13295
|
-
|
|
13296
|
-
|
|
13297
|
-
|
|
13298
|
-
|
|
13299
|
-
|
|
13300
|
-
|
|
13301
|
-
|
|
13240
|
+
|
|
13241
|
+
// packages/workit-core/src/core/policy/substance.ts
|
|
13242
|
+
var SLASH_BLOCK = [["/*", "*/"]];
|
|
13243
|
+
var C_LIKE = { line: ["//"], block: SLASH_BLOCK, quotes: `"'` };
|
|
13244
|
+
var SYNTAXES = Object.fromEntries([
|
|
13245
|
+
...["ts", "mts", "cts", "js", "mjs", "cjs"].map((ext) => [ext, { ...C_LIKE, js: true }]),
|
|
13246
|
+
...["java", "kt", "kts", "scala", "swift", "dart", "groovy"].map((ext) => [
|
|
13247
|
+
ext,
|
|
13248
|
+
{ ...C_LIKE, triple: true }
|
|
13249
|
+
]),
|
|
13250
|
+
["go", { ...C_LIKE, rawBacktick: true }],
|
|
13251
|
+
...["css", "scss", "less"].map((ext) => [
|
|
13252
|
+
ext,
|
|
13253
|
+
{ line: [], block: SLASH_BLOCK, quotes: `"'`, cssUrl: true }
|
|
13254
|
+
]),
|
|
13255
|
+
...["py", "pyi"].map((ext) => [
|
|
13256
|
+
ext,
|
|
13257
|
+
{ line: ["#"], block: [], quotes: `"'`, triple: true, indented: true, fstring: true }
|
|
13258
|
+
]),
|
|
13259
|
+
["toml", { line: ["#"], block: [], quotes: `"`, literal: "'", triple: true }],
|
|
13260
|
+
...["html", "htm", "xml", "svg", "xhtml"].map((ext) => [
|
|
13261
|
+
ext,
|
|
13262
|
+
{ line: [], block: [["<!--", "-->"]], quotes: "", indented: true }
|
|
13263
|
+
])
|
|
13302
13264
|
]);
|
|
13303
|
-
var
|
|
13304
|
-
var
|
|
13305
|
-
|
|
13306
|
-
|
|
13307
|
-
|
|
13308
|
-
return isNetworkFsType(fs13.statfsSync(dir).type);
|
|
13309
|
-
} catch {
|
|
13310
|
-
return false;
|
|
13311
|
-
}
|
|
13312
|
-
};
|
|
13313
|
-
var LOCK_WAIT_MS = 5000;
|
|
13314
|
-
var LOCK_STALE_MS = 30000;
|
|
13315
|
-
var LOCK_OWNER = "owner";
|
|
13316
|
-
var lockToken = (lock) => {
|
|
13317
|
-
try {
|
|
13318
|
-
return fs13.readFileSync(path15.join(lock, LOCK_OWNER), "utf8");
|
|
13319
|
-
} catch {
|
|
13320
|
-
return null;
|
|
13321
|
-
}
|
|
13265
|
+
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;
|
|
13266
|
+
var extensionOf = (file) => {
|
|
13267
|
+
const name = file.slice(file.lastIndexOf("/") + 1);
|
|
13268
|
+
const dot = name.lastIndexOf(".");
|
|
13269
|
+
return dot > 0 ? name.slice(dot + 1).toLowerCase() : "";
|
|
13322
13270
|
};
|
|
13323
|
-
var
|
|
13324
|
-
|
|
13325
|
-
|
|
13326
|
-
|
|
13327
|
-
|
|
13271
|
+
var CI_PATH = /^\.github\/|(?:^|\/)(?:\.gitlab-ci\.ya?ml|Jenkinsfile|azure-pipelines\.ya?ml|\.pre-commit-config\.ya?ml|\.circleci\/.*)$/u;
|
|
13272
|
+
var syntaxOf = (file) => CI_PATH.test(file) ? null : SYNTAXES[extensionOf(file)] ?? null;
|
|
13273
|
+
var SPACE = /[ \t\r\f\v]/u;
|
|
13274
|
+
var WORD = /[\w$]/u;
|
|
13275
|
+
var NO_SPACE_AFTER = "([{,;";
|
|
13276
|
+
var NO_SPACE_BEFORE = ")]},;";
|
|
13277
|
+
var REGEX_AFTER = "(,=:[!&|?{};+-*%<>~^";
|
|
13278
|
+
var REGEX_KEYWORDS = new Set([
|
|
13279
|
+
"return",
|
|
13280
|
+
"typeof",
|
|
13281
|
+
"instanceof",
|
|
13282
|
+
"case",
|
|
13283
|
+
"do",
|
|
13284
|
+
"else",
|
|
13285
|
+
"in",
|
|
13286
|
+
"of",
|
|
13287
|
+
"new",
|
|
13288
|
+
"delete",
|
|
13289
|
+
"void",
|
|
13290
|
+
"throw",
|
|
13291
|
+
"yield",
|
|
13292
|
+
"await"
|
|
13293
|
+
]);
|
|
13294
|
+
function substanceOf(file, text) {
|
|
13295
|
+
const syntax = syntaxOf(file);
|
|
13296
|
+
if (!syntax)
|
|
13297
|
+
return plainSubstance(text);
|
|
13298
|
+
const markup = syntax.block.some(([open]) => open === "<!--");
|
|
13299
|
+
if (markup && (/<(?:script|style)\b|<!\[CDATA\[|<\?/iu.test(text) || /[^>\s][ \t]*<!--/u.test(text)))
|
|
13300
|
+
return plainSubstance(text);
|
|
13301
|
+
return lex(syntax, text);
|
|
13302
|
+
}
|
|
13303
|
+
function plainSubstance(text) {
|
|
13304
|
+
return text.split(`
|
|
13305
|
+
`).map((line) => line.replace(/[ \t\r\f\v]+$/u, "")).filter(Boolean).join(`
|
|
13306
|
+
`);
|
|
13307
|
+
}
|
|
13308
|
+
function lex(syntax, text) {
|
|
13309
|
+
const collapse = !syntax.indented;
|
|
13310
|
+
let out = "";
|
|
13311
|
+
let lineStart = 0;
|
|
13312
|
+
let kept = 0;
|
|
13313
|
+
let pendingSpace = false;
|
|
13314
|
+
const frames = [{ kind: "code", braces: 0 }];
|
|
13315
|
+
let index = 0;
|
|
13316
|
+
if (text.startsWith("#!")) {
|
|
13317
|
+
const end = text.indexOf(`
|
|
13318
|
+
`);
|
|
13319
|
+
out = end < 0 ? text : text.slice(0, end);
|
|
13320
|
+
kept = out.length;
|
|
13321
|
+
index = end < 0 ? text.length : end;
|
|
13328
13322
|
}
|
|
13329
|
-
|
|
13330
|
-
|
|
13331
|
-
|
|
13332
|
-
|
|
13333
|
-
|
|
13334
|
-
fs13.mkdirSync(lock);
|
|
13335
|
-
} catch (error) {
|
|
13336
|
-
if (error.code === "EEXIST")
|
|
13337
|
-
return null;
|
|
13338
|
-
throw error;
|
|
13339
|
-
}
|
|
13340
|
-
const token = `${process.pid}-${randomBytes3(8).toString("hex")}`;
|
|
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;
|
|
13323
|
+
const emitCode = (chunk) => {
|
|
13324
|
+
if (collapse && pendingSpace && out.length > lineStart) {
|
|
13325
|
+
const before = out[out.length - 1];
|
|
13326
|
+
if (!NO_SPACE_AFTER.includes(before) && !NO_SPACE_BEFORE.includes(chunk[0]))
|
|
13327
|
+
out += " ";
|
|
13352
13328
|
}
|
|
13353
|
-
|
|
13354
|
-
|
|
13355
|
-
|
|
13356
|
-
|
|
13357
|
-
|
|
13329
|
+
pendingSpace = false;
|
|
13330
|
+
out += chunk;
|
|
13331
|
+
};
|
|
13332
|
+
const emitVerbatim = (chunk) => {
|
|
13333
|
+
emitCode(chunk);
|
|
13334
|
+
kept = out.length;
|
|
13335
|
+
};
|
|
13336
|
+
const newline = () => {
|
|
13337
|
+
if (!collapse) {
|
|
13338
|
+
let end = out.length;
|
|
13339
|
+
while (end > Math.max(lineStart, kept) && SPACE.test(out[end - 1]))
|
|
13340
|
+
end -= 1;
|
|
13341
|
+
out = out.slice(0, end);
|
|
13358
13342
|
}
|
|
13359
|
-
|
|
13360
|
-
|
|
13361
|
-
|
|
13362
|
-
|
|
13363
|
-
|
|
13364
|
-
const tomb = `${lock}.reap-${randomBytes3(6).toString("hex")}`;
|
|
13365
|
-
try {
|
|
13366
|
-
fs13.renameSync(lock, tomb);
|
|
13367
|
-
} catch {
|
|
13368
|
-
return false;
|
|
13343
|
+
pendingSpace = false;
|
|
13344
|
+
if (out.length > lineStart) {
|
|
13345
|
+
out += `
|
|
13346
|
+
`;
|
|
13347
|
+
lineStart = out.length;
|
|
13369
13348
|
}
|
|
13370
|
-
|
|
13371
|
-
|
|
13349
|
+
};
|
|
13350
|
+
const regexAllowed = () => {
|
|
13351
|
+
let end = out.length;
|
|
13352
|
+
while (end > 0 && /\s/u.test(out[end - 1]))
|
|
13353
|
+
end -= 1;
|
|
13354
|
+
if (end === 0 || REGEX_AFTER.includes(out[end - 1]))
|
|
13372
13355
|
return true;
|
|
13373
|
-
|
|
13374
|
-
|
|
13375
|
-
|
|
13376
|
-
|
|
13377
|
-
|
|
13356
|
+
let start = end;
|
|
13357
|
+
while (start > 0 && WORD.test(out[start - 1]))
|
|
13358
|
+
start -= 1;
|
|
13359
|
+
return REGEX_KEYWORDS.has(out.slice(start, end));
|
|
13360
|
+
};
|
|
13361
|
+
const scanString = (quote, escapes, multiline) => {
|
|
13362
|
+
const prefix = text.slice(Math.max(0, index - 2), index).match(/[a-z]*$/iu)?.[0] ?? "";
|
|
13363
|
+
const fstring = syntax.fstring === true && /f/iu.test(prefix);
|
|
13364
|
+
let end = index + quote.length;
|
|
13365
|
+
while (end < text.length) {
|
|
13366
|
+
const char = text[end];
|
|
13367
|
+
if (escapes && char === "\\") {
|
|
13368
|
+
end += 2;
|
|
13369
|
+
continue;
|
|
13370
|
+
}
|
|
13371
|
+
if (text.startsWith(quote, end)) {
|
|
13372
|
+
if (fstring && unbalanced(text.slice(index + quote.length, end)))
|
|
13373
|
+
return false;
|
|
13374
|
+
emitVerbatim(text.slice(index, end + quote.length));
|
|
13375
|
+
index = end + quote.length;
|
|
13376
|
+
return true;
|
|
13377
|
+
}
|
|
13378
|
+
if (char === `
|
|
13379
|
+
` && !multiline)
|
|
13380
|
+
return false;
|
|
13381
|
+
end += 1;
|
|
13378
13382
|
}
|
|
13379
13383
|
return false;
|
|
13380
|
-
}
|
|
13381
|
-
|
|
13382
|
-
|
|
13383
|
-
|
|
13384
|
-
|
|
13385
|
-
|
|
13386
|
-
|
|
13387
|
-
|
|
13388
|
-
|
|
13389
|
-
|
|
13390
|
-
|
|
13391
|
-
|
|
13392
|
-
|
|
13384
|
+
};
|
|
13385
|
+
while (index < text.length) {
|
|
13386
|
+
const frame = frames[frames.length - 1];
|
|
13387
|
+
const char = text[index];
|
|
13388
|
+
if (frame.kind === "template") {
|
|
13389
|
+
if (char === "\\") {
|
|
13390
|
+
emitVerbatim(text.slice(index, index + 2));
|
|
13391
|
+
index += 2;
|
|
13392
|
+
} else if (char === "`") {
|
|
13393
|
+
emitVerbatim(char);
|
|
13394
|
+
frames.pop();
|
|
13395
|
+
index += 1;
|
|
13396
|
+
} else if (text.startsWith("${", index)) {
|
|
13397
|
+
emitVerbatim("${");
|
|
13398
|
+
frames.push({ kind: "code", braces: 0 });
|
|
13399
|
+
index += 2;
|
|
13400
|
+
} else {
|
|
13401
|
+
out += char;
|
|
13402
|
+
kept = out.length;
|
|
13403
|
+
index += 1;
|
|
13404
|
+
}
|
|
13393
13405
|
continue;
|
|
13394
|
-
|
|
13395
|
-
|
|
13396
|
-
|
|
13397
|
-
|
|
13398
|
-
|
|
13399
|
-
return fn();
|
|
13400
|
-
} finally {
|
|
13401
|
-
ledgerLock.release(lock, token);
|
|
13402
|
-
}
|
|
13403
|
-
}
|
|
13404
|
-
var sha2562 = (text) => createHash11("sha256").update(text).digest("hex");
|
|
13405
|
-
var chainHash = (prevHash, body) => sha2562(`${prevHash ?? ""}
|
|
13406
|
-
${body}`);
|
|
13407
|
-
var TAIL_BYTES = 64 * 1024;
|
|
13408
|
-
var lastLine = (fd, size) => {
|
|
13409
|
-
for (let span = Math.min(size, TAIL_BYTES);span > 0; span = Math.min(size, span * 2)) {
|
|
13410
|
-
const buffer = Buffer.alloc(span);
|
|
13411
|
-
fs13.readSync(fd, buffer, 0, span, size - span);
|
|
13412
|
-
const end = buffer.lastIndexOf(10);
|
|
13413
|
-
if (end < 0) {
|
|
13414
|
-
if (span === size)
|
|
13415
|
-
return null;
|
|
13406
|
+
}
|
|
13407
|
+
if (char === `
|
|
13408
|
+
`) {
|
|
13409
|
+
newline();
|
|
13410
|
+
index += 1;
|
|
13416
13411
|
continue;
|
|
13417
13412
|
}
|
|
13418
|
-
|
|
13419
|
-
|
|
13420
|
-
|
|
13421
|
-
|
|
13422
|
-
|
|
13423
|
-
|
|
13424
|
-
|
|
13425
|
-
return text;
|
|
13426
|
-
break;
|
|
13427
|
-
}
|
|
13428
|
-
if (start < 0) {
|
|
13429
|
-
if (span === size)
|
|
13430
|
-
return null;
|
|
13431
|
-
break;
|
|
13432
|
-
}
|
|
13433
|
-
stop = start;
|
|
13413
|
+
if (SPACE.test(char)) {
|
|
13414
|
+
if (collapse)
|
|
13415
|
+
pendingSpace = true;
|
|
13416
|
+
else
|
|
13417
|
+
out += char;
|
|
13418
|
+
index += 1;
|
|
13419
|
+
continue;
|
|
13434
13420
|
}
|
|
13435
|
-
|
|
13436
|
-
|
|
13437
|
-
|
|
13438
|
-
|
|
13439
|
-
|
|
13440
|
-
|
|
13441
|
-
|
|
13442
|
-
|
|
13443
|
-
|
|
13444
|
-
|
|
13445
|
-
|
|
13446
|
-
|
|
13447
|
-
|
|
13448
|
-
|
|
13449
|
-
|
|
13450
|
-
if (
|
|
13451
|
-
|
|
13452
|
-
const
|
|
13453
|
-
|
|
13454
|
-
|
|
13455
|
-
|
|
13456
|
-
|
|
13457
|
-
|
|
13458
|
-
|
|
13459
|
-
|
|
13421
|
+
const lineOpener = syntax.line.find((opener) => text.startsWith(opener, index));
|
|
13422
|
+
if (lineOpener) {
|
|
13423
|
+
const end = text.indexOf(`
|
|
13424
|
+
`, index);
|
|
13425
|
+
const comment = text.slice(index, end < 0 ? text.length : end);
|
|
13426
|
+
if (DIRECTIVE.test(comment.slice(lineOpener.length)))
|
|
13427
|
+
emitVerbatim(comment);
|
|
13428
|
+
else
|
|
13429
|
+
pendingSpace = true;
|
|
13430
|
+
index = end < 0 ? text.length : end;
|
|
13431
|
+
continue;
|
|
13432
|
+
}
|
|
13433
|
+
const block = syntax.block.find(([open]) => text.startsWith(open, index));
|
|
13434
|
+
if (block) {
|
|
13435
|
+
const end = text.indexOf(block[1], index + block[0].length);
|
|
13436
|
+
if (end < 0)
|
|
13437
|
+
return null;
|
|
13438
|
+
const comment = text.slice(index, end + block[1].length);
|
|
13439
|
+
if (DIRECTIVE.test(comment.slice(block[0].length, -block[1].length)))
|
|
13440
|
+
emitVerbatim(comment);
|
|
13441
|
+
else
|
|
13442
|
+
pendingSpace = true;
|
|
13443
|
+
if (comment.includes(`
|
|
13444
|
+
`))
|
|
13445
|
+
newline();
|
|
13446
|
+
index = end + block[1].length;
|
|
13447
|
+
continue;
|
|
13448
|
+
}
|
|
13449
|
+
if (syntax.triple && (char === '"' || char === "'") && text.startsWith(char.repeat(3), index)) {
|
|
13450
|
+
if (!scanString(char.repeat(3), !syntax.literal?.includes(char), true))
|
|
13451
|
+
return null;
|
|
13452
|
+
continue;
|
|
13453
|
+
}
|
|
13454
|
+
if (syntax.quotes.includes(char)) {
|
|
13455
|
+
if (!scanString(char, true, Boolean(syntax.multiline)))
|
|
13456
|
+
return null;
|
|
13457
|
+
continue;
|
|
13458
|
+
}
|
|
13459
|
+
if (syntax.literal?.includes(char)) {
|
|
13460
|
+
if (!scanString(char, false, false))
|
|
13461
|
+
return null;
|
|
13462
|
+
continue;
|
|
13463
|
+
}
|
|
13464
|
+
if (syntax.rawBacktick && char === "`") {
|
|
13465
|
+
if (!scanString("`", false, true))
|
|
13466
|
+
return null;
|
|
13467
|
+
continue;
|
|
13468
|
+
}
|
|
13469
|
+
if (syntax.js && char === "`") {
|
|
13470
|
+
emitVerbatim(char);
|
|
13471
|
+
frames.push({ kind: "template" });
|
|
13472
|
+
index += 1;
|
|
13473
|
+
continue;
|
|
13474
|
+
}
|
|
13475
|
+
if (syntax.cssUrl && /^url\(\s*[^"')\s]/iu.test(text.slice(index, index + 64)))
|
|
13476
|
+
return null;
|
|
13477
|
+
if (syntax.js && char === "/" && !regexAllowed() && out.trimEnd().endsWith(")")) {
|
|
13478
|
+
const lineEnd = text.indexOf(`
|
|
13479
|
+
`, index);
|
|
13480
|
+
if (text.slice(index + 1, lineEnd < 0 ? text.length : lineEnd).includes("/"))
|
|
13481
|
+
return null;
|
|
13482
|
+
}
|
|
13483
|
+
if (syntax.js && char === "/" && regexAllowed()) {
|
|
13484
|
+
let end = index + 1;
|
|
13485
|
+
let inClass = false;
|
|
13486
|
+
while (end < text.length && (inClass || text[end] !== "/")) {
|
|
13487
|
+
if (text[end] === `
|
|
13488
|
+
`)
|
|
13489
|
+
return null;
|
|
13490
|
+
if (text[end] === "\\")
|
|
13491
|
+
end += 1;
|
|
13492
|
+
else if (text[end] === "[")
|
|
13493
|
+
inClass = true;
|
|
13494
|
+
else if (text[end] === "]")
|
|
13495
|
+
inClass = false;
|
|
13496
|
+
end += 1;
|
|
13460
13497
|
}
|
|
13461
|
-
|
|
13462
|
-
|
|
13463
|
-
|
|
13464
|
-
|
|
13465
|
-
|
|
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);
|
|
13498
|
+
if (end >= text.length)
|
|
13499
|
+
return null;
|
|
13500
|
+
emitVerbatim(text.slice(index, end + 1));
|
|
13501
|
+
index = end + 1;
|
|
13502
|
+
continue;
|
|
13471
13503
|
}
|
|
13472
|
-
|
|
13473
|
-
|
|
13474
|
-
|
|
13475
|
-
|
|
13476
|
-
|
|
13477
|
-
|
|
13478
|
-
|
|
13479
|
-
|
|
13480
|
-
|
|
13504
|
+
if (frames.length > 1 && frame.kind === "code") {
|
|
13505
|
+
if (char === "{")
|
|
13506
|
+
frame.braces += 1;
|
|
13507
|
+
else if (char === "}") {
|
|
13508
|
+
if (frame.braces === 0) {
|
|
13509
|
+
emitVerbatim(char);
|
|
13510
|
+
frames.pop();
|
|
13511
|
+
index += 1;
|
|
13512
|
+
continue;
|
|
13513
|
+
}
|
|
13514
|
+
frame.braces -= 1;
|
|
13515
|
+
}
|
|
13516
|
+
}
|
|
13517
|
+
emitCode(char);
|
|
13518
|
+
index += 1;
|
|
13481
13519
|
}
|
|
13520
|
+
if (frames.length !== 1)
|
|
13521
|
+
return null;
|
|
13522
|
+
newline();
|
|
13523
|
+
return out;
|
|
13482
13524
|
}
|
|
13483
|
-
|
|
13484
|
-
|
|
13485
|
-
const
|
|
13486
|
-
|
|
13487
|
-
|
|
13488
|
-
|
|
13489
|
-
|
|
13490
|
-
}
|
|
13491
|
-
|
|
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;
|
|
13525
|
+
function unbalanced(body) {
|
|
13526
|
+
let depth = 0;
|
|
13527
|
+
for (const part of body.replaceAll("{{", "").replaceAll("}}", "")) {
|
|
13528
|
+
if (part === "{")
|
|
13529
|
+
depth += 1;
|
|
13530
|
+
else if (part === "}")
|
|
13531
|
+
depth = Math.max(0, depth - 1);
|
|
13532
|
+
}
|
|
13533
|
+
return depth > 0;
|
|
13507
13534
|
}
|
|
13508
|
-
function
|
|
13509
|
-
|
|
13510
|
-
return Buffer.byteLength(sample.line);
|
|
13535
|
+
function hasOpaqueLineBreak(text) {
|
|
13536
|
+
return /\r(?!\n)|[\u2028\u2029]/u.test(text);
|
|
13511
13537
|
}
|
|
13512
|
-
|
|
13513
|
-
|
|
13538
|
+
var decoder = new TextDecoder("utf-8", { fatal: true });
|
|
13539
|
+
function textOf(bytes) {
|
|
13540
|
+
if (bytes.includes(0))
|
|
13541
|
+
return null;
|
|
13542
|
+
try {
|
|
13543
|
+
return decoder.decode(bytes);
|
|
13544
|
+
} catch {
|
|
13545
|
+
return null;
|
|
13546
|
+
}
|
|
13514
13547
|
}
|
|
13515
|
-
function
|
|
13516
|
-
|
|
13548
|
+
function cosmeticChange(file, before, after) {
|
|
13549
|
+
const left = textOf(before);
|
|
13550
|
+
const right = textOf(after);
|
|
13551
|
+
if (left === null || right === null)
|
|
13552
|
+
return false;
|
|
13553
|
+
if (hasOpaqueLineBreak(left) || hasOpaqueLineBreak(right))
|
|
13554
|
+
return false;
|
|
13555
|
+
const a = substanceOf(file, left);
|
|
13556
|
+
return a !== null && a === substanceOf(file, right);
|
|
13517
13557
|
}
|
|
13518
|
-
|
|
13519
|
-
|
|
13520
|
-
var
|
|
13521
|
-
|
|
13522
|
-
|
|
13523
|
-
|
|
13524
|
-
session: str2(actor.session),
|
|
13525
|
-
agentId: str2(actor.agentId)
|
|
13526
|
-
};
|
|
13558
|
+
|
|
13559
|
+
// packages/workit-core/src/core/policy/lane.ts
|
|
13560
|
+
var LANE_THRESHOLDS = {
|
|
13561
|
+
selfMaxLines: 100,
|
|
13562
|
+
selfMaxFiles: 5,
|
|
13563
|
+
fullMinLines: 500
|
|
13527
13564
|
};
|
|
13528
|
-
|
|
13529
|
-
|
|
13530
|
-
|
|
13531
|
-
|
|
13532
|
-
|
|
13533
|
-
|
|
13534
|
-
|
|
13535
|
-
|
|
13536
|
-
|
|
13537
|
-
|
|
13538
|
-
|
|
13539
|
-
|
|
13540
|
-
|
|
13541
|
-
|
|
13542
|
-
|
|
13543
|
-
|
|
13544
|
-
|
|
13545
|
-
|
|
13546
|
-
|
|
13547
|
-
|
|
13548
|
-
|
|
13549
|
-
|
|
13550
|
-
|
|
13551
|
-
|
|
13552
|
-
|
|
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;
|
|
13583
|
-
}
|
|
13584
|
-
if (!isRecord3(parsed) || !str2(parsed.id) || !str2(parsed.type) || !str2(parsed.at)) {
|
|
13585
|
-
skipped += 1;
|
|
13586
|
-
continue;
|
|
13587
|
-
}
|
|
13588
|
-
let rowIntegrity = options.integrity ? "verified" : undefined;
|
|
13589
|
-
let reason = null;
|
|
13590
|
-
if (!options.integrity) {} else if (typeof parsed.rowHash === "string") {
|
|
13591
|
-
chainStarted = true;
|
|
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)
|
|
13634
|
-
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
|
-
}
|
|
13744
|
-
}
|
|
13745
|
-
return (row) => {
|
|
13746
|
-
if (row.branch === branch)
|
|
13747
|
-
return true;
|
|
13748
|
-
const until = row.branch === null ? undefined : renamedAt.get(row.branch);
|
|
13749
|
-
if (until === undefined)
|
|
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"
|
|
13565
|
+
var RISK_TOKENS = new Set([
|
|
13566
|
+
"auth",
|
|
13567
|
+
"authn",
|
|
13568
|
+
"authz",
|
|
13569
|
+
"oauth",
|
|
13570
|
+
"security",
|
|
13571
|
+
"permission",
|
|
13572
|
+
"permissions",
|
|
13573
|
+
"secret",
|
|
13574
|
+
"secrets",
|
|
13575
|
+
"crypto",
|
|
13576
|
+
"gate",
|
|
13577
|
+
"gates",
|
|
13578
|
+
"policy",
|
|
13579
|
+
"policies",
|
|
13580
|
+
"hook",
|
|
13581
|
+
"hooks",
|
|
13582
|
+
"husky",
|
|
13583
|
+
"lefthook",
|
|
13584
|
+
"release",
|
|
13585
|
+
"releases",
|
|
13586
|
+
"migration",
|
|
13587
|
+
"migrations",
|
|
13588
|
+
"migrate",
|
|
13589
|
+
"workflows"
|
|
13795
13590
|
]);
|
|
13796
|
-
var
|
|
13797
|
-
"
|
|
13798
|
-
"
|
|
13799
|
-
"
|
|
13800
|
-
"
|
|
13801
|
-
"
|
|
13591
|
+
var RISK_FILES = new Set([
|
|
13592
|
+
".gitlab-ci.yml",
|
|
13593
|
+
"Jenkinsfile",
|
|
13594
|
+
"azure-pipelines.yml",
|
|
13595
|
+
".pre-commit-config.yaml",
|
|
13596
|
+
".workit/risk-paths"
|
|
13802
13597
|
]);
|
|
13803
|
-
|
|
13804
|
-
|
|
13805
|
-
|
|
13806
|
-
|
|
13807
|
-
|
|
13808
|
-
|
|
13809
|
-
|
|
13810
|
-
|
|
13811
|
-
|
|
13812
|
-
|
|
13813
|
-
|
|
13814
|
-
|
|
13815
|
-
|
|
13816
|
-
|
|
13817
|
-
|
|
13818
|
-
|
|
13819
|
-
|
|
13820
|
-
|
|
13821
|
-
|
|
13822
|
-
|
|
13823
|
-
|
|
13824
|
-
|
|
13825
|
-
|
|
13826
|
-
|
|
13827
|
-
|
|
13828
|
-
|
|
13829
|
-
|
|
13830
|
-
|
|
13831
|
-
|
|
13832
|
-
|
|
13833
|
-
|
|
13834
|
-
return
|
|
13835
|
-
|
|
13836
|
-
|
|
13837
|
-
|
|
13838
|
-
|
|
13839
|
-
|
|
13840
|
-
|
|
13841
|
-
|
|
13842
|
-
|
|
13843
|
-
|
|
13844
|
-
|
|
13845
|
-
|
|
13846
|
-
|
|
13847
|
-
|
|
13848
|
-
|
|
13849
|
-
|
|
13850
|
-
|
|
13851
|
-
|
|
13852
|
-
|
|
13853
|
-
|
|
13854
|
-
|
|
13855
|
-
if (untrusted(reasonsOf(row)) && !untrusted(reasonsOf(effective)))
|
|
13856
|
-
continue;
|
|
13857
|
-
const rowIndependent = reasonsOf(row).length === 0;
|
|
13858
|
-
const effIndependent = reasonsOf(effective).length === 0;
|
|
13859
|
-
if (effIndependent && !rowIndependent && isCurrent(effective))
|
|
13860
|
-
continue;
|
|
13861
|
-
if (effIndependent && FAILING.has(String(effective.result)) && row.actor.session === effective.actor.session && sameCode(effective, row))
|
|
13862
|
-
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
|
-
}
|
|
13903
|
-
return key;
|
|
13904
|
-
};
|
|
13905
|
-
const keyFor = (base) => keysAt(base, head);
|
|
13906
|
-
const basisOf = (row) => {
|
|
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);
|
|
13925
|
-
}
|
|
13926
|
-
return set;
|
|
13927
|
-
};
|
|
13928
|
-
const reasonCache = new Map;
|
|
13929
|
-
const reasonsOf = (row) => {
|
|
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);
|
|
13938
|
-
}
|
|
13939
|
-
return reasons;
|
|
13940
|
-
};
|
|
13941
|
-
const retired = new Set;
|
|
13942
|
-
for (const row of rows)
|
|
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]);
|
|
13950
|
-
}
|
|
13951
|
-
const verdicts = [];
|
|
13952
|
-
const warnings = [];
|
|
13953
|
-
for (const [kind, list] of byKind) {
|
|
13954
|
-
const row = effectiveOf(list, reasonsOf, sameCode, (candidate) => basisOf(candidate) !== "stale");
|
|
13955
|
-
if (!row)
|
|
13956
|
-
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`);
|
|
13598
|
+
var RISK_NAMES = new Set([".gitattributes"]);
|
|
13599
|
+
var RISK_PATHS_FILE = ".workit/risk-paths";
|
|
13600
|
+
var LOCKFILES = new Set([
|
|
13601
|
+
"bun.lock",
|
|
13602
|
+
"bun.lockb",
|
|
13603
|
+
"package-lock.json",
|
|
13604
|
+
"npm-shrinkwrap.json",
|
|
13605
|
+
"yarn.lock",
|
|
13606
|
+
"pnpm-lock.yaml",
|
|
13607
|
+
"Cargo.lock",
|
|
13608
|
+
"go.sum",
|
|
13609
|
+
"poetry.lock",
|
|
13610
|
+
"uv.lock",
|
|
13611
|
+
"Pipfile.lock",
|
|
13612
|
+
"Gemfile.lock",
|
|
13613
|
+
"composer.lock"
|
|
13614
|
+
]);
|
|
13615
|
+
var DOC_EXT = /\.(?:md|mdx|markdown|rst|adoc)$/iu;
|
|
13616
|
+
var DOC_NAMES = /^(?:license|licence|notice|authors|changelog|changes|contributors)$/iu;
|
|
13617
|
+
var INSTRUCTIONS = /(?:^|\/)(?:AGENTS|CLAUDE|GEMINI|SKILL)\.md$|(?:^|\/)(?:skills|agents|commands|rules|prompts)\/[^/]+/iu;
|
|
13618
|
+
var TEST_PATH = /(?:^|\/)(?:test|tests|__tests__)\/|\.(?:test|spec)\.[^/]+$|_test\.[^/]+$/iu;
|
|
13619
|
+
var basename = (file) => file.slice(file.lastIndexOf("/") + 1);
|
|
13620
|
+
var isDoc = (file) => !INSTRUCTIONS.test(file) && (DOC_EXT.test(file) || DOC_NAMES.test(basename(file)));
|
|
13621
|
+
function kindOf(file) {
|
|
13622
|
+
if (file.binary)
|
|
13623
|
+
return "behavior";
|
|
13624
|
+
if (file.generated)
|
|
13625
|
+
return "generated";
|
|
13626
|
+
if (LOCKFILES.has(basename(file.path)))
|
|
13627
|
+
return "lockfile";
|
|
13628
|
+
if (isDoc(file.path))
|
|
13629
|
+
return "doc";
|
|
13630
|
+
if (file.cosmetic)
|
|
13631
|
+
return "cosmetic";
|
|
13632
|
+
return TEST_PATH.test(file.path) ? "test" : "behavior";
|
|
13633
|
+
}
|
|
13634
|
+
var globCache = new Map;
|
|
13635
|
+
function globMatch(pattern, file) {
|
|
13636
|
+
let regex = globCache.get(pattern);
|
|
13637
|
+
if (!regex) {
|
|
13638
|
+
let source = "";
|
|
13639
|
+
for (let index = 0;index < pattern.length; index += 1) {
|
|
13640
|
+
const char = pattern[index];
|
|
13641
|
+
if (char === "*" && pattern[index + 1] === "*") {
|
|
13642
|
+
source += pattern[index + 2] === "/" ? "(?:[^/]*/)*" : ".*";
|
|
13643
|
+
index += pattern[index + 2] === "/" ? 2 : 1;
|
|
13644
|
+
} else if (char === "*")
|
|
13645
|
+
source += "[^/]*";
|
|
13646
|
+
else if (char === "?")
|
|
13647
|
+
source += "[^/]";
|
|
13648
|
+
else
|
|
13649
|
+
source += char.replace(/[.+^${}()|[\]\\]/gu, "\\$&");
|
|
13978
13650
|
}
|
|
13651
|
+
regex = new RegExp(`^${source}$`, "u");
|
|
13652
|
+
globCache.set(pattern, regex);
|
|
13979
13653
|
}
|
|
13980
|
-
|
|
13981
|
-
|
|
13982
|
-
|
|
13983
|
-
|
|
13984
|
-
|
|
13985
|
-
|
|
13986
|
-
|
|
13987
|
-
|
|
13988
|
-
|
|
13989
|
-
|
|
13654
|
+
return regex.test(file) || file.startsWith(`${pattern.replace(/\/$/u, "")}/`);
|
|
13655
|
+
}
|
|
13656
|
+
function isRiskPath(file, extra = []) {
|
|
13657
|
+
if (extra.some((pattern) => globMatch(pattern, file)))
|
|
13658
|
+
return true;
|
|
13659
|
+
if (RISK_FILES.has(file) || RISK_NAMES.has(basename(file)))
|
|
13660
|
+
return true;
|
|
13661
|
+
if (TEST_PATH.test(file) || isDoc(file))
|
|
13662
|
+
return false;
|
|
13663
|
+
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)));
|
|
13664
|
+
}
|
|
13665
|
+
var parseRiskPaths = (text) => text.split(`
|
|
13666
|
+
`).map((line) => line.trim()).filter((line) => line && !line.startsWith("#")).map((line) => line.replace(/^\//u, ""));
|
|
13667
|
+
function computeLane(input) {
|
|
13668
|
+
const riskFiles = input.files.filter((file) => isRiskPath(file.path, input.riskPaths)).map((file) => file.path);
|
|
13669
|
+
const risky = new Set(riskFiles);
|
|
13670
|
+
const kinds = input.files.map((file) => ({
|
|
13671
|
+
file,
|
|
13672
|
+
kind: risky.has(file.path) ? "behavior" : kindOf(file)
|
|
13673
|
+
}));
|
|
13674
|
+
const behavior = kinds.filter((entry) => entry.kind === "behavior" || entry.kind === "test" || entry.kind === "lockfile");
|
|
13675
|
+
const lines = behavior.filter((entry) => entry.kind !== "lockfile").reduce((sum, entry) => sum + entry.file.added + entry.file.deleted, 0);
|
|
13676
|
+
const opaque = behavior.filter((entry) => entry.kind === "lockfile" || entry.file.binary);
|
|
13677
|
+
const result = (lane, reasons) => ({
|
|
13678
|
+
lane,
|
|
13679
|
+
reasons,
|
|
13680
|
+
lines,
|
|
13681
|
+
files: behavior.length,
|
|
13682
|
+
riskFiles
|
|
13683
|
+
});
|
|
13684
|
+
const { selfMaxLines, selfMaxFiles, fullMinLines } = LANE_THRESHOLDS;
|
|
13685
|
+
if (input.riskTier === "high")
|
|
13686
|
+
return result("full", ["the task judgment records high risk"]);
|
|
13687
|
+
if (riskFiles.length)
|
|
13688
|
+
return result("full", [
|
|
13689
|
+
`touches a risk path: ${riskFiles.slice(0, 3).join(", ")}${riskFiles.length > 3 ? ", …" : ""}`
|
|
13690
|
+
]);
|
|
13691
|
+
if (!behavior.length)
|
|
13692
|
+
return result("none", [
|
|
13693
|
+
input.files.length ? "only docs, comments, formatting or generated copies changed" : "no changes"
|
|
13694
|
+
]);
|
|
13695
|
+
if (lines >= fullMinLines)
|
|
13696
|
+
return result("full", [`large diff: ${lines} lines (≥ ${fullMinLines})`]);
|
|
13697
|
+
if (lines > selfMaxLines)
|
|
13698
|
+
return result("focused", [`${lines} lines changed (> ${selfMaxLines})`]);
|
|
13699
|
+
if (behavior.length > selfMaxFiles)
|
|
13700
|
+
return result("focused", [`${behavior.length} behavior files changed (> ${selfMaxFiles})`]);
|
|
13701
|
+
if (opaque.length)
|
|
13702
|
+
return result("focused", [
|
|
13703
|
+
`a lockfile or binary changed: ${opaque.slice(0, 3).map((entry) => entry.file.path).join(", ")}${opaque.length > 3 ? ", …" : ""}`
|
|
13704
|
+
]);
|
|
13705
|
+
return result("self", [
|
|
13706
|
+
`small change: ${lines} line${lines === 1 ? "" : "s"} in ${behavior.length} file${behavior.length === 1 ? "" : "s"}, no risk path`
|
|
13707
|
+
]);
|
|
13708
|
+
}
|
|
13709
|
+
function untrackedStat(cwd, file) {
|
|
13710
|
+
try {
|
|
13711
|
+
const bytes = readFileSync4(path14.join(cwd, file));
|
|
13712
|
+
if (bytes.includes(0))
|
|
13713
|
+
return { path: file, added: 0, deleted: 0, binary: true };
|
|
13714
|
+
const text = bytes.toString("utf8");
|
|
13715
|
+
if (hasOpaqueLineBreak(text))
|
|
13716
|
+
return { path: file, added: 0, deleted: 0, binary: true };
|
|
13717
|
+
const lines = text.split(`
|
|
13718
|
+
`).length - (text.endsWith(`
|
|
13719
|
+
`) ? 1 : 0);
|
|
13720
|
+
return { path: file, added: lines, deleted: 0 };
|
|
13721
|
+
} catch {
|
|
13722
|
+
return { path: file, added: 0, deleted: 0, binary: true };
|
|
13990
13723
|
}
|
|
13991
|
-
verdicts.sort((a, b) => a.verdict.seq - b.verdict.seq);
|
|
13992
|
-
const newestCurrent = verdicts.findLast((entry) => entry.current) ?? verdicts.at(-1) ?? null;
|
|
13993
|
-
const failing = verdicts.find((entry) => entry.current && entry.independent && FAILING.has(String(entry.verdict.result)));
|
|
13994
|
-
const newestAccepted = verdicts.findLast((entry) => entry.accepted && STRONG_RESULTS.has(String(entry.verdict.result))) ?? null;
|
|
13995
|
-
const accepted = tampered.length ? { accepted: false, verdict: null, reasons: ["ledger_tampered"] } : newestAccepted && !failing ? { accepted: true, verdict: newestAccepted.verdict, reasons: [] } : {
|
|
13996
|
-
accepted: false,
|
|
13997
|
-
verdict: failing?.verdict ?? null,
|
|
13998
|
-
reasons: failing ? ["failing_verdict"] : !newestCurrent ? ["no_verdict"] : newestCurrent.reasons.length ? newestCurrent.reasons : ["type_check_only"]
|
|
13999
|
-
};
|
|
14000
|
-
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))));
|
|
14001
|
-
const selfEntry = latestSelf && STRONG_RESULTS.has(String(latestSelf.verdict.result)) ? latestSelf : undefined;
|
|
14002
|
-
return {
|
|
14003
|
-
branch,
|
|
14004
|
-
head,
|
|
14005
|
-
review: accepted.accepted ? "verified" : selfEntry ? "self-reviewed" : "unreviewed",
|
|
14006
|
-
selfVerdict: accepted.accepted ? null : selfEntry?.verdict ?? null,
|
|
14007
|
-
base: newestCurrent?.verdict.base ?? fallbackBase,
|
|
14008
|
-
patchId: keyFor(newestCurrent?.verdict.base ?? fallbackBase).patchId,
|
|
14009
|
-
current: { basis: newestCurrent?.basis ?? "none", verdict: newestCurrent?.verdict ?? null },
|
|
14010
|
-
accepted,
|
|
14011
|
-
verdicts,
|
|
14012
|
-
authors: [...authors].toSorted(),
|
|
14013
|
-
warnings
|
|
14014
|
-
};
|
|
14015
13724
|
}
|
|
14016
|
-
var
|
|
14017
|
-
|
|
14018
|
-
|
|
14019
|
-
|
|
14020
|
-
|
|
14021
|
-
|
|
14022
|
-
|
|
14023
|
-
|
|
14024
|
-
|
|
14025
|
-
|
|
14026
|
-
|
|
14027
|
-
|
|
14028
|
-
|
|
14029
|
-
|
|
14030
|
-
|
|
14031
|
-
|
|
14032
|
-
|
|
13725
|
+
var REGULAR = new Set(["100644", "100755"]);
|
|
13726
|
+
var ABSENT = /^0+$/u;
|
|
13727
|
+
function cosmeticFiles(cwd, fork, head) {
|
|
13728
|
+
const raw = diffRaw(cwd, fork, head);
|
|
13729
|
+
if (!raw)
|
|
13730
|
+
return null;
|
|
13731
|
+
const candidates = raw.filter((entry) => REGULAR.has(entry.oldMode) && entry.oldMode === entry.newMode && !ABSENT.test(entry.oldBlob) && (head === null || !ABSENT.test(entry.newBlob)));
|
|
13732
|
+
const blobs = readBlobs(cwd, candidates.flatMap((entry) => [entry.oldBlob, entry.newBlob]));
|
|
13733
|
+
if (!blobs)
|
|
13734
|
+
return null;
|
|
13735
|
+
const out = new Set;
|
|
13736
|
+
const opaque = new Set;
|
|
13737
|
+
for (const entry of candidates) {
|
|
13738
|
+
const before = blobs.get(entry.oldBlob);
|
|
13739
|
+
let after = blobs.get(entry.newBlob);
|
|
13740
|
+
if (!after && head === null && ABSENT.test(entry.newBlob))
|
|
13741
|
+
try {
|
|
13742
|
+
after = readFileSync4(path14.join(cwd, entry.path));
|
|
13743
|
+
} catch {
|
|
13744
|
+
after = undefined;
|
|
14033
13745
|
}
|
|
14034
|
-
|
|
13746
|
+
if (before && after && cosmeticChange(entry.path, before, after))
|
|
13747
|
+
out.add(entry.path);
|
|
13748
|
+
else if ([before, after].some((bytes) => bytes && hasOpaqueLineBreak(bytes.toString("utf8"))))
|
|
13749
|
+
opaque.add(entry.path);
|
|
14035
13750
|
}
|
|
14036
|
-
return
|
|
13751
|
+
return { cosmetic: out, opaque };
|
|
14037
13752
|
}
|
|
14038
|
-
|
|
14039
|
-
"
|
|
14040
|
-
|
|
14041
|
-
|
|
14042
|
-
|
|
14043
|
-
|
|
14044
|
-
|
|
14045
|
-
|
|
14046
|
-
|
|
14047
|
-
|
|
14048
|
-
|
|
14049
|
-
|
|
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(/^#/, "");
|
|
13753
|
+
function generatedAt(cwd, fork, paths) {
|
|
13754
|
+
const files = treeFilesNamed(cwd, fork, ".gitattributes");
|
|
13755
|
+
const blobs = files?.size ? readBlobs(cwd, [...files.values()]) : null;
|
|
13756
|
+
if (!files || !blobs)
|
|
13757
|
+
return new Set;
|
|
13758
|
+
const texts = new Map;
|
|
13759
|
+
for (const [file, id] of files) {
|
|
13760
|
+
const bytes = blobs.get(id);
|
|
13761
|
+
if (bytes)
|
|
13762
|
+
texts.set(file, bytes.toString("utf8"));
|
|
13763
|
+
}
|
|
13764
|
+
return generatedByAttributes(texts, paths);
|
|
14057
13765
|
}
|
|
14058
|
-
|
|
14059
|
-
|
|
14060
|
-
|
|
14061
|
-
const
|
|
14062
|
-
|
|
14063
|
-
|
|
14064
|
-
|
|
14065
|
-
|
|
14066
|
-
|
|
14067
|
-
|
|
14068
|
-
|
|
14069
|
-
|
|
14070
|
-
|
|
14071
|
-
|
|
13766
|
+
var forkOf = (cwd, base, head) => mergeBase(cwd, base, head ?? "HEAD");
|
|
13767
|
+
function laneFiles(cwd, fork, head, attributesAt = fork) {
|
|
13768
|
+
const stats = diffNumstat(cwd, fork, head);
|
|
13769
|
+
const cosmetic = stats ? cosmeticFiles(cwd, fork, head) : null;
|
|
13770
|
+
if (!stats || !cosmetic)
|
|
13771
|
+
return null;
|
|
13772
|
+
const files = [
|
|
13773
|
+
...stats.map((entry) => ({
|
|
13774
|
+
...entry,
|
|
13775
|
+
...cosmetic.opaque.has(entry.path) ? { binary: true } : {},
|
|
13776
|
+
cosmetic: !entry.binary && cosmetic.cosmetic.has(entry.path)
|
|
13777
|
+
})),
|
|
13778
|
+
...head === null ? untrackedFiles(cwd).map((file) => untrackedStat(cwd, file)) : []
|
|
13779
|
+
];
|
|
13780
|
+
const generated = generatedAt(cwd, attributesAt, files.map((entry) => entry.path));
|
|
13781
|
+
return files.map((entry) => ({
|
|
13782
|
+
...entry,
|
|
13783
|
+
generated: generated.has(entry.path)
|
|
13784
|
+
}));
|
|
13785
|
+
}
|
|
13786
|
+
function driftFiles(cwd, range) {
|
|
13787
|
+
if (range.fromFork === range.toFork)
|
|
13788
|
+
return laneFiles(cwd, range.from, range.to, range.toFork);
|
|
13789
|
+
const drifted = ownChangeDrift(cwd, range);
|
|
13790
|
+
const stats = drifted ? diffNumstat(cwd, range.from, range.to) : null;
|
|
13791
|
+
if (!drifted || !stats)
|
|
13792
|
+
return null;
|
|
13793
|
+
const byPath = new Map(stats.map((entry) => [entry.path, entry]));
|
|
13794
|
+
const generated = generatedAt(cwd, range.toFork, drifted);
|
|
13795
|
+
return drifted.map((file) => ({
|
|
13796
|
+
path: file,
|
|
13797
|
+
added: byPath.get(file)?.added ?? 0,
|
|
13798
|
+
deleted: byPath.get(file)?.deleted ?? 0,
|
|
13799
|
+
...byPath.get(file)?.binary ? { binary: true } : {},
|
|
13800
|
+
cosmetic: false,
|
|
13801
|
+
generated: generated.has(file)
|
|
13802
|
+
}));
|
|
13803
|
+
}
|
|
13804
|
+
function branchLane(cwd, options) {
|
|
13805
|
+
const candidates = options.base ? [`origin/${options.base}`, options.base] : [];
|
|
13806
|
+
const base = candidates.find((ref) => resolveRef(cwd, ref) !== null) ?? defaultBase(cwd) ?? null;
|
|
13807
|
+
const head = options.workingTree ? null : options.head;
|
|
13808
|
+
const fork = base && (options.workingTree || options.head) ? forkOf(cwd, base, head) : null;
|
|
13809
|
+
const files = fork ? laneFiles(cwd, fork, head) : null;
|
|
13810
|
+
const riskTier = options.judgment?.riskTier ?? null;
|
|
13811
|
+
if (!fork || !files) {
|
|
13812
|
+
const high = riskTier === "high";
|
|
13813
|
+
return {
|
|
13814
|
+
lane: high ? "full" : "focused",
|
|
13815
|
+
reasons: [
|
|
13816
|
+
`the diff of ${options.head?.slice(0, 12) ?? "(no head)"} against ${base ?? "(no base)"} cannot be read`,
|
|
13817
|
+
...high ? ["the task judgment records high risk"] : []
|
|
13818
|
+
],
|
|
13819
|
+
lines: 0,
|
|
13820
|
+
files: 0,
|
|
13821
|
+
riskFiles: [],
|
|
13822
|
+
base,
|
|
13823
|
+
head: options.head
|
|
13824
|
+
};
|
|
14072
13825
|
}
|
|
14073
|
-
|
|
13826
|
+
const repoRisk = parseRiskPaths(fileAt(cwd, fork, RISK_PATHS_FILE) ?? "");
|
|
13827
|
+
return {
|
|
13828
|
+
...computeLane({
|
|
13829
|
+
files,
|
|
13830
|
+
riskTier,
|
|
13831
|
+
riskPaths: [...repoRisk, ...options.riskPaths ?? []]
|
|
13832
|
+
}),
|
|
13833
|
+
base,
|
|
13834
|
+
head: options.head
|
|
13835
|
+
};
|
|
14074
13836
|
}
|
|
14075
|
-
var
|
|
14076
|
-
var pushRemote = (cwd) => {
|
|
14077
|
-
const result = spawnSync10("git", ["remote", "get-url", "--push", "--all", "origin"], {
|
|
14078
|
-
cwd,
|
|
14079
|
-
encoding: "utf8"
|
|
14080
|
-
});
|
|
14081
|
-
if (result.status !== 0)
|
|
14082
|
-
return null;
|
|
14083
|
-
const urls = (result.stdout ?? "").split(/\r?\n/u).map((url) => url.trim()).filter(Boolean);
|
|
14084
|
-
return urls.length === 1 ? urls[0] : null;
|
|
14085
|
-
};
|
|
14086
|
-
|
|
14087
|
-
// packages/workit-core/src/forge/redact.ts
|
|
14088
|
-
var KEY = String.raw`[A-Za-z0-9_.-]*?(?:token|secret|password|passwd|pwd|api[_-]?key|access[_-]?key|account[_-]?key|private[_-]?key|client[_-]?secret|credentials?|authorization|cookie|signature)[A-Za-z0-9_.-]*`;
|
|
14089
|
-
var ASSIGNMENT = new RegExp(String.raw`(?<![A-Za-z0-9])(["']?${KEY}["']?\s*[:=]\s*["']?)((?:bearer|basic|token|digest)\s+)?([^\s"',;&]+)`, "giu");
|
|
14090
|
-
var QUOTED_ASSIGNMENT = new RegExp(String.raw`(?<![A-Za-z0-9])(["']?${KEY}["']?\s*[:=]\s*)(["'])([^"'\n]*[^\s"'][^"'\n]*)\2`, "giu");
|
|
14091
|
-
var ANSI = new RegExp(String.raw`\u001b\[[0-9;?]*[ -/]*[@-~]|\u001b\][^\u0007]*\u0007`, "gu");
|
|
13837
|
+
var laneNeed = (lane) => lane === "none" ? "none" : lane === "self" ? "self" : "non-author";
|
|
14092
13838
|
|
|
14093
|
-
// packages/workit-core/src/
|
|
14094
|
-
var
|
|
13839
|
+
// packages/workit-core/src/core/policy/carry.ts
|
|
13840
|
+
var TEST_ONLY_KINDS = new Set(["doc", "cosmetic", "generated", "test"]);
|
|
13841
|
+
function verdictDrift(cwd, input) {
|
|
13842
|
+
const since = input.from;
|
|
13843
|
+
const stale = (reason, files = null, focused = false) => ({
|
|
13844
|
+
carries: false,
|
|
13845
|
+
since,
|
|
13846
|
+
files,
|
|
13847
|
+
focused,
|
|
13848
|
+
reason
|
|
13849
|
+
});
|
|
13850
|
+
const fromFork = mergeBase(cwd, input.fromBase, input.from);
|
|
13851
|
+
const toFork = mergeBase(cwd, input.toBase, input.to);
|
|
13852
|
+
const files = fromFork && toFork ? driftFiles(cwd, { from: input.from, to: input.to, fromFork, toFork }) : null;
|
|
13853
|
+
if (!fromFork || !toFork || !files)
|
|
13854
|
+
return stale(`the change since ${since.slice(0, 12)} cannot be read`);
|
|
13855
|
+
const riskPaths = [fromFork, toFork].flatMap((fork) => parseRiskPaths(fileAt(cwd, fork, RISK_PATHS_FILE) ?? ""));
|
|
13856
|
+
const lane = computeLane({ files, riskPaths });
|
|
13857
|
+
const paths = files.map((file) => file.path);
|
|
13858
|
+
if (lane.lane === "none")
|
|
13859
|
+
return { carries: true, rule: "lane-none", since, files: paths };
|
|
13860
|
+
if (input.kind === "unit" && lane.lane === "self" && files.every((file) => TEST_ONLY_KINDS.has(kindOf(file))))
|
|
13861
|
+
return { carries: true, rule: "tests-only", since, files: paths };
|
|
13862
|
+
return stale(lane.reasons[0] ?? `lane ${lane.lane}`, paths, lane.lane === "self");
|
|
13863
|
+
}
|
|
14095
13864
|
|
|
14096
|
-
// packages/workit-core/src/
|
|
14097
|
-
|
|
14098
|
-
|
|
14099
|
-
|
|
14100
|
-
|
|
14101
|
-
|
|
14102
|
-
|
|
14103
|
-
|
|
14104
|
-
|
|
14105
|
-
|
|
14106
|
-
|
|
14107
|
-
|
|
14108
|
-
|
|
14109
|
-
|
|
14110
|
-
|
|
14111
|
-
|
|
14112
|
-
|
|
14113
|
-
|
|
14114
|
-
|
|
14115
|
-
|
|
13865
|
+
// packages/workit-core/src/hooks/standing-signal.ts
|
|
13866
|
+
import { createHash as createHash10 } from "node:crypto";
|
|
13867
|
+
import fs12 from "node:fs";
|
|
13868
|
+
import path15 from "node:path";
|
|
13869
|
+
var gitStoreDir = (cwd) => {
|
|
13870
|
+
try {
|
|
13871
|
+
for (let dir = path15.resolve(cwd);; dir = path15.dirname(dir)) {
|
|
13872
|
+
const dotGit = path15.join(dir, ".git");
|
|
13873
|
+
if (fs12.existsSync(dotGit)) {
|
|
13874
|
+
let gitDir = dotGit;
|
|
13875
|
+
if (fs12.statSync(dotGit).isFile()) {
|
|
13876
|
+
const target = /^gitdir:\s*(.+)$/mu.exec(fs12.readFileSync(dotGit, "utf8"))?.[1]?.trim();
|
|
13877
|
+
if (!target)
|
|
13878
|
+
return null;
|
|
13879
|
+
gitDir = path15.resolve(dir, target);
|
|
13880
|
+
const common = path15.join(gitDir, "commondir");
|
|
13881
|
+
if (fs12.existsSync(common))
|
|
13882
|
+
gitDir = path15.resolve(gitDir, fs12.readFileSync(common, "utf8").trim());
|
|
13883
|
+
}
|
|
13884
|
+
return path15.join(gitDir, "workit");
|
|
13885
|
+
}
|
|
13886
|
+
if (path15.dirname(dir) === dir)
|
|
14116
13887
|
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, "\\$&");
|
|
13888
|
+
}
|
|
13889
|
+
} catch {
|
|
13890
|
+
return null;
|
|
14125
13891
|
}
|
|
14126
|
-
|
|
13892
|
+
};
|
|
13893
|
+
var standingRevFile = (store) => path15.join(store, "standing", "rev");
|
|
13894
|
+
var standingSeenFile = (store, session, agentId) => path15.join(store, "standing", "seen", `${createHash10("sha256").update(`${session}\x00${agentId ?? ""}`).digest("hex").slice(0, 32)}.json`);
|
|
13895
|
+
var readStandingRev = (store) => {
|
|
14127
13896
|
try {
|
|
14128
|
-
return
|
|
13897
|
+
return fs12.readFileSync(standingRevFile(store), "utf8").trim() || null;
|
|
14129
13898
|
} catch {
|
|
14130
13899
|
return null;
|
|
14131
13900
|
}
|
|
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("/")) : "";
|
|
13901
|
+
};
|
|
13902
|
+
var readStandingSeen = (file) => {
|
|
13903
|
+
try {
|
|
13904
|
+
const value = JSON.parse(fs12.readFileSync(file, "utf8"));
|
|
14159
13905
|
return {
|
|
14160
|
-
|
|
14161
|
-
|
|
13906
|
+
rev: typeof value.rev === "string" ? value.rev : null,
|
|
13907
|
+
ids: Array.isArray(value.ids) ? value.ids.filter((id) => typeof id === "string") : []
|
|
14162
13908
|
};
|
|
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);
|
|
13909
|
+
} catch {
|
|
13910
|
+
return { rev: null, ids: [] };
|
|
14169
13911
|
}
|
|
14170
|
-
|
|
14171
|
-
|
|
13912
|
+
};
|
|
13913
|
+
var writeStandingSeen = (file, seen) => {
|
|
13914
|
+
try {
|
|
13915
|
+
fs12.mkdirSync(path15.dirname(file), { recursive: true });
|
|
13916
|
+
fs12.writeFileSync(file, JSON.stringify(seen));
|
|
13917
|
+
} catch {}
|
|
13918
|
+
};
|
|
14172
13919
|
|
|
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
|
-
])
|
|
13920
|
+
// packages/workit-core/src/ledger.ts
|
|
13921
|
+
var LEDGER_VERSION = 1;
|
|
13922
|
+
var MAX_LINE_BYTES = 4096;
|
|
13923
|
+
var PASSING = new Set(["verified", "tests-verified", "type-check-only"]);
|
|
13924
|
+
var FAILING = new Set(["failed", "blocked"]);
|
|
13925
|
+
var STRONG_RESULTS = new Set(["verified", "tests-verified"]);
|
|
13926
|
+
var PR_ROW_TYPES = new Set([
|
|
13927
|
+
"pr.created",
|
|
13928
|
+
"pr.status",
|
|
13929
|
+
"pr.merged",
|
|
13930
|
+
"pr.linked"
|
|
14196
13931
|
]);
|
|
14197
|
-
var
|
|
14198
|
-
|
|
14199
|
-
|
|
14200
|
-
|
|
14201
|
-
|
|
13932
|
+
var err = (code, error, unblock) => ({
|
|
13933
|
+
ok: false,
|
|
13934
|
+
code,
|
|
13935
|
+
error,
|
|
13936
|
+
...unblock ? { unblock } : {}
|
|
13937
|
+
});
|
|
13938
|
+
var gitRun2 = (cwd, args, timeoutMs = GIT_TIMEOUTS.local) => {
|
|
13939
|
+
const run = spawnSync9("git", args, {
|
|
13940
|
+
cwd,
|
|
13941
|
+
encoding: "utf8",
|
|
13942
|
+
timeout: timeoutMs,
|
|
13943
|
+
killSignal: "SIGKILL",
|
|
13944
|
+
windowsHide: true,
|
|
13945
|
+
maxBuffer: 256 * 1024 * 1024,
|
|
13946
|
+
env: {
|
|
13947
|
+
...process.env,
|
|
13948
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
13949
|
+
GIT_OPTIONAL_LOCKS: "0",
|
|
13950
|
+
LC_ALL: "C",
|
|
13951
|
+
LANGUAGE: "C"
|
|
13952
|
+
}
|
|
13953
|
+
});
|
|
13954
|
+
return {
|
|
13955
|
+
ok: run.status === 0,
|
|
13956
|
+
stdout: run.stdout ?? "",
|
|
13957
|
+
stderr: run.error ? String(run.error.message) : run.stderr ?? ""
|
|
13958
|
+
};
|
|
14202
13959
|
};
|
|
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);
|
|
13960
|
+
var git4 = (cwd, args) => {
|
|
13961
|
+
const run = gitRun2(cwd, args);
|
|
13962
|
+
const value = run.ok ? run.stdout.trim() : "";
|
|
13963
|
+
return value ? value : null;
|
|
13964
|
+
};
|
|
13965
|
+
var safeRef2 = (value) => value.length > 0 && !value.startsWith("-");
|
|
13966
|
+
function resolveCommit(cwd, ref) {
|
|
13967
|
+
if (!safeRef2(ref))
|
|
13968
|
+
return null;
|
|
13969
|
+
return git4(cwd, ["rev-parse", "--verify", "-q", `${ref}^{commit}`]);
|
|
14234
13970
|
}
|
|
14235
|
-
function
|
|
14236
|
-
|
|
14237
|
-
|
|
13971
|
+
function branchHead(cwd, branch) {
|
|
13972
|
+
if (!safeRef2(branch))
|
|
13973
|
+
return null;
|
|
13974
|
+
return resolveCommit(cwd, `refs/heads/${branch}`) ?? resolveCommit(cwd, `refs/remotes/origin/${branch}`);
|
|
13975
|
+
}
|
|
13976
|
+
function diffHash(cwd, base, head) {
|
|
13977
|
+
if (!safeRef2(base) || !safeRef2(head))
|
|
13978
|
+
return null;
|
|
13979
|
+
const diff = gitRun2(cwd, [
|
|
13980
|
+
"diff",
|
|
13981
|
+
"-U3",
|
|
13982
|
+
"--diff-algorithm=myers",
|
|
13983
|
+
"--indent-heuristic",
|
|
13984
|
+
"--no-color",
|
|
13985
|
+
"--no-ext-diff",
|
|
13986
|
+
"--no-textconv",
|
|
13987
|
+
"--no-renames",
|
|
13988
|
+
"--full-index",
|
|
13989
|
+
"--binary",
|
|
13990
|
+
"--src-prefix=a/",
|
|
13991
|
+
"--dst-prefix=b/",
|
|
13992
|
+
`${base}...${head}`,
|
|
13993
|
+
"--"
|
|
13994
|
+
], GIT_TIMEOUTS.worktree);
|
|
13995
|
+
if (!diff.ok || !diff.stdout)
|
|
13996
|
+
return null;
|
|
13997
|
+
const hash = createHash11("sha256");
|
|
13998
|
+
for (const line of diff.stdout.split(`
|
|
13999
|
+
`)) {
|
|
14000
|
+
if (line.startsWith("index "))
|
|
14001
|
+
continue;
|
|
14002
|
+
hash.update(line.startsWith("@@ ") ? "@@" : line);
|
|
14003
|
+
hash.update(`
|
|
14238
14004
|
`);
|
|
14005
|
+
}
|
|
14006
|
+
return `sha256:${hash.digest("hex")}`;
|
|
14239
14007
|
}
|
|
14240
|
-
function
|
|
14241
|
-
const
|
|
14242
|
-
|
|
14243
|
-
|
|
14244
|
-
|
|
14245
|
-
|
|
14246
|
-
|
|
14247
|
-
|
|
14248
|
-
|
|
14249
|
-
|
|
14250
|
-
|
|
14251
|
-
|
|
14252
|
-
|
|
14253
|
-
|
|
14008
|
+
function storeRoot(cwd) {
|
|
14009
|
+
const location = resolveStore(path16.resolve(cwd));
|
|
14010
|
+
if (location instanceof Error)
|
|
14011
|
+
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");
|
|
14012
|
+
return { ok: true, value: { root: location.dir, shared: location.shared } };
|
|
14013
|
+
}
|
|
14014
|
+
function ledgerPath(cwd) {
|
|
14015
|
+
const root = storeRoot(cwd);
|
|
14016
|
+
return root.ok ? { ok: true, value: path16.join(root.value.root, "ledger", "ledger.jsonl") } : root;
|
|
14017
|
+
}
|
|
14018
|
+
var newId2 = () => `${Date.now().toString(36)}-${randomBytes3(6).toString("hex")}`;
|
|
14019
|
+
var NETWORK_FS_TYPES = new Set([
|
|
14020
|
+
26985,
|
|
14021
|
+
20859,
|
|
14022
|
+
4266872130,
|
|
14023
|
+
4283649346,
|
|
14024
|
+
1702057286,
|
|
14025
|
+
1397113167,
|
|
14026
|
+
12805120,
|
|
14027
|
+
16914839,
|
|
14028
|
+
1196443219,
|
|
14029
|
+
198183888
|
|
14030
|
+
]);
|
|
14031
|
+
var isNetworkFsType = (type) => NETWORK_FS_TYPES.has(type >>> 0);
|
|
14032
|
+
var onNetworkFs = (dir) => {
|
|
14033
|
+
if (process.platform !== "linux")
|
|
14034
|
+
return false;
|
|
14035
|
+
try {
|
|
14036
|
+
return isNetworkFsType(fs13.statfsSync(dir).type);
|
|
14037
|
+
} catch {
|
|
14038
|
+
return false;
|
|
14254
14039
|
}
|
|
14255
|
-
|
|
14256
|
-
|
|
14257
|
-
|
|
14258
|
-
|
|
14259
|
-
|
|
14260
|
-
|
|
14261
|
-
|
|
14262
|
-
|
|
14263
|
-
|
|
14264
|
-
|
|
14265
|
-
|
|
14266
|
-
|
|
14267
|
-
|
|
14268
|
-
|
|
14269
|
-
|
|
14270
|
-
|
|
14271
|
-
|
|
14272
|
-
|
|
14273
|
-
|
|
14274
|
-
|
|
14275
|
-
|
|
14276
|
-
|
|
14277
|
-
|
|
14278
|
-
|
|
14279
|
-
|
|
14040
|
+
};
|
|
14041
|
+
var LOCK_WAIT_MS = 5000;
|
|
14042
|
+
var LOCK_STALE_MS = 30000;
|
|
14043
|
+
var LOCK_OWNER = "owner";
|
|
14044
|
+
var lockToken = (lock) => {
|
|
14045
|
+
try {
|
|
14046
|
+
return fs13.readFileSync(path16.join(lock, LOCK_OWNER), "utf8");
|
|
14047
|
+
} catch {
|
|
14048
|
+
return null;
|
|
14049
|
+
}
|
|
14050
|
+
};
|
|
14051
|
+
var lockAgeMs = (lock) => {
|
|
14052
|
+
for (const target of [path16.join(lock, LOCK_OWNER), lock]) {
|
|
14053
|
+
try {
|
|
14054
|
+
return Date.now() - fs13.statSync(target).mtimeMs;
|
|
14055
|
+
} catch {}
|
|
14056
|
+
}
|
|
14057
|
+
return null;
|
|
14058
|
+
};
|
|
14059
|
+
var ledgerLock = {
|
|
14060
|
+
acquire(lock) {
|
|
14061
|
+
try {
|
|
14062
|
+
fs13.mkdirSync(lock);
|
|
14063
|
+
} catch (error) {
|
|
14064
|
+
if (error.code === "EEXIST")
|
|
14065
|
+
return null;
|
|
14066
|
+
throw error;
|
|
14280
14067
|
}
|
|
14281
|
-
|
|
14282
|
-
|
|
14283
|
-
|
|
14284
|
-
|
|
14285
|
-
|
|
14286
|
-
if (
|
|
14287
|
-
return
|
|
14288
|
-
|
|
14289
|
-
|
|
14290
|
-
|
|
14291
|
-
|
|
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;
|
|
14068
|
+
const token = `${process.pid}-${randomBytes3(8).toString("hex")}`;
|
|
14069
|
+
fs13.writeFileSync(path16.join(lock, LOCK_OWNER), token);
|
|
14070
|
+
return token;
|
|
14071
|
+
},
|
|
14072
|
+
release(lock, token) {
|
|
14073
|
+
if (lockToken(lock) !== token)
|
|
14074
|
+
return false;
|
|
14075
|
+
const tomb = `${lock}.release-${randomBytes3(6).toString("hex")}`;
|
|
14076
|
+
try {
|
|
14077
|
+
fs13.renameSync(lock, tomb);
|
|
14078
|
+
} catch {
|
|
14079
|
+
return false;
|
|
14314
14080
|
}
|
|
14315
|
-
|
|
14316
|
-
|
|
14317
|
-
|
|
14318
|
-
|
|
14319
|
-
|
|
14320
|
-
if (frame.kind === "template") {
|
|
14321
|
-
if (char === "\\") {
|
|
14322
|
-
emitVerbatim(text.slice(index, index + 2));
|
|
14323
|
-
index += 2;
|
|
14324
|
-
} else if (char === "`") {
|
|
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;
|
|
14081
|
+
if (lockToken(tomb) !== token) {
|
|
14082
|
+
try {
|
|
14083
|
+
fs13.renameSync(tomb, lock);
|
|
14084
|
+
} catch {}
|
|
14085
|
+
return false;
|
|
14338
14086
|
}
|
|
14339
|
-
|
|
14340
|
-
|
|
14341
|
-
|
|
14342
|
-
|
|
14343
|
-
|
|
14087
|
+
fs13.rmSync(tomb, { recursive: true, force: true });
|
|
14088
|
+
return true;
|
|
14089
|
+
},
|
|
14090
|
+
token: lockToken,
|
|
14091
|
+
reap(lock, observed) {
|
|
14092
|
+
const tomb = `${lock}.reap-${randomBytes3(6).toString("hex")}`;
|
|
14093
|
+
try {
|
|
14094
|
+
fs13.renameSync(lock, tomb);
|
|
14095
|
+
} catch {
|
|
14096
|
+
return false;
|
|
14344
14097
|
}
|
|
14345
|
-
if (
|
|
14346
|
-
|
|
14347
|
-
|
|
14348
|
-
else
|
|
14349
|
-
out += char;
|
|
14350
|
-
index += 1;
|
|
14351
|
-
continue;
|
|
14098
|
+
if (lockToken(tomb) === observed) {
|
|
14099
|
+
fs13.rmSync(tomb, { recursive: true, force: true });
|
|
14100
|
+
return true;
|
|
14352
14101
|
}
|
|
14353
|
-
|
|
14354
|
-
|
|
14355
|
-
|
|
14356
|
-
|
|
14357
|
-
const comment = text.slice(index, end < 0 ? text.length : end);
|
|
14358
|
-
if (DIRECTIVE.test(comment.slice(lineOpener.length)))
|
|
14359
|
-
emitVerbatim(comment);
|
|
14360
|
-
else
|
|
14361
|
-
pendingSpace = true;
|
|
14362
|
-
index = end < 0 ? text.length : end;
|
|
14363
|
-
continue;
|
|
14102
|
+
try {
|
|
14103
|
+
fs13.renameSync(tomb, lock);
|
|
14104
|
+
} catch {
|
|
14105
|
+
fs13.rmSync(tomb, { recursive: true, force: true });
|
|
14364
14106
|
}
|
|
14365
|
-
|
|
14366
|
-
|
|
14367
|
-
|
|
14368
|
-
|
|
14369
|
-
|
|
14370
|
-
|
|
14371
|
-
|
|
14372
|
-
|
|
14373
|
-
|
|
14374
|
-
|
|
14375
|
-
|
|
14376
|
-
|
|
14377
|
-
|
|
14378
|
-
|
|
14107
|
+
return false;
|
|
14108
|
+
}
|
|
14109
|
+
};
|
|
14110
|
+
function withLedgerLock(file, fn, waitMs = hostAwareLockTimeout(LOCK_WAIT_MS)) {
|
|
14111
|
+
const lock = `${file}.lock`;
|
|
14112
|
+
const deadline = Date.now() + waitMs;
|
|
14113
|
+
let token = null;
|
|
14114
|
+
for (;; ) {
|
|
14115
|
+
token = ledgerLock.acquire(lock);
|
|
14116
|
+
if (token)
|
|
14117
|
+
break;
|
|
14118
|
+
const observed = lockToken(lock);
|
|
14119
|
+
const age = lockAgeMs(lock);
|
|
14120
|
+
if (age !== null && age > LOCK_STALE_MS && ledgerLock.reap(lock, observed))
|
|
14379
14121
|
continue;
|
|
14380
|
-
|
|
14381
|
-
|
|
14382
|
-
|
|
14122
|
+
if (Date.now() >= deadline)
|
|
14123
|
+
return null;
|
|
14124
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 25);
|
|
14125
|
+
}
|
|
14126
|
+
try {
|
|
14127
|
+
return fn();
|
|
14128
|
+
} finally {
|
|
14129
|
+
ledgerLock.release(lock, token);
|
|
14130
|
+
}
|
|
14131
|
+
}
|
|
14132
|
+
var sha2562 = (text) => createHash11("sha256").update(text).digest("hex");
|
|
14133
|
+
var chainHash = (prevHash, body) => sha2562(`${prevHash ?? ""}
|
|
14134
|
+
${body}`);
|
|
14135
|
+
var TAIL_BYTES = 64 * 1024;
|
|
14136
|
+
var lastLine = (fd, size) => {
|
|
14137
|
+
for (let span = Math.min(size, TAIL_BYTES);span > 0; span = Math.min(size, span * 2)) {
|
|
14138
|
+
const buffer = Buffer.alloc(span);
|
|
14139
|
+
fs13.readSync(fd, buffer, 0, span, size - span);
|
|
14140
|
+
const end = buffer.lastIndexOf(10);
|
|
14141
|
+
if (end < 0) {
|
|
14142
|
+
if (span === size)
|
|
14383
14143
|
return null;
|
|
14384
14144
|
continue;
|
|
14385
14145
|
}
|
|
14386
|
-
|
|
14387
|
-
|
|
14388
|
-
|
|
14389
|
-
|
|
14146
|
+
let stop = end;
|
|
14147
|
+
let text = "";
|
|
14148
|
+
while (stop >= 0) {
|
|
14149
|
+
const start = buffer.lastIndexOf(10, stop - 1);
|
|
14150
|
+
text = buffer.toString("utf8", start + 1, stop).trim();
|
|
14151
|
+
if (text) {
|
|
14152
|
+
if (start >= 0 || span === size)
|
|
14153
|
+
return text;
|
|
14154
|
+
break;
|
|
14155
|
+
}
|
|
14156
|
+
if (start < 0) {
|
|
14157
|
+
if (span === size)
|
|
14158
|
+
return null;
|
|
14159
|
+
break;
|
|
14160
|
+
}
|
|
14161
|
+
stop = start;
|
|
14390
14162
|
}
|
|
14391
|
-
if (
|
|
14392
|
-
|
|
14393
|
-
|
|
14394
|
-
|
|
14163
|
+
if (span === size)
|
|
14164
|
+
return text || null;
|
|
14165
|
+
}
|
|
14166
|
+
return null;
|
|
14167
|
+
};
|
|
14168
|
+
var tighten = (fd) => {
|
|
14169
|
+
try {
|
|
14170
|
+
if ((fs13.fstatSync(fd).mode & 63) !== 0)
|
|
14171
|
+
fs13.fchmodSync(fd, 384);
|
|
14172
|
+
} catch {}
|
|
14173
|
+
};
|
|
14174
|
+
function appendLine(file, build, options) {
|
|
14175
|
+
const write = () => {
|
|
14176
|
+
const fd = fs13.openSync(file, "a+", 384);
|
|
14177
|
+
try {
|
|
14178
|
+
if (process.platform !== "win32")
|
|
14179
|
+
tighten(fd);
|
|
14180
|
+
const size = fs13.fstatSync(fd).size;
|
|
14181
|
+
let fence = "";
|
|
14182
|
+
if (size > 0) {
|
|
14183
|
+
const last = Buffer.alloc(1);
|
|
14184
|
+
fs13.readSync(fd, last, 0, 1, size - 1);
|
|
14185
|
+
if (last[0] !== 10)
|
|
14186
|
+
fence = `
|
|
14187
|
+
`;
|
|
14188
|
+
}
|
|
14189
|
+
const line = build(lastLine(fd, size));
|
|
14190
|
+
if (!line.ok)
|
|
14191
|
+
return line;
|
|
14192
|
+
const bytes = Buffer.from(fence + line.value);
|
|
14193
|
+
const written = fs13.writeSync(fd, bytes);
|
|
14194
|
+
if (written !== bytes.length)
|
|
14195
|
+
return err("unavailable", `short write to ${file}: ${written} of ${bytes.length} bytes; the row is torn and will be skipped`);
|
|
14196
|
+
return { ok: true, value: undefined };
|
|
14197
|
+
} finally {
|
|
14198
|
+
fs13.closeSync(fd);
|
|
14395
14199
|
}
|
|
14396
|
-
|
|
14397
|
-
|
|
14398
|
-
|
|
14200
|
+
};
|
|
14201
|
+
try {
|
|
14202
|
+
fs13.mkdirSync(path16.dirname(file), { recursive: true });
|
|
14203
|
+
if (!options.forceLock && !onNetworkFs(path16.dirname(file)))
|
|
14204
|
+
return write();
|
|
14205
|
+
const locked = withLedgerLock(file, write);
|
|
14206
|
+
return locked ?? err("busy", `ledger lock ${file}.lock is held`, "retry; remove the .lock directory if no workit process is running");
|
|
14207
|
+
} catch (error) {
|
|
14208
|
+
return err("unavailable", `cannot append to ${file}: ${error.message}`);
|
|
14209
|
+
}
|
|
14210
|
+
}
|
|
14211
|
+
var sealed = (row, id, at, previous) => {
|
|
14212
|
+
const { prevHash: _prev, rowHash: _row, ...fields } = row;
|
|
14213
|
+
const prevHash = previous === null ? null : sha2562(previous);
|
|
14214
|
+
const body = { v: LEDGER_VERSION, id, at, ...fields, prevHash };
|
|
14215
|
+
const full = { ...body, rowHash: chainHash(prevHash, JSON.stringify(body)) };
|
|
14216
|
+
return { full, line: `${JSON.stringify(full)}
|
|
14217
|
+
` };
|
|
14218
|
+
};
|
|
14219
|
+
function appendRaw(cwd, row, options) {
|
|
14220
|
+
const file = ledgerPath(cwd);
|
|
14221
|
+
if (!file.ok)
|
|
14222
|
+
return file;
|
|
14223
|
+
const id = newId2();
|
|
14224
|
+
const at = (options.now ?? new Date).toISOString();
|
|
14225
|
+
let full = null;
|
|
14226
|
+
const written = appendLine(file.value, (previous) => {
|
|
14227
|
+
const next = sealed(row, id, at, previous);
|
|
14228
|
+
const bytes = Buffer.byteLength(next.line);
|
|
14229
|
+
if (bytes > MAX_LINE_BYTES)
|
|
14230
|
+
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>`);
|
|
14231
|
+
full = next.full;
|
|
14232
|
+
return { ok: true, value: next.line };
|
|
14233
|
+
}, options);
|
|
14234
|
+
return written.ok ? { ok: true, value: full } : written;
|
|
14235
|
+
}
|
|
14236
|
+
function observedRowBytes(row) {
|
|
14237
|
+
const sample = sealed({ ...row, observer: "workit_cli" }, newId2(), new Date().toISOString(), "");
|
|
14238
|
+
return Buffer.byteLength(sample.line);
|
|
14239
|
+
}
|
|
14240
|
+
function appendObserved(cwd, row, options = {}) {
|
|
14241
|
+
return appendRaw(cwd, { ...row, observer: "workit_cli" }, options);
|
|
14242
|
+
}
|
|
14243
|
+
function appendHookObserved(cwd, row) {
|
|
14244
|
+
return appendRaw(cwd, { ...row, observer: "host_hook" }, {});
|
|
14245
|
+
}
|
|
14246
|
+
var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
14247
|
+
var str2 = (value) => typeof value === "string" ? value : null;
|
|
14248
|
+
var normalizeActor = (value) => {
|
|
14249
|
+
const actor = isRecord3(value) ? value : {};
|
|
14250
|
+
return {
|
|
14251
|
+
host: str2(actor.host) ?? "unknown",
|
|
14252
|
+
session: str2(actor.session),
|
|
14253
|
+
agentId: str2(actor.agentId)
|
|
14254
|
+
};
|
|
14255
|
+
};
|
|
14256
|
+
function supersedeAllowed(by, target, onBranch = (row) => (by.branch ?? null) === (row.branch ?? null)) {
|
|
14257
|
+
if (by.type !== target.type)
|
|
14258
|
+
return `a ${by.type} row cannot supersede a ${target.type} row`;
|
|
14259
|
+
if (!by.actor.session || by.actor.session !== target.actor.session)
|
|
14260
|
+
return "only the session that wrote a row can supersede it";
|
|
14261
|
+
if (!onBranch(target))
|
|
14262
|
+
return `the row is on another branch (${String(target.branch ?? "none")}); a row supersedes only one on its own branch`;
|
|
14263
|
+
if (by.type === "verdict" && (by.kind ?? "review") !== (target.kind ?? "review"))
|
|
14264
|
+
return `a ${String(by.kind ?? "review")} verdict cannot supersede a ${String(target.kind ?? "review")} verdict; record it with --kind ${String(target.kind ?? "review")}`;
|
|
14265
|
+
if (by.type === "verdict" && by.self === true && target.self !== true)
|
|
14266
|
+
return "a self verdict cannot replace an independent verdict";
|
|
14267
|
+
return null;
|
|
14268
|
+
}
|
|
14269
|
+
function readLedger(cwd, options = {}) {
|
|
14270
|
+
const file = ledgerPath(cwd);
|
|
14271
|
+
if (!file.ok)
|
|
14272
|
+
return file;
|
|
14273
|
+
let text = "";
|
|
14274
|
+
let stat = null;
|
|
14275
|
+
try {
|
|
14276
|
+
stat = fs13.statSync(file.value);
|
|
14277
|
+
text = fs13.readFileSync(file.value, "utf8");
|
|
14278
|
+
} catch (error) {
|
|
14279
|
+
if (error.code === "ENOENT")
|
|
14280
|
+
return {
|
|
14281
|
+
ok: true,
|
|
14282
|
+
value: {
|
|
14283
|
+
path: file.value,
|
|
14284
|
+
rows: [],
|
|
14285
|
+
skipped: 0,
|
|
14286
|
+
integrity: options.integrity ? { chained: 0, legacy: 0, unverified: [] } : null
|
|
14287
|
+
}
|
|
14288
|
+
};
|
|
14289
|
+
return err("unavailable", `cannot read ${file.value}: ${error.message}`);
|
|
14290
|
+
}
|
|
14291
|
+
const rows = [];
|
|
14292
|
+
let skipped = 0;
|
|
14293
|
+
const integrity = { chained: 0, legacy: 0, unverified: [] };
|
|
14294
|
+
const earlier = new Set([null]);
|
|
14295
|
+
let chainStarted = false;
|
|
14296
|
+
const ids = new Set;
|
|
14297
|
+
for (const raw of text.split(`
|
|
14298
|
+
`)) {
|
|
14299
|
+
const trimmed = raw.trim();
|
|
14300
|
+
if (!trimmed)
|
|
14399
14301
|
continue;
|
|
14400
|
-
|
|
14401
|
-
if (
|
|
14402
|
-
|
|
14403
|
-
|
|
14404
|
-
|
|
14302
|
+
const linked = (prevHash) => earlier.has(prevHash);
|
|
14303
|
+
if (options.integrity)
|
|
14304
|
+
earlier.add(sha2562(trimmed));
|
|
14305
|
+
let parsed;
|
|
14306
|
+
try {
|
|
14307
|
+
parsed = JSON.parse(trimmed);
|
|
14308
|
+
} catch {
|
|
14309
|
+
skipped += 1;
|
|
14405
14310
|
continue;
|
|
14406
14311
|
}
|
|
14407
|
-
if (
|
|
14408
|
-
|
|
14409
|
-
|
|
14410
|
-
const lineEnd = text.indexOf(`
|
|
14411
|
-
`, index);
|
|
14412
|
-
if (text.slice(index + 1, lineEnd < 0 ? text.length : lineEnd).includes("/"))
|
|
14413
|
-
return null;
|
|
14312
|
+
if (!isRecord3(parsed) || !str2(parsed.id) || !str2(parsed.type) || !str2(parsed.at)) {
|
|
14313
|
+
skipped += 1;
|
|
14314
|
+
continue;
|
|
14414
14315
|
}
|
|
14415
|
-
|
|
14416
|
-
|
|
14417
|
-
|
|
14418
|
-
|
|
14419
|
-
|
|
14420
|
-
|
|
14421
|
-
|
|
14422
|
-
|
|
14423
|
-
|
|
14424
|
-
|
|
14425
|
-
|
|
14426
|
-
|
|
14427
|
-
|
|
14428
|
-
|
|
14429
|
-
|
|
14430
|
-
|
|
14431
|
-
|
|
14432
|
-
|
|
14433
|
-
|
|
14316
|
+
let rowIntegrity = options.integrity ? "verified" : undefined;
|
|
14317
|
+
let reason = null;
|
|
14318
|
+
if (!options.integrity) {} else if (typeof parsed.rowHash === "string") {
|
|
14319
|
+
chainStarted = true;
|
|
14320
|
+
const { rowHash, ...body } = parsed;
|
|
14321
|
+
const prevHash = str2(parsed.prevHash);
|
|
14322
|
+
if (chainHash(prevHash, JSON.stringify(body)) !== rowHash)
|
|
14323
|
+
reason = "hash_mismatch";
|
|
14324
|
+
else if (!linked(prevHash))
|
|
14325
|
+
reason = "broken_link";
|
|
14326
|
+
} else if (chainStarted)
|
|
14327
|
+
reason = "unsigned";
|
|
14328
|
+
else
|
|
14329
|
+
rowIntegrity = "legacy";
|
|
14330
|
+
if (options.integrity && !reason && ids.has(parsed.id))
|
|
14331
|
+
reason = "duplicate_id";
|
|
14332
|
+
if (options.integrity)
|
|
14333
|
+
ids.add(parsed.id);
|
|
14334
|
+
if (reason)
|
|
14335
|
+
rowIntegrity = "unverified";
|
|
14336
|
+
const pr = typeof parsed.pr === "number" && Number.isInteger(parsed.pr) ? parsed.pr : undefined;
|
|
14337
|
+
const row = {
|
|
14338
|
+
...parsed,
|
|
14339
|
+
v: typeof parsed.v === "number" ? parsed.v : LEDGER_VERSION,
|
|
14340
|
+
id: parsed.id,
|
|
14341
|
+
at: parsed.at,
|
|
14342
|
+
type: parsed.type,
|
|
14343
|
+
actor: normalizeActor(parsed.actor),
|
|
14344
|
+
branch: str2(parsed.branch),
|
|
14345
|
+
head: str2(parsed.head),
|
|
14346
|
+
base: str2(parsed.base),
|
|
14347
|
+
baseSha: str2(parsed.baseSha),
|
|
14348
|
+
patchId: str2(parsed.patchId),
|
|
14349
|
+
diffHash: str2(parsed.diffHash),
|
|
14350
|
+
tree: str2(parsed.tree),
|
|
14351
|
+
dirty: typeof parsed.dirty === "boolean" ? parsed.dirty : null,
|
|
14352
|
+
...pr === undefined ? {} : { pr },
|
|
14353
|
+
...str2(parsed.supersedes) ? { supersedes: parsed.supersedes } : {},
|
|
14354
|
+
seq: rows.length + 1,
|
|
14355
|
+
superseded: false,
|
|
14356
|
+
supersedeIgnored: false,
|
|
14357
|
+
...rowIntegrity ? { integrity: rowIntegrity } : {},
|
|
14358
|
+
...reason ? { integrityReason: reason } : {}
|
|
14359
|
+
};
|
|
14360
|
+
rows.push(row);
|
|
14361
|
+
if (!options.integrity)
|
|
14434
14362
|
continue;
|
|
14435
|
-
|
|
14436
|
-
|
|
14437
|
-
|
|
14438
|
-
|
|
14439
|
-
|
|
14440
|
-
|
|
14441
|
-
|
|
14442
|
-
|
|
14443
|
-
|
|
14444
|
-
|
|
14445
|
-
|
|
14446
|
-
|
|
14447
|
-
}
|
|
14448
|
-
}
|
|
14449
|
-
emitCode(char);
|
|
14450
|
-
index += 1;
|
|
14363
|
+
if (rowIntegrity === "verified")
|
|
14364
|
+
integrity.chained += 1;
|
|
14365
|
+
else if (rowIntegrity === "legacy")
|
|
14366
|
+
integrity.legacy += 1;
|
|
14367
|
+
else if (reason)
|
|
14368
|
+
integrity.unverified.push({
|
|
14369
|
+
seq: row.seq,
|
|
14370
|
+
id: row.id,
|
|
14371
|
+
type: row.type,
|
|
14372
|
+
branch: row.branch,
|
|
14373
|
+
reason
|
|
14374
|
+
});
|
|
14451
14375
|
}
|
|
14452
|
-
|
|
14453
|
-
|
|
14454
|
-
|
|
14455
|
-
|
|
14456
|
-
|
|
14457
|
-
|
|
14458
|
-
|
|
14459
|
-
|
|
14460
|
-
|
|
14461
|
-
|
|
14462
|
-
|
|
14463
|
-
|
|
14376
|
+
const scopes = new Map;
|
|
14377
|
+
const scopeOf = (branch) => {
|
|
14378
|
+
let scope = scopes.get(branch);
|
|
14379
|
+
if (!scope)
|
|
14380
|
+
scopes.set(branch, scope = branchScope(cwd, branch));
|
|
14381
|
+
return scope;
|
|
14382
|
+
};
|
|
14383
|
+
const byId = new Map;
|
|
14384
|
+
for (const row of rows) {
|
|
14385
|
+
const target = row.supersedes ? byId.get(row.supersedes) : undefined;
|
|
14386
|
+
if (target && row.integrity !== "unverified" && supersedeAllowed(row, target, renameAwareBranch(scopeOf, row.branch)) === null)
|
|
14387
|
+
target.superseded = true;
|
|
14388
|
+
else if (row.supersedes)
|
|
14389
|
+
row.supersedeIgnored = true;
|
|
14390
|
+
if (!byId.has(row.id))
|
|
14391
|
+
byId.set(row.id, row);
|
|
14464
14392
|
}
|
|
14465
|
-
|
|
14466
|
-
|
|
14467
|
-
|
|
14468
|
-
|
|
14393
|
+
if (options.integrity && stat && Buffer.byteLength(text) === stat.size)
|
|
14394
|
+
tamperCache.set(file.value, {
|
|
14395
|
+
size: stat.size,
|
|
14396
|
+
stamp: fileStamp(stat),
|
|
14397
|
+
breaks: integrity.unverified.filter((row) => TAMPER_REASONS.has(row.reason))
|
|
14398
|
+
});
|
|
14399
|
+
return {
|
|
14400
|
+
ok: true,
|
|
14401
|
+
value: { path: file.value, rows, skipped, integrity: options.integrity ? integrity : null }
|
|
14402
|
+
};
|
|
14469
14403
|
}
|
|
14470
|
-
var
|
|
14471
|
-
|
|
14472
|
-
|
|
14473
|
-
|
|
14404
|
+
var TAMPER_REASONS = new Set(["hash_mismatch", "broken_link"]);
|
|
14405
|
+
var tamperCache = new Map;
|
|
14406
|
+
var fileStamp = (stat) => `${stat.ino}:${stat.mtimeMs}:${stat.ctimeMs}`;
|
|
14407
|
+
function ledgerTampering(cwd) {
|
|
14408
|
+
const file = ledgerPath(cwd);
|
|
14409
|
+
if (!file.ok)
|
|
14410
|
+
return [];
|
|
14474
14411
|
try {
|
|
14475
|
-
|
|
14412
|
+
const stat = fs13.statSync(file.value);
|
|
14413
|
+
const cached = tamperCache.get(file.value);
|
|
14414
|
+
if (cached && cached.size === stat.size && cached.stamp === fileStamp(stat))
|
|
14415
|
+
return cached.breaks;
|
|
14476
14416
|
} catch {
|
|
14477
|
-
return
|
|
14417
|
+
return [];
|
|
14478
14418
|
}
|
|
14419
|
+
const read = readLedger(cwd, { integrity: true });
|
|
14420
|
+
return read.ok ? (read.value.integrity?.unverified ?? []).filter((row) => TAMPER_REASONS.has(row.reason)) : [];
|
|
14479
14421
|
}
|
|
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);
|
|
14422
|
+
function tamperedUnblock(cwd) {
|
|
14423
|
+
const file = ledgerPath(cwd);
|
|
14424
|
+
const where = file.ok ? file.value : "the ledger file";
|
|
14425
|
+
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
14426
|
}
|
|
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";
|
|
14427
|
+
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)}`;
|
|
14428
|
+
function verdictBasis(verdict, current) {
|
|
14429
|
+
if (verdict.head && verdict.head === current.head)
|
|
14430
|
+
return "fresh";
|
|
14431
|
+
if (verdict.patchId && verdict.patchId === current.patchId && verdict.diffHash && verdict.diffHash === current.diffHash)
|
|
14432
|
+
return "carried";
|
|
14433
|
+
return "stale";
|
|
14565
14434
|
}
|
|
14566
|
-
var
|
|
14567
|
-
function
|
|
14568
|
-
|
|
14569
|
-
|
|
14570
|
-
|
|
14571
|
-
|
|
14572
|
-
|
|
14573
|
-
|
|
14574
|
-
|
|
14575
|
-
|
|
14576
|
-
|
|
14577
|
-
|
|
14578
|
-
|
|
14579
|
-
|
|
14580
|
-
|
|
14581
|
-
|
|
14435
|
+
var isLiveVerdict = (row, retired) => row.type === "verdict" && typeof row.result === "string" && !retired.has(row.id);
|
|
14436
|
+
function trailerSessions(cwd, base, head) {
|
|
14437
|
+
if (!base || !head || !safeRef2(base) || !safeRef2(head))
|
|
14438
|
+
return [];
|
|
14439
|
+
const out = git4(cwd, [
|
|
14440
|
+
"log",
|
|
14441
|
+
"--format=%(trailers:key=Workit-Session,valueonly,separator=%x00)",
|
|
14442
|
+
`${base}..${head}`
|
|
14443
|
+
]);
|
|
14444
|
+
return (out ?? "").split(/[\0\n]/u).map((value) => value.trim()).filter(Boolean);
|
|
14445
|
+
}
|
|
14446
|
+
var RENAMED = /^branch: renamed refs\/heads\/(.+) to refs\/heads\/(.+)$/iu;
|
|
14447
|
+
function branchScope(cwd, branch) {
|
|
14448
|
+
const renamedAt = new Map;
|
|
14449
|
+
let since = Number.NEGATIVE_INFINITY;
|
|
14450
|
+
if (safeRef2(branch)) {
|
|
14451
|
+
const log = git4(cwd, [
|
|
14452
|
+
"log",
|
|
14453
|
+
"-g",
|
|
14454
|
+
"--date=unix",
|
|
14455
|
+
"--format=%gd%x09%gs",
|
|
14456
|
+
`refs/heads/${branch}`,
|
|
14457
|
+
"--"
|
|
14458
|
+
]);
|
|
14459
|
+
for (const line of (log ?? "").split(`
|
|
14460
|
+
`)) {
|
|
14461
|
+
const [selector = "", subject = ""] = line.split("\t");
|
|
14462
|
+
const seconds = Number(/@\{(\d+)\}$/u.exec(selector)?.[1]);
|
|
14463
|
+
if (!Number.isFinite(seconds))
|
|
14464
|
+
continue;
|
|
14465
|
+
since = seconds * 1000;
|
|
14466
|
+
const match = RENAMED.exec(subject);
|
|
14467
|
+
if (!match || match[1] === branch)
|
|
14468
|
+
continue;
|
|
14469
|
+
const until = (seconds + 1) * 1000;
|
|
14470
|
+
renamedAt.set(match[1], Math.max(renamedAt.get(match[1]) ?? 0, until));
|
|
14582
14471
|
}
|
|
14583
|
-
regex = new RegExp(`^${source}$`, "u");
|
|
14584
|
-
globCache.set(pattern, regex);
|
|
14585
14472
|
}
|
|
14586
|
-
return
|
|
14473
|
+
return (row) => {
|
|
14474
|
+
if (row.branch === branch)
|
|
14475
|
+
return true;
|
|
14476
|
+
const until = row.branch === null ? undefined : renamedAt.get(row.branch);
|
|
14477
|
+
if (until === undefined)
|
|
14478
|
+
return false;
|
|
14479
|
+
const at = Date.parse(row.at);
|
|
14480
|
+
return at >= since && at <= until;
|
|
14481
|
+
};
|
|
14482
|
+
}
|
|
14483
|
+
var renameAwareBranch = (scopeOf, branch) => {
|
|
14484
|
+
return (target) => {
|
|
14485
|
+
const own = typeof branch === "string" ? branch : null;
|
|
14486
|
+
const theirs = typeof target.branch === "string" ? target.branch : null;
|
|
14487
|
+
if (own === theirs)
|
|
14488
|
+
return true;
|
|
14489
|
+
if (own === null || theirs === null || typeof target.at !== "string")
|
|
14490
|
+
return false;
|
|
14491
|
+
return scopeOf(own)({ branch: theirs, at: target.at });
|
|
14492
|
+
};
|
|
14493
|
+
};
|
|
14494
|
+
function authorSessions(cwd, rows, branch, range = { base: null, head: null }, scope = branchScope(cwd, branch)) {
|
|
14495
|
+
const sessions = new Set;
|
|
14496
|
+
for (const row of rows) {
|
|
14497
|
+
if (!scope(row) || row.type !== "commit.recorded" && row.type !== "task.opened")
|
|
14498
|
+
continue;
|
|
14499
|
+
const session = str2(row.session) ?? row.actor.session;
|
|
14500
|
+
if (session)
|
|
14501
|
+
sessions.add(session);
|
|
14502
|
+
}
|
|
14503
|
+
for (const session of trailerSessions(cwd, range.base, range.head))
|
|
14504
|
+
sessions.add(session);
|
|
14505
|
+
return sessions;
|
|
14587
14506
|
}
|
|
14588
|
-
|
|
14589
|
-
|
|
14590
|
-
|
|
14591
|
-
if (RISK_FILES.has(file) || RISK_NAMES.has(basename(file)))
|
|
14507
|
+
var derivesFrom = (derived, bare) => !bare.includes(":") && derived.endsWith(`:${bare}`);
|
|
14508
|
+
var authoredBy = (authors, session) => {
|
|
14509
|
+
if (authors.has(session))
|
|
14592
14510
|
return true;
|
|
14593
|
-
|
|
14594
|
-
|
|
14595
|
-
|
|
14511
|
+
for (const author of authors)
|
|
14512
|
+
if (derivesFrom(author, session) || derivesFrom(session, author))
|
|
14513
|
+
return true;
|
|
14514
|
+
return false;
|
|
14515
|
+
};
|
|
14516
|
+
var TRUST_REASONS = new Set([
|
|
14517
|
+
"unsigned",
|
|
14518
|
+
"hash_mismatch",
|
|
14519
|
+
"broken_link",
|
|
14520
|
+
"duplicate_id",
|
|
14521
|
+
"unissued_session",
|
|
14522
|
+
"unbound_session"
|
|
14523
|
+
]);
|
|
14524
|
+
var SHELL_VOUCHED_HOSTS = new Set([
|
|
14525
|
+
"cli",
|
|
14526
|
+
"opencode",
|
|
14527
|
+
"pi",
|
|
14528
|
+
"codex_cli",
|
|
14529
|
+
"codex_desktop"
|
|
14530
|
+
]);
|
|
14531
|
+
function sessionTrust(cwd, row) {
|
|
14532
|
+
const session = row.actor.session;
|
|
14533
|
+
if (!session || row.self === true)
|
|
14534
|
+
return null;
|
|
14535
|
+
const source = typeof row.sessionSource === "string" ? row.sessionSource : "env";
|
|
14536
|
+
const host = row.actor.host;
|
|
14537
|
+
const issuedOnly = source === "flag" ? host !== "cli" : source === "env" && session.includes(":") && !SHELL_VOUCHED_HOSTS.has(host);
|
|
14538
|
+
if (!issuedOnly)
|
|
14539
|
+
return null;
|
|
14540
|
+
const binding = issuedBinding(cwd, session);
|
|
14541
|
+
if (binding === null)
|
|
14542
|
+
return "unissued_session";
|
|
14543
|
+
return binding === "token" && row.sessionBound !== true ? "unbound_session" : null;
|
|
14596
14544
|
}
|
|
14597
|
-
|
|
14598
|
-
|
|
14599
|
-
|
|
14600
|
-
|
|
14601
|
-
|
|
14602
|
-
const
|
|
14603
|
-
|
|
14604
|
-
|
|
14605
|
-
|
|
14606
|
-
|
|
14607
|
-
|
|
14608
|
-
const
|
|
14609
|
-
const
|
|
14610
|
-
|
|
14611
|
-
|
|
14612
|
-
|
|
14613
|
-
|
|
14614
|
-
|
|
14545
|
+
function trustReasons(cwd, row) {
|
|
14546
|
+
if (row.integrity === "unverified")
|
|
14547
|
+
return [row.integrityReason ?? "unsigned"];
|
|
14548
|
+
if (row.integrity !== "verified")
|
|
14549
|
+
return ["unsigned"];
|
|
14550
|
+
const session = sessionTrust(cwd, row);
|
|
14551
|
+
return session ? [session] : [];
|
|
14552
|
+
}
|
|
14553
|
+
function withIntegrity(cwd, rows) {
|
|
14554
|
+
if (rows.every((row) => row.integrity !== undefined))
|
|
14555
|
+
return rows;
|
|
14556
|
+
const read = readLedger(cwd, { integrity: true });
|
|
14557
|
+
const bySeq = new Map(read.ok ? read.value.rows.map((row) => [row.seq, row]) : []);
|
|
14558
|
+
return rows.map((row) => {
|
|
14559
|
+
if (row.integrity !== undefined)
|
|
14560
|
+
return row;
|
|
14561
|
+
const fresh = bySeq.get(row.seq);
|
|
14562
|
+
return fresh && fresh.id === row.id && fresh.at === row.at ? fresh : row;
|
|
14615
14563
|
});
|
|
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
14564
|
}
|
|
14641
|
-
|
|
14642
|
-
|
|
14643
|
-
|
|
14644
|
-
|
|
14645
|
-
|
|
14646
|
-
|
|
14647
|
-
|
|
14648
|
-
|
|
14649
|
-
|
|
14650
|
-
|
|
14651
|
-
|
|
14652
|
-
|
|
14653
|
-
|
|
14654
|
-
|
|
14565
|
+
var untrusted = (reasons) => reasons.some((reason) => TRUST_REASONS.has(reason));
|
|
14566
|
+
var independenceReasons = (row, authors) => {
|
|
14567
|
+
const reasons = [];
|
|
14568
|
+
if (!row.actor.session)
|
|
14569
|
+
reasons.push("no_session");
|
|
14570
|
+
else if (authoredBy(authors, row.actor.session))
|
|
14571
|
+
reasons.push("author_session");
|
|
14572
|
+
if (row.self === true && !reasons.includes("no_session"))
|
|
14573
|
+
reasons.push("self");
|
|
14574
|
+
return reasons;
|
|
14575
|
+
};
|
|
14576
|
+
function effectiveOf(rows, reasonsOf, sameCode, isCurrent) {
|
|
14577
|
+
let effective = null;
|
|
14578
|
+
for (const row of rows) {
|
|
14579
|
+
if (!effective) {
|
|
14580
|
+
effective = row;
|
|
14581
|
+
continue;
|
|
14582
|
+
}
|
|
14583
|
+
if (untrusted(reasonsOf(row)) && !untrusted(reasonsOf(effective)))
|
|
14584
|
+
continue;
|
|
14585
|
+
const rowIndependent = reasonsOf(row).length === 0;
|
|
14586
|
+
const effIndependent = reasonsOf(effective).length === 0;
|
|
14587
|
+
if (effIndependent && !rowIndependent && isCurrent(effective))
|
|
14588
|
+
continue;
|
|
14589
|
+
if (effIndependent && FAILING.has(String(effective.result)) && row.actor.session === effective.actor.session && sameCode(effective, row))
|
|
14590
|
+
continue;
|
|
14591
|
+
effective = row;
|
|
14655
14592
|
}
|
|
14593
|
+
return effective;
|
|
14656
14594
|
}
|
|
14657
|
-
var
|
|
14658
|
-
|
|
14659
|
-
|
|
14660
|
-
|
|
14661
|
-
|
|
14662
|
-
|
|
14663
|
-
|
|
14664
|
-
|
|
14665
|
-
|
|
14666
|
-
|
|
14667
|
-
|
|
14668
|
-
|
|
14669
|
-
|
|
14670
|
-
|
|
14671
|
-
|
|
14672
|
-
|
|
14673
|
-
|
|
14674
|
-
|
|
14675
|
-
|
|
14676
|
-
|
|
14595
|
+
var restackExact = new WeakMap;
|
|
14596
|
+
function restackCarries(exactIn, rows, branch, from, to) {
|
|
14597
|
+
if (!from || !to)
|
|
14598
|
+
return false;
|
|
14599
|
+
const exact = (row, fromHead, head) => {
|
|
14600
|
+
if (exactIn === null)
|
|
14601
|
+
return true;
|
|
14602
|
+
const cwd = exactIn;
|
|
14603
|
+
let known = restackExact.get(row);
|
|
14604
|
+
if (known === undefined) {
|
|
14605
|
+
known = typeof row.fromBase === "string" && typeof row.toBase === "string" && sameOwnChange(cwd, {
|
|
14606
|
+
from: fromHead,
|
|
14607
|
+
to: head,
|
|
14608
|
+
fromBase: row.fromBase,
|
|
14609
|
+
toBase: row.toBase
|
|
14610
|
+
});
|
|
14611
|
+
restackExact.set(row, known);
|
|
14612
|
+
}
|
|
14613
|
+
return known;
|
|
14614
|
+
};
|
|
14615
|
+
const edges = new Map;
|
|
14616
|
+
for (const row of rows)
|
|
14617
|
+
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))
|
|
14618
|
+
edges.set(row.fromHead, [...edges.get(row.fromHead) ?? [], row.head]);
|
|
14619
|
+
const seen = new Set([from]);
|
|
14620
|
+
const queue = [from];
|
|
14621
|
+
while (queue.length) {
|
|
14622
|
+
const next = queue.shift();
|
|
14623
|
+
if (next === to)
|
|
14624
|
+
return true;
|
|
14625
|
+
for (const target of edges.get(next) ?? [])
|
|
14626
|
+
if (!seen.has(target)) {
|
|
14627
|
+
seen.add(target);
|
|
14628
|
+
queue.push(target);
|
|
14677
14629
|
}
|
|
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
14630
|
}
|
|
14683
|
-
return
|
|
14631
|
+
return false;
|
|
14684
14632
|
}
|
|
14685
|
-
function
|
|
14686
|
-
const
|
|
14687
|
-
const
|
|
14688
|
-
|
|
14689
|
-
|
|
14690
|
-
const
|
|
14691
|
-
|
|
14692
|
-
|
|
14693
|
-
|
|
14694
|
-
|
|
14633
|
+
function checkVerdicts(cwd, branch, ledgerRows) {
|
|
14634
|
+
const rows = withIntegrity(cwd, ledgerRows);
|
|
14635
|
+
const head = branchHead(cwd, branch);
|
|
14636
|
+
const fallbackBase = defaultBase(cwd);
|
|
14637
|
+
const scope = branchScope(cwd, branch);
|
|
14638
|
+
const keyCache = new Map;
|
|
14639
|
+
const keysAt = (base, at) => {
|
|
14640
|
+
if (!base || !at)
|
|
14641
|
+
return { patchId: null, diffHash: null };
|
|
14642
|
+
const cacheKey = `${base}\x00${at}`;
|
|
14643
|
+
let key = keyCache.get(cacheKey);
|
|
14644
|
+
if (!key) {
|
|
14645
|
+
key = { patchId: patchId(cwd, base, at), diffHash: diffHash(cwd, base, at) };
|
|
14646
|
+
keyCache.set(cacheKey, key);
|
|
14647
|
+
}
|
|
14648
|
+
return key;
|
|
14649
|
+
};
|
|
14650
|
+
const keyFor = (base) => keysAt(base, head);
|
|
14651
|
+
const driftCache = new Map;
|
|
14652
|
+
const driftOf = (row) => {
|
|
14653
|
+
if (driftCache.has(row))
|
|
14654
|
+
return driftCache.get(row) ?? null;
|
|
14655
|
+
let drift = null;
|
|
14656
|
+
const toBase = row.base ?? fallbackBase;
|
|
14657
|
+
const toBaseSha = toBase ? resolveCommit(cwd, toBase) : null;
|
|
14658
|
+
const fromBase = row.baseSha ?? toBaseSha;
|
|
14659
|
+
if (row.head && head && row.head !== head && toBaseSha && fromBase && !untrusted(reasonsOf(row)))
|
|
14660
|
+
drift = verdictDrift(cwd, {
|
|
14661
|
+
from: row.head,
|
|
14662
|
+
to: head,
|
|
14663
|
+
fromBase,
|
|
14664
|
+
toBase: toBaseSha,
|
|
14665
|
+
kind: typeof row.kind === "string" ? row.kind : "review"
|
|
14666
|
+
});
|
|
14667
|
+
driftCache.set(row, drift);
|
|
14668
|
+
return drift;
|
|
14669
|
+
};
|
|
14670
|
+
const exactCache = new Map;
|
|
14671
|
+
const exactCarry = (row) => {
|
|
14672
|
+
if (FAILING.has(String(row.result)))
|
|
14673
|
+
return true;
|
|
14674
|
+
let exact = exactCache.get(row);
|
|
14675
|
+
if (exact === undefined) {
|
|
14676
|
+
const toBase = row.base ?? fallbackBase;
|
|
14677
|
+
const toBaseSha = toBase ? resolveCommit(cwd, toBase) : null;
|
|
14678
|
+
const fromBase = row.baseSha ?? toBaseSha;
|
|
14679
|
+
exact = Boolean(row.head && head && toBaseSha && fromBase && sameOwnChange(cwd, { from: row.head, to: head, fromBase, toBase: toBaseSha }));
|
|
14680
|
+
exactCache.set(row, exact);
|
|
14681
|
+
}
|
|
14682
|
+
return exact;
|
|
14683
|
+
};
|
|
14684
|
+
const keyBasis = (row) => {
|
|
14685
|
+
const basis = verdictBasis(row, { head, ...keyFor(row.base ?? fallbackBase) });
|
|
14686
|
+
return basis === "carried" && !exactCarry(row) ? "stale" : basis;
|
|
14687
|
+
};
|
|
14688
|
+
const restacked = (row) => restackCarries(FAILING.has(String(row.result)) ? null : cwd, rows, branch, row.head, head);
|
|
14689
|
+
const carryOf = (row) => {
|
|
14690
|
+
if (keyBasis(row) !== "stale")
|
|
14691
|
+
return null;
|
|
14692
|
+
if (restacked(row))
|
|
14693
|
+
return null;
|
|
14694
|
+
const drift = driftOf(row);
|
|
14695
|
+
return drift?.carries ? { rule: drift.rule, since: drift.since, files: drift.files } : null;
|
|
14696
|
+
};
|
|
14697
|
+
const basisOf = (row) => {
|
|
14698
|
+
const basis = keyBasis(row);
|
|
14699
|
+
if (basis !== "stale")
|
|
14700
|
+
return basis;
|
|
14701
|
+
return restacked(row) || carryOf(row) ? "carried" : basis;
|
|
14702
|
+
};
|
|
14703
|
+
const sameCode = (failed, row) => {
|
|
14704
|
+
if (failed.head && failed.head === row.head)
|
|
14705
|
+
return true;
|
|
14706
|
+
const base = failed.base ?? fallbackBase;
|
|
14707
|
+
const stored = (row.base ?? fallbackBase) === base && row.baseSha !== null && row.baseSha === failed.baseSha;
|
|
14708
|
+
const keys = stored ? { patchId: row.patchId, diffHash: row.diffHash } : keysAt(base, row.head);
|
|
14709
|
+
return verdictBasis(failed, { head: row.head, ...keys }) !== "stale" || restackCarries(null, rows, branch, failed.head, row.head);
|
|
14710
|
+
};
|
|
14711
|
+
const authors = authorSessions(cwd, rows, branch, { base: fallbackBase, head }, scope);
|
|
14712
|
+
const failureAuthorCache = new Map;
|
|
14713
|
+
const failureAuthors = (row) => {
|
|
14714
|
+
let set = failureAuthorCache.get(row.seq);
|
|
14715
|
+
if (!set) {
|
|
14716
|
+
set = authorSessions(cwd, rows.filter((other) => other.integrity === "verified" && other.seq < row.seq), branch, { base: null, head: null }, scope);
|
|
14717
|
+
failureAuthorCache.set(row.seq, set);
|
|
14718
|
+
}
|
|
14719
|
+
return set;
|
|
14720
|
+
};
|
|
14721
|
+
const reasonCache = new Map;
|
|
14722
|
+
const reasonsOf = (row) => {
|
|
14723
|
+
let reasons = reasonCache.get(row);
|
|
14724
|
+
if (!reasons) {
|
|
14725
|
+
const failing = FAILING.has(String(row.result));
|
|
14726
|
+
reasons = [
|
|
14727
|
+
...trustReasons(cwd, row),
|
|
14728
|
+
...independenceReasons(row, failing ? failureAuthors(row) : authors)
|
|
14729
|
+
];
|
|
14730
|
+
reasonCache.set(row, reasons);
|
|
14731
|
+
}
|
|
14732
|
+
return reasons;
|
|
14733
|
+
};
|
|
14734
|
+
const retired = new Set;
|
|
14735
|
+
for (const row of rows)
|
|
14736
|
+
if (row.type === "verdict" && row.supersedes && !row.supersedeIgnored && trustReasons(cwd, row).length === 0)
|
|
14737
|
+
retired.add(row.supersedes);
|
|
14738
|
+
const byKind = new Map;
|
|
14739
|
+
for (const row of rows)
|
|
14740
|
+
if (isLiveVerdict(row, retired) && scope(row)) {
|
|
14741
|
+
const kind = typeof row.kind === "string" ? row.kind : "review";
|
|
14742
|
+
byKind.set(kind, [...byKind.get(kind) ?? [], row]);
|
|
14743
|
+
}
|
|
14744
|
+
const verdicts = [];
|
|
14745
|
+
const warnings = [];
|
|
14746
|
+
for (const [kind, list] of byKind) {
|
|
14747
|
+
const row = effectiveOf(list, reasonsOf, sameCode, (candidate) => basisOf(candidate) !== "stale");
|
|
14748
|
+
if (!row)
|
|
14749
|
+
continue;
|
|
14750
|
+
const basis = basisOf(row);
|
|
14751
|
+
const current = basis !== "stale";
|
|
14752
|
+
const independence = reasonsOf(row);
|
|
14753
|
+
const reasons = [
|
|
14754
|
+
...current ? [] : ["stale"],
|
|
14755
|
+
...PASSING.has(String(row.result)) ? [] : ["not_passing"],
|
|
14756
|
+
...independence
|
|
14757
|
+
];
|
|
14758
|
+
verdicts.push({
|
|
14759
|
+
kind,
|
|
14760
|
+
basis,
|
|
14761
|
+
current,
|
|
14762
|
+
independent: independence.length === 0,
|
|
14763
|
+
accepted: reasons.length === 0,
|
|
14764
|
+
reasons,
|
|
14765
|
+
carry: basis === "carried" ? carryOf(row) : null,
|
|
14766
|
+
verdict: row
|
|
14767
|
+
});
|
|
14768
|
+
if (reasons.length === 0) {
|
|
14769
|
+
for (const later of list)
|
|
14770
|
+
if (later.seq > row.seq && reasonsOf(later).length > 0 && !untrusted(reasonsOf(later)) && FAILING.has(String(later.result)) && basisOf(later) !== "stale")
|
|
14771
|
+
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`);
|
|
14772
|
+
}
|
|
14695
14773
|
}
|
|
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
|
-
};
|
|
14774
|
+
for (const row of rows)
|
|
14775
|
+
if (row.integrity === "unverified" && scope(row))
|
|
14776
|
+
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)`);
|
|
14777
|
+
const tampered = ledgerTampering(cwd);
|
|
14778
|
+
if (tampered.length) {
|
|
14779
|
+
warnings.push(tamperedWarning(cwd, tampered));
|
|
14780
|
+
for (const entry of verdicts) {
|
|
14781
|
+
entry.accepted = false;
|
|
14782
|
+
entry.reasons = [...entry.reasons, "ledger_tampered"];
|
|
14783
|
+
}
|
|
14739
14784
|
}
|
|
14740
|
-
|
|
14785
|
+
verdicts.sort((a, b) => a.verdict.seq - b.verdict.seq);
|
|
14786
|
+
const newestCurrent = verdicts.findLast((entry) => entry.current) ?? verdicts.at(-1) ?? null;
|
|
14787
|
+
const failing = verdicts.find((entry) => entry.current && entry.independent && FAILING.has(String(entry.verdict.result)));
|
|
14788
|
+
const newestAccepted = verdicts.findLast((entry) => entry.accepted && STRONG_RESULTS.has(String(entry.verdict.result))) ?? null;
|
|
14789
|
+
const accepted = tampered.length ? { accepted: false, verdict: null, reasons: ["ledger_tampered"] } : newestAccepted && !failing ? { accepted: true, verdict: newestAccepted.verdict, reasons: [] } : {
|
|
14790
|
+
accepted: false,
|
|
14791
|
+
verdict: failing?.verdict ?? null,
|
|
14792
|
+
reasons: failing ? ["failing_verdict"] : !newestCurrent ? ["no_verdict"] : newestCurrent.reasons.length ? newestCurrent.reasons : ["type_check_only"]
|
|
14793
|
+
};
|
|
14794
|
+
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))));
|
|
14795
|
+
const selfEntry = latestSelf && STRONG_RESULTS.has(String(latestSelf.verdict.result)) ? latestSelf : undefined;
|
|
14796
|
+
const carryEntry = verdicts.find((entry) => entry.verdict === accepted.verdict && entry.accepted) ?? newestCurrent;
|
|
14797
|
+
const lastPass = accepted.accepted || failing || tampered.length ? undefined : verdicts.findLast((entry) => !entry.current && !untrusted(entry.reasons) && STRONG_RESULTS.has(String(entry.verdict.result)));
|
|
14798
|
+
const drift = lastPass ? driftOf(lastPass.verdict) : null;
|
|
14799
|
+
const reverify = drift && !drift.carries && drift.focused && drift.files ? { scope: "focused", since: drift.since, files: drift.files } : null;
|
|
14741
14800
|
return {
|
|
14742
|
-
|
|
14743
|
-
|
|
14744
|
-
|
|
14745
|
-
|
|
14746
|
-
|
|
14747
|
-
base,
|
|
14748
|
-
|
|
14801
|
+
branch,
|
|
14802
|
+
head,
|
|
14803
|
+
review: accepted.accepted ? "verified" : selfEntry ? "self-reviewed" : "unreviewed",
|
|
14804
|
+
selfVerdict: accepted.accepted ? null : selfEntry?.verdict ?? null,
|
|
14805
|
+
base: newestCurrent?.verdict.base ?? fallbackBase,
|
|
14806
|
+
patchId: keyFor(newestCurrent?.verdict.base ?? fallbackBase).patchId,
|
|
14807
|
+
current: { basis: newestCurrent?.basis ?? "none", verdict: newestCurrent?.verdict ?? null },
|
|
14808
|
+
carry: carryEntry?.carry ?? null,
|
|
14809
|
+
reverify,
|
|
14810
|
+
accepted,
|
|
14811
|
+
verdicts,
|
|
14812
|
+
authors: [...authors].toSorted(),
|
|
14813
|
+
warnings
|
|
14749
14814
|
};
|
|
14750
14815
|
}
|
|
14751
|
-
var
|
|
14816
|
+
var fromLead = (row, leads) => leads === null || row.actor.session !== null && leads.includes(row.actor.session);
|
|
14817
|
+
function activeStanding(rows, fanout, leads = null) {
|
|
14818
|
+
const active = [];
|
|
14819
|
+
for (const row of rows) {
|
|
14820
|
+
if (row.fanout !== fanout || !fromLead(row, leads))
|
|
14821
|
+
continue;
|
|
14822
|
+
if (row.type === "standing" && typeof row.what === "string" && row.what.trim()) {
|
|
14823
|
+
if (!row.superseded)
|
|
14824
|
+
active.push(row);
|
|
14825
|
+
} else if (row.type === "standing.cleared") {
|
|
14826
|
+
const target = str2(row.target);
|
|
14827
|
+
if (target === null)
|
|
14828
|
+
active.length = 0;
|
|
14829
|
+
else {
|
|
14830
|
+
const index = active.findIndex((order) => order.id === target);
|
|
14831
|
+
if (index >= 0)
|
|
14832
|
+
active.splice(index, 1);
|
|
14833
|
+
}
|
|
14834
|
+
}
|
|
14835
|
+
}
|
|
14836
|
+
return active;
|
|
14837
|
+
}
|
|
14838
|
+
var OBSERVED_ONLY = new Set([
|
|
14839
|
+
"check",
|
|
14840
|
+
"merge.unverified",
|
|
14841
|
+
"policy.judged",
|
|
14842
|
+
...PR_ROW_TYPES
|
|
14843
|
+
]);
|
|
14752
14844
|
|
|
14753
14845
|
// packages/workit-core/src/core/policy/derive.ts
|
|
14754
14846
|
var RULES = {
|
|
@@ -14903,7 +14995,7 @@ var productDecision = (task, ledger) => {
|
|
|
14903
14995
|
};
|
|
14904
14996
|
};
|
|
14905
14997
|
var planRecorded = (task, root) => {
|
|
14906
|
-
const cited = (latestJudgment(task)?.refs ?? []).find((ref) => ref.kind === "external" || ref.kind === "file" && existsSync5(
|
|
14998
|
+
const cited = (latestJudgment(task)?.refs ?? []).find((ref) => ref.kind === "external" || ref.kind === "file" && existsSync5(path17.resolve(root, ref.path)));
|
|
14907
14999
|
if (cited)
|
|
14908
15000
|
return {
|
|
14909
15001
|
met: true,
|
|
@@ -14981,14 +15073,14 @@ function writeBlockers(task, root) {
|
|
|
14981
15073
|
|
|
14982
15074
|
// packages/workit-core/src/core/task-evaluation.ts
|
|
14983
15075
|
var digestBytes = (value) => createHash12("sha256").update(value).digest("hex");
|
|
14984
|
-
var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${
|
|
14985
|
-
var relative = (root, candidate) =>
|
|
15076
|
+
var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path18.sep}`);
|
|
15077
|
+
var relative = (root, candidate) => path18.relative(root, candidate).split(path18.sep).join("/") || ".";
|
|
14986
15078
|
var canonicalPath = (value) => {
|
|
14987
15079
|
if (typeof value !== "string" || !value)
|
|
14988
15080
|
return null;
|
|
14989
15081
|
if (value.includes("\\") || value.split("/").includes(".."))
|
|
14990
15082
|
return null;
|
|
14991
|
-
const normalized =
|
|
15083
|
+
const normalized = path18.posix.normalize(value);
|
|
14992
15084
|
if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
|
|
14993
15085
|
return null;
|
|
14994
15086
|
return normalized === "." ? "." : normalized.replace(/\/$/, "");
|
|
@@ -15024,7 +15116,7 @@ var compareCodeUnits2 = (left, right) => {
|
|
|
15024
15116
|
return left.length - right.length;
|
|
15025
15117
|
};
|
|
15026
15118
|
var gitPaths = (root) => {
|
|
15027
|
-
const result =
|
|
15119
|
+
const result = spawnSync10("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
|
|
15028
15120
|
cwd: root,
|
|
15029
15121
|
encoding: "buffer"
|
|
15030
15122
|
});
|
|
@@ -15036,21 +15128,21 @@ var gitPaths = (root) => {
|
|
|
15036
15128
|
git: !/not a git repository/i.test(stderr)
|
|
15037
15129
|
};
|
|
15038
15130
|
}
|
|
15039
|
-
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(
|
|
15040
|
-
const stagedDeleted =
|
|
15131
|
+
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path18.sep).join("/")) : [];
|
|
15132
|
+
const stagedDeleted = spawnSync10("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
|
|
15041
15133
|
if (stagedDeleted.status !== 0) {
|
|
15042
15134
|
const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
|
|
15043
15135
|
if (!/not a git repository/i.test(stderr))
|
|
15044
15136
|
return { paths, uncertain: true, git: true };
|
|
15045
15137
|
} else if (stagedDeleted.stdout) {
|
|
15046
|
-
paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(
|
|
15138
|
+
paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path18.sep).join("/")));
|
|
15047
15139
|
}
|
|
15048
15140
|
return { paths: [...new Set(paths)], uncertain: false, git: true };
|
|
15049
15141
|
};
|
|
15050
15142
|
var walk = (root, directory, output) => {
|
|
15051
15143
|
let names;
|
|
15052
15144
|
try {
|
|
15053
|
-
names =
|
|
15145
|
+
names = fs14.readdirSync(directory);
|
|
15054
15146
|
} catch {
|
|
15055
15147
|
return true;
|
|
15056
15148
|
}
|
|
@@ -15058,10 +15150,10 @@ var walk = (root, directory, output) => {
|
|
|
15058
15150
|
for (const name of names) {
|
|
15059
15151
|
if (name === ".git" || name === ".workit")
|
|
15060
15152
|
continue;
|
|
15061
|
-
const target =
|
|
15153
|
+
const target = path18.join(directory, name);
|
|
15062
15154
|
let stat;
|
|
15063
15155
|
try {
|
|
15064
|
-
stat =
|
|
15156
|
+
stat = fs14.lstatSync(target);
|
|
15065
15157
|
} catch {
|
|
15066
15158
|
uncertain = true;
|
|
15067
15159
|
continue;
|
|
@@ -15076,14 +15168,14 @@ var walk = (root, directory, output) => {
|
|
|
15076
15168
|
var scopeRoots = (root, scope) => {
|
|
15077
15169
|
const roots = [];
|
|
15078
15170
|
for (const item of scope.paths.length ? scope.paths : ["."]) {
|
|
15079
|
-
const target =
|
|
15171
|
+
const target = path18.resolve(root, item);
|
|
15080
15172
|
if (!inside(root, target))
|
|
15081
15173
|
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
15174
|
let ancestor = target;
|
|
15083
|
-
while (!
|
|
15084
|
-
ancestor =
|
|
15175
|
+
while (!fs14.existsSync(ancestor) && ancestor !== root)
|
|
15176
|
+
ancestor = path18.dirname(ancestor);
|
|
15085
15177
|
try {
|
|
15086
|
-
if (!inside(root,
|
|
15178
|
+
if (!inside(root, fs14.realpathSync(ancestor)))
|
|
15087
15179
|
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
15180
|
} catch {
|
|
15089
15181
|
return failure2("invalid_input", `candidate scope cannot be inspected: ${item}`);
|
|
@@ -15095,7 +15187,7 @@ var scopeRoots = (root, scope) => {
|
|
|
15095
15187
|
function captureCandidate(root, scope, environment = []) {
|
|
15096
15188
|
let checkout;
|
|
15097
15189
|
try {
|
|
15098
|
-
checkout =
|
|
15190
|
+
checkout = fs14.realpathSync(root);
|
|
15099
15191
|
} catch {
|
|
15100
15192
|
return failure2("invalid_input", "candidate root does not exist");
|
|
15101
15193
|
}
|
|
@@ -15113,11 +15205,11 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
15113
15205
|
if (item === ".git" || item === ".workit")
|
|
15114
15206
|
continue;
|
|
15115
15207
|
names.add(item);
|
|
15116
|
-
if (!
|
|
15208
|
+
if (!fs14.existsSync(target))
|
|
15117
15209
|
continue;
|
|
15118
15210
|
let stat;
|
|
15119
15211
|
try {
|
|
15120
|
-
stat =
|
|
15212
|
+
stat = fs14.lstatSync(target);
|
|
15121
15213
|
} catch {
|
|
15122
15214
|
uncertain = true;
|
|
15123
15215
|
continue;
|
|
@@ -15133,17 +15225,17 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
15133
15225
|
for (const item of [...names].toSorted()) {
|
|
15134
15226
|
if (!scopeMatches(item, normalizedScope))
|
|
15135
15227
|
continue;
|
|
15136
|
-
const target =
|
|
15228
|
+
const target = path18.join(checkout, item);
|
|
15137
15229
|
try {
|
|
15138
|
-
const stat =
|
|
15230
|
+
const stat = fs14.lstatSync(target);
|
|
15139
15231
|
if (stat.isSymbolicLink()) {
|
|
15140
|
-
const link =
|
|
15232
|
+
const link = fs14.readlinkSync(target);
|
|
15141
15233
|
files.push({ path: item, kind: "symlink", digest: digestBytes(link), executable: null });
|
|
15142
15234
|
} else if (stat.isFile()) {
|
|
15143
15235
|
files.push({
|
|
15144
15236
|
path: item,
|
|
15145
15237
|
kind: "file",
|
|
15146
|
-
digest: digestBytes(
|
|
15238
|
+
digest: digestBytes(fs14.readFileSync(target)),
|
|
15147
15239
|
executable: (stat.mode & 73) !== 0
|
|
15148
15240
|
});
|
|
15149
15241
|
} else if (!stat.isDirectory()) {
|
|
@@ -15168,7 +15260,7 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
15168
15260
|
const values = supplied.toSorted((left, right) => compareCodeUnits2(left.name, right.name)).map(({ name, value }) => ({ name, value, refs: [] }));
|
|
15169
15261
|
if (values.some(({ value }) => value === null))
|
|
15170
15262
|
completeness = "uncertain";
|
|
15171
|
-
const headResult =
|
|
15263
|
+
const headResult = spawnSync10("git", ["rev-parse", "HEAD"], { cwd: checkout, encoding: "utf8" });
|
|
15172
15264
|
if (headResult.status !== 0 && !/not a git repository/i.test(headResult.stderr ?? ""))
|
|
15173
15265
|
completeness = "uncertain";
|
|
15174
15266
|
const head = headResult.status === 0 ? headResult.stdout.trim() : null;
|
|
@@ -15308,7 +15400,7 @@ var findingVerificationPasses = (findingCandidateId, evidence) => evidence.statu
|
|
|
15308
15400
|
var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
15309
15401
|
let root;
|
|
15310
15402
|
try {
|
|
15311
|
-
root =
|
|
15403
|
+
root = fs14.realpathSync(checkoutRoot);
|
|
15312
15404
|
} catch {
|
|
15313
15405
|
return failure2("permission_denied", "checkout root is unavailable");
|
|
15314
15406
|
}
|
|
@@ -15317,23 +15409,23 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
|
15317
15409
|
continue;
|
|
15318
15410
|
if (!reference.digest)
|
|
15319
15411
|
return failure2("invalid_input", "document references require a byte digest");
|
|
15320
|
-
const target =
|
|
15321
|
-
if (target !== root && !target.startsWith(`${root}${
|
|
15412
|
+
const target = path18.resolve(root, reference.path);
|
|
15413
|
+
if (target !== root && !target.startsWith(`${root}${path18.sep}`))
|
|
15322
15414
|
return failure2("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
|
|
15323
15415
|
fields: [
|
|
15324
15416
|
{ path: "contentRefs.path", reason: `${reference.path} is outside the checkout` }
|
|
15325
15417
|
]
|
|
15326
15418
|
});
|
|
15327
15419
|
try {
|
|
15328
|
-
const below =
|
|
15420
|
+
const below = path18.relative(root, target);
|
|
15329
15421
|
let current = root;
|
|
15330
|
-
for (const segment of below.split(
|
|
15422
|
+
for (const segment of below.split(path18.sep)) {
|
|
15331
15423
|
if (!segment)
|
|
15332
15424
|
continue;
|
|
15333
|
-
current =
|
|
15334
|
-
const stat =
|
|
15335
|
-
const real =
|
|
15336
|
-
if (real !== root && !real.startsWith(`${root}${
|
|
15425
|
+
current = path18.join(current, segment);
|
|
15426
|
+
const stat = fs14.lstatSync(current);
|
|
15427
|
+
const real = fs14.realpathSync(current);
|
|
15428
|
+
if (real !== root && !real.startsWith(`${root}${path18.sep}`))
|
|
15337
15429
|
return failure2("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
|
|
15338
15430
|
fields: [
|
|
15339
15431
|
{
|
|
@@ -15349,7 +15441,7 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
|
15349
15441
|
if (current !== target && !stat.isDirectory())
|
|
15350
15442
|
return failure2("invalid_input", "document reference ancestor is not a directory");
|
|
15351
15443
|
}
|
|
15352
|
-
if (digestBytes(
|
|
15444
|
+
if (digestBytes(fs14.readFileSync(target)) !== reference.digest)
|
|
15353
15445
|
return failure2("permission_denied", "decision document bytes have changed");
|
|
15354
15446
|
} catch {
|
|
15355
15447
|
return failure2("invalid_input", "decision document is unavailable");
|
|
@@ -15780,7 +15872,7 @@ Autonomy contract:
|
|
|
15780
15872
|
CLI first: if a step has one right answer, use the \`workit\` verb instead of
|
|
15781
15873
|
hand-running git, gh or glab (\`workit git|pr|ci|check|help\`;
|
|
15782
15874
|
else \`npx -y @brainervirus/workit-cli\`).
|
|
15783
|
-
Other checkout: \`workit --cwd <dir>
|
|
15875
|
+
Other checkout: \`workit <verb> --cwd <dir> …\` (verb first: host rules match it); one workit
|
|
15784
15876
|
verb per shell call, never \`cd <dir> && workit\`. zsh-safe: no \`echo =====\`
|
|
15785
15877
|
(\`printf '%s\\n' ---\`), quote globs and URLs, no variable named \`status\` or \`path\`.
|
|
15786
15878
|
Add Co-Authored-By via \`workit git commit --trailer "K: v" -- <paths>\`, never
|
|
@@ -15792,7 +15884,7 @@ authorizes an effect. Judge work once with
|
|
|
15792
15884
|
\`workit policy assess --judge risk= behavior= product-choice= plan=\`.
|
|
15793
15885
|
Across repositories, bind items to checkout, branch and endpoint, resolve
|
|
15794
15886
|
competing targets before a mutation, and reconcile an uncertain
|
|
15795
|
-
|
|
15887
|
+
effect before a retry.
|
|
15796
15888
|
|
|
15797
15889
|
Mid-task: never silently drop or resume an objective (workit-continue).
|
|
15798
15890
|
|
|
@@ -15934,21 +16026,21 @@ var exportDigest = (bundle) => sha256({
|
|
|
15934
16026
|
task: bundle.task
|
|
15935
16027
|
});
|
|
15936
16028
|
// packages/workit-core/src/core/route-intent.ts
|
|
15937
|
-
import { spawnSync as
|
|
16029
|
+
import { spawnSync as spawnSync12 } from "node:child_process";
|
|
15938
16030
|
import { existsSync as existsSync7 } from "node:fs";
|
|
15939
|
-
import
|
|
16031
|
+
import path21 from "node:path";
|
|
15940
16032
|
|
|
15941
16033
|
// packages/workit-core/src/hooks/raw-git.ts
|
|
15942
|
-
import { spawnSync as
|
|
16034
|
+
import { spawnSync as spawnSync11 } from "node:child_process";
|
|
15943
16035
|
import { createHash as createHash14 } from "node:crypto";
|
|
15944
|
-
import
|
|
16036
|
+
import fs16 from "node:fs";
|
|
15945
16037
|
import os4 from "node:os";
|
|
15946
|
-
import
|
|
16038
|
+
import path20 from "node:path";
|
|
15947
16039
|
|
|
15948
16040
|
// packages/workit-core/src/hooks/session-marker.ts
|
|
15949
16041
|
import { createHash as createHash13 } from "node:crypto";
|
|
15950
|
-
import
|
|
15951
|
-
import
|
|
16042
|
+
import fs15 from "node:fs";
|
|
16043
|
+
import path19 from "node:path";
|
|
15952
16044
|
var sessionMarkerFile = (input, session, store) => {
|
|
15953
16045
|
let dir = store;
|
|
15954
16046
|
if (dir === undefined) {
|
|
@@ -15958,13 +16050,13 @@ var sessionMarkerFile = (input, session, store) => {
|
|
|
15958
16050
|
dir = location.dir;
|
|
15959
16051
|
}
|
|
15960
16052
|
const name = createHash13("sha256").update(`${input.host}\x00${session}`).digest("hex").slice(0, 32);
|
|
15961
|
-
return
|
|
16053
|
+
return path19.join(dir, "hooks", `skills-${name}.json`);
|
|
15962
16054
|
};
|
|
15963
16055
|
var list2 = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
15964
16056
|
var EMPTY = { nudged: [], loaded: [], raw: [], stops: [], judges: [] };
|
|
15965
16057
|
var readRaw = (file) => {
|
|
15966
16058
|
try {
|
|
15967
|
-
return
|
|
16059
|
+
return fs15.readFileSync(file, "utf8");
|
|
15968
16060
|
} catch {
|
|
15969
16061
|
return "";
|
|
15970
16062
|
}
|
|
@@ -15985,13 +16077,13 @@ var parseMarker = (text) => {
|
|
|
15985
16077
|
};
|
|
15986
16078
|
var readSessionMarker = (file) => parseMarker(readRaw(file));
|
|
15987
16079
|
var writeAtomic = (file, marker) => {
|
|
15988
|
-
|
|
16080
|
+
fs15.mkdirSync(path19.dirname(file), { recursive: true });
|
|
15989
16081
|
const temp = `${file}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
15990
16082
|
try {
|
|
15991
|
-
|
|
15992
|
-
|
|
16083
|
+
fs15.writeFileSync(temp, JSON.stringify(marker));
|
|
16084
|
+
fs15.renameSync(temp, file);
|
|
15993
16085
|
} catch (error) {
|
|
15994
|
-
|
|
16086
|
+
fs15.rmSync(temp, { force: true });
|
|
15995
16087
|
throw error;
|
|
15996
16088
|
}
|
|
15997
16089
|
};
|
|
@@ -16272,6 +16364,18 @@ var substitutions = (body) => {
|
|
|
16272
16364
|
};
|
|
16273
16365
|
var PLAIN_POSIX = /[^\s'"\\`$(){},<>|;&#]+/uy;
|
|
16274
16366
|
var PLAIN_POWERSHELL = /[^\s'"`(){}<>|;&#]+/uy;
|
|
16367
|
+
var expandsInQuotes = (body, dialect) => {
|
|
16368
|
+
const escape = dialect === "posix" ? "\\" : "`";
|
|
16369
|
+
for (let index = 0;index < body.length; index++) {
|
|
16370
|
+
const char = body[index];
|
|
16371
|
+
if (char === escape)
|
|
16372
|
+
index++;
|
|
16373
|
+
else if (char === "$" || dialect === "posix" && char === "`")
|
|
16374
|
+
return true;
|
|
16375
|
+
}
|
|
16376
|
+
return false;
|
|
16377
|
+
};
|
|
16378
|
+
var EXPANDING_RUN = /[*?[]/u;
|
|
16275
16379
|
var arithmeticEnd = (command, start) => {
|
|
16276
16380
|
const end = substitutionEnd(command, start);
|
|
16277
16381
|
return command[end - 1] === ")" && end > start + 2 ? end : 0;
|
|
@@ -16290,6 +16394,7 @@ var tokenize = (command, dialect) => {
|
|
|
16290
16394
|
let current = { words: [], redirects: [] };
|
|
16291
16395
|
let word = "";
|
|
16292
16396
|
let inWord = false;
|
|
16397
|
+
let expanding = false;
|
|
16293
16398
|
let pending = null;
|
|
16294
16399
|
const heredocs = [];
|
|
16295
16400
|
const escape = posix ? "\\" : "`";
|
|
@@ -16304,11 +16409,16 @@ var tokenize = (command, dialect) => {
|
|
|
16304
16409
|
heredocs.push({ delimiter: text.replace(/^-/, ""), owner: current });
|
|
16305
16410
|
else if (pending === "herestring")
|
|
16306
16411
|
(current.stdin ??= []).push(text);
|
|
16307
|
-
else if (pending !== "input")
|
|
16412
|
+
else if (pending !== "input") {
|
|
16413
|
+
const at = current.words.length;
|
|
16308
16414
|
current.words.push(...MARKERS.test(word) ? expandBraces(word) : [word]);
|
|
16415
|
+
if (expanding || !posix && word.startsWith("@"))
|
|
16416
|
+
(current.expands ??= []).push(...current.words.slice(at).map((_, i) => at + i));
|
|
16417
|
+
}
|
|
16309
16418
|
pending = null;
|
|
16310
16419
|
word = "";
|
|
16311
16420
|
inWord = false;
|
|
16421
|
+
expanding = false;
|
|
16312
16422
|
};
|
|
16313
16423
|
const flushSegment = () => {
|
|
16314
16424
|
flushWord();
|
|
@@ -16323,6 +16433,7 @@ var tokenize = (command, dialect) => {
|
|
|
16323
16433
|
};
|
|
16324
16434
|
const substitution = (inner, text) => {
|
|
16325
16435
|
(current.subs ??= []).push(inner);
|
|
16436
|
+
expanding = true;
|
|
16326
16437
|
word += text;
|
|
16327
16438
|
inWord = true;
|
|
16328
16439
|
};
|
|
@@ -16333,6 +16444,8 @@ var tokenize = (command, dialect) => {
|
|
|
16333
16444
|
if (run) {
|
|
16334
16445
|
word += run[0];
|
|
16335
16446
|
inWord = true;
|
|
16447
|
+
if (EXPANDING_RUN.test(run[0]))
|
|
16448
|
+
expanding = true;
|
|
16336
16449
|
index += run[0].length - 1;
|
|
16337
16450
|
continue;
|
|
16338
16451
|
}
|
|
@@ -16361,6 +16474,8 @@ var tokenize = (command, dialect) => {
|
|
|
16361
16474
|
const body = command.slice(index + 1, end < 0 ? undefined : end);
|
|
16362
16475
|
if (posix && char === '"' && /\$\(|`/.test(body))
|
|
16363
16476
|
(current.subs ??= []).push(...substitutions(body));
|
|
16477
|
+
if (char === '"' && expandsInQuotes(body, dialect))
|
|
16478
|
+
expanding = true;
|
|
16364
16479
|
word += unquote(body, char, dialect);
|
|
16365
16480
|
inWord = true;
|
|
16366
16481
|
index = end < 0 ? command.length : end;
|
|
@@ -16406,6 +16521,8 @@ var tokenize = (command, dialect) => {
|
|
|
16406
16521
|
if (char === "(" && inWord && word.endsWith("$")) {
|
|
16407
16522
|
word = word.slice(0, -1);
|
|
16408
16523
|
inWord = word !== "";
|
|
16524
|
+
if (!inWord)
|
|
16525
|
+
expanding = false;
|
|
16409
16526
|
}
|
|
16410
16527
|
group(char === "(" ? "open" : "close");
|
|
16411
16528
|
} else if (!posix && (char === "{" || char === "}")) {
|
|
@@ -16459,6 +16576,8 @@ var tokenize = (command, dialect) => {
|
|
|
16459
16576
|
else if (posix && char === "}" && word.includes(OPEN))
|
|
16460
16577
|
word += CLOSE;
|
|
16461
16578
|
else {
|
|
16579
|
+
if (char === "$")
|
|
16580
|
+
expanding = true;
|
|
16462
16581
|
word += char;
|
|
16463
16582
|
inWord = true;
|
|
16464
16583
|
}
|
|
@@ -16513,17 +16632,17 @@ var GIT_VALUE_OPTIONS = new Set([
|
|
|
16513
16632
|
]);
|
|
16514
16633
|
var GIT_CONFIG_OPTIONS = new Set(["-c", "--config-env", "--git-dir"]);
|
|
16515
16634
|
var GIT_CONFIG_ENV = /^GIT_(?:CONFIG\w*|DIR|COMMON_DIR|WORK_TREE)$/;
|
|
16516
|
-
var
|
|
16635
|
+
var ASSIGNMENT = /^([A-Za-z_]\w*)=([\s\S]*)$/;
|
|
16517
16636
|
var MAX_DEPTH = 4;
|
|
16518
16637
|
var MAX_EXPANSIONS = 16;
|
|
16519
|
-
var isAbsolute = (value) =>
|
|
16638
|
+
var isAbsolute = (value) => path20.isAbsolute(value) || /^[A-Za-z]:[\\/]/.test(value) || value.startsWith("\\\\");
|
|
16520
16639
|
var HERE = /^(?:\$PWD|\$\{PWD\}|\$\(\s*pwd\s*\)|`pwd`|\$\(\s*git\s+rev-parse\s+--show-toplevel\s*\))(?=$|[\\/])/;
|
|
16521
16640
|
var joinDir = (base, raw) => {
|
|
16522
16641
|
const next = raw.replace(HERE, ".");
|
|
16523
16642
|
if (base === undefined || /[$`*?]/.test(next) || next === "-")
|
|
16524
16643
|
return;
|
|
16525
|
-
const expanded = next === "~" || next.startsWith("~/") ?
|
|
16526
|
-
return base === null || isAbsolute(expanded) ? expanded :
|
|
16644
|
+
const expanded = next === "~" || next.startsWith("~/") ? path20.join(os4.homedir(), next.slice(1)) : next;
|
|
16645
|
+
return base === null || isAbsolute(expanded) ? expanded : path20.join(base, expanded);
|
|
16527
16646
|
};
|
|
16528
16647
|
var rebase = (base, inner) => inner === null ? base : inner === undefined ? undefined : joinDir(base, inner);
|
|
16529
16648
|
var cdTarget = (words) => {
|
|
@@ -16540,7 +16659,7 @@ var stripPrefixes = (input) => {
|
|
|
16540
16659
|
const first = words[0];
|
|
16541
16660
|
if (first === undefined)
|
|
16542
16661
|
return { words, env };
|
|
16543
|
-
const assignment =
|
|
16662
|
+
const assignment = ASSIGNMENT.exec(first);
|
|
16544
16663
|
if (assignment) {
|
|
16545
16664
|
if (GIT_CONFIG_ENV.test(assignment[1]))
|
|
16546
16665
|
env[assignment[1]] = assignment[2];
|
|
@@ -16599,10 +16718,10 @@ var assignments = (words, vars) => {
|
|
|
16599
16718
|
while (rest[0]?.startsWith("-"))
|
|
16600
16719
|
rest = rest.slice(1);
|
|
16601
16720
|
}
|
|
16602
|
-
if (rest.length === 0 || !rest.every((word) =>
|
|
16721
|
+
if (rest.length === 0 || !rest.every((word) => ASSIGNMENT.test(word)))
|
|
16603
16722
|
return null;
|
|
16604
16723
|
return rest.map((word) => {
|
|
16605
|
-
const [, name, value] =
|
|
16724
|
+
const [, name, value] = ASSIGNMENT.exec(word);
|
|
16606
16725
|
return [name, expandWord(value, vars)];
|
|
16607
16726
|
});
|
|
16608
16727
|
};
|
|
@@ -16790,7 +16909,7 @@ var splitString = (line) => {
|
|
|
16790
16909
|
return line.slice(index).join(" ").slice(15);
|
|
16791
16910
|
if (/^-S./.test(word))
|
|
16792
16911
|
return [word.slice(2), ...line.slice(index + 1)].join(" ");
|
|
16793
|
-
if (!word.startsWith("-") && !
|
|
16912
|
+
if (!word.startsWith("-") && !ASSIGNMENT.test(word))
|
|
16794
16913
|
return null;
|
|
16795
16914
|
}
|
|
16796
16915
|
return null;
|
|
@@ -16903,8 +17022,17 @@ function walk2(command, dialect, depth, seen) {
|
|
|
16903
17022
|
visit(next, undefined, hops + 1);
|
|
16904
17023
|
};
|
|
16905
17024
|
const wrapped = seen ? null : workitCheckTail(words);
|
|
16906
|
-
if (wrapped)
|
|
16907
|
-
|
|
17025
|
+
if (wrapped) {
|
|
17026
|
+
const outer = dir;
|
|
17027
|
+
if (wrapped.cwd !== null)
|
|
17028
|
+
dir = joinDir(dir, wrapped.cwd);
|
|
17029
|
+
if ("script" in wrapped)
|
|
17030
|
+
nested(wrapped.script, "posix");
|
|
17031
|
+
else
|
|
17032
|
+
runs([wrapped.argv]);
|
|
17033
|
+
dir = outer;
|
|
17034
|
+
return;
|
|
17035
|
+
}
|
|
16908
17036
|
if (CD.has(name)) {
|
|
16909
17037
|
const target = cdTarget(words);
|
|
16910
17038
|
dir = target === null ? os4.homedir() : joinDir(dir, target);
|
|
@@ -17005,14 +17133,14 @@ function workitCheckTail(words) {
|
|
|
17005
17133
|
return null;
|
|
17006
17134
|
} else if (!/^workit(?:\.exe|\.cmd)?$/.test(commandName(words[0])))
|
|
17007
17135
|
return null;
|
|
17008
|
-
const args = words.slice(at + 1);
|
|
17136
|
+
const { rest: args, cwd } = skipWorkitGlobals(words.slice(at + 1));
|
|
17009
17137
|
if (args[0] !== "check" && !(args[0] === "lock" && args[1] === "run"))
|
|
17010
17138
|
return null;
|
|
17011
17139
|
const split = args.indexOf("--");
|
|
17012
17140
|
const tail = split < 0 ? [] : args.slice(split + 1);
|
|
17013
17141
|
if (!tail.length)
|
|
17014
17142
|
return null;
|
|
17015
|
-
return args.slice(0, split).includes("--shell") ? { script: tail.join(" ") } : { argv: tail };
|
|
17143
|
+
return args.slice(0, split).includes("--shell") ? { script: tail.join(" "), cwd } : { argv: tail, cwd };
|
|
17016
17144
|
}
|
|
17017
17145
|
function simpleCommand(command, dialect = "posix", depth = 0) {
|
|
17018
17146
|
let segments;
|
|
@@ -17073,7 +17201,21 @@ function skipWorkitGlobals(args) {
|
|
|
17073
17201
|
} else if (args[at] !== "--json")
|
|
17074
17202
|
break;
|
|
17075
17203
|
}
|
|
17076
|
-
|
|
17204
|
+
const rest = [];
|
|
17205
|
+
for (;at < args.length; at++) {
|
|
17206
|
+
const arg = args[at];
|
|
17207
|
+
if (arg === "--") {
|
|
17208
|
+
rest.push(...args.slice(at));
|
|
17209
|
+
break;
|
|
17210
|
+
}
|
|
17211
|
+
if (arg.startsWith("--cwd="))
|
|
17212
|
+
cwd = arg.slice("--cwd=".length);
|
|
17213
|
+
else if (arg === "--cwd" && args[at + 1] !== undefined)
|
|
17214
|
+
cwd = args[++at];
|
|
17215
|
+
else
|
|
17216
|
+
rest.push(arg);
|
|
17217
|
+
}
|
|
17218
|
+
return { rest, cwd };
|
|
17077
17219
|
}
|
|
17078
17220
|
var PUSH_VALUE_OPTIONS = new Set(["-o", "--push-option", "--receive-pack", "--exec", "--repo"]);
|
|
17079
17221
|
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 +17495,7 @@ var repoAt = (dir) => {
|
|
|
17353
17495
|
return { location, dir, branch: resolveTaskKey(dir, location).branch };
|
|
17354
17496
|
};
|
|
17355
17497
|
var isWorkitWorkspace = (repo) => {
|
|
17356
|
-
if (
|
|
17498
|
+
if (fs16.existsSync(repo.location.dir))
|
|
17357
17499
|
return true;
|
|
17358
17500
|
try {
|
|
17359
17501
|
return resolveRuntimeWorkspaceVcs(repo.location.top ?? repo.dir) !== null;
|
|
@@ -17374,17 +17516,17 @@ var headSha2 = (repo) => {
|
|
|
17374
17516
|
if (!git || git.reftable)
|
|
17375
17517
|
return null;
|
|
17376
17518
|
try {
|
|
17377
|
-
const head =
|
|
17519
|
+
const head = fs16.readFileSync(path20.join(git.gitDir, "HEAD"), "utf8").trim();
|
|
17378
17520
|
if (/^[0-9a-f]{40,64}$/.test(head))
|
|
17379
17521
|
return head;
|
|
17380
17522
|
const ref = /^ref: (refs\/.+)$/.exec(head)?.[1];
|
|
17381
17523
|
if (!ref)
|
|
17382
17524
|
return null;
|
|
17383
|
-
const common =
|
|
17525
|
+
const common = path20.dirname(repo.location.dir);
|
|
17384
17526
|
try {
|
|
17385
|
-
return
|
|
17527
|
+
return fs16.readFileSync(path20.join(common, ref), "utf8").trim() || null;
|
|
17386
17528
|
} catch {}
|
|
17387
|
-
const packed =
|
|
17529
|
+
const packed = fs16.readFileSync(path20.join(common, "packed-refs"), "utf8");
|
|
17388
17530
|
return new RegExp(`^([0-9a-f]{40,64}) ${ref.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`, "m").exec(packed)?.[1] ?? null;
|
|
17389
17531
|
} catch {
|
|
17390
17532
|
return null;
|
|
@@ -17396,7 +17538,7 @@ var gitConfig = (dir, invocation, cache) => {
|
|
|
17396
17538
|
if (cached)
|
|
17397
17539
|
return cached;
|
|
17398
17540
|
const config = new Map;
|
|
17399
|
-
const listed =
|
|
17541
|
+
const listed = spawnSync11("git", [...invocation.config ?? [], "config", "-z", "--list"], {
|
|
17400
17542
|
cwd: dir,
|
|
17401
17543
|
encoding: "utf8",
|
|
17402
17544
|
timeout: 3000,
|
|
@@ -17472,8 +17614,8 @@ var expandGitAliases = (root, invocation, cache = new Map, depth = 0) => {
|
|
|
17472
17614
|
const sub = invocation.args[0];
|
|
17473
17615
|
if (invocation.tool !== "git" || invocation.dir === undefined || !sub || sub.startsWith("-") || GIT_COMMANDS.has(sub) || depth >= MAX_DEPTH)
|
|
17474
17616
|
return [invocation];
|
|
17475
|
-
const dir =
|
|
17476
|
-
if (!
|
|
17617
|
+
const dir = path20.resolve(root, invocation.dir ?? ".");
|
|
17618
|
+
if (!fs16.existsSync(dir))
|
|
17477
17619
|
return [invocation];
|
|
17478
17620
|
const config = gitConfig(dir, invocation, cache);
|
|
17479
17621
|
const alias = configValue(config, `alias.${sub.toLowerCase()}`);
|
|
@@ -17495,11 +17637,11 @@ var expandGitAliases = (root, invocation, cache = new Map, depth = 0) => {
|
|
|
17495
17637
|
var normalizedUrl = (url, dir) => {
|
|
17496
17638
|
const trimmed = url.trim();
|
|
17497
17639
|
if (/^file:\/\//i.test(trimmed))
|
|
17498
|
-
return
|
|
17640
|
+
return path20.resolve(dir, trimmed.slice(7)).replace(/\/+$/, "").replace(/\.git$/, "");
|
|
17499
17641
|
const scheme = /^[a-z][a-z0-9+.-]*:\/\//i.exec(trimmed);
|
|
17500
17642
|
const scp = !scheme && /^(?:[^@/\\]+@)?[^/\\:]{2,}:/.test(trimmed);
|
|
17501
17643
|
if (!scheme && !scp)
|
|
17502
|
-
return
|
|
17644
|
+
return path20.resolve(dir, trimmed).replace(/\/+$/, "").replace(/\.git$/, "");
|
|
17503
17645
|
const value = scheme ? trimmed.slice(scheme[0].length).replace(/^[^@/]*@/, "").replace(/^([^/:]+):\d+(?=\/)/, "$1") : trimmed.replace(/^[^@/]*@/, "").replace(":", "/");
|
|
17504
17646
|
return value.toLowerCase().replace(/\/{2,}/g, "/").replace(/^www\./, "").replace(/\/+$/, "").replace(/\.git$/, "");
|
|
17505
17647
|
};
|
|
@@ -17519,7 +17661,7 @@ var rewrittenUrl = (url, config) => {
|
|
|
17519
17661
|
}
|
|
17520
17662
|
return url;
|
|
17521
17663
|
};
|
|
17522
|
-
var
|
|
17664
|
+
var pushUrl = (config, remote, dir) => {
|
|
17523
17665
|
const configured = configValue(config, `remote.${remote}.pushurl`) ?? configValue(config, `remote.${remote}.url`);
|
|
17524
17666
|
return normalizedUrl(rewrittenUrl(configured ?? remote, config), dir);
|
|
17525
17667
|
};
|
|
@@ -17547,7 +17689,7 @@ var toPushRemote = (repo, remote, config) => {
|
|
|
17547
17689
|
const pushRemote = pushRemoteOf(config, repo.branch);
|
|
17548
17690
|
if (pushRemote === null || pushRemote === remote)
|
|
17549
17691
|
return true;
|
|
17550
|
-
return
|
|
17692
|
+
return pushUrl(config, remote, repo.dir) === pushUrl(config, pushRemote, repo.dir);
|
|
17551
17693
|
};
|
|
17552
17694
|
var quote = (text) => `\`${text}\``;
|
|
17553
17695
|
var refusal = (action, branch, dir) => {
|
|
@@ -17560,7 +17702,7 @@ var refusal = (action, branch, dir) => {
|
|
|
17560
17702
|
};
|
|
17561
17703
|
var refFacts = (repo, remote) => {
|
|
17562
17704
|
const facts = { heads: new Set, tags: new Set, remoteHeads: new Set };
|
|
17563
|
-
const listed =
|
|
17705
|
+
const listed = spawnSync11("git", ["for-each-ref", "--format=%(refname)", "refs/heads", "refs/tags", `refs/remotes/${remote}`], { cwd: repo.dir, encoding: "utf8", timeout: 5000 });
|
|
17564
17706
|
const remotePrefix = `refs/remotes/${remote}/`;
|
|
17565
17707
|
for (const ref of (listed.stdout ?? "").split(`
|
|
17566
17708
|
`)) {
|
|
@@ -17577,7 +17719,7 @@ var globBranches = (repo, glob) => {
|
|
|
17577
17719
|
const source = glob.source.startsWith("refs/") ? glob.source : `refs/heads/${glob.source}`;
|
|
17578
17720
|
const [sourcePrefix, sourceSuffix = ""] = source.split("*");
|
|
17579
17721
|
const [destinationPrefix, destinationSuffix = ""] = glob.destination.split("*");
|
|
17580
|
-
const listed =
|
|
17722
|
+
const listed = spawnSync11("git", [
|
|
17581
17723
|
"for-each-ref",
|
|
17582
17724
|
"--format=%(refname)",
|
|
17583
17725
|
sourcePrefix.slice(0, sourcePrefix.lastIndexOf("/") + 1)
|
|
@@ -17654,7 +17796,7 @@ var tagRefusal = (action, facts, dir) => {
|
|
|
17654
17796
|
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
17797
|
};
|
|
17656
17798
|
var remoteHeads = (repo, remote) => {
|
|
17657
|
-
const listed =
|
|
17799
|
+
const listed = spawnSync11("git", ["ls-remote", "--heads", remote], {
|
|
17658
17800
|
cwd: repo.dir,
|
|
17659
17801
|
encoding: "utf8",
|
|
17660
17802
|
timeout: 3000,
|
|
@@ -17723,7 +17865,7 @@ var actionsIn = (cwd, command, dialect, cache = new Map) => {
|
|
|
17723
17865
|
const out = [];
|
|
17724
17866
|
const repos = new Map;
|
|
17725
17867
|
const repoFor = (relative) => {
|
|
17726
|
-
const dir =
|
|
17868
|
+
const dir = path20.resolve(cwd, relative ?? ".");
|
|
17727
17869
|
if (!repos.has(dir)) {
|
|
17728
17870
|
const repo = repoAt(dir);
|
|
17729
17871
|
repos.set(dir, repo && isWorkitWorkspace(repo) ? repo : null);
|
|
@@ -17809,7 +17951,7 @@ var analysisDenial = (error) => ({
|
|
|
17809
17951
|
});
|
|
17810
17952
|
var NEXT_COMMAND = "next";
|
|
17811
17953
|
var MARKER_TTL_SECONDS = 3600;
|
|
17812
|
-
var markerFile = (repo, session, key) =>
|
|
17954
|
+
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
17955
|
var callKey = (toolUseId, command) => toolUseId ?? `cmd:${createHash14("sha256").update(command).digest("hex")}`;
|
|
17814
17956
|
function noteRawCommit(input, command, key) {
|
|
17815
17957
|
try {
|
|
@@ -17820,14 +17962,14 @@ function noteRawCommit(input, command, key) {
|
|
|
17820
17962
|
if (action.kind !== "commit")
|
|
17821
17963
|
continue;
|
|
17822
17964
|
const file = markerFile(repo, input.session.id, key);
|
|
17823
|
-
|
|
17824
|
-
|
|
17965
|
+
fs16.mkdirSync(path20.dirname(file), { recursive: true });
|
|
17966
|
+
fs16.writeFileSync(file, `${JSON.stringify({ head: headSha2(repo), at })}
|
|
17825
17967
|
`);
|
|
17826
17968
|
}
|
|
17827
17969
|
} catch {}
|
|
17828
17970
|
}
|
|
17829
17971
|
var headCommit = (dir) => {
|
|
17830
|
-
const run =
|
|
17972
|
+
const run = spawnSync11("git", [
|
|
17831
17973
|
"log",
|
|
17832
17974
|
"-1",
|
|
17833
17975
|
"--format=%H%x1f%ct%x1f%s%x1f%(trailers:key=Workit-Session,valueonly,separator=%x2c)"
|
|
@@ -17844,11 +17986,11 @@ var headCommit = (dir) => {
|
|
|
17844
17986
|
var settle2 = (input, repo, file) => {
|
|
17845
17987
|
let marker;
|
|
17846
17988
|
try {
|
|
17847
|
-
marker = JSON.parse(
|
|
17989
|
+
marker = JSON.parse(fs16.readFileSync(file, "utf8"));
|
|
17848
17990
|
} catch {
|
|
17849
17991
|
return;
|
|
17850
17992
|
}
|
|
17851
|
-
|
|
17993
|
+
fs16.rmSync(file, { force: true });
|
|
17852
17994
|
const session = input.session.id;
|
|
17853
17995
|
const now = Date.now() / 1000;
|
|
17854
17996
|
if (typeof marker.at !== "number" || now - marker.at > MARKER_TTL_SECONDS)
|
|
@@ -17897,7 +18039,7 @@ function settlePendingCommit(input) {
|
|
|
17897
18039
|
if (!repo)
|
|
17898
18040
|
return;
|
|
17899
18041
|
const file = markerFile(repo, input.session.id, NEXT_COMMAND);
|
|
17900
|
-
if (
|
|
18042
|
+
if (fs16.existsSync(file))
|
|
17901
18043
|
settle2(input, repo, file);
|
|
17902
18044
|
} catch {}
|
|
17903
18045
|
}
|
|
@@ -18041,7 +18183,7 @@ var targetsOf = (invocations) => invocations.flatMap((invocation) => invocation.
|
|
|
18041
18183
|
branch,
|
|
18042
18184
|
dir: invocation.dir
|
|
18043
18185
|
})) : []);
|
|
18044
|
-
var localBranchExists = (dir, branch) =>
|
|
18186
|
+
var localBranchExists = (dir, branch) => spawnSync12("git", ["show-ref", "--verify", "--quiet", `refs/heads/${branch}`], {
|
|
18045
18187
|
cwd: dir,
|
|
18046
18188
|
stdio: "ignore",
|
|
18047
18189
|
timeout: 5000
|
|
@@ -18051,7 +18193,7 @@ var judgedDir = (root, dir) => {
|
|
|
18051
18193
|
return null;
|
|
18052
18194
|
if (dir === null)
|
|
18053
18195
|
return root;
|
|
18054
|
-
const resolved =
|
|
18196
|
+
const resolved = path21.resolve(root, dir);
|
|
18055
18197
|
return existsSync7(resolved) && inWorkitWorkspace(resolved) ? resolved : null;
|
|
18056
18198
|
};
|
|
18057
18199
|
var shellBranchPolicyViolation = (root, command, dialect = "posix") => {
|
|
@@ -18287,7 +18429,7 @@ var judgeInputSummary = (raw) => {
|
|
|
18287
18429
|
|
|
18288
18430
|
// packages/workit-core/src/autonomy.ts
|
|
18289
18431
|
import os5 from "node:os";
|
|
18290
|
-
import
|
|
18432
|
+
import path22 from "node:path";
|
|
18291
18433
|
var accountHome = () => {
|
|
18292
18434
|
try {
|
|
18293
18435
|
return os5.userInfo().homedir;
|
|
@@ -18296,10 +18438,10 @@ var accountHome = () => {
|
|
|
18296
18438
|
}
|
|
18297
18439
|
};
|
|
18298
18440
|
var grantsHome = { resolve: accountHome };
|
|
18299
|
-
var grantsDir = () =>
|
|
18441
|
+
var grantsDir = () => path22.join(grantsHome.resolve(), ".config", "workit");
|
|
18300
18442
|
var grantsOverride = () => {
|
|
18301
|
-
const configured =
|
|
18302
|
-
return configured ===
|
|
18443
|
+
const configured = path22.resolve(resolveConfigDir());
|
|
18444
|
+
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
18445
|
};
|
|
18304
18446
|
var GRANT_KINDS = ["push", "pr", "merge", "release", "rerun"];
|
|
18305
18447
|
var DEFAULT_GRANTS = Object.freeze({
|
|
@@ -20108,9 +20250,9 @@ class WorkitCore {
|
|
|
20108
20250
|
}
|
|
20109
20251
|
}
|
|
20110
20252
|
// packages/workit-core/src/core/repo-context.ts
|
|
20111
|
-
import { spawnSync as
|
|
20253
|
+
import { spawnSync as spawnSync13 } from "node:child_process";
|
|
20112
20254
|
import { existsSync as existsSync8, readFileSync as readFileSync6, readdirSync as readdirSync4 } from "node:fs";
|
|
20113
|
-
import
|
|
20255
|
+
import path23 from "node:path";
|
|
20114
20256
|
var isSafeContextRange = (value) => value.length <= 4096 && (() => {
|
|
20115
20257
|
try {
|
|
20116
20258
|
gitRevisionParts(value);
|
|
@@ -20137,7 +20279,7 @@ var invalidRange = (cwd) => ({
|
|
|
20137
20279
|
cwd
|
|
20138
20280
|
});
|
|
20139
20281
|
var runGit = (cwd, args) => {
|
|
20140
|
-
const result =
|
|
20282
|
+
const result = spawnSync13("git", args, { cwd, encoding: "utf8" });
|
|
20141
20283
|
return {
|
|
20142
20284
|
stdout: (result.stdout ?? "").trimEnd(),
|
|
20143
20285
|
stderr: (result.stderr ?? "").trimEnd(),
|
|
@@ -20244,7 +20386,7 @@ var findPrTemplate = (cwd) => {
|
|
|
20244
20386
|
]) {
|
|
20245
20387
|
const parts = rel.split("/");
|
|
20246
20388
|
const base = parts[parts.length - 1];
|
|
20247
|
-
const dir = parts.length > 1 ?
|
|
20389
|
+
const dir = parts.length > 1 ? path23.join(cwd, ...parts.slice(0, -1)) : cwd;
|
|
20248
20390
|
const actual = probeCaseInsensitive(dir, base);
|
|
20249
20391
|
if (actual)
|
|
20250
20392
|
return [...parts.slice(0, -1), actual].join("/");
|
|
@@ -20275,7 +20417,7 @@ var diffStatForRange = (cwd, range) => {
|
|
|
20275
20417
|
return runGit(cwd, ["diff", "--stat", "--"]).stdout;
|
|
20276
20418
|
};
|
|
20277
20419
|
var packageScripts = (cwd, keys) => {
|
|
20278
|
-
const pkgPath =
|
|
20420
|
+
const pkgPath = path23.join(cwd, "package.json");
|
|
20279
20421
|
if (!existsSync8(pkgPath))
|
|
20280
20422
|
return [];
|
|
20281
20423
|
try {
|
|
@@ -20301,9 +20443,9 @@ var documentationFiles = (cwd) => {
|
|
|
20301
20443
|
if (entry.isDirectory()) {
|
|
20302
20444
|
if (excluded.has(entry.name))
|
|
20303
20445
|
continue;
|
|
20304
|
-
walk(
|
|
20446
|
+
walk(path23.join(dir, entry.name), depth + 1);
|
|
20305
20447
|
} else if (entry.isFile() && (entry.name === "README.md" || entry.name.endsWith(".md"))) {
|
|
20306
|
-
matches.push("./" +
|
|
20448
|
+
matches.push("./" + path23.relative(cwd, path23.join(dir, entry.name)).replaceAll("\\", "/"));
|
|
20307
20449
|
}
|
|
20308
20450
|
}
|
|
20309
20451
|
};
|
|
@@ -20319,7 +20461,7 @@ var readTrimmed2 = (file, maxLines) => {
|
|
|
20319
20461
|
`);
|
|
20320
20462
|
};
|
|
20321
20463
|
function prReadyContext(root, range) {
|
|
20322
|
-
const cwd =
|
|
20464
|
+
const cwd = path23.resolve(repoRoot(root));
|
|
20323
20465
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
20324
20466
|
return invalidRange(cwd);
|
|
20325
20467
|
let stdout = "";
|
|
@@ -20377,7 +20519,7 @@ ${status.stderr}` : "");
|
|
|
20377
20519
|
stdout += `template_path: ${template}
|
|
20378
20520
|
|
|
20379
20521
|
`;
|
|
20380
|
-
stdout += readTrimmed2(
|
|
20522
|
+
stdout += readTrimmed2(path23.join(cwd, template), 220);
|
|
20381
20523
|
} else {
|
|
20382
20524
|
stdout += `template_path: none
|
|
20383
20525
|
|
|
@@ -20387,7 +20529,7 @@ ${status.stderr}` : "");
|
|
|
20387
20529
|
stdout += `
|
|
20388
20530
|
`;
|
|
20389
20531
|
stdout += printSection("Recent Validation Signals");
|
|
20390
|
-
if (existsSync8(
|
|
20532
|
+
if (existsSync8(path23.join(cwd, "package.json"))) {
|
|
20391
20533
|
stdout += `package.json detected. Common scripts:
|
|
20392
20534
|
`;
|
|
20393
20535
|
const found = packageScripts(cwd, ["lint", "format:check", "test", "build"]);
|
|
@@ -20396,7 +20538,7 @@ ${status.stderr}` : "");
|
|
|
20396
20538
|
`) + `
|
|
20397
20539
|
`;
|
|
20398
20540
|
}
|
|
20399
|
-
if (existsSync8(
|
|
20541
|
+
if (existsSync8(path23.join(cwd, "Cargo.toml")) || existsSync8(path23.join(cwd, "src-tauri/Cargo.toml"))) {
|
|
20400
20542
|
stdout += `Rust project detected.
|
|
20401
20543
|
`;
|
|
20402
20544
|
}
|
|
@@ -20429,7 +20571,7 @@ var CHANGELOG_RULES = `- Use an [Unreleased] section.
|
|
|
20429
20571
|
- Apply with the host-native editor, preserving the repository changelog conventions.
|
|
20430
20572
|
- If Unreleased already has duplicate category headings, normalize_only first.`;
|
|
20431
20573
|
function changelogContext(root, range) {
|
|
20432
|
-
const cwd =
|
|
20574
|
+
const cwd = path23.resolve(repoRoot(root));
|
|
20433
20575
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
20434
20576
|
return invalidRange(cwd);
|
|
20435
20577
|
const resolvedRange = rangeArgOrDefault(range, cwd);
|
|
@@ -20447,7 +20589,7 @@ function changelogContext(root, range) {
|
|
|
20447
20589
|
stdout += CHANGELOG_RULES + `
|
|
20448
20590
|
`;
|
|
20449
20591
|
stdout += printSection("Existing CHANGELOG.md");
|
|
20450
|
-
const changelogPath =
|
|
20592
|
+
const changelogPath = path23.join(cwd, "CHANGELOG.md");
|
|
20451
20593
|
if (existsSync8(changelogPath)) {
|
|
20452
20594
|
stdout += readTrimmed2(changelogPath, 260) + `
|
|
20453
20595
|
`;
|
|
@@ -20467,7 +20609,7 @@ function changelogContext(root, range) {
|
|
|
20467
20609
|
return { stdout, stderr: "", exitCode: 0, cwd };
|
|
20468
20610
|
}
|
|
20469
20611
|
function docsRefreshContext(root, range) {
|
|
20470
|
-
const cwd =
|
|
20612
|
+
const cwd = path23.resolve(repoRoot(root));
|
|
20471
20613
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
20472
20614
|
return invalidRange(cwd);
|
|
20473
20615
|
const resolvedRange = rangeArgOrDefault(range, cwd);
|
|
@@ -20489,7 +20631,7 @@ function docsRefreshContext(root, range) {
|
|
|
20489
20631
|
`) + `
|
|
20490
20632
|
`;
|
|
20491
20633
|
stdout += printSection("README Preview");
|
|
20492
|
-
const readme =
|
|
20634
|
+
const readme = path23.join(cwd, "README.md");
|
|
20493
20635
|
if (existsSync8(readme)) {
|
|
20494
20636
|
stdout += readTrimmed2(readme, 220) + `
|
|
20495
20637
|
`;
|
|
@@ -20498,7 +20640,7 @@ function docsRefreshContext(root, range) {
|
|
|
20498
20640
|
`;
|
|
20499
20641
|
}
|
|
20500
20642
|
stdout += printSection("Package Scripts");
|
|
20501
|
-
const pkgPath =
|
|
20643
|
+
const pkgPath = path23.join(cwd, "package.json");
|
|
20502
20644
|
if (existsSync8(pkgPath)) {
|
|
20503
20645
|
try {
|
|
20504
20646
|
const pkg = JSON.parse(readFileSync6(pkgPath, "utf8"));
|
|
@@ -20512,7 +20654,7 @@ function docsRefreshContext(root, range) {
|
|
|
20512
20654
|
return { stdout, stderr: "", exitCode: 0, cwd };
|
|
20513
20655
|
}
|
|
20514
20656
|
function releaseNotesContext(root, rangeOrTag) {
|
|
20515
|
-
const cwd =
|
|
20657
|
+
const cwd = path23.resolve(repoRoot(root));
|
|
20516
20658
|
if (!rangeOrTag) {
|
|
20517
20659
|
return { stdout: "", stderr: `ERROR: release tag or range required
|
|
20518
20660
|
`, exitCode: 1, cwd };
|
|
@@ -20550,7 +20692,7 @@ function releaseNotesContext(root, rangeOrTag) {
|
|
|
20550
20692
|
`;
|
|
20551
20693
|
stdout += printSection("Existing Release Files");
|
|
20552
20694
|
for (const rel of ["CHANGELOG.md", "RELEASE_NOTES.md", ".github/releases.md"]) {
|
|
20553
|
-
if (existsSync8(
|
|
20695
|
+
if (existsSync8(path23.join(cwd, rel)))
|
|
20554
20696
|
stdout += rel + `
|
|
20555
20697
|
`;
|
|
20556
20698
|
}
|
|
@@ -20579,6 +20721,46 @@ function releaseNotesContext(root, rangeOrTag) {
|
|
|
20579
20721
|
|
|
20580
20722
|
// packages/workit-core/src/core/tracker-issues.ts
|
|
20581
20723
|
import { spawnSync as spawnSync15 } from "node:child_process";
|
|
20724
|
+
|
|
20725
|
+
// packages/workit-core/src/core/pr-create.ts
|
|
20726
|
+
import fs17 from "node:fs";
|
|
20727
|
+
import path24 from "node:path";
|
|
20728
|
+
import { spawnSync as spawnSync14 } from "node:child_process";
|
|
20729
|
+
// packages/workit-core/src/forge/pr-body.ts
|
|
20730
|
+
function parseGhIssue(value) {
|
|
20731
|
+
const m = /issues\/(\d+)/.exec(value);
|
|
20732
|
+
return m ? m[1] : String(value).trim().replace(/^#/, "");
|
|
20733
|
+
}
|
|
20734
|
+
|
|
20735
|
+
// packages/workit-core/src/core/pr-create.ts
|
|
20736
|
+
function whichOnPath(tool) {
|
|
20737
|
+
const names = process.platform === "win32" ? [tool, `${tool}.exe`, `${tool}.cmd`] : [tool];
|
|
20738
|
+
for (const dir of (process.env.PATH ?? "").split(path24.delimiter)) {
|
|
20739
|
+
if (!dir)
|
|
20740
|
+
continue;
|
|
20741
|
+
for (const name of names) {
|
|
20742
|
+
const candidate = path24.join(dir, name);
|
|
20743
|
+
try {
|
|
20744
|
+
fs17.accessSync(candidate, fs17.constants.X_OK);
|
|
20745
|
+
return candidate;
|
|
20746
|
+
} catch {}
|
|
20747
|
+
}
|
|
20748
|
+
}
|
|
20749
|
+
return null;
|
|
20750
|
+
}
|
|
20751
|
+
var hostingCliAvailable = (provider) => whichOnPath(provider === "gitlab" ? "glab" : "gh") !== null;
|
|
20752
|
+
var pushRemote = (cwd) => {
|
|
20753
|
+
const result = spawnSync14("git", ["remote", "get-url", "--push", "--all", "origin"], {
|
|
20754
|
+
cwd,
|
|
20755
|
+
encoding: "utf8"
|
|
20756
|
+
});
|
|
20757
|
+
if (result.status !== 0)
|
|
20758
|
+
return null;
|
|
20759
|
+
const urls = (result.stdout ?? "").split(/\r?\n/u).map((url) => url.trim()).filter(Boolean);
|
|
20760
|
+
return urls.length === 1 ? urls[0] : null;
|
|
20761
|
+
};
|
|
20762
|
+
|
|
20763
|
+
// packages/workit-core/src/core/tracker-issues.ts
|
|
20582
20764
|
var repoPathFromRemote = (remote) => {
|
|
20583
20765
|
let rest = (remote || "").trim().replace(/\/+$/, "");
|
|
20584
20766
|
if (!rest)
|
|
@@ -21404,6 +21586,53 @@ var shellPolicy = (cwd, command, dialect = "posix") => {
|
|
|
21404
21586
|
}
|
|
21405
21587
|
return policy && !policy.ok ? { kind: "deny", reason: `branch_policy_denied: ${policy.error}`, unblock: null } : { kind: "none" };
|
|
21406
21588
|
};
|
|
21589
|
+
// packages/workit-core/src/hooks/delivery-verbs.ts
|
|
21590
|
+
var WORKIT_ALLOWED_VERBS = [
|
|
21591
|
+
"git",
|
|
21592
|
+
"pr",
|
|
21593
|
+
"ci",
|
|
21594
|
+
"stack",
|
|
21595
|
+
"release",
|
|
21596
|
+
"task",
|
|
21597
|
+
"ledger",
|
|
21598
|
+
"evidence",
|
|
21599
|
+
"finding",
|
|
21600
|
+
"decision",
|
|
21601
|
+
"worker",
|
|
21602
|
+
"verify-delivery",
|
|
21603
|
+
"policy",
|
|
21604
|
+
"doctor",
|
|
21605
|
+
"help"
|
|
21606
|
+
];
|
|
21607
|
+
var WORKIT_BYPASS_FLAGS = ["--unverified", "--overwrite-unintegrated"];
|
|
21608
|
+
var ALLOWED = new Set(WORKIT_ALLOWED_VERBS);
|
|
21609
|
+
var isBypass = (word) => WORKIT_BYPASS_FLAGS.some((flag) => word === flag || word.startsWith(`${flag}=`));
|
|
21610
|
+
var isWorkitWord = (word) => /^(?:.*[\\/])?workit(?:-cli)?$/u.test(word);
|
|
21611
|
+
var bypassReason = (flag) => `Workit: \`${flag}\` overrides a Workit gate, so the user decides this call (no allow rule covers it).`;
|
|
21612
|
+
var EXPANSION_REASON = "Workit: an expansion before `--` could become a bypass flag, so the user decides this call (no allow rule covers it).";
|
|
21613
|
+
var workitBypassFlag = (command, dialect = "posix") => {
|
|
21614
|
+
for (const segment of segmentsOf(command, dialect)) {
|
|
21615
|
+
if (segment.unanalyzable) {
|
|
21616
|
+
const text = command.replaceAll(/['"\\`]/gu, "");
|
|
21617
|
+
if (!/\bworkit\b/u.test(text))
|
|
21618
|
+
return null;
|
|
21619
|
+
const found = WORKIT_BYPASS_FLAGS.find((flag) => text.includes(flag));
|
|
21620
|
+
return found ? bypassReason(found) : null;
|
|
21621
|
+
}
|
|
21622
|
+
const start = segment.words.findIndex(isWorkitWord);
|
|
21623
|
+
if (start < 0)
|
|
21624
|
+
continue;
|
|
21625
|
+
const words = segment.words.slice(start + 1);
|
|
21626
|
+
const end = words.indexOf("--");
|
|
21627
|
+
const flag = (end < 0 ? words : words.slice(0, end)).find(isBypass);
|
|
21628
|
+
if (flag)
|
|
21629
|
+
return bypassReason(flag.split("=")[0] ?? flag);
|
|
21630
|
+
const limit = start + 1 + (end < 0 ? words.length : end);
|
|
21631
|
+
if (segment.expands?.some((index) => index > start && index < limit))
|
|
21632
|
+
return EXPANSION_REASON;
|
|
21633
|
+
}
|
|
21634
|
+
return null;
|
|
21635
|
+
};
|
|
21407
21636
|
// packages/workit-core/src/core/heavy-rules.ts
|
|
21408
21637
|
import fs20 from "node:fs";
|
|
21409
21638
|
import os6 from "node:os";
|
|
@@ -22788,7 +23017,7 @@ var segmentTargets = (cwd, command, dialect, depth = 0, strict = false) => {
|
|
|
22788
23017
|
}
|
|
22789
23018
|
const inner = depth < 4 ? wrappedScript(words) ?? (strict ? shellScript2(words) : null) : null;
|
|
22790
23019
|
if (inner !== null && dir !== undefined)
|
|
22791
|
-
targets.push(...segmentTargets(dir, inner, "posix", depth + 1, strict));
|
|
23020
|
+
targets.push(...segmentTargets(workitRunDir(dir, words), inner, "posix", depth + 1, strict));
|
|
22792
23021
|
if (strict && dir !== undefined) {
|
|
22793
23022
|
const runIn = dir;
|
|
22794
23023
|
for (const sub of depth < 4 ? segment.subs ?? [] : [])
|
|
@@ -22828,6 +23057,14 @@ var segmentTargets = (cwd, command, dialect, depth = 0, strict = false) => {
|
|
|
22828
23057
|
}
|
|
22829
23058
|
return targets;
|
|
22830
23059
|
};
|
|
23060
|
+
var workitRunDir = (dir, words) => {
|
|
23061
|
+
for (const invocation of workitInvocations(words.map(quoteWord2).join(" "), "posix")) {
|
|
23062
|
+
const { at, args } = workitCwd(dir, invocation);
|
|
23063
|
+
if (args[0] === "check" || args[0] === "lock" && args[1] === "run")
|
|
23064
|
+
return at;
|
|
23065
|
+
}
|
|
23066
|
+
return dir;
|
|
23067
|
+
};
|
|
22831
23068
|
var workitCwd = (dir, args) => {
|
|
22832
23069
|
const { rest, cwd } = skipWorkitGlobals(args);
|
|
22833
23070
|
return { at: cwd === null ? dir : path32.resolve(dir, homeExpanded(cwd)), args: rest };
|
|
@@ -23575,8 +23812,13 @@ var standingDelta = (input) => {
|
|
|
23575
23812
|
};
|
|
23576
23813
|
var sinceStart = new Map;
|
|
23577
23814
|
// packages/workit-core/src/hooks/cwd-normalize.ts
|
|
23578
|
-
var
|
|
23815
|
+
var DIR = String.raw`[A-Za-z0-9_./@%+:,=-]+|'[^'$${"`"}\\\n]*'|"[^"$${"`"}\\!\n]*"`;
|
|
23816
|
+
var COMPOUND = new RegExp(String.raw`^\s*cd\s+(${DIR})\s*&&\s*(workit(?:\s[\s\S]*)?)$`, "u");
|
|
23817
|
+
var GLOBALS_FIRST = new RegExp(String.raw`^(\s*workit)((?:\s+(?:--json|--cwd(?:=|\s+)(?:${DIR})))+)\s+([a-z][a-z-]*)(?=\s|$)`, "u");
|
|
23818
|
+
var VERB_FIRST = /^((?:--json\s+)*)([a-z][a-z-]*)(?=\s|$)/u;
|
|
23819
|
+
var VERBS = new Set(WORKIT_ALLOWED_VERBS);
|
|
23579
23820
|
var FILTERS = new Set(["tail", "head", "grep"]);
|
|
23821
|
+
var clean = (s) => !s.unanalyzable && !s.group && !s.subs?.length && !s.stdin?.length && !s.redirects.length;
|
|
23580
23822
|
var stdinOnly = (words) => {
|
|
23581
23823
|
const [name, ...args] = words;
|
|
23582
23824
|
const operands = args.filter((w) => !w.startsWith("-"));
|
|
@@ -23598,14 +23840,28 @@ var cwdRewrite = (command) => {
|
|
|
23598
23840
|
return null;
|
|
23599
23841
|
if (head.words.some((w) => w === "--cwd" || w.startsWith("--cwd=")))
|
|
23600
23842
|
return null;
|
|
23601
|
-
const clean = (s) => !s.unanalyzable && !s.group && !s.subs?.length && !s.stdin?.length && !s.redirects.length;
|
|
23602
23843
|
if (!segments.every(clean))
|
|
23603
23844
|
return null;
|
|
23604
23845
|
if (!segments.slice(0, -1).every((s) => s.pipe))
|
|
23605
23846
|
return null;
|
|
23606
23847
|
if (!filters.every((s) => FILTERS.has(s.words[0] ?? "") && stdinOnly(s.words)))
|
|
23607
23848
|
return null;
|
|
23608
|
-
|
|
23849
|
+
const args = rest.slice("workit".length).trimStart();
|
|
23850
|
+
const verb = VERB_FIRST.exec(args);
|
|
23851
|
+
if (!verb?.[2] || !VERBS.has(verb[2]))
|
|
23852
|
+
return `workit --cwd ${dir} ${args}`.trimEnd();
|
|
23853
|
+
const json = verb[1] ? " --json" : "";
|
|
23854
|
+
return `workit ${verb[2]} --cwd ${dir}${json}${args.slice(verb[0].length)}`.trimEnd();
|
|
23855
|
+
};
|
|
23856
|
+
var verbFirst = (command) => {
|
|
23857
|
+
const match = GLOBALS_FIRST.exec(command);
|
|
23858
|
+
const [whole, lead, globals, verb] = match ?? [];
|
|
23859
|
+
if (!whole || !verb || !VERBS.has(verb) || !globals?.includes("--cwd"))
|
|
23860
|
+
return null;
|
|
23861
|
+
const segments = segmentsOf(command);
|
|
23862
|
+
if (segments.length !== 1 || !segments.every(clean))
|
|
23863
|
+
return null;
|
|
23864
|
+
return `${lead} ${verb}${globals}${command.slice(whole.length)}`;
|
|
23609
23865
|
};
|
|
23610
23866
|
|
|
23611
23867
|
// packages/workit-core/src/hooks/worktree-confine.ts
|
|
@@ -23721,7 +23977,7 @@ var hostIsolated = (cwd, agentId) => {
|
|
|
23721
23977
|
};
|
|
23722
23978
|
var shown = (target) => path34.basename(target) === ".workit-live-target" ? path34.dirname(target) : target;
|
|
23723
23979
|
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>
|
|
23980
|
+
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
23981
|
return {
|
|
23726
23982
|
kind: "deny",
|
|
23727
23983
|
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 +23985,7 @@ var unboundDeny = (input, target) => {
|
|
|
23729
23985
|
};
|
|
23730
23986
|
};
|
|
23731
23987
|
var outsideDeny = (input, worktree, target) => {
|
|
23732
|
-
const unblock = `Edit only under ${worktree} and run git there, one verb per call: \`workit --cwd ${worktree}
|
|
23988
|
+
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
23989
|
return {
|
|
23734
23990
|
kind: "deny",
|
|
23735
23991
|
reason: `workit worktree confinement: implementer ${input.session.agentId} is bound to the worktree ${worktree}, and ${shown(target)} is outside it. ${unblock}`,
|
|
@@ -23738,7 +23994,7 @@ var outsideDeny = (input, worktree, target) => {
|
|
|
23738
23994
|
};
|
|
23739
23995
|
var unknownDirDeny = (input, worktree) => {
|
|
23740
23996
|
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}\`.`;
|
|
23997
|
+
const unblock = `Workit cannot resolve \`cd -\`, \`cd "$X"\` or \`popd\`: write by absolute path, \`git -C ${wt}\` or \`workit <verb> --cwd ${wt}\`.`;
|
|
23742
23998
|
return {
|
|
23743
23999
|
kind: "deny",
|
|
23744
24000
|
reason: `workit worktree confinement: implementer ${input.session.agentId} writes after an unknown directory change. ${unblock}`,
|
|
@@ -23896,7 +24152,7 @@ var subagentStartText = (input, descriptor, event) => {
|
|
|
23896
24152
|
return `${isolatedGit(input.cwd)}
|
|
23897
24153
|
${subagentStartText({ ...input, cwd: "" }, descriptor, event)}`;
|
|
23898
24154
|
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>
|
|
24155
|
+
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
24156
|
const readOnly = `Workit observed ${who} as read-only/agent-guided.`;
|
|
23901
24157
|
if (role !== "judge" || !event.agentId)
|
|
23902
24158
|
return readOnly;
|
|
@@ -23913,7 +24169,7 @@ ${subagentStartText({ ...input, cwd: "" }, descriptor, event)}`;
|
|
|
23913
24169
|
var PRE_TOOL = new Set(["shell.pre", "write.pre", "tool.pre"]);
|
|
23914
24170
|
function handleHook(input, deps) {
|
|
23915
24171
|
const decision = decide(input, deps);
|
|
23916
|
-
if (deps.descriptor.transport !== "hook-process" || !PRE_TOOL.has(input.event.kind) || decision.kind === "deny")
|
|
24172
|
+
if (deps.descriptor.transport !== "hook-process" || !PRE_TOOL.has(input.event.kind) || decision.kind === "deny" || decision.kind === "ask")
|
|
23917
24173
|
return decision;
|
|
23918
24174
|
const delta = standingDelta(input);
|
|
23919
24175
|
if (!delta)
|
|
@@ -23980,9 +24236,12 @@ function decide(input, deps) {
|
|
|
23980
24236
|
if (plan.kind === "rewrite" && !rewrites && canDeny)
|
|
23981
24237
|
return heavyDenial(event.command, plan);
|
|
23982
24238
|
const lease = plan.kind === "rewrite" && rewrites ? plan : null;
|
|
23983
|
-
const cwdCommand = !lease && rewrites && input.host === "claude_code" ? cwdRewrite(event.command) : null;
|
|
24239
|
+
const cwdCommand = !lease && rewrites && input.host === "claude_code" ? cwdRewrite(event.command) ?? verbFirst(event.command) : null;
|
|
23984
24240
|
const aside = plan.kind === "passthrough" ? plan.note : null;
|
|
23985
24241
|
noteRawCommit(input, event.command, postTool ? callKey(event.toolUseId, event.command) : NEXT_COMMAND);
|
|
24242
|
+
const bypass = !lease && input.host === "claude_code" ? workitBypassFlag(event.command, event.dialect) : null;
|
|
24243
|
+
if (bypass)
|
|
24244
|
+
return { kind: "ask", reason: bypass };
|
|
23986
24245
|
const rewrittenTo = lease?.command ?? cwdCommand;
|
|
23987
24246
|
const rewritten = (text) => rewrittenTo ? {
|
|
23988
24247
|
kind: "rewrite",
|