@brainervirus/workit-pi 8.23.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 +100 -1
- package/dist/workit.js +1411 -306
- 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-implement/SKILL.md +5 -5
- package/skills/workit-review/SKILL.md +5 -0
- package/skills/workit-ship/SKILL.md +1 -2
package/dist/workit.js
CHANGED
|
@@ -4,8 +4,8 @@ import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
|
4
4
|
|
|
5
5
|
// packages/workit-core/src/core.ts
|
|
6
6
|
import { spawnSync as spawnSync16 } from "node:child_process";
|
|
7
|
-
import { existsSync as
|
|
8
|
-
import
|
|
7
|
+
import { existsSync as existsSync9, realpathSync as realpathSync5 } from "node:fs";
|
|
8
|
+
import path26 from "node:path";
|
|
9
9
|
|
|
10
10
|
// packages/workit-core/src/core/task-contract.ts
|
|
11
11
|
import { createHash, randomUUID } from "node:crypto";
|
|
@@ -8402,7 +8402,7 @@ var workspaceConfigSchema = object({
|
|
|
8402
8402
|
}).passthrough().optional(),
|
|
8403
8403
|
autoApprove: union([boolean2(), array(nonBlank)]).optional(),
|
|
8404
8404
|
defaultEndpoint: unknown().optional(),
|
|
8405
|
-
verification: _enum(["self", "independent"]).optional(),
|
|
8405
|
+
verification: _enum(["proportional", "self", "independent"]).optional(),
|
|
8406
8406
|
delegation: unknown().optional(),
|
|
8407
8407
|
trailers: boolean2().optional(),
|
|
8408
8408
|
liveSessionGuard: boolean2().optional(),
|
|
@@ -8965,6 +8965,144 @@ 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
|
+
}
|
|
8988
|
+
var LANE_DIFF = [
|
|
8989
|
+
"-c",
|
|
8990
|
+
"core.quotePath=false",
|
|
8991
|
+
"diff",
|
|
8992
|
+
"--no-color",
|
|
8993
|
+
"--no-ext-diff",
|
|
8994
|
+
"--no-textconv",
|
|
8995
|
+
"--no-renames"
|
|
8996
|
+
];
|
|
8997
|
+
var laneRange = (fork, head) => {
|
|
8998
|
+
if (!safeArg(fork) || head !== null && !safeArg(head))
|
|
8999
|
+
return null;
|
|
9000
|
+
return head === null ? [fork] : [fork, head];
|
|
9001
|
+
};
|
|
9002
|
+
function diffNumstat(cwd, fork, head) {
|
|
9003
|
+
const range = laneRange(fork, head);
|
|
9004
|
+
if (!range)
|
|
9005
|
+
return null;
|
|
9006
|
+
const run = git(cwd, [...LANE_DIFF, "--numstat", "-z", ...range, "--"]);
|
|
9007
|
+
if (!run.ok)
|
|
9008
|
+
return null;
|
|
9009
|
+
const out = [];
|
|
9010
|
+
for (const record of run.stdout.split("\x00")) {
|
|
9011
|
+
const match = /^(-|\d+)\t(-|\d+)\t(.+)$/su.exec(record.replace(/^\n/u, ""));
|
|
9012
|
+
if (!match)
|
|
9013
|
+
continue;
|
|
9014
|
+
const binary = match[1] === "-";
|
|
9015
|
+
out.push({
|
|
9016
|
+
path: match[3],
|
|
9017
|
+
added: binary ? 0 : Number(match[1]),
|
|
9018
|
+
deleted: binary ? 0 : Number(match[2]),
|
|
9019
|
+
binary
|
|
9020
|
+
});
|
|
9021
|
+
}
|
|
9022
|
+
return out;
|
|
9023
|
+
}
|
|
9024
|
+
function diffRaw(cwd, fork, head) {
|
|
9025
|
+
const range = laneRange(fork, head);
|
|
9026
|
+
if (!range)
|
|
9027
|
+
return null;
|
|
9028
|
+
const run = git(cwd, [...LANE_DIFF, "--raw", "-z", "--no-abbrev", ...range, "--"]);
|
|
9029
|
+
if (!run.ok)
|
|
9030
|
+
return null;
|
|
9031
|
+
const fields = run.stdout.split("\x00");
|
|
9032
|
+
const out = [];
|
|
9033
|
+
for (let index = 0;index + 1 < fields.length; index += 2) {
|
|
9034
|
+
const meta = /^:(\d{6}) (\d{6}) ([0-9a-f]+) ([0-9a-f]+) [A-Z]\d*$/u.exec(fields[index].replace(/^\n/u, ""));
|
|
9035
|
+
if (!meta)
|
|
9036
|
+
return null;
|
|
9037
|
+
out.push({
|
|
9038
|
+
path: fields[index + 1],
|
|
9039
|
+
oldMode: meta[1],
|
|
9040
|
+
newMode: meta[2],
|
|
9041
|
+
oldBlob: meta[3],
|
|
9042
|
+
newBlob: meta[4]
|
|
9043
|
+
});
|
|
9044
|
+
}
|
|
9045
|
+
return out;
|
|
9046
|
+
}
|
|
9047
|
+
function readBlobs(cwd, ids) {
|
|
9048
|
+
const out = new Map;
|
|
9049
|
+
const wanted = [...new Set(ids)].filter((id) => /^[0-9a-f]{40,64}$/u.test(id));
|
|
9050
|
+
if (!wanted.length)
|
|
9051
|
+
return out;
|
|
9052
|
+
const run = spawnSync3("git", ["cat-file", "--batch"], {
|
|
9053
|
+
cwd,
|
|
9054
|
+
input: `${wanted.join(`
|
|
9055
|
+
`)}
|
|
9056
|
+
`,
|
|
9057
|
+
maxBuffer: MAX_BUFFER,
|
|
9058
|
+
timeout: GIT_TIMEOUTS.local,
|
|
9059
|
+
killSignal: "SIGKILL",
|
|
9060
|
+
windowsHide: true
|
|
9061
|
+
});
|
|
9062
|
+
if (run.status !== 0 || !run.stdout)
|
|
9063
|
+
return null;
|
|
9064
|
+
const bytes = run.stdout;
|
|
9065
|
+
let at = 0;
|
|
9066
|
+
for (const id of wanted) {
|
|
9067
|
+
const end = bytes.indexOf(10, at);
|
|
9068
|
+
if (end < 0)
|
|
9069
|
+
return null;
|
|
9070
|
+
const header = bytes.subarray(at, end).toString("utf8").split(" ");
|
|
9071
|
+
at = end + 1;
|
|
9072
|
+
if (header[1] === "missing")
|
|
9073
|
+
continue;
|
|
9074
|
+
const size = Number(header[2]);
|
|
9075
|
+
if (header[0] !== id || !Number.isSafeInteger(size))
|
|
9076
|
+
return null;
|
|
9077
|
+
out.set(id, bytes.subarray(at, at + size));
|
|
9078
|
+
at += size + 1;
|
|
9079
|
+
}
|
|
9080
|
+
return out;
|
|
9081
|
+
}
|
|
9082
|
+
function treeFilesNamed(cwd, rev, name) {
|
|
9083
|
+
if (!safeArg(rev))
|
|
9084
|
+
return null;
|
|
9085
|
+
const run = git(cwd, ["ls-tree", "-r", "-z", "--full-tree", rev]);
|
|
9086
|
+
if (!run.ok)
|
|
9087
|
+
return null;
|
|
9088
|
+
const out = new Map;
|
|
9089
|
+
for (const record of run.stdout.split("\x00")) {
|
|
9090
|
+
const match = /^\d{6} blob ([0-9a-f]+)\t(.+)$/su.exec(record);
|
|
9091
|
+
if (match && (match[2] === name || match[2].endsWith(`/${name}`)))
|
|
9092
|
+
out.set(match[2], match[1]);
|
|
9093
|
+
}
|
|
9094
|
+
return out;
|
|
9095
|
+
}
|
|
9096
|
+
function fileAt(cwd, rev, file) {
|
|
9097
|
+
if (!safeArg(rev))
|
|
9098
|
+
return null;
|
|
9099
|
+
const run = git(cwd, ["cat-file", "blob", `${rev}:${file}`]);
|
|
9100
|
+
return run.ok ? run.stdout : null;
|
|
9101
|
+
}
|
|
9102
|
+
function untrackedFiles(cwd) {
|
|
9103
|
+
const run = git(cwd, ["ls-files", "-z", "--others", "--exclude-standard"]);
|
|
9104
|
+
return run.ok ? run.stdout.split("\x00").filter(Boolean) : [];
|
|
9105
|
+
}
|
|
8968
9106
|
var config2 = (cwd, key) => line(git(cwd, ["config", "--get", key]));
|
|
8969
9107
|
function currentBranch(cwd) {
|
|
8970
9108
|
return line(git(cwd, ["symbolic-ref", "--short", "-q", "HEAD"]));
|
|
@@ -12744,7 +12882,7 @@ var workerBlocksTransition = (transition, state) => transition === "pause" ? fal
|
|
|
12744
12882
|
import { createHash as createHash12 } from "node:crypto";
|
|
12745
12883
|
import * as fs14 from "node:fs";
|
|
12746
12884
|
import { spawnSync as spawnSync10 } from "node:child_process";
|
|
12747
|
-
import
|
|
12885
|
+
import path18 from "node:path";
|
|
12748
12886
|
|
|
12749
12887
|
// packages/workit-core/src/check-config.ts
|
|
12750
12888
|
import { spawnSync as spawnSync8 } from "node:child_process";
|
|
@@ -12974,45 +13112,786 @@ function checkCommand(args, version, onPath = workitAvailable()) {
|
|
|
12974
13112
|
}
|
|
12975
13113
|
|
|
12976
13114
|
// packages/workit-core/src/core/policy/derive.ts
|
|
12977
|
-
import { existsSync as
|
|
12978
|
-
import
|
|
13115
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
13116
|
+
import path17 from "node:path";
|
|
12979
13117
|
|
|
12980
13118
|
// packages/workit-core/src/ledger.ts
|
|
12981
13119
|
import { spawnSync as spawnSync9 } from "node:child_process";
|
|
12982
13120
|
import { createHash as createHash11, randomBytes as randomBytes3 } from "node:crypto";
|
|
12983
13121
|
import fs13 from "node:fs";
|
|
12984
|
-
import
|
|
13122
|
+
import path16 from "node:path";
|
|
13123
|
+
|
|
13124
|
+
// packages/workit-core/src/core/policy/lane.ts
|
|
13125
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4 } from "node:fs";
|
|
13126
|
+
import path14 from "node:path";
|
|
13127
|
+
|
|
13128
|
+
// packages/workit-core/src/git/default-base.ts
|
|
13129
|
+
var safeRef = (value) => value.length > 0 && !value.startsWith("-");
|
|
13130
|
+
var trackTrunk = (cwd) => {
|
|
13131
|
+
try {
|
|
13132
|
+
const resolved = vcsConfig("resolve", cwd);
|
|
13133
|
+
if (resolved.ok === false || !resolved.releaseTrack || resolved.releaseTrack.blocking)
|
|
13134
|
+
return null;
|
|
13135
|
+
const target = String(resolved.defaultTargetBranch ?? "").trim();
|
|
13136
|
+
return target && safeRef(target) ? target : null;
|
|
13137
|
+
} catch {
|
|
13138
|
+
return null;
|
|
13139
|
+
}
|
|
13140
|
+
};
|
|
13141
|
+
function defaultBase(cwd) {
|
|
13142
|
+
const trunk = trackTrunk(cwd);
|
|
13143
|
+
if (trunk) {
|
|
13144
|
+
for (const candidate of [`origin/${trunk}`, trunk])
|
|
13145
|
+
if (resolveRef(cwd, candidate))
|
|
13146
|
+
return candidate;
|
|
13147
|
+
}
|
|
13148
|
+
const originHead = originHeadRef(cwd);
|
|
13149
|
+
if (originHead && resolveRef(cwd, originHead))
|
|
13150
|
+
return originHead;
|
|
13151
|
+
for (const candidate of [
|
|
13152
|
+
"origin/main",
|
|
13153
|
+
"origin/master",
|
|
13154
|
+
"main",
|
|
13155
|
+
"master",
|
|
13156
|
+
"origin/develop",
|
|
13157
|
+
"develop"
|
|
13158
|
+
])
|
|
13159
|
+
if (resolveRef(cwd, candidate))
|
|
13160
|
+
return candidate;
|
|
13161
|
+
return null;
|
|
13162
|
+
}
|
|
13163
|
+
|
|
13164
|
+
// packages/workit-core/src/core/policy/attributes.ts
|
|
13165
|
+
function patternRegex(dir, pattern) {
|
|
13166
|
+
if (!pattern || pattern.endsWith("/") || pattern.startsWith('"') || pattern.startsWith("!"))
|
|
13167
|
+
return null;
|
|
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 {
|
|
13198
|
+
return null;
|
|
13199
|
+
}
|
|
13200
|
+
}
|
|
13201
|
+
function rulesOf(dir, text) {
|
|
13202
|
+
const out = [];
|
|
13203
|
+
for (const raw of text.split(`
|
|
13204
|
+
`)) {
|
|
13205
|
+
const line = raw.trim();
|
|
13206
|
+
if (!line || line.startsWith("#") || line.startsWith("[attr]"))
|
|
13207
|
+
continue;
|
|
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 });
|
|
13221
|
+
}
|
|
13222
|
+
return out;
|
|
13223
|
+
}
|
|
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;
|
|
13239
|
+
}
|
|
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
|
+
])
|
|
13264
|
+
]);
|
|
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() : "";
|
|
13270
|
+
};
|
|
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;
|
|
13322
|
+
}
|
|
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 += " ";
|
|
13328
|
+
}
|
|
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);
|
|
13342
|
+
}
|
|
13343
|
+
pendingSpace = false;
|
|
13344
|
+
if (out.length > lineStart) {
|
|
13345
|
+
out += `
|
|
13346
|
+
`;
|
|
13347
|
+
lineStart = out.length;
|
|
13348
|
+
}
|
|
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]))
|
|
13355
|
+
return true;
|
|
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;
|
|
13382
|
+
}
|
|
13383
|
+
return false;
|
|
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
|
+
}
|
|
13405
|
+
continue;
|
|
13406
|
+
}
|
|
13407
|
+
if (char === `
|
|
13408
|
+
`) {
|
|
13409
|
+
newline();
|
|
13410
|
+
index += 1;
|
|
13411
|
+
continue;
|
|
13412
|
+
}
|
|
13413
|
+
if (SPACE.test(char)) {
|
|
13414
|
+
if (collapse)
|
|
13415
|
+
pendingSpace = true;
|
|
13416
|
+
else
|
|
13417
|
+
out += char;
|
|
13418
|
+
index += 1;
|
|
13419
|
+
continue;
|
|
13420
|
+
}
|
|
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;
|
|
13497
|
+
}
|
|
13498
|
+
if (end >= text.length)
|
|
13499
|
+
return null;
|
|
13500
|
+
emitVerbatim(text.slice(index, end + 1));
|
|
13501
|
+
index = end + 1;
|
|
13502
|
+
continue;
|
|
13503
|
+
}
|
|
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;
|
|
13519
|
+
}
|
|
13520
|
+
if (frames.length !== 1)
|
|
13521
|
+
return null;
|
|
13522
|
+
newline();
|
|
13523
|
+
return out;
|
|
13524
|
+
}
|
|
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;
|
|
13534
|
+
}
|
|
13535
|
+
function hasOpaqueLineBreak(text) {
|
|
13536
|
+
return /\r(?!\n)|[\u2028\u2029]/u.test(text);
|
|
13537
|
+
}
|
|
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
|
+
}
|
|
13547
|
+
}
|
|
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);
|
|
13557
|
+
}
|
|
13558
|
+
|
|
13559
|
+
// packages/workit-core/src/core/policy/lane.ts
|
|
13560
|
+
var LANE_THRESHOLDS = {
|
|
13561
|
+
selfMaxLines: 100,
|
|
13562
|
+
selfMaxFiles: 5,
|
|
13563
|
+
fullMinLines: 500
|
|
13564
|
+
};
|
|
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"
|
|
13590
|
+
]);
|
|
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"
|
|
13597
|
+
]);
|
|
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, "\\$&");
|
|
13650
|
+
}
|
|
13651
|
+
regex = new RegExp(`^${source}$`, "u");
|
|
13652
|
+
globCache.set(pattern, regex);
|
|
13653
|
+
}
|
|
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 };
|
|
13723
|
+
}
|
|
13724
|
+
}
|
|
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;
|
|
13745
|
+
}
|
|
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);
|
|
13750
|
+
}
|
|
13751
|
+
return { cosmetic: out, opaque };
|
|
13752
|
+
}
|
|
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);
|
|
13765
|
+
}
|
|
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
|
+
};
|
|
13825
|
+
}
|
|
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
|
+
};
|
|
13836
|
+
}
|
|
13837
|
+
var laneNeed = (lane) => lane === "none" ? "none" : lane === "self" ? "self" : "non-author";
|
|
13838
|
+
|
|
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
|
+
}
|
|
12985
13864
|
|
|
12986
13865
|
// packages/workit-core/src/hooks/standing-signal.ts
|
|
12987
13866
|
import { createHash as createHash10 } from "node:crypto";
|
|
12988
13867
|
import fs12 from "node:fs";
|
|
12989
|
-
import
|
|
13868
|
+
import path15 from "node:path";
|
|
12990
13869
|
var gitStoreDir = (cwd) => {
|
|
12991
13870
|
try {
|
|
12992
|
-
for (let dir =
|
|
12993
|
-
const dotGit =
|
|
13871
|
+
for (let dir = path15.resolve(cwd);; dir = path15.dirname(dir)) {
|
|
13872
|
+
const dotGit = path15.join(dir, ".git");
|
|
12994
13873
|
if (fs12.existsSync(dotGit)) {
|
|
12995
13874
|
let gitDir = dotGit;
|
|
12996
13875
|
if (fs12.statSync(dotGit).isFile()) {
|
|
12997
13876
|
const target = /^gitdir:\s*(.+)$/mu.exec(fs12.readFileSync(dotGit, "utf8"))?.[1]?.trim();
|
|
12998
13877
|
if (!target)
|
|
12999
13878
|
return null;
|
|
13000
|
-
gitDir =
|
|
13001
|
-
const common =
|
|
13879
|
+
gitDir = path15.resolve(dir, target);
|
|
13880
|
+
const common = path15.join(gitDir, "commondir");
|
|
13002
13881
|
if (fs12.existsSync(common))
|
|
13003
|
-
gitDir =
|
|
13882
|
+
gitDir = path15.resolve(gitDir, fs12.readFileSync(common, "utf8").trim());
|
|
13004
13883
|
}
|
|
13005
|
-
return
|
|
13884
|
+
return path15.join(gitDir, "workit");
|
|
13006
13885
|
}
|
|
13007
|
-
if (
|
|
13886
|
+
if (path15.dirname(dir) === dir)
|
|
13008
13887
|
return null;
|
|
13009
13888
|
}
|
|
13010
13889
|
} catch {
|
|
13011
13890
|
return null;
|
|
13012
13891
|
}
|
|
13013
13892
|
};
|
|
13014
|
-
var standingRevFile = (store) =>
|
|
13015
|
-
var standingSeenFile = (store, session, agentId) =>
|
|
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`);
|
|
13016
13895
|
var readStandingRev = (store) => {
|
|
13017
13896
|
try {
|
|
13018
13897
|
return fs12.readFileSync(standingRevFile(store), "utf8").trim() || null;
|
|
@@ -13033,7 +13912,7 @@ var readStandingSeen = (file) => {
|
|
|
13033
13912
|
};
|
|
13034
13913
|
var writeStandingSeen = (file, seen) => {
|
|
13035
13914
|
try {
|
|
13036
|
-
fs12.mkdirSync(
|
|
13915
|
+
fs12.mkdirSync(path15.dirname(file), { recursive: true });
|
|
13037
13916
|
fs12.writeFileSync(file, JSON.stringify(seen));
|
|
13038
13917
|
} catch {}
|
|
13039
13918
|
};
|
|
@@ -13083,52 +13962,19 @@ var git4 = (cwd, args) => {
|
|
|
13083
13962
|
const value = run.ok ? run.stdout.trim() : "";
|
|
13084
13963
|
return value ? value : null;
|
|
13085
13964
|
};
|
|
13086
|
-
var
|
|
13965
|
+
var safeRef2 = (value) => value.length > 0 && !value.startsWith("-");
|
|
13087
13966
|
function resolveCommit(cwd, ref) {
|
|
13088
|
-
if (!
|
|
13967
|
+
if (!safeRef2(ref))
|
|
13089
13968
|
return null;
|
|
13090
13969
|
return git4(cwd, ["rev-parse", "--verify", "-q", `${ref}^{commit}`]);
|
|
13091
13970
|
}
|
|
13092
13971
|
function branchHead(cwd, branch) {
|
|
13093
|
-
if (!
|
|
13972
|
+
if (!safeRef2(branch))
|
|
13094
13973
|
return null;
|
|
13095
13974
|
return resolveCommit(cwd, `refs/heads/${branch}`) ?? resolveCommit(cwd, `refs/remotes/origin/${branch}`);
|
|
13096
13975
|
}
|
|
13097
|
-
var trackTrunk = (cwd) => {
|
|
13098
|
-
try {
|
|
13099
|
-
const resolved = vcsConfig("resolve", cwd);
|
|
13100
|
-
if (resolved.ok === false || !resolved.releaseTrack || resolved.releaseTrack.blocking)
|
|
13101
|
-
return null;
|
|
13102
|
-
const target = String(resolved.defaultTargetBranch ?? "").trim();
|
|
13103
|
-
return target && safeRef(target) ? target : null;
|
|
13104
|
-
} catch {
|
|
13105
|
-
return null;
|
|
13106
|
-
}
|
|
13107
|
-
};
|
|
13108
|
-
function defaultBase(cwd) {
|
|
13109
|
-
const trunk = trackTrunk(cwd);
|
|
13110
|
-
if (trunk) {
|
|
13111
|
-
for (const candidate of [`origin/${trunk}`, trunk])
|
|
13112
|
-
if (resolveCommit(cwd, candidate))
|
|
13113
|
-
return candidate;
|
|
13114
|
-
}
|
|
13115
|
-
const originHead = git4(cwd, ["symbolic-ref", "-q", "--short", "refs/remotes/origin/HEAD"]);
|
|
13116
|
-
if (originHead && resolveCommit(cwd, originHead))
|
|
13117
|
-
return originHead;
|
|
13118
|
-
for (const candidate of [
|
|
13119
|
-
"origin/main",
|
|
13120
|
-
"origin/master",
|
|
13121
|
-
"main",
|
|
13122
|
-
"master",
|
|
13123
|
-
"origin/develop",
|
|
13124
|
-
"develop"
|
|
13125
|
-
])
|
|
13126
|
-
if (resolveCommit(cwd, candidate))
|
|
13127
|
-
return candidate;
|
|
13128
|
-
return null;
|
|
13129
|
-
}
|
|
13130
13976
|
function diffHash(cwd, base, head) {
|
|
13131
|
-
if (!
|
|
13977
|
+
if (!safeRef2(base) || !safeRef2(head))
|
|
13132
13978
|
return null;
|
|
13133
13979
|
const diff = gitRun2(cwd, [
|
|
13134
13980
|
"diff",
|
|
@@ -13160,14 +14006,14 @@ function diffHash(cwd, base, head) {
|
|
|
13160
14006
|
return `sha256:${hash.digest("hex")}`;
|
|
13161
14007
|
}
|
|
13162
14008
|
function storeRoot(cwd) {
|
|
13163
|
-
const location = resolveStore(
|
|
14009
|
+
const location = resolveStore(path16.resolve(cwd));
|
|
13164
14010
|
if (location instanceof Error)
|
|
13165
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");
|
|
13166
14012
|
return { ok: true, value: { root: location.dir, shared: location.shared } };
|
|
13167
14013
|
}
|
|
13168
14014
|
function ledgerPath(cwd) {
|
|
13169
14015
|
const root = storeRoot(cwd);
|
|
13170
|
-
return root.ok ? { ok: true, value:
|
|
14016
|
+
return root.ok ? { ok: true, value: path16.join(root.value.root, "ledger", "ledger.jsonl") } : root;
|
|
13171
14017
|
}
|
|
13172
14018
|
var newId2 = () => `${Date.now().toString(36)}-${randomBytes3(6).toString("hex")}`;
|
|
13173
14019
|
var NETWORK_FS_TYPES = new Set([
|
|
@@ -13197,13 +14043,13 @@ var LOCK_STALE_MS = 30000;
|
|
|
13197
14043
|
var LOCK_OWNER = "owner";
|
|
13198
14044
|
var lockToken = (lock) => {
|
|
13199
14045
|
try {
|
|
13200
|
-
return fs13.readFileSync(
|
|
14046
|
+
return fs13.readFileSync(path16.join(lock, LOCK_OWNER), "utf8");
|
|
13201
14047
|
} catch {
|
|
13202
14048
|
return null;
|
|
13203
14049
|
}
|
|
13204
14050
|
};
|
|
13205
14051
|
var lockAgeMs = (lock) => {
|
|
13206
|
-
for (const target of [
|
|
14052
|
+
for (const target of [path16.join(lock, LOCK_OWNER), lock]) {
|
|
13207
14053
|
try {
|
|
13208
14054
|
return Date.now() - fs13.statSync(target).mtimeMs;
|
|
13209
14055
|
} catch {}
|
|
@@ -13220,7 +14066,7 @@ var ledgerLock = {
|
|
|
13220
14066
|
throw error;
|
|
13221
14067
|
}
|
|
13222
14068
|
const token = `${process.pid}-${randomBytes3(8).toString("hex")}`;
|
|
13223
|
-
fs13.writeFileSync(
|
|
14069
|
+
fs13.writeFileSync(path16.join(lock, LOCK_OWNER), token);
|
|
13224
14070
|
return token;
|
|
13225
14071
|
},
|
|
13226
14072
|
release(lock, token) {
|
|
@@ -13353,8 +14199,8 @@ function appendLine(file, build, options) {
|
|
|
13353
14199
|
}
|
|
13354
14200
|
};
|
|
13355
14201
|
try {
|
|
13356
|
-
fs13.mkdirSync(
|
|
13357
|
-
if (!options.forceLock && !onNetworkFs(
|
|
14202
|
+
fs13.mkdirSync(path16.dirname(file), { recursive: true });
|
|
14203
|
+
if (!options.forceLock && !onNetworkFs(path16.dirname(file)))
|
|
13358
14204
|
return write();
|
|
13359
14205
|
const locked = withLedgerLock(file, write);
|
|
13360
14206
|
return locked ?? err("busy", `ledger lock ${file}.lock is held`, "retry; remove the .lock directory if no workit process is running");
|
|
@@ -13588,7 +14434,7 @@ function verdictBasis(verdict, current) {
|
|
|
13588
14434
|
}
|
|
13589
14435
|
var isLiveVerdict = (row, retired) => row.type === "verdict" && typeof row.result === "string" && !retired.has(row.id);
|
|
13590
14436
|
function trailerSessions(cwd, base, head) {
|
|
13591
|
-
if (!base || !head || !
|
|
14437
|
+
if (!base || !head || !safeRef2(base) || !safeRef2(head))
|
|
13592
14438
|
return [];
|
|
13593
14439
|
const out = git4(cwd, [
|
|
13594
14440
|
"log",
|
|
@@ -13601,7 +14447,7 @@ var RENAMED = /^branch: renamed refs\/heads\/(.+) to refs\/heads\/(.+)$/iu;
|
|
|
13601
14447
|
function branchScope(cwd, branch) {
|
|
13602
14448
|
const renamedAt = new Map;
|
|
13603
14449
|
let since = Number.NEGATIVE_INFINITY;
|
|
13604
|
-
if (
|
|
14450
|
+
if (safeRef2(branch)) {
|
|
13605
14451
|
const log = git4(cwd, [
|
|
13606
14452
|
"log",
|
|
13607
14453
|
"-g",
|
|
@@ -13746,12 +14592,29 @@ function effectiveOf(rows, reasonsOf, sameCode, isCurrent) {
|
|
|
13746
14592
|
}
|
|
13747
14593
|
return effective;
|
|
13748
14594
|
}
|
|
13749
|
-
|
|
14595
|
+
var restackExact = new WeakMap;
|
|
14596
|
+
function restackCarries(exactIn, rows, branch, from, to) {
|
|
13750
14597
|
if (!from || !to)
|
|
13751
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
|
+
};
|
|
13752
14615
|
const edges = new Map;
|
|
13753
14616
|
for (const row of rows)
|
|
13754
|
-
if (row.type === "stack.restacked" && row.observer === "workit_cli" && row.integrity === "verified" && row.branch === branch && row.patchEqual === true && typeof row.fromHead === "string" && row.head)
|
|
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))
|
|
13755
14618
|
edges.set(row.fromHead, [...edges.get(row.fromHead) ?? [], row.head]);
|
|
13756
14619
|
const seen = new Set([from]);
|
|
13757
14620
|
const queue = [from];
|
|
@@ -13785,9 +14648,57 @@ function checkVerdicts(cwd, branch, ledgerRows) {
|
|
|
13785
14648
|
return key;
|
|
13786
14649
|
};
|
|
13787
14650
|
const keyFor = (base) => keysAt(base, head);
|
|
13788
|
-
const
|
|
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) => {
|
|
13789
14685
|
const basis = verdictBasis(row, { head, ...keyFor(row.base ?? fallbackBase) });
|
|
13790
|
-
return basis === "
|
|
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;
|
|
13791
14702
|
};
|
|
13792
14703
|
const sameCode = (failed, row) => {
|
|
13793
14704
|
if (failed.head && failed.head === row.head)
|
|
@@ -13795,7 +14706,7 @@ function checkVerdicts(cwd, branch, ledgerRows) {
|
|
|
13795
14706
|
const base = failed.base ?? fallbackBase;
|
|
13796
14707
|
const stored = (row.base ?? fallbackBase) === base && row.baseSha !== null && row.baseSha === failed.baseSha;
|
|
13797
14708
|
const keys = stored ? { patchId: row.patchId, diffHash: row.diffHash } : keysAt(base, row.head);
|
|
13798
|
-
return verdictBasis(failed, { head: row.head, ...keys }) !== "stale" || restackCarries(rows, branch, failed.head, row.head);
|
|
14709
|
+
return verdictBasis(failed, { head: row.head, ...keys }) !== "stale" || restackCarries(null, rows, branch, failed.head, row.head);
|
|
13799
14710
|
};
|
|
13800
14711
|
const authors = authorSessions(cwd, rows, branch, { base: fallbackBase, head }, scope);
|
|
13801
14712
|
const failureAuthorCache = new Map;
|
|
@@ -13851,6 +14762,7 @@ function checkVerdicts(cwd, branch, ledgerRows) {
|
|
|
13851
14762
|
independent: independence.length === 0,
|
|
13852
14763
|
accepted: reasons.length === 0,
|
|
13853
14764
|
reasons,
|
|
14765
|
+
carry: basis === "carried" ? carryOf(row) : null,
|
|
13854
14766
|
verdict: row
|
|
13855
14767
|
});
|
|
13856
14768
|
if (reasons.length === 0) {
|
|
@@ -13879,7 +14791,12 @@ function checkVerdicts(cwd, branch, ledgerRows) {
|
|
|
13879
14791
|
verdict: failing?.verdict ?? null,
|
|
13880
14792
|
reasons: failing ? ["failing_verdict"] : !newestCurrent ? ["no_verdict"] : newestCurrent.reasons.length ? newestCurrent.reasons : ["type_check_only"]
|
|
13881
14793
|
};
|
|
13882
|
-
const
|
|
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;
|
|
13883
14800
|
return {
|
|
13884
14801
|
branch,
|
|
13885
14802
|
head,
|
|
@@ -13888,6 +14805,8 @@ function checkVerdicts(cwd, branch, ledgerRows) {
|
|
|
13888
14805
|
base: newestCurrent?.verdict.base ?? fallbackBase,
|
|
13889
14806
|
patchId: keyFor(newestCurrent?.verdict.base ?? fallbackBase).patchId,
|
|
13890
14807
|
current: { basis: newestCurrent?.basis ?? "none", verdict: newestCurrent?.verdict ?? null },
|
|
14808
|
+
carry: carryEntry?.carry ?? null,
|
|
14809
|
+
reverify,
|
|
13891
14810
|
accepted,
|
|
13892
14811
|
verdicts,
|
|
13893
14812
|
authors: [...authors].toSorted(),
|
|
@@ -13929,6 +14848,7 @@ var RULES = {
|
|
|
13929
14848
|
self: "verdict:self",
|
|
13930
14849
|
nonAuthor: "verdict:non-author",
|
|
13931
14850
|
verified: "verdict:verified",
|
|
14851
|
+
lane: "verdict:lane",
|
|
13932
14852
|
product: "decision:product",
|
|
13933
14853
|
plan: "plan"
|
|
13934
14854
|
};
|
|
@@ -13951,6 +14871,7 @@ var normalizedScope = (scope) => ({
|
|
|
13951
14871
|
function deriveRequirements(judgment, scope, constraints, verification = "self") {
|
|
13952
14872
|
const out = [];
|
|
13953
14873
|
const base = { scope, dependentAction: null };
|
|
14874
|
+
const proportional = verification === "proportional";
|
|
13954
14875
|
if (judgment.behaviorChange)
|
|
13955
14876
|
out.push(derived2({
|
|
13956
14877
|
...base,
|
|
@@ -13961,7 +14882,17 @@ function deriveRequirements(judgment, scope, constraints, verification = "self")
|
|
|
13961
14882
|
before: "close",
|
|
13962
14883
|
acceptanceAllowed: false
|
|
13963
14884
|
}));
|
|
13964
|
-
if (
|
|
14885
|
+
if (proportional && judgment.riskTier === "normal")
|
|
14886
|
+
out.push(derived2({
|
|
14887
|
+
...base,
|
|
14888
|
+
ruleId: RULES.lane,
|
|
14889
|
+
dimension: "review",
|
|
14890
|
+
reason: "The review lane computed from the diff decides the verdict: none needs none, self the author's own (shown as self-reviewed), focused and full a non-author one.",
|
|
14891
|
+
satisfaction: "Read the lane in `workit policy assess` or `workit pr status`; record what it asks: `workit ledger verdict tests-verified --self --how <what you ran>` for self, or hand focused and full to a verifier.",
|
|
14892
|
+
before: "close",
|
|
14893
|
+
acceptanceAllowed: false
|
|
14894
|
+
}));
|
|
14895
|
+
if (!proportional && judgment.behaviorChange && judgment.riskTier === "normal")
|
|
13965
14896
|
out.push(verification === "independent" ? derived2({
|
|
13966
14897
|
...base,
|
|
13967
14898
|
ruleId: RULES.nonAuthor,
|
|
@@ -14064,7 +14995,7 @@ var productDecision = (task, ledger) => {
|
|
|
14064
14995
|
};
|
|
14065
14996
|
};
|
|
14066
14997
|
var planRecorded = (task, root) => {
|
|
14067
|
-
const cited = (latestJudgment(task)?.refs ?? []).find((ref) => ref.kind === "external" || ref.kind === "file" &&
|
|
14998
|
+
const cited = (latestJudgment(task)?.refs ?? []).find((ref) => ref.kind === "external" || ref.kind === "file" && existsSync5(path17.resolve(root, ref.path)));
|
|
14068
14999
|
if (cited)
|
|
14069
15000
|
return {
|
|
14070
15001
|
met: true,
|
|
@@ -14078,14 +15009,30 @@ var planRecorded = (task, root) => {
|
|
|
14078
15009
|
decisionIds: []
|
|
14079
15010
|
};
|
|
14080
15011
|
};
|
|
14081
|
-
var
|
|
15012
|
+
var workingLane = (task, root) => branchLane(root, { head: null, workingTree: true, judgment: latestJudgment(task) });
|
|
15013
|
+
var laneVerdict = (task, root, ledger) => {
|
|
15014
|
+
if (!ledger.branch() && latestJudgment(task)?.behaviorChange === false)
|
|
15015
|
+
return {
|
|
15016
|
+
met: true,
|
|
15017
|
+
reason: "no branch to read a lane from, and the judgment records no behavior change",
|
|
15018
|
+
decisionIds: []
|
|
15019
|
+
};
|
|
15020
|
+
const lane = workingLane(task, root);
|
|
15021
|
+
const label = `lane ${lane.lane} (${lane.reasons.join("; ")})`;
|
|
15022
|
+
const need = laneNeed(lane.lane);
|
|
15023
|
+
if (need === "none")
|
|
15024
|
+
return { met: true, reason: `${label}: no verdict needed`, decisionIds: [] };
|
|
15025
|
+
const status = verdict(need === "self" ? RULES.self : RULES.nonAuthor, root, ledger);
|
|
15026
|
+
return { ...status, reason: `${label}: ${status.reason}` };
|
|
15027
|
+
};
|
|
15028
|
+
var verdict = (ruleId, root, ledger) => {
|
|
14082
15029
|
const branch = ledger.branch();
|
|
14083
15030
|
if (!branch)
|
|
14084
15031
|
return { met: false, reason: "no branch is checked out to hold a verdict", decisionIds: [] };
|
|
14085
15032
|
const check = checkVerdicts(root, branch, ledger.rows());
|
|
14086
15033
|
const failing = check.accepted.reasons.includes("failing_verdict");
|
|
14087
15034
|
const strong = (entry) => STRONG_RESULTS.has(String(entry.verdict.result));
|
|
14088
|
-
const met = !failing && (
|
|
15035
|
+
const met = !failing && (ruleId === RULES.verified ? check.verdicts.find((entry) => entry.accepted && entry.kind === "live" && String(entry.verdict.result) === "verified") : ruleId === RULES.nonAuthor ? check.verdicts.find((entry) => entry.accepted && strong(entry)) : check.verdicts.find((entry) => entry.current && strong(entry)));
|
|
14089
15036
|
if (met)
|
|
14090
15037
|
return {
|
|
14091
15038
|
met: true,
|
|
@@ -14093,7 +15040,7 @@ var verdict = (requirement, root, ledger) => {
|
|
|
14093
15040
|
decisionIds: []
|
|
14094
15041
|
};
|
|
14095
15042
|
const why = failing ? "a current independent verdict fails" : check.verdicts.some((entry) => entry.current && entry.verdict.result === "type-check-only") ? "type-check-only does not prove a behavior change" : (check.accepted.reasons.join(", ") || "no_verdict").replace(/_/g, " ");
|
|
14096
|
-
const unblock =
|
|
15043
|
+
const unblock = ruleId === RULES.self ? "record your own verdict after an observed `workit check test`: `workit ledger verdict tests-verified --self --how <what you ran>` (shown as self-reviewed), or have a verifier record one" : `an independent session records \`workit ledger verdict verified${ruleId === RULES.verified ? " --kind live" : ""} --how <what it exercised>\``;
|
|
14097
15044
|
return { met: false, reason: `${why}: ${unblock}`, decisionIds: [] };
|
|
14098
15045
|
};
|
|
14099
15046
|
function derivedRuleStatus(task, requirement, root, ledger) {
|
|
@@ -14105,7 +15052,9 @@ function derivedRuleStatus(task, requirement, root, ledger) {
|
|
|
14105
15052
|
case RULES.self:
|
|
14106
15053
|
case RULES.nonAuthor:
|
|
14107
15054
|
case RULES.verified:
|
|
14108
|
-
return verdict(requirement, root, ledger);
|
|
15055
|
+
return verdict(requirement.ruleId, root, ledger);
|
|
15056
|
+
case RULES.lane:
|
|
15057
|
+
return laneVerdict(task, root, ledger);
|
|
14109
15058
|
default:
|
|
14110
15059
|
return null;
|
|
14111
15060
|
}
|
|
@@ -14124,14 +15073,14 @@ function writeBlockers(task, root) {
|
|
|
14124
15073
|
|
|
14125
15074
|
// packages/workit-core/src/core/task-evaluation.ts
|
|
14126
15075
|
var digestBytes = (value) => createHash12("sha256").update(value).digest("hex");
|
|
14127
|
-
var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${
|
|
14128
|
-
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("/") || ".";
|
|
14129
15078
|
var canonicalPath = (value) => {
|
|
14130
15079
|
if (typeof value !== "string" || !value)
|
|
14131
15080
|
return null;
|
|
14132
15081
|
if (value.includes("\\") || value.split("/").includes(".."))
|
|
14133
15082
|
return null;
|
|
14134
|
-
const normalized =
|
|
15083
|
+
const normalized = path18.posix.normalize(value);
|
|
14135
15084
|
if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
|
|
14136
15085
|
return null;
|
|
14137
15086
|
return normalized === "." ? "." : normalized.replace(/\/$/, "");
|
|
@@ -14179,14 +15128,14 @@ var gitPaths = (root) => {
|
|
|
14179
15128
|
git: !/not a git repository/i.test(stderr)
|
|
14180
15129
|
};
|
|
14181
15130
|
}
|
|
14182
|
-
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(
|
|
15131
|
+
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path18.sep).join("/")) : [];
|
|
14183
15132
|
const stagedDeleted = spawnSync10("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
|
|
14184
15133
|
if (stagedDeleted.status !== 0) {
|
|
14185
15134
|
const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
|
|
14186
15135
|
if (!/not a git repository/i.test(stderr))
|
|
14187
15136
|
return { paths, uncertain: true, git: true };
|
|
14188
15137
|
} else if (stagedDeleted.stdout) {
|
|
14189
|
-
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("/")));
|
|
14190
15139
|
}
|
|
14191
15140
|
return { paths: [...new Set(paths)], uncertain: false, git: true };
|
|
14192
15141
|
};
|
|
@@ -14201,7 +15150,7 @@ var walk = (root, directory, output) => {
|
|
|
14201
15150
|
for (const name of names) {
|
|
14202
15151
|
if (name === ".git" || name === ".workit")
|
|
14203
15152
|
continue;
|
|
14204
|
-
const target =
|
|
15153
|
+
const target = path18.join(directory, name);
|
|
14205
15154
|
let stat;
|
|
14206
15155
|
try {
|
|
14207
15156
|
stat = fs14.lstatSync(target);
|
|
@@ -14219,12 +15168,12 @@ var walk = (root, directory, output) => {
|
|
|
14219
15168
|
var scopeRoots = (root, scope) => {
|
|
14220
15169
|
const roots = [];
|
|
14221
15170
|
for (const item of scope.paths.length ? scope.paths : ["."]) {
|
|
14222
|
-
const target =
|
|
15171
|
+
const target = path18.resolve(root, item);
|
|
14223
15172
|
if (!inside(root, target))
|
|
14224
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` }] });
|
|
14225
15174
|
let ancestor = target;
|
|
14226
15175
|
while (!fs14.existsSync(ancestor) && ancestor !== root)
|
|
14227
|
-
ancestor =
|
|
15176
|
+
ancestor = path18.dirname(ancestor);
|
|
14228
15177
|
try {
|
|
14229
15178
|
if (!inside(root, fs14.realpathSync(ancestor)))
|
|
14230
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` }] });
|
|
@@ -14276,7 +15225,7 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
14276
15225
|
for (const item of [...names].toSorted()) {
|
|
14277
15226
|
if (!scopeMatches(item, normalizedScope))
|
|
14278
15227
|
continue;
|
|
14279
|
-
const target =
|
|
15228
|
+
const target = path18.join(checkout, item);
|
|
14280
15229
|
try {
|
|
14281
15230
|
const stat = fs14.lstatSync(target);
|
|
14282
15231
|
if (stat.isSymbolicLink()) {
|
|
@@ -14460,23 +15409,23 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
|
14460
15409
|
continue;
|
|
14461
15410
|
if (!reference.digest)
|
|
14462
15411
|
return failure2("invalid_input", "document references require a byte digest");
|
|
14463
|
-
const target =
|
|
14464
|
-
if (target !== root && !target.startsWith(`${root}${
|
|
15412
|
+
const target = path18.resolve(root, reference.path);
|
|
15413
|
+
if (target !== root && !target.startsWith(`${root}${path18.sep}`))
|
|
14465
15414
|
return failure2("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
|
|
14466
15415
|
fields: [
|
|
14467
15416
|
{ path: "contentRefs.path", reason: `${reference.path} is outside the checkout` }
|
|
14468
15417
|
]
|
|
14469
15418
|
});
|
|
14470
15419
|
try {
|
|
14471
|
-
const below =
|
|
15420
|
+
const below = path18.relative(root, target);
|
|
14472
15421
|
let current = root;
|
|
14473
|
-
for (const segment of below.split(
|
|
15422
|
+
for (const segment of below.split(path18.sep)) {
|
|
14474
15423
|
if (!segment)
|
|
14475
15424
|
continue;
|
|
14476
|
-
current =
|
|
15425
|
+
current = path18.join(current, segment);
|
|
14477
15426
|
const stat = fs14.lstatSync(current);
|
|
14478
15427
|
const real = fs14.realpathSync(current);
|
|
14479
|
-
if (real !== root && !real.startsWith(`${root}${
|
|
15428
|
+
if (real !== root && !real.startsWith(`${root}${path18.sep}`))
|
|
14480
15429
|
return failure2("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
|
|
14481
15430
|
fields: [
|
|
14482
15431
|
{
|
|
@@ -14913,9 +15862,9 @@ Autonomy contract:
|
|
|
14913
15862
|
Prose is a note: only a configured check the CLI ran (\`workit check <name>\`)
|
|
14914
15863
|
satisfies a gate; an ad-hoc \`workit check -- <cmd>\` never does.
|
|
14915
15864
|
- Author is not verifier: your own verdict is \`--self\`, shown as
|
|
14916
|
-
self-reviewed, never verified. High risk,
|
|
14917
|
-
merge need a verifier with its
|
|
14918
|
-
\`<lead>:<child>\`); never hand-mint one (\`<lead>-v1\`).
|
|
15865
|
+
self-reviewed, never verified. High risk, a focused or full lane and
|
|
15866
|
+
merge under \`verification: self|independent\` need a verifier with its
|
|
15867
|
+
own host-derived session (a subagent's \`<lead>:<child>\`); never hand-mint one (\`<lead>-v1\`).
|
|
14919
15868
|
- A local-commit endpoint does not imply PR readiness, and a local commit alone
|
|
14920
15869
|
is not evidence of a requested remote push. Before reporting delivery,
|
|
14921
15870
|
reconcile every requested item and observe it (\`workit verify-delivery\`).
|
|
@@ -14923,7 +15872,7 @@ Autonomy contract:
|
|
|
14923
15872
|
CLI first: if a step has one right answer, use the \`workit\` verb instead of
|
|
14924
15873
|
hand-running git, gh or glab (\`workit git|pr|ci|check|help\`;
|
|
14925
15874
|
else \`npx -y @brainervirus/workit-cli\`).
|
|
14926
|
-
Other checkout: \`workit --cwd <dir>
|
|
15875
|
+
Other checkout: \`workit <verb> --cwd <dir> …\` (verb first: host rules match it); one workit
|
|
14927
15876
|
verb per shell call, never \`cd <dir> && workit\`. zsh-safe: no \`echo =====\`
|
|
14928
15877
|
(\`printf '%s\\n' ---\`), quote globs and URLs, no variable named \`status\` or \`path\`.
|
|
14929
15878
|
Add Co-Authored-By via \`workit git commit --trailer "K: v" -- <paths>\`, never
|
|
@@ -14935,7 +15884,7 @@ authorizes an effect. Judge work once with
|
|
|
14935
15884
|
\`workit policy assess --judge risk= behavior= product-choice= plan=\`.
|
|
14936
15885
|
Across repositories, bind items to checkout, branch and endpoint, resolve
|
|
14937
15886
|
competing targets before a mutation, and reconcile an uncertain
|
|
14938
|
-
|
|
15887
|
+
effect before a retry.
|
|
14939
15888
|
|
|
14940
15889
|
Mid-task: never silently drop or resume an objective (workit-continue).
|
|
14941
15890
|
|
|
@@ -15078,20 +16027,20 @@ var exportDigest = (bundle) => sha256({
|
|
|
15078
16027
|
});
|
|
15079
16028
|
// packages/workit-core/src/core/route-intent.ts
|
|
15080
16029
|
import { spawnSync as spawnSync12 } from "node:child_process";
|
|
15081
|
-
import { existsSync as
|
|
15082
|
-
import
|
|
16030
|
+
import { existsSync as existsSync7 } from "node:fs";
|
|
16031
|
+
import path21 from "node:path";
|
|
15083
16032
|
|
|
15084
16033
|
// packages/workit-core/src/hooks/raw-git.ts
|
|
15085
16034
|
import { spawnSync as spawnSync11 } from "node:child_process";
|
|
15086
16035
|
import { createHash as createHash14 } from "node:crypto";
|
|
15087
16036
|
import fs16 from "node:fs";
|
|
15088
16037
|
import os4 from "node:os";
|
|
15089
|
-
import
|
|
16038
|
+
import path20 from "node:path";
|
|
15090
16039
|
|
|
15091
16040
|
// packages/workit-core/src/hooks/session-marker.ts
|
|
15092
16041
|
import { createHash as createHash13 } from "node:crypto";
|
|
15093
16042
|
import fs15 from "node:fs";
|
|
15094
|
-
import
|
|
16043
|
+
import path19 from "node:path";
|
|
15095
16044
|
var sessionMarkerFile = (input, session, store) => {
|
|
15096
16045
|
let dir = store;
|
|
15097
16046
|
if (dir === undefined) {
|
|
@@ -15101,7 +16050,7 @@ var sessionMarkerFile = (input, session, store) => {
|
|
|
15101
16050
|
dir = location.dir;
|
|
15102
16051
|
}
|
|
15103
16052
|
const name = createHash13("sha256").update(`${input.host}\x00${session}`).digest("hex").slice(0, 32);
|
|
15104
|
-
return
|
|
16053
|
+
return path19.join(dir, "hooks", `skills-${name}.json`);
|
|
15105
16054
|
};
|
|
15106
16055
|
var list2 = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
15107
16056
|
var EMPTY = { nudged: [], loaded: [], raw: [], stops: [], judges: [] };
|
|
@@ -15128,7 +16077,7 @@ var parseMarker = (text) => {
|
|
|
15128
16077
|
};
|
|
15129
16078
|
var readSessionMarker = (file) => parseMarker(readRaw(file));
|
|
15130
16079
|
var writeAtomic = (file, marker) => {
|
|
15131
|
-
fs15.mkdirSync(
|
|
16080
|
+
fs15.mkdirSync(path19.dirname(file), { recursive: true });
|
|
15132
16081
|
const temp = `${file}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
15133
16082
|
try {
|
|
15134
16083
|
fs15.writeFileSync(temp, JSON.stringify(marker));
|
|
@@ -15415,6 +16364,18 @@ var substitutions = (body) => {
|
|
|
15415
16364
|
};
|
|
15416
16365
|
var PLAIN_POSIX = /[^\s'"\\`$(){},<>|;&#]+/uy;
|
|
15417
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;
|
|
15418
16379
|
var arithmeticEnd = (command, start) => {
|
|
15419
16380
|
const end = substitutionEnd(command, start);
|
|
15420
16381
|
return command[end - 1] === ")" && end > start + 2 ? end : 0;
|
|
@@ -15433,6 +16394,7 @@ var tokenize = (command, dialect) => {
|
|
|
15433
16394
|
let current = { words: [], redirects: [] };
|
|
15434
16395
|
let word = "";
|
|
15435
16396
|
let inWord = false;
|
|
16397
|
+
let expanding = false;
|
|
15436
16398
|
let pending = null;
|
|
15437
16399
|
const heredocs = [];
|
|
15438
16400
|
const escape = posix ? "\\" : "`";
|
|
@@ -15447,11 +16409,16 @@ var tokenize = (command, dialect) => {
|
|
|
15447
16409
|
heredocs.push({ delimiter: text.replace(/^-/, ""), owner: current });
|
|
15448
16410
|
else if (pending === "herestring")
|
|
15449
16411
|
(current.stdin ??= []).push(text);
|
|
15450
|
-
else if (pending !== "input")
|
|
16412
|
+
else if (pending !== "input") {
|
|
16413
|
+
const at = current.words.length;
|
|
15451
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
|
+
}
|
|
15452
16418
|
pending = null;
|
|
15453
16419
|
word = "";
|
|
15454
16420
|
inWord = false;
|
|
16421
|
+
expanding = false;
|
|
15455
16422
|
};
|
|
15456
16423
|
const flushSegment = () => {
|
|
15457
16424
|
flushWord();
|
|
@@ -15466,6 +16433,7 @@ var tokenize = (command, dialect) => {
|
|
|
15466
16433
|
};
|
|
15467
16434
|
const substitution = (inner, text) => {
|
|
15468
16435
|
(current.subs ??= []).push(inner);
|
|
16436
|
+
expanding = true;
|
|
15469
16437
|
word += text;
|
|
15470
16438
|
inWord = true;
|
|
15471
16439
|
};
|
|
@@ -15476,6 +16444,8 @@ var tokenize = (command, dialect) => {
|
|
|
15476
16444
|
if (run) {
|
|
15477
16445
|
word += run[0];
|
|
15478
16446
|
inWord = true;
|
|
16447
|
+
if (EXPANDING_RUN.test(run[0]))
|
|
16448
|
+
expanding = true;
|
|
15479
16449
|
index += run[0].length - 1;
|
|
15480
16450
|
continue;
|
|
15481
16451
|
}
|
|
@@ -15504,6 +16474,8 @@ var tokenize = (command, dialect) => {
|
|
|
15504
16474
|
const body = command.slice(index + 1, end < 0 ? undefined : end);
|
|
15505
16475
|
if (posix && char === '"' && /\$\(|`/.test(body))
|
|
15506
16476
|
(current.subs ??= []).push(...substitutions(body));
|
|
16477
|
+
if (char === '"' && expandsInQuotes(body, dialect))
|
|
16478
|
+
expanding = true;
|
|
15507
16479
|
word += unquote(body, char, dialect);
|
|
15508
16480
|
inWord = true;
|
|
15509
16481
|
index = end < 0 ? command.length : end;
|
|
@@ -15549,6 +16521,8 @@ var tokenize = (command, dialect) => {
|
|
|
15549
16521
|
if (char === "(" && inWord && word.endsWith("$")) {
|
|
15550
16522
|
word = word.slice(0, -1);
|
|
15551
16523
|
inWord = word !== "";
|
|
16524
|
+
if (!inWord)
|
|
16525
|
+
expanding = false;
|
|
15552
16526
|
}
|
|
15553
16527
|
group(char === "(" ? "open" : "close");
|
|
15554
16528
|
} else if (!posix && (char === "{" || char === "}")) {
|
|
@@ -15602,6 +16576,8 @@ var tokenize = (command, dialect) => {
|
|
|
15602
16576
|
else if (posix && char === "}" && word.includes(OPEN))
|
|
15603
16577
|
word += CLOSE;
|
|
15604
16578
|
else {
|
|
16579
|
+
if (char === "$")
|
|
16580
|
+
expanding = true;
|
|
15605
16581
|
word += char;
|
|
15606
16582
|
inWord = true;
|
|
15607
16583
|
}
|
|
@@ -15659,14 +16635,14 @@ var GIT_CONFIG_ENV = /^GIT_(?:CONFIG\w*|DIR|COMMON_DIR|WORK_TREE)$/;
|
|
|
15659
16635
|
var ASSIGNMENT = /^([A-Za-z_]\w*)=([\s\S]*)$/;
|
|
15660
16636
|
var MAX_DEPTH = 4;
|
|
15661
16637
|
var MAX_EXPANSIONS = 16;
|
|
15662
|
-
var isAbsolute = (value) =>
|
|
16638
|
+
var isAbsolute = (value) => path20.isAbsolute(value) || /^[A-Za-z]:[\\/]/.test(value) || value.startsWith("\\\\");
|
|
15663
16639
|
var HERE = /^(?:\$PWD|\$\{PWD\}|\$\(\s*pwd\s*\)|`pwd`|\$\(\s*git\s+rev-parse\s+--show-toplevel\s*\))(?=$|[\\/])/;
|
|
15664
16640
|
var joinDir = (base, raw) => {
|
|
15665
16641
|
const next = raw.replace(HERE, ".");
|
|
15666
16642
|
if (base === undefined || /[$`*?]/.test(next) || next === "-")
|
|
15667
16643
|
return;
|
|
15668
|
-
const expanded = next === "~" || next.startsWith("~/") ?
|
|
15669
|
-
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);
|
|
15670
16646
|
};
|
|
15671
16647
|
var rebase = (base, inner) => inner === null ? base : inner === undefined ? undefined : joinDir(base, inner);
|
|
15672
16648
|
var cdTarget = (words) => {
|
|
@@ -16046,8 +17022,17 @@ function walk2(command, dialect, depth, seen) {
|
|
|
16046
17022
|
visit(next, undefined, hops + 1);
|
|
16047
17023
|
};
|
|
16048
17024
|
const wrapped = seen ? null : workitCheckTail(words);
|
|
16049
|
-
if (wrapped)
|
|
16050
|
-
|
|
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
|
+
}
|
|
16051
17036
|
if (CD.has(name)) {
|
|
16052
17037
|
const target = cdTarget(words);
|
|
16053
17038
|
dir = target === null ? os4.homedir() : joinDir(dir, target);
|
|
@@ -16148,14 +17133,14 @@ function workitCheckTail(words) {
|
|
|
16148
17133
|
return null;
|
|
16149
17134
|
} else if (!/^workit(?:\.exe|\.cmd)?$/.test(commandName(words[0])))
|
|
16150
17135
|
return null;
|
|
16151
|
-
const args = words.slice(at + 1);
|
|
17136
|
+
const { rest: args, cwd } = skipWorkitGlobals(words.slice(at + 1));
|
|
16152
17137
|
if (args[0] !== "check" && !(args[0] === "lock" && args[1] === "run"))
|
|
16153
17138
|
return null;
|
|
16154
17139
|
const split = args.indexOf("--");
|
|
16155
17140
|
const tail = split < 0 ? [] : args.slice(split + 1);
|
|
16156
17141
|
if (!tail.length)
|
|
16157
17142
|
return null;
|
|
16158
|
-
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 };
|
|
16159
17144
|
}
|
|
16160
17145
|
function simpleCommand(command, dialect = "posix", depth = 0) {
|
|
16161
17146
|
let segments;
|
|
@@ -16216,7 +17201,21 @@ function skipWorkitGlobals(args) {
|
|
|
16216
17201
|
} else if (args[at] !== "--json")
|
|
16217
17202
|
break;
|
|
16218
17203
|
}
|
|
16219
|
-
|
|
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 };
|
|
16220
17219
|
}
|
|
16221
17220
|
var PUSH_VALUE_OPTIONS = new Set(["-o", "--push-option", "--receive-pack", "--exec", "--repo"]);
|
|
16222
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*\)$/;
|
|
@@ -16517,17 +17516,17 @@ var headSha2 = (repo) => {
|
|
|
16517
17516
|
if (!git || git.reftable)
|
|
16518
17517
|
return null;
|
|
16519
17518
|
try {
|
|
16520
|
-
const head = fs16.readFileSync(
|
|
17519
|
+
const head = fs16.readFileSync(path20.join(git.gitDir, "HEAD"), "utf8").trim();
|
|
16521
17520
|
if (/^[0-9a-f]{40,64}$/.test(head))
|
|
16522
17521
|
return head;
|
|
16523
17522
|
const ref = /^ref: (refs\/.+)$/.exec(head)?.[1];
|
|
16524
17523
|
if (!ref)
|
|
16525
17524
|
return null;
|
|
16526
|
-
const common =
|
|
17525
|
+
const common = path20.dirname(repo.location.dir);
|
|
16527
17526
|
try {
|
|
16528
|
-
return fs16.readFileSync(
|
|
17527
|
+
return fs16.readFileSync(path20.join(common, ref), "utf8").trim() || null;
|
|
16529
17528
|
} catch {}
|
|
16530
|
-
const packed = fs16.readFileSync(
|
|
17529
|
+
const packed = fs16.readFileSync(path20.join(common, "packed-refs"), "utf8");
|
|
16531
17530
|
return new RegExp(`^([0-9a-f]{40,64}) ${ref.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`, "m").exec(packed)?.[1] ?? null;
|
|
16532
17531
|
} catch {
|
|
16533
17532
|
return null;
|
|
@@ -16615,7 +17614,7 @@ var expandGitAliases = (root, invocation, cache = new Map, depth = 0) => {
|
|
|
16615
17614
|
const sub = invocation.args[0];
|
|
16616
17615
|
if (invocation.tool !== "git" || invocation.dir === undefined || !sub || sub.startsWith("-") || GIT_COMMANDS.has(sub) || depth >= MAX_DEPTH)
|
|
16617
17616
|
return [invocation];
|
|
16618
|
-
const dir =
|
|
17617
|
+
const dir = path20.resolve(root, invocation.dir ?? ".");
|
|
16619
17618
|
if (!fs16.existsSync(dir))
|
|
16620
17619
|
return [invocation];
|
|
16621
17620
|
const config = gitConfig(dir, invocation, cache);
|
|
@@ -16638,11 +17637,11 @@ var expandGitAliases = (root, invocation, cache = new Map, depth = 0) => {
|
|
|
16638
17637
|
var normalizedUrl = (url, dir) => {
|
|
16639
17638
|
const trimmed = url.trim();
|
|
16640
17639
|
if (/^file:\/\//i.test(trimmed))
|
|
16641
|
-
return
|
|
17640
|
+
return path20.resolve(dir, trimmed.slice(7)).replace(/\/+$/, "").replace(/\.git$/, "");
|
|
16642
17641
|
const scheme = /^[a-z][a-z0-9+.-]*:\/\//i.exec(trimmed);
|
|
16643
17642
|
const scp = !scheme && /^(?:[^@/\\]+@)?[^/\\:]{2,}:/.test(trimmed);
|
|
16644
17643
|
if (!scheme && !scp)
|
|
16645
|
-
return
|
|
17644
|
+
return path20.resolve(dir, trimmed).replace(/\/+$/, "").replace(/\.git$/, "");
|
|
16646
17645
|
const value = scheme ? trimmed.slice(scheme[0].length).replace(/^[^@/]*@/, "").replace(/^([^/:]+):\d+(?=\/)/, "$1") : trimmed.replace(/^[^@/]*@/, "").replace(":", "/");
|
|
16647
17646
|
return value.toLowerCase().replace(/\/{2,}/g, "/").replace(/^www\./, "").replace(/\/+$/, "").replace(/\.git$/, "");
|
|
16648
17647
|
};
|
|
@@ -16866,7 +17865,7 @@ var actionsIn = (cwd, command, dialect, cache = new Map) => {
|
|
|
16866
17865
|
const out = [];
|
|
16867
17866
|
const repos = new Map;
|
|
16868
17867
|
const repoFor = (relative) => {
|
|
16869
|
-
const dir =
|
|
17868
|
+
const dir = path20.resolve(cwd, relative ?? ".");
|
|
16870
17869
|
if (!repos.has(dir)) {
|
|
16871
17870
|
const repo = repoAt(dir);
|
|
16872
17871
|
repos.set(dir, repo && isWorkitWorkspace(repo) ? repo : null);
|
|
@@ -16952,7 +17951,7 @@ var analysisDenial = (error) => ({
|
|
|
16952
17951
|
});
|
|
16953
17952
|
var NEXT_COMMAND = "next";
|
|
16954
17953
|
var MARKER_TTL_SECONDS = 3600;
|
|
16955
|
-
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`);
|
|
16956
17955
|
var callKey = (toolUseId, command) => toolUseId ?? `cmd:${createHash14("sha256").update(command).digest("hex")}`;
|
|
16957
17956
|
function noteRawCommit(input, command, key) {
|
|
16958
17957
|
try {
|
|
@@ -16963,7 +17962,7 @@ function noteRawCommit(input, command, key) {
|
|
|
16963
17962
|
if (action.kind !== "commit")
|
|
16964
17963
|
continue;
|
|
16965
17964
|
const file = markerFile(repo, input.session.id, key);
|
|
16966
|
-
fs16.mkdirSync(
|
|
17965
|
+
fs16.mkdirSync(path20.dirname(file), { recursive: true });
|
|
16967
17966
|
fs16.writeFileSync(file, `${JSON.stringify({ head: headSha2(repo), at })}
|
|
16968
17967
|
`);
|
|
16969
17968
|
}
|
|
@@ -17194,8 +18193,8 @@ var judgedDir = (root, dir) => {
|
|
|
17194
18193
|
return null;
|
|
17195
18194
|
if (dir === null)
|
|
17196
18195
|
return root;
|
|
17197
|
-
const resolved =
|
|
17198
|
-
return
|
|
18196
|
+
const resolved = path21.resolve(root, dir);
|
|
18197
|
+
return existsSync7(resolved) && inWorkitWorkspace(resolved) ? resolved : null;
|
|
17199
18198
|
};
|
|
17200
18199
|
var shellBranchPolicyViolation = (root, command, dialect = "posix") => {
|
|
17201
18200
|
let judged = false;
|
|
@@ -17430,7 +18429,7 @@ var judgeInputSummary = (raw) => {
|
|
|
17430
18429
|
|
|
17431
18430
|
// packages/workit-core/src/autonomy.ts
|
|
17432
18431
|
import os5 from "node:os";
|
|
17433
|
-
import
|
|
18432
|
+
import path22 from "node:path";
|
|
17434
18433
|
var accountHome = () => {
|
|
17435
18434
|
try {
|
|
17436
18435
|
return os5.userInfo().homedir;
|
|
@@ -17439,10 +18438,10 @@ var accountHome = () => {
|
|
|
17439
18438
|
}
|
|
17440
18439
|
};
|
|
17441
18440
|
var grantsHome = { resolve: accountHome };
|
|
17442
|
-
var grantsDir = () =>
|
|
18441
|
+
var grantsDir = () => path22.join(grantsHome.resolve(), ".config", "workit");
|
|
17443
18442
|
var grantsOverride = () => {
|
|
17444
|
-
const configured =
|
|
17445
|
-
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`;
|
|
17446
18445
|
};
|
|
17447
18446
|
var GRANT_KINDS = ["push", "pr", "merge", "release", "rerun"];
|
|
17448
18447
|
var DEFAULT_GRANTS = Object.freeze({
|
|
@@ -17495,8 +18494,16 @@ function endpointView(entry, context) {
|
|
|
17495
18494
|
...issue ? { endpointIssue: issue } : {}
|
|
17496
18495
|
};
|
|
17497
18496
|
}
|
|
17498
|
-
var
|
|
17499
|
-
|
|
18497
|
+
var VERIFICATION_MODES = [
|
|
18498
|
+
"proportional",
|
|
18499
|
+
"self",
|
|
18500
|
+
"independent"
|
|
18501
|
+
];
|
|
18502
|
+
var DEFAULT_VERIFICATION = "proportional";
|
|
18503
|
+
var verificationOf = (entry) => {
|
|
18504
|
+
const value = entry?.verification;
|
|
18505
|
+
return VERIFICATION_MODES.includes(value) ? value : DEFAULT_VERIFICATION;
|
|
18506
|
+
};
|
|
17500
18507
|
var isGrantValue = (value) => value === true || value === false || value === "verified";
|
|
17501
18508
|
var normalized = (kind, value) => value === "verified" && kind !== "merge" ? true : value;
|
|
17502
18509
|
var fromAutoApprove = (value) => {
|
|
@@ -19244,8 +20251,8 @@ class WorkitCore {
|
|
|
19244
20251
|
}
|
|
19245
20252
|
// packages/workit-core/src/core/repo-context.ts
|
|
19246
20253
|
import { spawnSync as spawnSync13 } from "node:child_process";
|
|
19247
|
-
import { existsSync as
|
|
19248
|
-
import
|
|
20254
|
+
import { existsSync as existsSync8, readFileSync as readFileSync6, readdirSync as readdirSync4 } from "node:fs";
|
|
20255
|
+
import path23 from "node:path";
|
|
19249
20256
|
var isSafeContextRange = (value) => value.length <= 4096 && (() => {
|
|
19250
20257
|
try {
|
|
19251
20258
|
gitRevisionParts(value);
|
|
@@ -19379,7 +20386,7 @@ var findPrTemplate = (cwd) => {
|
|
|
19379
20386
|
]) {
|
|
19380
20387
|
const parts = rel.split("/");
|
|
19381
20388
|
const base = parts[parts.length - 1];
|
|
19382
|
-
const dir = parts.length > 1 ?
|
|
20389
|
+
const dir = parts.length > 1 ? path23.join(cwd, ...parts.slice(0, -1)) : cwd;
|
|
19383
20390
|
const actual = probeCaseInsensitive(dir, base);
|
|
19384
20391
|
if (actual)
|
|
19385
20392
|
return [...parts.slice(0, -1), actual].join("/");
|
|
@@ -19410,11 +20417,11 @@ var diffStatForRange = (cwd, range) => {
|
|
|
19410
20417
|
return runGit(cwd, ["diff", "--stat", "--"]).stdout;
|
|
19411
20418
|
};
|
|
19412
20419
|
var packageScripts = (cwd, keys) => {
|
|
19413
|
-
const pkgPath =
|
|
19414
|
-
if (!
|
|
20420
|
+
const pkgPath = path23.join(cwd, "package.json");
|
|
20421
|
+
if (!existsSync8(pkgPath))
|
|
19415
20422
|
return [];
|
|
19416
20423
|
try {
|
|
19417
|
-
const pkg = JSON.parse(
|
|
20424
|
+
const pkg = JSON.parse(readFileSync6(pkgPath, "utf8"));
|
|
19418
20425
|
return keys.filter((k) => pkg.scripts && typeof pkg.scripts[k] === "string").map((k) => `${k}: ${pkg.scripts?.[k]}`);
|
|
19419
20426
|
} catch {
|
|
19420
20427
|
return [];
|
|
@@ -19436,9 +20443,9 @@ var documentationFiles = (cwd) => {
|
|
|
19436
20443
|
if (entry.isDirectory()) {
|
|
19437
20444
|
if (excluded.has(entry.name))
|
|
19438
20445
|
continue;
|
|
19439
|
-
walk(
|
|
20446
|
+
walk(path23.join(dir, entry.name), depth + 1);
|
|
19440
20447
|
} else if (entry.isFile() && (entry.name === "README.md" || entry.name.endsWith(".md"))) {
|
|
19441
|
-
matches.push("./" +
|
|
20448
|
+
matches.push("./" + path23.relative(cwd, path23.join(dir, entry.name)).replaceAll("\\", "/"));
|
|
19442
20449
|
}
|
|
19443
20450
|
}
|
|
19444
20451
|
};
|
|
@@ -19446,15 +20453,15 @@ var documentationFiles = (cwd) => {
|
|
|
19446
20453
|
return matches.toSorted().slice(0, 200);
|
|
19447
20454
|
};
|
|
19448
20455
|
var readTrimmed2 = (file, maxLines) => {
|
|
19449
|
-
if (!
|
|
20456
|
+
if (!existsSync8(file))
|
|
19450
20457
|
return "";
|
|
19451
|
-
const lines =
|
|
20458
|
+
const lines = readFileSync6(file, "utf8").split(`
|
|
19452
20459
|
`);
|
|
19453
20460
|
return lines.slice(0, maxLines).join(`
|
|
19454
20461
|
`);
|
|
19455
20462
|
};
|
|
19456
20463
|
function prReadyContext(root, range) {
|
|
19457
|
-
const cwd =
|
|
20464
|
+
const cwd = path23.resolve(repoRoot(root));
|
|
19458
20465
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
19459
20466
|
return invalidRange(cwd);
|
|
19460
20467
|
let stdout = "";
|
|
@@ -19512,7 +20519,7 @@ ${status.stderr}` : "");
|
|
|
19512
20519
|
stdout += `template_path: ${template}
|
|
19513
20520
|
|
|
19514
20521
|
`;
|
|
19515
|
-
stdout += readTrimmed2(
|
|
20522
|
+
stdout += readTrimmed2(path23.join(cwd, template), 220);
|
|
19516
20523
|
} else {
|
|
19517
20524
|
stdout += `template_path: none
|
|
19518
20525
|
|
|
@@ -19522,7 +20529,7 @@ ${status.stderr}` : "");
|
|
|
19522
20529
|
stdout += `
|
|
19523
20530
|
`;
|
|
19524
20531
|
stdout += printSection("Recent Validation Signals");
|
|
19525
|
-
if (
|
|
20532
|
+
if (existsSync8(path23.join(cwd, "package.json"))) {
|
|
19526
20533
|
stdout += `package.json detected. Common scripts:
|
|
19527
20534
|
`;
|
|
19528
20535
|
const found = packageScripts(cwd, ["lint", "format:check", "test", "build"]);
|
|
@@ -19531,7 +20538,7 @@ ${status.stderr}` : "");
|
|
|
19531
20538
|
`) + `
|
|
19532
20539
|
`;
|
|
19533
20540
|
}
|
|
19534
|
-
if (
|
|
20541
|
+
if (existsSync8(path23.join(cwd, "Cargo.toml")) || existsSync8(path23.join(cwd, "src-tauri/Cargo.toml"))) {
|
|
19535
20542
|
stdout += `Rust project detected.
|
|
19536
20543
|
`;
|
|
19537
20544
|
}
|
|
@@ -19564,7 +20571,7 @@ var CHANGELOG_RULES = `- Use an [Unreleased] section.
|
|
|
19564
20571
|
- Apply with the host-native editor, preserving the repository changelog conventions.
|
|
19565
20572
|
- If Unreleased already has duplicate category headings, normalize_only first.`;
|
|
19566
20573
|
function changelogContext(root, range) {
|
|
19567
|
-
const cwd =
|
|
20574
|
+
const cwd = path23.resolve(repoRoot(root));
|
|
19568
20575
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
19569
20576
|
return invalidRange(cwd);
|
|
19570
20577
|
const resolvedRange = rangeArgOrDefault(range, cwd);
|
|
@@ -19582,8 +20589,8 @@ function changelogContext(root, range) {
|
|
|
19582
20589
|
stdout += CHANGELOG_RULES + `
|
|
19583
20590
|
`;
|
|
19584
20591
|
stdout += printSection("Existing CHANGELOG.md");
|
|
19585
|
-
const changelogPath =
|
|
19586
|
-
if (
|
|
20592
|
+
const changelogPath = path23.join(cwd, "CHANGELOG.md");
|
|
20593
|
+
if (existsSync8(changelogPath)) {
|
|
19587
20594
|
stdout += readTrimmed2(changelogPath, 260) + `
|
|
19588
20595
|
`;
|
|
19589
20596
|
} else {
|
|
@@ -19602,7 +20609,7 @@ function changelogContext(root, range) {
|
|
|
19602
20609
|
return { stdout, stderr: "", exitCode: 0, cwd };
|
|
19603
20610
|
}
|
|
19604
20611
|
function docsRefreshContext(root, range) {
|
|
19605
|
-
const cwd =
|
|
20612
|
+
const cwd = path23.resolve(repoRoot(root));
|
|
19606
20613
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
19607
20614
|
return invalidRange(cwd);
|
|
19608
20615
|
const resolvedRange = rangeArgOrDefault(range, cwd);
|
|
@@ -19624,8 +20631,8 @@ function docsRefreshContext(root, range) {
|
|
|
19624
20631
|
`) + `
|
|
19625
20632
|
`;
|
|
19626
20633
|
stdout += printSection("README Preview");
|
|
19627
|
-
const readme =
|
|
19628
|
-
if (
|
|
20634
|
+
const readme = path23.join(cwd, "README.md");
|
|
20635
|
+
if (existsSync8(readme)) {
|
|
19629
20636
|
stdout += readTrimmed2(readme, 220) + `
|
|
19630
20637
|
`;
|
|
19631
20638
|
} else {
|
|
@@ -19633,10 +20640,10 @@ function docsRefreshContext(root, range) {
|
|
|
19633
20640
|
`;
|
|
19634
20641
|
}
|
|
19635
20642
|
stdout += printSection("Package Scripts");
|
|
19636
|
-
const pkgPath =
|
|
19637
|
-
if (
|
|
20643
|
+
const pkgPath = path23.join(cwd, "package.json");
|
|
20644
|
+
if (existsSync8(pkgPath)) {
|
|
19638
20645
|
try {
|
|
19639
|
-
const pkg = JSON.parse(
|
|
20646
|
+
const pkg = JSON.parse(readFileSync6(pkgPath, "utf8"));
|
|
19640
20647
|
stdout += JSON.stringify({ name: pkg.name, version: pkg.version, scripts: pkg.scripts }, null, 2) + `
|
|
19641
20648
|
`;
|
|
19642
20649
|
} catch {}
|
|
@@ -19647,7 +20654,7 @@ function docsRefreshContext(root, range) {
|
|
|
19647
20654
|
return { stdout, stderr: "", exitCode: 0, cwd };
|
|
19648
20655
|
}
|
|
19649
20656
|
function releaseNotesContext(root, rangeOrTag) {
|
|
19650
|
-
const cwd =
|
|
20657
|
+
const cwd = path23.resolve(repoRoot(root));
|
|
19651
20658
|
if (!rangeOrTag) {
|
|
19652
20659
|
return { stdout: "", stderr: `ERROR: release tag or range required
|
|
19653
20660
|
`, exitCode: 1, cwd };
|
|
@@ -19685,7 +20692,7 @@ function releaseNotesContext(root, rangeOrTag) {
|
|
|
19685
20692
|
`;
|
|
19686
20693
|
stdout += printSection("Existing Release Files");
|
|
19687
20694
|
for (const rel of ["CHANGELOG.md", "RELEASE_NOTES.md", ".github/releases.md"]) {
|
|
19688
|
-
if (
|
|
20695
|
+
if (existsSync8(path23.join(cwd, rel)))
|
|
19689
20696
|
stdout += rel + `
|
|
19690
20697
|
`;
|
|
19691
20698
|
}
|
|
@@ -19717,7 +20724,7 @@ import { spawnSync as spawnSync15 } from "node:child_process";
|
|
|
19717
20724
|
|
|
19718
20725
|
// packages/workit-core/src/core/pr-create.ts
|
|
19719
20726
|
import fs17 from "node:fs";
|
|
19720
|
-
import
|
|
20727
|
+
import path24 from "node:path";
|
|
19721
20728
|
import { spawnSync as spawnSync14 } from "node:child_process";
|
|
19722
20729
|
// packages/workit-core/src/forge/pr-body.ts
|
|
19723
20730
|
function parseGhIssue(value) {
|
|
@@ -19728,11 +20735,11 @@ function parseGhIssue(value) {
|
|
|
19728
20735
|
// packages/workit-core/src/core/pr-create.ts
|
|
19729
20736
|
function whichOnPath(tool) {
|
|
19730
20737
|
const names = process.platform === "win32" ? [tool, `${tool}.exe`, `${tool}.cmd`] : [tool];
|
|
19731
|
-
for (const dir of (process.env.PATH ?? "").split(
|
|
20738
|
+
for (const dir of (process.env.PATH ?? "").split(path24.delimiter)) {
|
|
19732
20739
|
if (!dir)
|
|
19733
20740
|
continue;
|
|
19734
20741
|
for (const name of names) {
|
|
19735
|
-
const candidate =
|
|
20742
|
+
const candidate = path24.join(dir, name);
|
|
19736
20743
|
try {
|
|
19737
20744
|
fs17.accessSync(candidate, fs17.constants.X_OK);
|
|
19738
20745
|
return candidate;
|
|
@@ -19853,7 +20860,7 @@ var fetchGitLabIssueBody = async (ref, root, deps = {}) => {
|
|
|
19853
20860
|
|
|
19854
20861
|
// packages/workit-core/src/core/youtrack.ts
|
|
19855
20862
|
import fs18 from "node:fs";
|
|
19856
|
-
import
|
|
20863
|
+
import path25 from "node:path";
|
|
19857
20864
|
|
|
19858
20865
|
// packages/workit-core/src/core/templates.ts
|
|
19859
20866
|
var repoRoot2 = assetRoot();
|
|
@@ -19866,8 +20873,8 @@ var resolveWorkspaceRoot = (explicit) => explicit || process.cwd();
|
|
|
19866
20873
|
// packages/workit-core/src/core/youtrack.ts
|
|
19867
20874
|
var ISSUE_RE = /^[A-Z]+-\d+$/;
|
|
19868
20875
|
var TOKEN_PLACEHOLDER = "YOUR_TOKEN_HERE";
|
|
19869
|
-
var youTrackConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ??
|
|
19870
|
-
var youTrackReadOnlyConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ??
|
|
20876
|
+
var youTrackConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path25.join(configDir(), "youtrack.json");
|
|
20877
|
+
var youTrackReadOnlyConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path25.join(resolveConfigDir(), "youtrack.json");
|
|
19871
20878
|
var youTrackTokenModeOk = (p) => {
|
|
19872
20879
|
if (process.platform === "win32")
|
|
19873
20880
|
return true;
|
|
@@ -19935,7 +20942,7 @@ var youTrackToken = () => {
|
|
|
19935
20942
|
if ("error" in loaded)
|
|
19936
20943
|
return loaded;
|
|
19937
20944
|
const tokenFile = String(loaded.config.tokenFile ?? "");
|
|
19938
|
-
const tokenPath = tokenFile ?
|
|
20945
|
+
const tokenPath = tokenFile ? path25.isAbsolute(tokenFile) ? path25.resolve(tokenFile) : path25.resolve(process.cwd(), tokenFile) : "";
|
|
19939
20946
|
if (!tokenPath || !fs18.existsSync(tokenPath))
|
|
19940
20947
|
return { error: "missing youtrack.token" };
|
|
19941
20948
|
if (!youTrackTokenModeOk(tokenPath))
|
|
@@ -20292,15 +21299,15 @@ function resolveGitRevision(root, value) {
|
|
|
20292
21299
|
}
|
|
20293
21300
|
function resolveInside(root, candidate) {
|
|
20294
21301
|
const base = realpathSync5(root);
|
|
20295
|
-
const target =
|
|
20296
|
-
if (target !== base && !target.startsWith(base +
|
|
21302
|
+
const target = path26.resolve(base, candidate);
|
|
21303
|
+
if (target !== base && !target.startsWith(base + path26.sep)) {
|
|
20297
21304
|
throw new Error("path must stay inside repository root");
|
|
20298
21305
|
}
|
|
20299
21306
|
let ancestor = target;
|
|
20300
|
-
while (!
|
|
20301
|
-
ancestor =
|
|
21307
|
+
while (!existsSync9(ancestor))
|
|
21308
|
+
ancestor = path26.dirname(ancestor);
|
|
20302
21309
|
const canonicalAncestor = realpathSync5(ancestor);
|
|
20303
|
-
if (canonicalAncestor !== base && !canonicalAncestor.startsWith(base +
|
|
21310
|
+
if (canonicalAncestor !== base && !canonicalAncestor.startsWith(base + path26.sep)) {
|
|
20304
21311
|
throw new Error("path must stay inside repository root");
|
|
20305
21312
|
}
|
|
20306
21313
|
return target;
|
|
@@ -20346,7 +21353,7 @@ var capabilitiesFor = (descriptor, observed = {}) => descriptor.capabilities.map
|
|
|
20346
21353
|
// packages/workit-core/src/hooks/context.ts
|
|
20347
21354
|
import { createHash as createHash15 } from "node:crypto";
|
|
20348
21355
|
import fs19 from "node:fs";
|
|
20349
|
-
import
|
|
21356
|
+
import path27 from "node:path";
|
|
20350
21357
|
var MIGRATION_PENDING_NOTE = "workit migration pending — run `workit task status`";
|
|
20351
21358
|
var migrationPending = (result) => !result.ok && result.code === "needs_input" && (result.error ?? "").startsWith(MIGRATION_PENDING);
|
|
20352
21359
|
var boundTo = (entry, session) => entry.sessions.some((item) => item.host === session.host && item.handle === session.handle);
|
|
@@ -20431,7 +21438,7 @@ function entryCompactContext(store, entry, session, context) {
|
|
|
20431
21438
|
return null;
|
|
20432
21439
|
const freshness = { mode: "signal", current: () => recentSignal(store.root) };
|
|
20433
21440
|
const observed = task?.data.evidence.some(isObservedCheck) ?? false;
|
|
20434
|
-
const files = (task?.data.decisions ?? []).flatMap((decision) => decision.data.binding.contentRefs.flatMap((ref) => ref.kind === "file" ? [
|
|
21441
|
+
const files = (task?.data.decisions ?? []).flatMap((decision) => decision.data.binding.contentRefs.flatMap((ref) => ref.kind === "file" ? [path27.resolve(store.root, ref.path)] : []));
|
|
20435
21442
|
const observedFiles = filesKey(files);
|
|
20436
21443
|
const compact = new WorkitCore(store, context).compactContext(entry?.id ?? null, freshness, origin && { origin: origin.origin, taskId: origin.entry.id });
|
|
20437
21444
|
if (!compact.ok || !compact.data)
|
|
@@ -20509,14 +21516,14 @@ var turnMarker = (input) => {
|
|
|
20509
21516
|
if (location instanceof Error)
|
|
20510
21517
|
return null;
|
|
20511
21518
|
const session = [input.host, input.session.id, input.session.agentId ?? ""].join("\x00");
|
|
20512
|
-
return
|
|
21519
|
+
return path27.join(location.dir, "hooks", `turn-${digestOf(session).slice(0, 32)}.json`);
|
|
20513
21520
|
} catch {
|
|
20514
21521
|
return null;
|
|
20515
21522
|
}
|
|
20516
21523
|
};
|
|
20517
21524
|
var writeTurnDigest = (file, digest) => {
|
|
20518
21525
|
try {
|
|
20519
|
-
fs19.mkdirSync(
|
|
21526
|
+
fs19.mkdirSync(path27.dirname(file), { recursive: true });
|
|
20520
21527
|
fs19.writeFileSync(file, `${JSON.stringify({ digest })}
|
|
20521
21528
|
`);
|
|
20522
21529
|
} catch {}
|
|
@@ -20531,7 +21538,7 @@ var pruneTurnDigests = (dir, now) => {
|
|
|
20531
21538
|
for (const name of fs19.readdirSync(dir)) {
|
|
20532
21539
|
if (!name.startsWith("turn-"))
|
|
20533
21540
|
continue;
|
|
20534
|
-
const file =
|
|
21541
|
+
const file = path27.join(dir, name);
|
|
20535
21542
|
try {
|
|
20536
21543
|
if (now - fs19.statSync(file).mtimeMs > TURN_TTL_MS)
|
|
20537
21544
|
fs19.rmSync(file, { force: true });
|
|
@@ -20561,7 +21568,7 @@ var seedTurnContext = (input, descriptor, text = turnContextText(input, descript
|
|
|
20561
21568
|
const file = turnMarker(input);
|
|
20562
21569
|
if (!file)
|
|
20563
21570
|
return;
|
|
20564
|
-
pruneTurnDigests(
|
|
21571
|
+
pruneTurnDigests(path27.dirname(file), Date.now());
|
|
20565
21572
|
if (text)
|
|
20566
21573
|
writeTurnDigest(file, digestOf(text));
|
|
20567
21574
|
else
|
|
@@ -20579,10 +21586,57 @@ var shellPolicy = (cwd, command, dialect = "posix") => {
|
|
|
20579
21586
|
}
|
|
20580
21587
|
return policy && !policy.ok ? { kind: "deny", reason: `branch_policy_denied: ${policy.error}`, unblock: null } : { kind: "none" };
|
|
20581
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
|
+
};
|
|
20582
21636
|
// packages/workit-core/src/core/heavy-rules.ts
|
|
20583
21637
|
import fs20 from "node:fs";
|
|
20584
21638
|
import os6 from "node:os";
|
|
20585
|
-
import
|
|
21639
|
+
import path28 from "node:path";
|
|
20586
21640
|
var EMPTY2 = () => ({ deny: [], allow: [] });
|
|
20587
21641
|
var readText2 = (file) => {
|
|
20588
21642
|
try {
|
|
@@ -20623,19 +21677,19 @@ function parseJsonc(text) {
|
|
|
20623
21677
|
return JSON.parse(out.replace(/,(\s*[}\]])/gu, "$1"));
|
|
20624
21678
|
}
|
|
20625
21679
|
function gitTop(cwd) {
|
|
20626
|
-
for (let dir =
|
|
20627
|
-
if (fs20.existsSync(
|
|
21680
|
+
for (let dir = path28.resolve(cwd);; dir = path28.dirname(dir)) {
|
|
21681
|
+
if (fs20.existsSync(path28.join(dir, ".git")))
|
|
20628
21682
|
return dir;
|
|
20629
|
-
if (
|
|
21683
|
+
if (path28.dirname(dir) === dir)
|
|
20630
21684
|
return null;
|
|
20631
21685
|
}
|
|
20632
21686
|
}
|
|
20633
21687
|
var projectDirs = (cwd) => {
|
|
20634
21688
|
const top = gitTop(cwd);
|
|
20635
21689
|
const dirs = [];
|
|
20636
|
-
for (let dir =
|
|
21690
|
+
for (let dir = path28.resolve(cwd);; dir = path28.dirname(dir)) {
|
|
20637
21691
|
dirs.push(dir);
|
|
20638
|
-
if (!top || dir === top ||
|
|
21692
|
+
if (!top || dir === top || path28.dirname(dir) === dir)
|
|
20639
21693
|
return dirs;
|
|
20640
21694
|
}
|
|
20641
21695
|
};
|
|
@@ -20647,12 +21701,12 @@ var CLAUDE_MANAGED = {
|
|
|
20647
21701
|
};
|
|
20648
21702
|
function claudeSettingsFiles(cwd, env, platform = process.platform) {
|
|
20649
21703
|
const project = env.CLAUDE_PROJECT_DIR || gitTop(cwd) || cwd;
|
|
20650
|
-
const user = env.CLAUDE_CONFIG_DIR ||
|
|
21704
|
+
const user = env.CLAUDE_CONFIG_DIR || path28.join(homeOf(env), ".claude");
|
|
20651
21705
|
return [
|
|
20652
21706
|
...CLAUDE_MANAGED[platform] ? [CLAUDE_MANAGED[platform]] : [],
|
|
20653
|
-
|
|
20654
|
-
|
|
20655
|
-
|
|
21707
|
+
path28.join(user, "settings.json"),
|
|
21708
|
+
path28.join(project, ".claude", "settings.json"),
|
|
21709
|
+
path28.join(project, ".claude", "settings.local.json")
|
|
20656
21710
|
];
|
|
20657
21711
|
}
|
|
20658
21712
|
var bashPattern = (entry) => {
|
|
@@ -20742,10 +21796,10 @@ var opencodeConfig = (value, rules) => {
|
|
|
20742
21796
|
function opencodeRules(cwd, env = process.env) {
|
|
20743
21797
|
const rules = EMPTY2();
|
|
20744
21798
|
try {
|
|
20745
|
-
const config = env.XDG_CONFIG_HOME ||
|
|
21799
|
+
const config = env.XDG_CONFIG_HOME || path28.join(homeOf(env), ".config");
|
|
20746
21800
|
const files = [
|
|
20747
|
-
...OPENCODE_NAMES.map((name) =>
|
|
20748
|
-
...projectDirs(cwd).flatMap((dir) => OPENCODE_NAMES.flatMap((name) => [
|
|
21801
|
+
...OPENCODE_NAMES.map((name) => path28.join(config, "opencode", name)),
|
|
21802
|
+
...projectDirs(cwd).flatMap((dir) => OPENCODE_NAMES.flatMap((name) => [path28.join(dir, name), path28.join(dir, ".opencode", name)])),
|
|
20749
21803
|
...env.OPENCODE_CONFIG ? [env.OPENCODE_CONFIG] : []
|
|
20750
21804
|
];
|
|
20751
21805
|
for (const file of files) {
|
|
@@ -20789,9 +21843,9 @@ var codexRule = (body) => {
|
|
|
20789
21843
|
function codexRules(cwd, env = process.env) {
|
|
20790
21844
|
const rules = EMPTY2();
|
|
20791
21845
|
try {
|
|
20792
|
-
const home = env.CODEX_HOME ||
|
|
21846
|
+
const home = env.CODEX_HOME || path28.join(homeOf(env), ".codex");
|
|
20793
21847
|
const top = gitTop(cwd) ?? cwd;
|
|
20794
|
-
for (const dir of [
|
|
21848
|
+
for (const dir of [path28.join(home, "rules"), path28.join(top, ".codex", "rules")]) {
|
|
20795
21849
|
let files;
|
|
20796
21850
|
try {
|
|
20797
21851
|
files = fs20.readdirSync(dir).filter((name) => name.endsWith(".rules"));
|
|
@@ -20801,7 +21855,7 @@ function codexRules(cwd, env = process.env) {
|
|
|
20801
21855
|
return "unreadable";
|
|
20802
21856
|
}
|
|
20803
21857
|
for (const name of files) {
|
|
20804
|
-
const text = readText2(
|
|
21858
|
+
const text = readText2(path28.join(dir, name)) ?? "";
|
|
20805
21859
|
for (const part of text.split(/\bprefix_rule\s*\(/u).slice(1)) {
|
|
20806
21860
|
const rule = codexRule(part);
|
|
20807
21861
|
if (!rule)
|
|
@@ -20820,8 +21874,8 @@ function cursorRules(cwd, env = process.env) {
|
|
|
20820
21874
|
try {
|
|
20821
21875
|
const top = gitTop(cwd) ?? cwd;
|
|
20822
21876
|
for (const file of [
|
|
20823
|
-
|
|
20824
|
-
|
|
21877
|
+
path28.join(homeOf(env), ".cursor", "cli-config.json"),
|
|
21878
|
+
path28.join(top, ".cursor", "cli.json")
|
|
20825
21879
|
]) {
|
|
20826
21880
|
const text = readText2(file);
|
|
20827
21881
|
if (text === null)
|
|
@@ -20897,21 +21951,21 @@ function wrapAllowed(read, words, command, options = {}) {
|
|
|
20897
21951
|
// packages/workit-core/src/core/heavy.ts
|
|
20898
21952
|
import fs21 from "node:fs";
|
|
20899
21953
|
import os7 from "node:os";
|
|
20900
|
-
import
|
|
21954
|
+
import path29 from "node:path";
|
|
20901
21955
|
function stateDir(env, platform = process.platform) {
|
|
20902
21956
|
if (env.WORKFLOW_TOOLKIT_STATE)
|
|
20903
21957
|
return env.WORKFLOW_TOOLKIT_STATE;
|
|
20904
21958
|
if (env.XDG_STATE_HOME)
|
|
20905
|
-
return
|
|
21959
|
+
return path29.join(env.XDG_STATE_HOME, "workit");
|
|
20906
21960
|
const home = env.HOME || os7.homedir();
|
|
20907
21961
|
if (platform === "darwin")
|
|
20908
|
-
return
|
|
21962
|
+
return path29.join(home, "Library", "Application Support", "workit");
|
|
20909
21963
|
if (platform === "win32")
|
|
20910
|
-
return
|
|
20911
|
-
return
|
|
21964
|
+
return path29.join(env.LOCALAPPDATA ?? path29.join(home, "AppData", "Local"), "workit");
|
|
21965
|
+
return path29.join(home, ".local", "state", "workit");
|
|
20912
21966
|
}
|
|
20913
21967
|
var HEAVY_COMMANDS_FILE = "heavy-commands.json";
|
|
20914
|
-
var heavyCommandsFile = (env) =>
|
|
21968
|
+
var heavyCommandsFile = (env) => path29.join(stateDir(env), HEAVY_COMMANDS_FILE);
|
|
20915
21969
|
var HEAVY_SCRIPT = /^(?:test|tests|t|tst|unit|integration|e2e|coverage|cov|lint|eslint|typecheck|type-check|types|tsc|check|verify|validate|build|compile|bundle|ci|sonar|cypress|playwright|jest|vitest|mocha|bench|benchmark|all)(?:$|[:_.-])/iu;
|
|
20916
21970
|
var INFO_FLAGS = new Set(["--version", "-v", "-V", "--help", "-h", "help", "version"]);
|
|
20917
21971
|
var WATCH_FLAG = /^(?:--watch(?:All)?(?:=.*)?|-w|--serve)$/u;
|
|
@@ -21186,7 +22240,7 @@ function machinePatterns(env) {
|
|
|
21186
22240
|
return isRecord5(value) ? stringList(value.patterns) : [];
|
|
21187
22241
|
}
|
|
21188
22242
|
function repoPatterns(cwd) {
|
|
21189
|
-
return repoHeavyCommands(gitTop(cwd) ??
|
|
22243
|
+
return repoHeavyCommands(gitTop(cwd) ?? path29.resolve(cwd));
|
|
21190
22244
|
}
|
|
21191
22245
|
function heavyLines(lines, options) {
|
|
21192
22246
|
const found = [];
|
|
@@ -21264,12 +22318,12 @@ function heavyPlan(command, options) {
|
|
|
21264
22318
|
}
|
|
21265
22319
|
function workitOnPath2(env = process.env) {
|
|
21266
22320
|
const names = process.platform === "win32" ? ["workit.exe", "workit.cmd", "workit"] : ["workit"];
|
|
21267
|
-
for (const dir of (env.PATH ?? "").split(
|
|
22321
|
+
for (const dir of (env.PATH ?? "").split(path29.delimiter))
|
|
21268
22322
|
for (const name of names) {
|
|
21269
22323
|
if (!dir)
|
|
21270
22324
|
continue;
|
|
21271
22325
|
try {
|
|
21272
|
-
fs21.accessSync(
|
|
22326
|
+
fs21.accessSync(path29.join(dir, name), fs21.constants.X_OK);
|
|
21273
22327
|
return true;
|
|
21274
22328
|
} catch {}
|
|
21275
22329
|
}
|
|
@@ -21288,20 +22342,20 @@ function leaseRewrite(command) {
|
|
|
21288
22342
|
// packages/workit-core/src/core/heavy-words.ts
|
|
21289
22343
|
import fs22 from "node:fs";
|
|
21290
22344
|
import os8 from "node:os";
|
|
21291
|
-
import
|
|
22345
|
+
import path30 from "node:path";
|
|
21292
22346
|
var HEAVY_WORDS = new Set(("npm npx pnpm pnpx yarn bun bunx deno corepack make gmake docker podman nerdctl " + "python python3 py uv poetry pdm pipenv hatch rye bundle rake nx " + "jest vitest mocha ava tap karma jasmine nyc c8 tsc vue-tsc svelte-check eslint stylelint " + "tslint webpack webpack-cli rollup parcel tsup vite next nuxt nuxi astro ng react-scripts " + "playwright cypress wdio testcafe turbo lerna sonar-scanner sonarless sonarless-mcp go " + "golangci-lint gotestsum staticcheck cargo mvn mvnw gradle gradlew ant sbt bazel bazelisk " + "buck2 dotnet msbuild pytest py.test tox nox mypy pyright pylint coverage maturin pip pip3 " + "rspec rails phpunit pest phpstan psalm composer mix swift xcodebuild flutter dart pod cmake " + "ninja meson ctest scons docker-compose buildah earthly").split(" "));
|
|
21293
22347
|
var wordsOf = (command) => command.split(/[\s;&|()<>`'"]+/u).map((word) => (word.split(/[\\/]/u).at(-1) ?? "").toLowerCase().replace(/\.(?:exe|cmd|bat)$/u, "")).filter(Boolean);
|
|
21294
22348
|
var stateDir2 = (env) => {
|
|
21295
22349
|
if (env.WORKFLOW_TOOLKIT_STATE)
|
|
21296
22350
|
return env.WORKFLOW_TOOLKIT_STATE;
|
|
21297
22351
|
if (env.XDG_STATE_HOME)
|
|
21298
|
-
return
|
|
22352
|
+
return path30.join(env.XDG_STATE_HOME, "workit");
|
|
21299
22353
|
const home = env.HOME || os8.homedir();
|
|
21300
22354
|
if (process.platform === "darwin")
|
|
21301
|
-
return
|
|
22355
|
+
return path30.join(home, "Library", "Application Support", "workit");
|
|
21302
22356
|
if (process.platform === "win32")
|
|
21303
|
-
return
|
|
21304
|
-
return
|
|
22357
|
+
return path30.join(env.LOCALAPPDATA ?? path30.join(home, "AppData", "Local"), "workit");
|
|
22358
|
+
return path30.join(home, ".local", "state", "workit");
|
|
21305
22359
|
};
|
|
21306
22360
|
var readJson2 = (file) => {
|
|
21307
22361
|
try {
|
|
@@ -21312,15 +22366,15 @@ var readJson2 = (file) => {
|
|
|
21312
22366
|
};
|
|
21313
22367
|
var patternWords = (cwd, env) => {
|
|
21314
22368
|
const patterns = [];
|
|
21315
|
-
const machine = readJson2(
|
|
22369
|
+
const machine = readJson2(path30.join(stateDir2(env), "heavy-commands.json"));
|
|
21316
22370
|
if (Array.isArray(machine?.patterns))
|
|
21317
22371
|
patterns.push(...machine.patterns);
|
|
21318
|
-
for (let dir =
|
|
21319
|
-
if (fs22.existsSync(
|
|
22372
|
+
for (let dir = path30.resolve(cwd);; dir = path30.dirname(dir)) {
|
|
22373
|
+
if (fs22.existsSync(path30.join(dir, ".git"))) {
|
|
21320
22374
|
patterns.push(...repoHeavyCommands(dir));
|
|
21321
22375
|
break;
|
|
21322
22376
|
}
|
|
21323
|
-
if (
|
|
22377
|
+
if (path30.dirname(dir) === dir)
|
|
21324
22378
|
break;
|
|
21325
22379
|
}
|
|
21326
22380
|
const words = new Set;
|
|
@@ -21390,16 +22444,16 @@ var splitDenial = (plan) => ({
|
|
|
21390
22444
|
unblock: plan.unblock
|
|
21391
22445
|
});
|
|
21392
22446
|
// packages/workit-core/src/hooks/write-gate.ts
|
|
21393
|
-
import { existsSync as
|
|
22447
|
+
import { existsSync as existsSync10, realpathSync as realpathSync6 } from "node:fs";
|
|
21394
22448
|
import os10 from "node:os";
|
|
21395
|
-
import
|
|
22449
|
+
import path32 from "node:path";
|
|
21396
22450
|
|
|
21397
22451
|
// packages/workit-core/src/core/live-session.ts
|
|
21398
22452
|
import { spawnSync as spawnSync17 } from "node:child_process";
|
|
21399
22453
|
import { createHash as createHash16 } from "node:crypto";
|
|
21400
22454
|
import fs23 from "node:fs";
|
|
21401
22455
|
import os9 from "node:os";
|
|
21402
|
-
import
|
|
22456
|
+
import path31 from "node:path";
|
|
21403
22457
|
var LIVE_SESSION_TTL_MS = 15 * 60000;
|
|
21404
22458
|
var HEARTBEAT_EVERY_MS = 30000;
|
|
21405
22459
|
var liveProbe = {
|
|
@@ -21473,15 +22527,15 @@ var liveOwner = (input, transport, env = process.env) => {
|
|
|
21473
22527
|
};
|
|
21474
22528
|
};
|
|
21475
22529
|
var checkoutOf = (target) => {
|
|
21476
|
-
let dir =
|
|
21477
|
-
while (!fs23.existsSync(dir) &&
|
|
21478
|
-
dir =
|
|
22530
|
+
let dir = path31.resolve(target);
|
|
22531
|
+
while (!fs23.existsSync(dir) && path31.dirname(dir) !== dir)
|
|
22532
|
+
dir = path31.dirname(dir);
|
|
21479
22533
|
if (fs23.existsSync(dir) && !fs23.statSync(dir).isDirectory())
|
|
21480
|
-
dir =
|
|
21481
|
-
const parts = dir.split(
|
|
22534
|
+
dir = path31.dirname(dir);
|
|
22535
|
+
const parts = dir.split(path31.sep);
|
|
21482
22536
|
const dotGit = parts.lastIndexOf(".git");
|
|
21483
22537
|
if (dotGit > 0)
|
|
21484
|
-
dir = parts.slice(0, dotGit).join(
|
|
22538
|
+
dir = parts.slice(0, dotGit).join(path31.sep) || path31.sep;
|
|
21485
22539
|
const location = resolveStore(dir);
|
|
21486
22540
|
if (location instanceof Error || !location.shared || !location.top)
|
|
21487
22541
|
return null;
|
|
@@ -21491,9 +22545,9 @@ var checkoutOf = (target) => {
|
|
|
21491
22545
|
} catch {}
|
|
21492
22546
|
return top;
|
|
21493
22547
|
};
|
|
21494
|
-
var markerFile2 = (top) =>
|
|
22548
|
+
var markerFile2 = (top) => path31.join(resolveStateDir(), "live-sessions", `${createHash16("sha256").update(checkoutKey(top)).digest("hex").slice(0, 32)}.json`);
|
|
21495
22549
|
var checkoutKey = (top, platform = process.platform) => {
|
|
21496
|
-
let key =
|
|
22550
|
+
let key = path31.resolve(top);
|
|
21497
22551
|
try {
|
|
21498
22552
|
key = fs23.realpathSync.native(key);
|
|
21499
22553
|
} catch {}
|
|
@@ -21588,7 +22642,7 @@ var claimLiveSession = (target, owner, deps = {}) => {
|
|
|
21588
22642
|
checkout,
|
|
21589
22643
|
heartbeatAt: now.toISOString()
|
|
21590
22644
|
};
|
|
21591
|
-
fs23.mkdirSync(
|
|
22645
|
+
fs23.mkdirSync(path31.dirname(file), { recursive: true });
|
|
21592
22646
|
const temp = `${file}.${process.pid}.tmp`;
|
|
21593
22647
|
fs23.writeFileSync(temp, JSON.stringify(marker));
|
|
21594
22648
|
fs23.renameSync(temp, file);
|
|
@@ -21600,22 +22654,22 @@ var NONE3 = { kind: "none" };
|
|
|
21600
22654
|
var CASE_INSENSITIVE = process.platform === "darwin" || process.platform === "win32";
|
|
21601
22655
|
var canonical2 = (file) => {
|
|
21602
22656
|
let rest = "";
|
|
21603
|
-
for (let dir =
|
|
22657
|
+
for (let dir = path32.resolve(file);; dir = path32.dirname(dir)) {
|
|
21604
22658
|
try {
|
|
21605
|
-
return
|
|
22659
|
+
return path32.join(realpathSync6.native(dir), rest);
|
|
21606
22660
|
} catch {
|
|
21607
|
-
if (
|
|
21608
|
-
return
|
|
21609
|
-
rest = rest ?
|
|
22661
|
+
if (path32.dirname(dir) === dir)
|
|
22662
|
+
return path32.resolve(file);
|
|
22663
|
+
rest = rest ? path32.join(path32.basename(dir), rest) : path32.basename(dir);
|
|
21610
22664
|
}
|
|
21611
22665
|
}
|
|
21612
22666
|
};
|
|
21613
22667
|
var exempt = (cwd, root, file, plans) => {
|
|
21614
22668
|
const fold = (value) => CASE_INSENSITIVE ? value.toLowerCase() : value;
|
|
21615
|
-
const relative =
|
|
21616
|
-
if (relative.startsWith("..") ||
|
|
22669
|
+
const relative = path32.relative(fold(canonical2(root)), fold(canonical2(path32.resolve(cwd, file))));
|
|
22670
|
+
if (relative.startsWith("..") || path32.isAbsolute(relative))
|
|
21617
22671
|
return true;
|
|
21618
|
-
const posix = relative.split(
|
|
22672
|
+
const posix = relative.split(path32.sep).join("/");
|
|
21619
22673
|
const segments = posix.split("/");
|
|
21620
22674
|
return /\.mdx?$/i.test(posix) || segments[0] === "docs" || segments.includes("plans") || [...plans].some((plan) => fold(plan) === posix);
|
|
21621
22675
|
};
|
|
@@ -21792,10 +22846,10 @@ var linkSources = (args, at) => {
|
|
|
21792
22846
|
const sources = names.filter((name, index) => into >= 0 ? name !== dest : index < names.length - 1 || names.length === 1);
|
|
21793
22847
|
const target = dest === undefined ? null : at(dest);
|
|
21794
22848
|
const symbolic = args.some((arg) => /^-[a-zA-Z]*s|^--symbolic$/.test(arg));
|
|
21795
|
-
const base = target === null ? null : into >= 0 || sources.length > 1 ||
|
|
22849
|
+
const base = target === null ? null : into >= 0 || sources.length > 1 || existsSync10(target) || /[\\/]$/.test(dest ?? "") ? target : path32.dirname(target);
|
|
21796
22850
|
return sources.flatMap((source) => {
|
|
21797
|
-
if (symbolic && base !== null && !
|
|
21798
|
-
return [
|
|
22851
|
+
if (symbolic && base !== null && !path32.isAbsolute(homeExpanded(source)))
|
|
22852
|
+
return [path32.resolve(base, source)];
|
|
21799
22853
|
const resolved = at(source);
|
|
21800
22854
|
return resolved ? [resolved] : [];
|
|
21801
22855
|
});
|
|
@@ -21824,7 +22878,7 @@ var shellWrites = (command, depth = 0) => {
|
|
|
21824
22878
|
return { writes: true, targets: unknown ? null : targets };
|
|
21825
22879
|
};
|
|
21826
22880
|
var cache2 = new Map;
|
|
21827
|
-
var planKeyOf = (root, plans) => [...plans].map((file) => fileSignature(
|
|
22881
|
+
var planKeyOf = (root, plans) => [...plans].map((file) => fileSignature(path32.resolve(root, file)) ?? "-").join("\x00");
|
|
21828
22882
|
var gateFor = (cwd) => {
|
|
21829
22883
|
const store = new TaskStore(cwd);
|
|
21830
22884
|
const listed = store.listTaskIndex();
|
|
@@ -21903,13 +22957,13 @@ var gitMutationDirs = (cwd, command, dialect) => {
|
|
|
21903
22957
|
return [];
|
|
21904
22958
|
if (sub === "stash" && ["list", "show"].includes(rest[0] ?? ""))
|
|
21905
22959
|
return [];
|
|
21906
|
-
return [
|
|
22960
|
+
return [path32.resolve(cwd, invocation.dir ?? ".")];
|
|
21907
22961
|
});
|
|
21908
22962
|
};
|
|
21909
22963
|
var CD2 = new Set(["cd", "chdir", "set-location", "sl"]);
|
|
21910
22964
|
var PUSHD2 = new Set(["pushd", "push-location"]);
|
|
21911
22965
|
var POPD2 = new Set(["popd", "pop-location"]);
|
|
21912
|
-
var UNKNOWN_DIR =
|
|
22966
|
+
var UNKNOWN_DIR = path32.resolve(path32.parse(process.cwd()).root, ".workit-unknown-dir");
|
|
21913
22967
|
var cdInto = (dir, words, strict = false) => {
|
|
21914
22968
|
const lost = strict ? UNKNOWN_DIR : undefined;
|
|
21915
22969
|
const named = words.slice(1).find((word) => !word.startsWith("-") || word === "-");
|
|
@@ -21918,7 +22972,7 @@ var cdInto = (dir, words, strict = false) => {
|
|
|
21918
22972
|
return lost;
|
|
21919
22973
|
if (dir === undefined)
|
|
21920
22974
|
return lost;
|
|
21921
|
-
return target === undefined ? os10.homedir() :
|
|
22975
|
+
return target === undefined ? os10.homedir() : path32.resolve(dir, target);
|
|
21922
22976
|
};
|
|
21923
22977
|
var quoteWord2 = (word) => /^[\w@%+=:,./-]+$/.test(word) ? word : `'${word.replaceAll("'", "'\\''")}'`;
|
|
21924
22978
|
var homeExpanded = (file) => {
|
|
@@ -21932,7 +22986,7 @@ var segmentTargets = (cwd, command, dialect, depth = 0, strict = false) => {
|
|
|
21932
22986
|
const groups = [];
|
|
21933
22987
|
const at = (file) => {
|
|
21934
22988
|
const home = homeExpanded(file);
|
|
21935
|
-
return
|
|
22989
|
+
return path32.isAbsolute(home) ? home : dir === undefined ? null : path32.resolve(dir, home);
|
|
21936
22990
|
};
|
|
21937
22991
|
for (const segment of segmentsOf(command, dialect)) {
|
|
21938
22992
|
if (segment.group === "open") {
|
|
@@ -21963,7 +23017,7 @@ var segmentTargets = (cwd, command, dialect, depth = 0, strict = false) => {
|
|
|
21963
23017
|
}
|
|
21964
23018
|
const inner = depth < 4 ? wrappedScript(words) ?? (strict ? shellScript2(words) : null) : null;
|
|
21965
23019
|
if (inner !== null && dir !== undefined)
|
|
21966
|
-
targets.push(...segmentTargets(dir, inner, "posix", depth + 1, strict));
|
|
23020
|
+
targets.push(...segmentTargets(workitRunDir(dir, words), inner, "posix", depth + 1, strict));
|
|
21967
23021
|
if (strict && dir !== undefined) {
|
|
21968
23022
|
const runIn = dir;
|
|
21969
23023
|
for (const sub of depth < 4 ? segment.subs ?? [] : [])
|
|
@@ -21972,7 +23026,7 @@ var segmentTargets = (cwd, command, dialect, depth = 0, strict = false) => {
|
|
|
21972
23026
|
for (const file of opaque ?? []) {
|
|
21973
23027
|
const resolved = at(file);
|
|
21974
23028
|
if (resolved)
|
|
21975
|
-
targets.push(file === "." ?
|
|
23029
|
+
targets.push(file === "." ? path32.join(resolved, DIR_TARGET) : resolved);
|
|
21976
23030
|
}
|
|
21977
23031
|
const ln = words.findIndex((word) => /^ln$/i.test(word.split(/[\\/]/).at(-1) ?? ""));
|
|
21978
23032
|
if (ln >= 0 && words.slice(0, ln).every((word) => PREFIXES2.has(word)))
|
|
@@ -21987,7 +23041,7 @@ var segmentTargets = (cwd, command, dialect, depth = 0, strict = false) => {
|
|
|
21987
23041
|
const edits = editsOf(segment.words);
|
|
21988
23042
|
if (edits === null) {
|
|
21989
23043
|
if (dir !== undefined)
|
|
21990
|
-
targets.push(
|
|
23044
|
+
targets.push(path32.join(dir, DIR_TARGET));
|
|
21991
23045
|
} else
|
|
21992
23046
|
for (const file of edits) {
|
|
21993
23047
|
const resolved = at(file);
|
|
@@ -21998,20 +23052,28 @@ var segmentTargets = (cwd, command, dialect, depth = 0, strict = false) => {
|
|
|
21998
23052
|
for (const invocation of workitInvocations(segment.words.map(quoteWord2).join(" "), "posix")) {
|
|
21999
23053
|
const { at: runsIn, args } = workitCwd(dir, invocation);
|
|
22000
23054
|
if (args[0] === "git" && MUTATING_WORKIT_GIT.has(args[1] ?? "") && !inert(args.slice(2), ""))
|
|
22001
|
-
targets.push(
|
|
23055
|
+
targets.push(path32.join(runsIn, DIR_TARGET));
|
|
22002
23056
|
}
|
|
22003
23057
|
}
|
|
22004
23058
|
return targets;
|
|
22005
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
|
+
};
|
|
22006
23068
|
var workitCwd = (dir, args) => {
|
|
22007
23069
|
const { rest, cwd } = skipWorkitGlobals(args);
|
|
22008
|
-
return { at: cwd === null ? dir :
|
|
23070
|
+
return { at: cwd === null ? dir : path32.resolve(dir, homeExpanded(cwd)), args: rest };
|
|
22009
23071
|
};
|
|
22010
23072
|
var shellMutationTargets = (cwd, command, dialect = "posix", strict = false) => {
|
|
22011
23073
|
try {
|
|
22012
23074
|
return [
|
|
22013
23075
|
...segmentTargets(cwd, command, dialect, 0, strict),
|
|
22014
|
-
...gitMutationDirs(cwd, command, dialect).map((dir) =>
|
|
23076
|
+
...gitMutationDirs(cwd, command, dialect).map((dir) => path32.join(dir, DIR_TARGET))
|
|
22015
23077
|
];
|
|
22016
23078
|
} catch {
|
|
22017
23079
|
return [];
|
|
@@ -22048,7 +23110,7 @@ var liveDeny = ({ marker, checkout, ageMs, file }) => {
|
|
|
22048
23110
|
var guarded = (targets) => {
|
|
22049
23111
|
const seen = new Set;
|
|
22050
23112
|
return targets.filter((target) => {
|
|
22051
|
-
const dir =
|
|
23113
|
+
const dir = path32.dirname(target);
|
|
22052
23114
|
if (seen.has(dir))
|
|
22053
23115
|
return false;
|
|
22054
23116
|
seen.add(dir);
|
|
@@ -22069,7 +23131,7 @@ var claimAll = (owner, targets, deps) => {
|
|
|
22069
23131
|
for (const target of guarded(targets))
|
|
22070
23132
|
claimLiveSession(target, owner, deps);
|
|
22071
23133
|
};
|
|
22072
|
-
var liveTargets = (cwd, paths) => paths?.length ? paths.map((file) =>
|
|
23134
|
+
var liveTargets = (cwd, paths) => paths?.length ? paths.map((file) => path32.resolve(cwd, file)) : [path32.join(cwd, DIR_TARGET)];
|
|
22073
23135
|
var issuedTarget = (cwd, targets) => {
|
|
22074
23136
|
const state = resolveStateDir();
|
|
22075
23137
|
const found = targets.find((target) => underIssued(state, cwd, homeExpanded(target), process.platform, canonical2));
|
|
@@ -22077,7 +23139,7 @@ var issuedTarget = (cwd, targets) => {
|
|
|
22077
23139
|
};
|
|
22078
23140
|
var underIssued = (state, cwd, target, platform = process.platform, real = (file) => file) => {
|
|
22079
23141
|
const win = platform === "win32";
|
|
22080
|
-
const p = win ?
|
|
23142
|
+
const p = win ? path32.win32 : path32.posix;
|
|
22081
23143
|
const fold = (value) => win || platform === "darwin" ? value.toLowerCase() : value;
|
|
22082
23144
|
const named = win ? target.replace(/^\/([a-zA-Z])(?=\/|$)/, "$1:") : target;
|
|
22083
23145
|
const relative = p.relative(fold(real(p.resolve(state))), fold(real(p.resolve(cwd, named))));
|
|
@@ -22168,6 +23230,24 @@ function beforeWriteGate(cwd, paths) {
|
|
|
22168
23230
|
}
|
|
22169
23231
|
// packages/workit-core/src/hooks/stop.ts
|
|
22170
23232
|
import { spawnSync as spawnSync18 } from "node:child_process";
|
|
23233
|
+
|
|
23234
|
+
// packages/workit-core/src/core/policy/current-judgment.ts
|
|
23235
|
+
function currentJudgment(cwd) {
|
|
23236
|
+
try {
|
|
23237
|
+
const store = new TaskStore(cwd);
|
|
23238
|
+
const listed = store.listTaskIndex();
|
|
23239
|
+
const key = store.currentKey();
|
|
23240
|
+
if (!listed.ok || !key.ok)
|
|
23241
|
+
return null;
|
|
23242
|
+
const entry = listed.data.filter((item) => item.key === key.data.key && item.status !== "closed").toSorted((left, right) => right.updatedAt.localeCompare(left.updatedAt))[0];
|
|
23243
|
+
const task = entry ? store.readTask(entry.id) : null;
|
|
23244
|
+
return task?.ok === true ? latestJudgment(task.data) : null;
|
|
23245
|
+
} catch {
|
|
23246
|
+
return null;
|
|
23247
|
+
}
|
|
23248
|
+
}
|
|
23249
|
+
|
|
23250
|
+
// packages/workit-core/src/hooks/stop.ts
|
|
22171
23251
|
var NONE4 = { kind: "none" };
|
|
22172
23252
|
var QUESTION = /[??؟](?=[\s*_`"')\]>]*(?:\s|$))|¿/;
|
|
22173
23253
|
var OFFER = /\b(?:let me know|want me to|should I|shall I|would you like|do you want|if you(?:'d| would) like|may I|can I go ahead)\b/i;
|
|
@@ -22260,29 +23340,32 @@ var redChecks = (rows, pr, head, endpoint) => {
|
|
|
22260
23340
|
reason: `PR #${pr}'s checks were last seen ${state} at ${short(head)}, and this workspace's endpoint is \`${endpoint}\`: run \`workit ci wait --pr ${pr}\`${state === "failing" ? " after fixing the failures `workit pr status` lists (then push)" : ""}.`
|
|
22261
23341
|
};
|
|
22262
23342
|
};
|
|
22263
|
-
var
|
|
22264
|
-
|
|
22265
|
-
|
|
22266
|
-
const listed = store.listTaskIndex();
|
|
22267
|
-
const key = store.currentKey();
|
|
22268
|
-
if (!listed.ok || !key.ok)
|
|
22269
|
-
return null;
|
|
22270
|
-
const entry = listed.data.filter((item) => item.key === key.data.key && item.status !== "closed").toSorted((left, right) => right.updatedAt.localeCompare(left.updatedAt))[0];
|
|
22271
|
-
const task = entry ? store.readTask(entry.id) : null;
|
|
22272
|
-
return task?.ok === true ? latestJudgment(task.data) : null;
|
|
22273
|
-
} catch {
|
|
22274
|
-
return null;
|
|
22275
|
-
}
|
|
23343
|
+
var prBase = (rows) => {
|
|
23344
|
+
const seen = rows.findLast((row) => (row.type === "pr.status" || row.type === "pr.created") && row.observer === "workit_cli" && typeof row.base === "string");
|
|
23345
|
+
return typeof seen?.base === "string" ? seen.base : null;
|
|
22276
23346
|
};
|
|
22277
|
-
var unverified = (cwd, branch, all, descriptor, verification) => {
|
|
23347
|
+
var unverified = (cwd, branch, all, descriptor, verification, head) => {
|
|
22278
23348
|
const check = checkVerdicts(cwd, branch, all);
|
|
22279
23349
|
if (check.accepted.accepted || check.accepted.reasons.includes("failing_verdict"))
|
|
22280
23350
|
return null;
|
|
22281
|
-
const judgment =
|
|
23351
|
+
const judgment = currentJudgment(cwd);
|
|
22282
23352
|
const high = judgment?.riskTier === "high";
|
|
22283
|
-
|
|
22284
|
-
|
|
22285
|
-
|
|
23353
|
+
let independent;
|
|
23354
|
+
if (verification === "proportional") {
|
|
23355
|
+
const lane = branchLane(cwd, {
|
|
23356
|
+
base: prBase(all.filter((row) => row.branch === branch)),
|
|
23357
|
+
head,
|
|
23358
|
+
judgment
|
|
23359
|
+
});
|
|
23360
|
+
const need = laneNeed(lane.lane);
|
|
23361
|
+
if (need === "none")
|
|
23362
|
+
return null;
|
|
23363
|
+
independent = need === "non-author";
|
|
23364
|
+
} else {
|
|
23365
|
+
if (judgment && !judgment.behaviorChange && !high)
|
|
23366
|
+
return null;
|
|
23367
|
+
independent = verification === "independent" || high;
|
|
23368
|
+
}
|
|
22286
23369
|
if (!independent && check.review === "self-reviewed")
|
|
22287
23370
|
return null;
|
|
22288
23371
|
const has = check.review === "self-reviewed" ? "only a self verdict" : "no current verdict";
|
|
@@ -22329,7 +23412,7 @@ function stopObligation(input, descriptor) {
|
|
|
22329
23412
|
}
|
|
22330
23413
|
if (!delivered || endpoint !== "green" && endpoint !== "merged")
|
|
22331
23414
|
return null;
|
|
22332
|
-
const left = unverified(input.cwd, branch, all, descriptor, autonomy.verification);
|
|
23415
|
+
const left = unverified(input.cwd, branch, all, descriptor, autonomy.verification, head);
|
|
22333
23416
|
return left ? { ...left, head } : null;
|
|
22334
23417
|
} catch {
|
|
22335
23418
|
return null;
|
|
@@ -22627,9 +23710,9 @@ var writePaths = (input) => {
|
|
|
22627
23710
|
};
|
|
22628
23711
|
// packages/workit-core/src/hooks/standing.ts
|
|
22629
23712
|
import fs24 from "node:fs";
|
|
22630
|
-
import
|
|
23713
|
+
import path33 from "node:path";
|
|
22631
23714
|
var readPlans = (store) => {
|
|
22632
|
-
const dir =
|
|
23715
|
+
const dir = path33.join(store, "fanouts");
|
|
22633
23716
|
let names;
|
|
22634
23717
|
try {
|
|
22635
23718
|
names = fs24.readdirSync(dir).filter((name) => name.endsWith(".json"));
|
|
@@ -22639,7 +23722,7 @@ var readPlans = (store) => {
|
|
|
22639
23722
|
const plans = [];
|
|
22640
23723
|
for (const name of names) {
|
|
22641
23724
|
try {
|
|
22642
|
-
const raw = JSON.parse(fs24.readFileSync(
|
|
23725
|
+
const raw = JSON.parse(fs24.readFileSync(path33.join(dir, name), "utf8"));
|
|
22643
23726
|
if (typeof raw.name !== "string" || !Array.isArray(raw.slices))
|
|
22644
23727
|
continue;
|
|
22645
23728
|
const texts = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string" && item !== "") : [];
|
|
@@ -22729,8 +23812,13 @@ var standingDelta = (input) => {
|
|
|
22729
23812
|
};
|
|
22730
23813
|
var sinceStart = new Map;
|
|
22731
23814
|
// packages/workit-core/src/hooks/cwd-normalize.ts
|
|
22732
|
-
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);
|
|
22733
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;
|
|
22734
23822
|
var stdinOnly = (words) => {
|
|
22735
23823
|
const [name, ...args] = words;
|
|
22736
23824
|
const operands = args.filter((w) => !w.startsWith("-"));
|
|
@@ -22752,53 +23840,67 @@ var cwdRewrite = (command) => {
|
|
|
22752
23840
|
return null;
|
|
22753
23841
|
if (head.words.some((w) => w === "--cwd" || w.startsWith("--cwd=")))
|
|
22754
23842
|
return null;
|
|
22755
|
-
const clean = (s) => !s.unanalyzable && !s.group && !s.subs?.length && !s.stdin?.length && !s.redirects.length;
|
|
22756
23843
|
if (!segments.every(clean))
|
|
22757
23844
|
return null;
|
|
22758
23845
|
if (!segments.slice(0, -1).every((s) => s.pipe))
|
|
22759
23846
|
return null;
|
|
22760
23847
|
if (!filters.every((s) => FILTERS.has(s.words[0] ?? "") && stdinOnly(s.words)))
|
|
22761
23848
|
return null;
|
|
22762
|
-
|
|
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)}`;
|
|
22763
23865
|
};
|
|
22764
23866
|
|
|
22765
23867
|
// packages/workit-core/src/hooks/worktree-confine.ts
|
|
22766
23868
|
import { createHash as createHash17 } from "node:crypto";
|
|
22767
23869
|
import fs25 from "node:fs";
|
|
22768
|
-
import
|
|
23870
|
+
import path34 from "node:path";
|
|
22769
23871
|
var FANOUT_TOKEN_FILE = "workit-fanout-token";
|
|
22770
23872
|
var NONE5 = { kind: "none" };
|
|
22771
23873
|
var CASE_INSENSITIVE2 = process.platform === "darwin" || process.platform === "win32";
|
|
22772
23874
|
var canonical3 = (file) => {
|
|
22773
23875
|
let rest = "";
|
|
22774
|
-
for (let dir =
|
|
23876
|
+
for (let dir = path34.resolve(file);; dir = path34.dirname(dir)) {
|
|
22775
23877
|
try {
|
|
22776
|
-
return
|
|
23878
|
+
return path34.join(fs25.realpathSync.native(dir), rest);
|
|
22777
23879
|
} catch {
|
|
22778
|
-
if (
|
|
22779
|
-
return
|
|
22780
|
-
rest = rest ?
|
|
23880
|
+
if (path34.dirname(dir) === dir)
|
|
23881
|
+
return path34.resolve(file);
|
|
23882
|
+
rest = rest ? path34.join(path34.basename(dir), rest) : path34.basename(dir);
|
|
22781
23883
|
}
|
|
22782
23884
|
}
|
|
22783
23885
|
};
|
|
22784
23886
|
var within = (child, parent) => {
|
|
22785
23887
|
const fold = (value) => CASE_INSENSITIVE2 ? value.toLowerCase() : value;
|
|
22786
|
-
const relative =
|
|
22787
|
-
return relative === "" || !relative.startsWith("..") && !
|
|
23888
|
+
const relative = path34.relative(fold(parent), fold(child));
|
|
23889
|
+
return relative === "" || !relative.startsWith("..") && !path34.isAbsolute(relative);
|
|
22788
23890
|
};
|
|
22789
23891
|
var checkoutOf2 = (file) => {
|
|
22790
|
-
for (let dir = canonical3(file);; dir =
|
|
22791
|
-
const dotGit =
|
|
23892
|
+
for (let dir = canonical3(file);; dir = path34.dirname(dir)) {
|
|
23893
|
+
const dotGit = path34.join(dir, ".git");
|
|
22792
23894
|
if (fs25.existsSync(dotGit)) {
|
|
22793
23895
|
if (!fs25.statSync(dotGit).isFile())
|
|
22794
23896
|
return { root: dir, workit: false };
|
|
22795
23897
|
const admin = /^gitdir:\s*(.+)$/mu.exec(fs25.readFileSync(dotGit, "utf8"))?.[1]?.trim();
|
|
22796
23898
|
return {
|
|
22797
23899
|
root: dir,
|
|
22798
|
-
workit: Boolean(admin) && fs25.existsSync(
|
|
23900
|
+
workit: Boolean(admin) && fs25.existsSync(path34.join(path34.resolve(dir, admin), FANOUT_TOKEN_FILE))
|
|
22799
23901
|
};
|
|
22800
23902
|
}
|
|
22801
|
-
if (
|
|
23903
|
+
if (path34.dirname(dir) === dir)
|
|
22802
23904
|
return null;
|
|
22803
23905
|
}
|
|
22804
23906
|
};
|
|
@@ -22810,8 +23912,8 @@ var workitWorktreeOf = (file) => {
|
|
|
22810
23912
|
return null;
|
|
22811
23913
|
}
|
|
22812
23914
|
};
|
|
22813
|
-
var bindingDir = () =>
|
|
22814
|
-
var bindingFile = (session, agentId) =>
|
|
23915
|
+
var bindingDir = () => path34.join(resolveStateDir(), "worktree-bind");
|
|
23916
|
+
var bindingFile = (session, agentId) => path34.join(bindingDir(), `${createHash17("sha256").update(`${session}\x00${agentId}`).digest("hex").slice(0, 32)}.json`);
|
|
22815
23917
|
var readBinding = (file) => {
|
|
22816
23918
|
try {
|
|
22817
23919
|
const value = JSON.parse(fs25.readFileSync(file, "utf8"));
|
|
@@ -22836,7 +23938,7 @@ var bind = (input, worktree) => {
|
|
|
22836
23938
|
agentId,
|
|
22837
23939
|
at: new Date().toISOString()
|
|
22838
23940
|
};
|
|
22839
|
-
fs25.mkdirSync(
|
|
23941
|
+
fs25.mkdirSync(path34.dirname(file), { recursive: true });
|
|
22840
23942
|
fs25.writeFileSync(file, JSON.stringify(binding));
|
|
22841
23943
|
try {
|
|
22842
23944
|
if (inWorkitWorkspace(input.cwd))
|
|
@@ -22855,27 +23957,27 @@ var hostIsolated = (cwd, agentId) => {
|
|
|
22855
23957
|
const checkout = checkoutOf2(cwd);
|
|
22856
23958
|
if (!checkout)
|
|
22857
23959
|
return false;
|
|
22858
|
-
const dotGit =
|
|
23960
|
+
const dotGit = path34.join(checkout.root, ".git");
|
|
22859
23961
|
if (!fs25.statSync(dotGit).isFile())
|
|
22860
23962
|
return false;
|
|
22861
23963
|
const admin = /^gitdir:\s*(.+)$/mu.exec(fs25.readFileSync(dotGit, "utf8"))?.[1]?.trim();
|
|
22862
23964
|
if (!admin)
|
|
22863
23965
|
return false;
|
|
22864
|
-
const adminDir =
|
|
22865
|
-
const back = fs25.readFileSync(
|
|
22866
|
-
if (
|
|
23966
|
+
const adminDir = path34.resolve(checkout.root, admin);
|
|
23967
|
+
const back = fs25.readFileSync(path34.join(adminDir, "gitdir"), "utf8").trim();
|
|
23968
|
+
if (path34.relative(canonical3(path34.resolve(adminDir, back)), canonical3(dotGit)) !== "")
|
|
22867
23969
|
return false;
|
|
22868
|
-
const common =
|
|
22869
|
-
const gitDir = fs25.existsSync(common) ?
|
|
22870
|
-
const expected =
|
|
22871
|
-
return
|
|
23970
|
+
const common = path34.join(adminDir, "commondir");
|
|
23971
|
+
const gitDir = fs25.existsSync(common) ? path34.resolve(adminDir, fs25.readFileSync(common, "utf8").trim()) : adminDir;
|
|
23972
|
+
const expected = path34.join(path34.dirname(canonical3(gitDir)), ".claude", "worktrees", `agent-${agentId}`);
|
|
23973
|
+
return path34.relative(canonical3(expected), checkout.root) === "";
|
|
22872
23974
|
} catch {
|
|
22873
23975
|
return false;
|
|
22874
23976
|
}
|
|
22875
23977
|
};
|
|
22876
|
-
var shown = (target) =>
|
|
23978
|
+
var shown = (target) => path34.basename(target) === ".workit-live-target" ? path34.dirname(target) : target;
|
|
22877
23979
|
var unboundDeny = (input, target) => {
|
|
22878
|
-
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.";
|
|
22879
23981
|
return {
|
|
22880
23982
|
kind: "deny",
|
|
22881
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}`,
|
|
@@ -22883,7 +23985,7 @@ var unboundDeny = (input, target) => {
|
|
|
22883
23985
|
};
|
|
22884
23986
|
};
|
|
22885
23987
|
var outsideDeny = (input, worktree, target) => {
|
|
22886
|
-
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.`;
|
|
22887
23989
|
return {
|
|
22888
23990
|
kind: "deny",
|
|
22889
23991
|
reason: `workit worktree confinement: implementer ${input.session.agentId} is bound to the worktree ${worktree}, and ${shown(target)} is outside it. ${unblock}`,
|
|
@@ -22892,7 +23994,7 @@ var outsideDeny = (input, worktree, target) => {
|
|
|
22892
23994
|
};
|
|
22893
23995
|
var unknownDirDeny = (input, worktree) => {
|
|
22894
23996
|
const wt = worktree ?? "<worktree>";
|
|
22895
|
-
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}\`.`;
|
|
22896
23998
|
return {
|
|
22897
23999
|
kind: "deny",
|
|
22898
24000
|
reason: `workit worktree confinement: implementer ${input.session.agentId} writes after an unknown directory change. ${unblock}`,
|
|
@@ -22903,7 +24005,7 @@ function confineImplementer(input, targets) {
|
|
|
22903
24005
|
try {
|
|
22904
24006
|
if (!input.session.agentId || hostIsolated(input.cwd, input.session.agentId))
|
|
22905
24007
|
return NONE5;
|
|
22906
|
-
if (targets.some((target) => within(
|
|
24008
|
+
if (targets.some((target) => within(path34.resolve(target), UNKNOWN_DIR)))
|
|
22907
24009
|
return unknownDirDeny(input, boundWorktree(input));
|
|
22908
24010
|
const bindings = canonical3(bindingDir());
|
|
22909
24011
|
const judged = targets.map((named) => ({ named, real: canonical3(named) })).filter(({ real }) => within(real, bindings) || checkoutOf2(real) !== null);
|
|
@@ -23050,7 +24152,7 @@ var subagentStartText = (input, descriptor, event) => {
|
|
|
23050
24152
|
return `${isolatedGit(input.cwd)}
|
|
23051
24153
|
${subagentStartText({ ...input, cwd: "" }, descriptor, event)}`;
|
|
23052
24154
|
if (role === "implementer")
|
|
23053
|
-
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.`;
|
|
23054
24156
|
const readOnly = `Workit observed ${who} as read-only/agent-guided.`;
|
|
23055
24157
|
if (role !== "judge" || !event.agentId)
|
|
23056
24158
|
return readOnly;
|
|
@@ -23067,7 +24169,7 @@ ${subagentStartText({ ...input, cwd: "" }, descriptor, event)}`;
|
|
|
23067
24169
|
var PRE_TOOL = new Set(["shell.pre", "write.pre", "tool.pre"]);
|
|
23068
24170
|
function handleHook(input, deps) {
|
|
23069
24171
|
const decision = decide(input, deps);
|
|
23070
|
-
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")
|
|
23071
24173
|
return decision;
|
|
23072
24174
|
const delta = standingDelta(input);
|
|
23073
24175
|
if (!delta)
|
|
@@ -23134,9 +24236,12 @@ function decide(input, deps) {
|
|
|
23134
24236
|
if (plan.kind === "rewrite" && !rewrites && canDeny)
|
|
23135
24237
|
return heavyDenial(event.command, plan);
|
|
23136
24238
|
const lease = plan.kind === "rewrite" && rewrites ? plan : null;
|
|
23137
|
-
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;
|
|
23138
24240
|
const aside = plan.kind === "passthrough" ? plan.note : null;
|
|
23139
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 };
|
|
23140
24245
|
const rewrittenTo = lease?.command ?? cwdCommand;
|
|
23141
24246
|
const rewritten = (text) => rewrittenTo ? {
|
|
23142
24247
|
kind: "rewrite",
|
|
@@ -23496,14 +24601,14 @@ var observeToolResult = (event, ctx) => {
|
|
|
23496
24601
|
var skillPromptNudge = (prompt, ctx) => process.env.WORKIT_PI_WORKER_SESSION ? null : promptNudge(hookInput(ctx, { kind: "context.turn", prompt }), prompt);
|
|
23497
24602
|
|
|
23498
24603
|
// packages/workit-pi/src/launches.ts
|
|
23499
|
-
import { mkdirSync as mkdirSync3, readFileSync as
|
|
24604
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync7, readdirSync as readdirSync5, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "node:fs";
|
|
23500
24605
|
import os11 from "node:os";
|
|
23501
|
-
import
|
|
24606
|
+
import path35 from "node:path";
|
|
23502
24607
|
var launchDir = (root) => {
|
|
23503
24608
|
const location = resolveStore(root);
|
|
23504
|
-
return location instanceof Error ? null :
|
|
24609
|
+
return location instanceof Error ? null : path35.join(location.dir, "pi-launches");
|
|
23505
24610
|
};
|
|
23506
|
-
var fileFor = (dir, workerId) =>
|
|
24611
|
+
var fileFor = (dir, workerId) => path35.join(dir, `${workerId.replace(/[^A-Za-z0-9-]/g, "_")}.json`);
|
|
23507
24612
|
var recordLaunch = (root, attempt) => {
|
|
23508
24613
|
const dir = launchDir(root);
|
|
23509
24614
|
if (!dir)
|
|
@@ -23549,7 +24654,7 @@ var abandonedLaunches = (root) => {
|
|
|
23549
24654
|
const found = [];
|
|
23550
24655
|
for (const name of names) {
|
|
23551
24656
|
try {
|
|
23552
|
-
const attempt = JSON.parse(
|
|
24657
|
+
const attempt = JSON.parse(readFileSync7(path35.join(dir, name), "utf8"));
|
|
23553
24658
|
if (typeof attempt.workerId === "string" && typeof attempt.session === "string") {
|
|
23554
24659
|
if (!alive(attempt))
|
|
23555
24660
|
found.push(attempt);
|