@brainervirus/workit-pi 8.22.3 → 8.24.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 +104 -1
- package/dist/workit.js +1715 -464
- package/package.json +1 -1
- package/skills/workit-fanout/SKILL.md +5 -5
- package/skills/workit-fanout/references/brief.md +17 -11
- package/skills/workit-fanout/references/orchestration.md +6 -6
- package/skills/workit-implement/SKILL.md +7 -5
- package/skills/workit-review/SKILL.md +3 -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,124 @@ function resolveRef(cwd, ref) {
|
|
|
8965
8965
|
return null;
|
|
8966
8966
|
return line(git(cwd, ["rev-parse", "--verify", "-q", `${ref}^{commit}`]));
|
|
8967
8967
|
}
|
|
8968
|
+
var LANE_DIFF = [
|
|
8969
|
+
"-c",
|
|
8970
|
+
"core.quotePath=false",
|
|
8971
|
+
"diff",
|
|
8972
|
+
"--no-color",
|
|
8973
|
+
"--no-ext-diff",
|
|
8974
|
+
"--no-textconv",
|
|
8975
|
+
"--no-renames"
|
|
8976
|
+
];
|
|
8977
|
+
var laneRange = (fork, head) => {
|
|
8978
|
+
if (!safeArg(fork) || head !== null && !safeArg(head))
|
|
8979
|
+
return null;
|
|
8980
|
+
return head === null ? [fork] : [fork, head];
|
|
8981
|
+
};
|
|
8982
|
+
function diffNumstat(cwd, fork, head) {
|
|
8983
|
+
const range = laneRange(fork, head);
|
|
8984
|
+
if (!range)
|
|
8985
|
+
return null;
|
|
8986
|
+
const run = git(cwd, [...LANE_DIFF, "--numstat", "-z", ...range, "--"]);
|
|
8987
|
+
if (!run.ok)
|
|
8988
|
+
return null;
|
|
8989
|
+
const out = [];
|
|
8990
|
+
for (const record of run.stdout.split("\x00")) {
|
|
8991
|
+
const match = /^(-|\d+)\t(-|\d+)\t(.+)$/su.exec(record.replace(/^\n/u, ""));
|
|
8992
|
+
if (!match)
|
|
8993
|
+
continue;
|
|
8994
|
+
const binary = match[1] === "-";
|
|
8995
|
+
out.push({
|
|
8996
|
+
path: match[3],
|
|
8997
|
+
added: binary ? 0 : Number(match[1]),
|
|
8998
|
+
deleted: binary ? 0 : Number(match[2]),
|
|
8999
|
+
binary
|
|
9000
|
+
});
|
|
9001
|
+
}
|
|
9002
|
+
return out;
|
|
9003
|
+
}
|
|
9004
|
+
function diffRaw(cwd, fork, head) {
|
|
9005
|
+
const range = laneRange(fork, head);
|
|
9006
|
+
if (!range)
|
|
9007
|
+
return null;
|
|
9008
|
+
const run = git(cwd, [...LANE_DIFF, "--raw", "-z", "--no-abbrev", ...range, "--"]);
|
|
9009
|
+
if (!run.ok)
|
|
9010
|
+
return null;
|
|
9011
|
+
const fields = run.stdout.split("\x00");
|
|
9012
|
+
const out = [];
|
|
9013
|
+
for (let index = 0;index + 1 < fields.length; index += 2) {
|
|
9014
|
+
const meta = /^:(\d{6}) (\d{6}) ([0-9a-f]+) ([0-9a-f]+) [A-Z]\d*$/u.exec(fields[index].replace(/^\n/u, ""));
|
|
9015
|
+
if (!meta)
|
|
9016
|
+
return null;
|
|
9017
|
+
out.push({
|
|
9018
|
+
path: fields[index + 1],
|
|
9019
|
+
oldMode: meta[1],
|
|
9020
|
+
newMode: meta[2],
|
|
9021
|
+
oldBlob: meta[3],
|
|
9022
|
+
newBlob: meta[4]
|
|
9023
|
+
});
|
|
9024
|
+
}
|
|
9025
|
+
return out;
|
|
9026
|
+
}
|
|
9027
|
+
function readBlobs(cwd, ids) {
|
|
9028
|
+
const out = new Map;
|
|
9029
|
+
const wanted = [...new Set(ids)].filter((id) => /^[0-9a-f]{40,64}$/u.test(id));
|
|
9030
|
+
if (!wanted.length)
|
|
9031
|
+
return out;
|
|
9032
|
+
const run = spawnSync3("git", ["cat-file", "--batch"], {
|
|
9033
|
+
cwd,
|
|
9034
|
+
input: `${wanted.join(`
|
|
9035
|
+
`)}
|
|
9036
|
+
`,
|
|
9037
|
+
maxBuffer: MAX_BUFFER,
|
|
9038
|
+
timeout: GIT_TIMEOUTS.local,
|
|
9039
|
+
killSignal: "SIGKILL",
|
|
9040
|
+
windowsHide: true
|
|
9041
|
+
});
|
|
9042
|
+
if (run.status !== 0 || !run.stdout)
|
|
9043
|
+
return null;
|
|
9044
|
+
const bytes = run.stdout;
|
|
9045
|
+
let at = 0;
|
|
9046
|
+
for (const id of wanted) {
|
|
9047
|
+
const end = bytes.indexOf(10, at);
|
|
9048
|
+
if (end < 0)
|
|
9049
|
+
return null;
|
|
9050
|
+
const header = bytes.subarray(at, end).toString("utf8").split(" ");
|
|
9051
|
+
at = end + 1;
|
|
9052
|
+
if (header[1] === "missing")
|
|
9053
|
+
continue;
|
|
9054
|
+
const size = Number(header[2]);
|
|
9055
|
+
if (header[0] !== id || !Number.isSafeInteger(size))
|
|
9056
|
+
return null;
|
|
9057
|
+
out.set(id, bytes.subarray(at, at + size));
|
|
9058
|
+
at += size + 1;
|
|
9059
|
+
}
|
|
9060
|
+
return out;
|
|
9061
|
+
}
|
|
9062
|
+
function treeFilesNamed(cwd, rev, name) {
|
|
9063
|
+
if (!safeArg(rev))
|
|
9064
|
+
return null;
|
|
9065
|
+
const run = git(cwd, ["ls-tree", "-r", "-z", "--full-tree", rev]);
|
|
9066
|
+
if (!run.ok)
|
|
9067
|
+
return null;
|
|
9068
|
+
const out = new Map;
|
|
9069
|
+
for (const record of run.stdout.split("\x00")) {
|
|
9070
|
+
const match = /^\d{6} blob ([0-9a-f]+)\t(.+)$/su.exec(record);
|
|
9071
|
+
if (match && (match[2] === name || match[2].endsWith(`/${name}`)))
|
|
9072
|
+
out.set(match[2], match[1]);
|
|
9073
|
+
}
|
|
9074
|
+
return out;
|
|
9075
|
+
}
|
|
9076
|
+
function fileAt(cwd, rev, file) {
|
|
9077
|
+
if (!safeArg(rev))
|
|
9078
|
+
return null;
|
|
9079
|
+
const run = git(cwd, ["cat-file", "blob", `${rev}:${file}`]);
|
|
9080
|
+
return run.ok ? run.stdout : null;
|
|
9081
|
+
}
|
|
9082
|
+
function untrackedFiles(cwd) {
|
|
9083
|
+
const run = git(cwd, ["ls-files", "-z", "--others", "--exclude-standard"]);
|
|
9084
|
+
return run.ok ? run.stdout.split("\x00").filter(Boolean) : [];
|
|
9085
|
+
}
|
|
8968
9086
|
var config2 = (cwd, key) => line(git(cwd, ["config", "--get", key]));
|
|
8969
9087
|
function currentBranch(cwd) {
|
|
8970
9088
|
return line(git(cwd, ["symbolic-ref", "--short", "-q", "HEAD"]));
|
|
@@ -12742,9 +12860,9 @@ var workerBlocksTransition = (transition, state) => transition === "pause" ? fal
|
|
|
12742
12860
|
|
|
12743
12861
|
// packages/workit-core/src/core/task-evaluation.ts
|
|
12744
12862
|
import { createHash as createHash12 } from "node:crypto";
|
|
12745
|
-
import * as
|
|
12746
|
-
import { spawnSync as
|
|
12747
|
-
import
|
|
12863
|
+
import * as fs15 from "node:fs";
|
|
12864
|
+
import { spawnSync as spawnSync11 } from "node:child_process";
|
|
12865
|
+
import path19 from "node:path";
|
|
12748
12866
|
|
|
12749
12867
|
// packages/workit-core/src/check-config.ts
|
|
12750
12868
|
import { spawnSync as spawnSync8 } from "node:child_process";
|
|
@@ -12974,8 +13092,8 @@ function checkCommand(args, version, onPath = workitAvailable()) {
|
|
|
12974
13092
|
}
|
|
12975
13093
|
|
|
12976
13094
|
// packages/workit-core/src/core/policy/derive.ts
|
|
12977
|
-
import { existsSync as
|
|
12978
|
-
import
|
|
13095
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
13096
|
+
import path18 from "node:path";
|
|
12979
13097
|
|
|
12980
13098
|
// packages/workit-core/src/ledger.ts
|
|
12981
13099
|
import { spawnSync as spawnSync9 } from "node:child_process";
|
|
@@ -13879,7 +13997,8 @@ function checkVerdicts(cwd, branch, ledgerRows) {
|
|
|
13879
13997
|
verdict: failing?.verdict ?? null,
|
|
13880
13998
|
reasons: failing ? ["failing_verdict"] : !newestCurrent ? ["no_verdict"] : newestCurrent.reasons.length ? newestCurrent.reasons : ["type_check_only"]
|
|
13881
13999
|
};
|
|
13882
|
-
const
|
|
14000
|
+
const latestSelf = failing ? undefined : verdicts.findLast((entry) => entry.current && !entry.independent && !untrusted(entry.reasons) && (STRONG_RESULTS.has(String(entry.verdict.result)) || FAILING.has(String(entry.verdict.result))));
|
|
14001
|
+
const selfEntry = latestSelf && STRONG_RESULTS.has(String(latestSelf.verdict.result)) ? latestSelf : undefined;
|
|
13883
14002
|
return {
|
|
13884
14003
|
branch,
|
|
13885
14004
|
head,
|
|
@@ -13923,169 +14042,905 @@ var OBSERVED_ONLY = new Set([
|
|
|
13923
14042
|
...PR_ROW_TYPES
|
|
13924
14043
|
]);
|
|
13925
14044
|
|
|
13926
|
-
// packages/workit-core/src/core/policy/
|
|
13927
|
-
|
|
13928
|
-
|
|
13929
|
-
|
|
13930
|
-
|
|
13931
|
-
|
|
13932
|
-
|
|
13933
|
-
|
|
13934
|
-
|
|
13935
|
-
|
|
13936
|
-
|
|
13937
|
-
|
|
13938
|
-
ruleId: spec.ruleId,
|
|
13939
|
-
scope: spec.scope,
|
|
13940
|
-
satisfaction: spec.satisfaction,
|
|
13941
|
-
before: spec.before,
|
|
13942
|
-
dependentAction: spec.dependentAction
|
|
13943
|
-
})
|
|
13944
|
-
});
|
|
13945
|
-
var sorted = (values) => [...new Set(values)].toSorted();
|
|
13946
|
-
var normalizedScope = (scope) => ({
|
|
13947
|
-
description: scope.description,
|
|
13948
|
-
paths: sorted(scope.paths),
|
|
13949
|
-
exclusions: sorted(scope.exclusions)
|
|
13950
|
-
});
|
|
13951
|
-
function deriveRequirements(judgment, scope, constraints, verification = "self") {
|
|
13952
|
-
const out = [];
|
|
13953
|
-
const base = { scope, dependentAction: null };
|
|
13954
|
-
if (judgment.behaviorChange)
|
|
13955
|
-
out.push(derived2({
|
|
13956
|
-
...base,
|
|
13957
|
-
ruleId: RULES.test,
|
|
13958
|
-
dimension: "testing",
|
|
13959
|
-
reason: "Behavior changes: the configured test check must pass on the final tree.",
|
|
13960
|
-
satisfaction: "Run `workit check test` on the final tree.",
|
|
13961
|
-
before: "close",
|
|
13962
|
-
acceptanceAllowed: false
|
|
13963
|
-
}));
|
|
13964
|
-
if (judgment.behaviorChange && judgment.riskTier === "normal")
|
|
13965
|
-
out.push(verification === "independent" ? derived2({
|
|
13966
|
-
...base,
|
|
13967
|
-
ruleId: RULES.nonAuthor,
|
|
13968
|
-
dimension: "review",
|
|
13969
|
-
reason: "This workspace requires independent verification: a session that did not author the change records the verdict.",
|
|
13970
|
-
satisfaction: "An independent session records a passing verdict: `workit ledger verdict verified --how <what it exercised>`.",
|
|
13971
|
-
before: "close",
|
|
13972
|
-
acceptanceAllowed: false
|
|
13973
|
-
}) : derived2({
|
|
13974
|
-
...base,
|
|
13975
|
-
ruleId: RULES.self,
|
|
13976
|
-
dimension: "review",
|
|
13977
|
-
reason: "A behavior change needs a recorded verdict; the author's own is allowed and is shown as self-reviewed, never verified.",
|
|
13978
|
-
satisfaction: "After an observed passing `workit check test`, record `workit ledger verdict tests-verified --self --how <what you ran>` (or have a verifier record one).",
|
|
13979
|
-
before: "close",
|
|
13980
|
-
acceptanceAllowed: false
|
|
13981
|
-
}));
|
|
13982
|
-
if (judgment.riskTier === "high")
|
|
13983
|
-
out.push(derived2({
|
|
13984
|
-
...base,
|
|
13985
|
-
ruleId: RULES.verified,
|
|
13986
|
-
dimension: "review",
|
|
13987
|
-
reason: "High risk: an independent verifier must exercise the live surface.",
|
|
13988
|
-
satisfaction: "An independent verifier runs the real surface and records `workit ledger verdict verified --kind live --how <what it exercised>`.",
|
|
13989
|
-
before: "close",
|
|
13990
|
-
acceptanceAllowed: false
|
|
13991
|
-
}));
|
|
13992
|
-
if (judgment.productChoiceOpen)
|
|
13993
|
-
out.push(derived2({
|
|
13994
|
-
...base,
|
|
13995
|
-
ruleId: RULES.product,
|
|
13996
|
-
dimension: "decisions",
|
|
13997
|
-
reason: "A product or preference choice is open; the user decides it before code is written.",
|
|
13998
|
-
satisfaction: 'Ask the user (with your recommendation), then record the answer: `workit ledger decision "<choice>" --why "<reason>"`.',
|
|
13999
|
-
before: "write",
|
|
14000
|
-
acceptanceAllowed: false
|
|
14001
|
-
}));
|
|
14002
|
-
if (judgment.needsPlan || judgment.riskTier === "high")
|
|
14003
|
-
out.push(derived2({
|
|
14004
|
-
...base,
|
|
14005
|
-
ruleId: RULES.plan,
|
|
14006
|
-
dimension: "artifacts",
|
|
14007
|
-
reason: judgment.needsPlan ? "The work needs a written plan or spec before implementation." : "High-risk work needs a written plan before implementation.",
|
|
14008
|
-
satisfaction: "Write the plan or spec (e.g. docs/plans/<slug>.md; docs and Markdown writes stay allowed), then record it: `workit policy assess --ref <path>`.",
|
|
14009
|
-
before: "write",
|
|
14010
|
-
acceptanceAllowed: true
|
|
14011
|
-
}));
|
|
14012
|
-
for (const constraint of constraints)
|
|
14013
|
-
for (const obligation of constraint.requires)
|
|
14014
|
-
out.push(derived2({
|
|
14015
|
-
ruleId: `project-constraint:${constraint.id}:${obligation.kind}:${sha256({
|
|
14016
|
-
kind: obligation.kind,
|
|
14017
|
-
scope: normalizedScope(obligation.scope),
|
|
14018
|
-
refs: obligation.refs.map((ref) => JSON.stringify(ref)).toSorted(),
|
|
14019
|
-
method: obligation.method,
|
|
14020
|
-
before: obligation.before,
|
|
14021
|
-
dependentAction: obligation.dependentAction
|
|
14022
|
-
})}`,
|
|
14023
|
-
dimension: obligation.method ?? (obligation.kind === "check" ? "verification" : obligation.kind === "decision" ? "decisions" : "challenge"),
|
|
14024
|
-
scope: obligation.scope,
|
|
14025
|
-
reason: `${constraint.kind} constraint requires ${obligation.kind}: ${constraint.statement}`,
|
|
14026
|
-
satisfaction: `Satisfy the constraint obligation using its referenced ${obligation.kind}.`,
|
|
14027
|
-
before: obligation.before,
|
|
14028
|
-
dependentAction: obligation.dependentAction,
|
|
14029
|
-
acceptanceAllowed: constraint.acceptanceAllowed && obligation.kind !== "decision"
|
|
14030
|
-
}));
|
|
14031
|
-
const seen = new Set;
|
|
14032
|
-
return out.filter((item) => seen.has(item.id) ? false : (seen.add(item.id), true));
|
|
14045
|
+
// packages/workit-core/src/core/policy/lane.ts
|
|
14046
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4 } from "node:fs";
|
|
14047
|
+
import path17 from "node:path";
|
|
14048
|
+
|
|
14049
|
+
// packages/workit-core/src/core/pr-create.ts
|
|
14050
|
+
import fs14 from "node:fs";
|
|
14051
|
+
import path16 from "node:path";
|
|
14052
|
+
import { spawnSync as spawnSync10 } from "node:child_process";
|
|
14053
|
+
// packages/workit-core/src/forge/pr-body.ts
|
|
14054
|
+
function parseGhIssue(value) {
|
|
14055
|
+
const m = /issues\/(\d+)/.exec(value);
|
|
14056
|
+
return m ? m[1] : String(value).trim().replace(/^#/, "");
|
|
14033
14057
|
}
|
|
14034
|
-
|
|
14035
|
-
|
|
14036
|
-
|
|
14037
|
-
|
|
14038
|
-
|
|
14039
|
-
|
|
14040
|
-
|
|
14041
|
-
|
|
14042
|
-
|
|
14043
|
-
|
|
14044
|
-
|
|
14045
|
-
|
|
14058
|
+
|
|
14059
|
+
// packages/workit-core/src/core/pr-create.ts
|
|
14060
|
+
function whichOnPath(tool) {
|
|
14061
|
+
const names = process.platform === "win32" ? [tool, `${tool}.exe`, `${tool}.cmd`] : [tool];
|
|
14062
|
+
for (const dir of (process.env.PATH ?? "").split(path16.delimiter)) {
|
|
14063
|
+
if (!dir)
|
|
14064
|
+
continue;
|
|
14065
|
+
for (const name of names) {
|
|
14066
|
+
const candidate = path16.join(dir, name);
|
|
14067
|
+
try {
|
|
14068
|
+
fs14.accessSync(candidate, fs14.constants.X_OK);
|
|
14069
|
+
return candidate;
|
|
14070
|
+
} catch {}
|
|
14046
14071
|
}
|
|
14047
|
-
}
|
|
14048
|
-
|
|
14049
|
-
|
|
14050
|
-
|
|
14051
|
-
|
|
14052
|
-
|
|
14053
|
-
|
|
14054
|
-
|
|
14055
|
-
|
|
14056
|
-
|
|
14057
|
-
|
|
14058
|
-
const
|
|
14059
|
-
|
|
14060
|
-
return row ? { met: true, reason: `ledger decision ${row.id} records the choice`, decisionIds: [] } : {
|
|
14061
|
-
met: false,
|
|
14062
|
-
reason: 'open product choice: ask the user, then `workit ledger decision "<choice>" --why "<reason>"` (or, if it is not open, `workit policy assess --judge product-choice=no --why "<reason>"`)',
|
|
14063
|
-
decisionIds: []
|
|
14064
|
-
};
|
|
14072
|
+
}
|
|
14073
|
+
return null;
|
|
14074
|
+
}
|
|
14075
|
+
var hostingCliAvailable = (provider) => whichOnPath(provider === "gitlab" ? "glab" : "gh") !== null;
|
|
14076
|
+
var pushRemote = (cwd) => {
|
|
14077
|
+
const result = spawnSync10("git", ["remote", "get-url", "--push", "--all", "origin"], {
|
|
14078
|
+
cwd,
|
|
14079
|
+
encoding: "utf8"
|
|
14080
|
+
});
|
|
14081
|
+
if (result.status !== 0)
|
|
14082
|
+
return null;
|
|
14083
|
+
const urls = (result.stdout ?? "").split(/\r?\n/u).map((url) => url.trim()).filter(Boolean);
|
|
14084
|
+
return urls.length === 1 ? urls[0] : null;
|
|
14065
14085
|
};
|
|
14066
|
-
|
|
14067
|
-
|
|
14068
|
-
|
|
14086
|
+
|
|
14087
|
+
// packages/workit-core/src/forge/redact.ts
|
|
14088
|
+
var KEY = String.raw`[A-Za-z0-9_.-]*?(?:token|secret|password|passwd|pwd|api[_-]?key|access[_-]?key|account[_-]?key|private[_-]?key|client[_-]?secret|credentials?|authorization|cookie|signature)[A-Za-z0-9_.-]*`;
|
|
14089
|
+
var ASSIGNMENT = new RegExp(String.raw`(?<![A-Za-z0-9])(["']?${KEY}["']?\s*[:=]\s*["']?)((?:bearer|basic|token|digest)\s+)?([^\s"',;&]+)`, "giu");
|
|
14090
|
+
var QUOTED_ASSIGNMENT = new RegExp(String.raw`(?<![A-Za-z0-9])(["']?${KEY}["']?\s*[:=]\s*)(["'])([^"'\n]*[^\s"'][^"'\n]*)\2`, "giu");
|
|
14091
|
+
var ANSI = new RegExp(String.raw`\u001b\[[0-9;?]*[ -/]*[@-~]|\u001b\][^\u0007]*\u0007`, "gu");
|
|
14092
|
+
|
|
14093
|
+
// packages/workit-core/src/git/ops.ts
|
|
14094
|
+
var COMMIT_TIMEOUT_MS = 10 * 60000;
|
|
14095
|
+
|
|
14096
|
+
// packages/workit-core/src/core/policy/attributes.ts
|
|
14097
|
+
function patternRegex(dir, pattern) {
|
|
14098
|
+
if (!pattern || pattern.endsWith("/") || pattern.startsWith('"') || pattern.startsWith("!"))
|
|
14099
|
+
return null;
|
|
14100
|
+
const anchored = pattern.includes("/");
|
|
14101
|
+
const body = pattern.replace(/^\//u, "");
|
|
14102
|
+
let source = "";
|
|
14103
|
+
for (let index = 0;index < body.length; index += 1) {
|
|
14104
|
+
const char = body[index];
|
|
14105
|
+
if (char === "*" && body[index + 1] === "*") {
|
|
14106
|
+
const slash = body[index + 2] === "/";
|
|
14107
|
+
source += slash ? "(?:.*/)?" : ".*";
|
|
14108
|
+
index += slash ? 2 : 1;
|
|
14109
|
+
} else if (char === "*")
|
|
14110
|
+
source += "[^/]*";
|
|
14111
|
+
else if (char === "?")
|
|
14112
|
+
source += "[^/]";
|
|
14113
|
+
else if (char === "[") {
|
|
14114
|
+
const end = body.indexOf("]", index + 2);
|
|
14115
|
+
if (end < 0)
|
|
14116
|
+
return null;
|
|
14117
|
+
const set = body.slice(index + 1, end).replace(/^!/u, "^").replace(/\\/gu, "\\\\");
|
|
14118
|
+
source += `[${set}]`;
|
|
14119
|
+
index = end;
|
|
14120
|
+
} else if (char === "\\" && index + 1 < body.length) {
|
|
14121
|
+
source += body[index + 1].replace(/[.*+?^${}()|[\]\\/]/gu, "\\$&");
|
|
14122
|
+
index += 1;
|
|
14123
|
+
} else
|
|
14124
|
+
source += char.replace(/[.*+?^${}()|[\]\\/]/gu, "\\$&");
|
|
14125
|
+
}
|
|
14126
|
+
const prefix = dir ? `${dir.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&")}/` : "";
|
|
14127
|
+
try {
|
|
14128
|
+
return new RegExp(anchored ? `^${prefix}${source}$` : `^${prefix}(?:.*/)?${source}$`, "u");
|
|
14129
|
+
} catch {
|
|
14130
|
+
return null;
|
|
14131
|
+
}
|
|
14132
|
+
}
|
|
14133
|
+
function rulesOf(dir, text) {
|
|
14134
|
+
const out = [];
|
|
14135
|
+
for (const raw of text.split(`
|
|
14136
|
+
`)) {
|
|
14137
|
+
const line = raw.trim();
|
|
14138
|
+
if (!line || line.startsWith("#") || line.startsWith("[attr]"))
|
|
14139
|
+
continue;
|
|
14140
|
+
const [pattern, ...attributes] = line.split(/\s+/u);
|
|
14141
|
+
let generated = null;
|
|
14142
|
+
for (const attribute of attributes) {
|
|
14143
|
+
if (attribute === "linguist-generated" || attribute === "linguist-generated=true")
|
|
14144
|
+
generated = true;
|
|
14145
|
+
else if (attribute === "-linguist-generated" || attribute === "!linguist-generated" || attribute.startsWith("linguist-generated="))
|
|
14146
|
+
generated = false;
|
|
14147
|
+
}
|
|
14148
|
+
if (generated === null)
|
|
14149
|
+
continue;
|
|
14150
|
+
const regex = patternRegex(dir, pattern);
|
|
14151
|
+
if (regex)
|
|
14152
|
+
out.push({ regex, generated });
|
|
14153
|
+
}
|
|
14154
|
+
return out;
|
|
14155
|
+
}
|
|
14156
|
+
function generatedByAttributes(files, paths) {
|
|
14157
|
+
const rules = [...files].map(([file, text]) => {
|
|
14158
|
+
const dir = file.includes("/") ? file.slice(0, file.lastIndexOf("/")) : "";
|
|
14069
14159
|
return {
|
|
14070
|
-
|
|
14071
|
-
|
|
14072
|
-
decisionIds: []
|
|
14160
|
+
depth: dir ? dir.split("/").length : 0,
|
|
14161
|
+
rules: rulesOf(dir, text)
|
|
14073
14162
|
};
|
|
14074
|
-
|
|
14075
|
-
|
|
14076
|
-
|
|
14163
|
+
}).toSorted((left, right) => left.depth - right.depth).flatMap((entry) => entry.rules);
|
|
14164
|
+
const out = new Set;
|
|
14165
|
+
for (const file of paths) {
|
|
14166
|
+
const last = rules.findLast((rule) => rule.regex.test(file));
|
|
14167
|
+
if (last?.generated)
|
|
14168
|
+
out.add(file);
|
|
14169
|
+
}
|
|
14170
|
+
return out;
|
|
14171
|
+
}
|
|
14172
|
+
|
|
14173
|
+
// packages/workit-core/src/core/policy/substance.ts
|
|
14174
|
+
var SLASH_BLOCK = [["/*", "*/"]];
|
|
14175
|
+
var C_LIKE = { line: ["//"], block: SLASH_BLOCK, quotes: `"'` };
|
|
14176
|
+
var SYNTAXES = Object.fromEntries([
|
|
14177
|
+
...["ts", "mts", "cts", "js", "mjs", "cjs"].map((ext) => [ext, { ...C_LIKE, js: true }]),
|
|
14178
|
+
...["java", "kt", "kts", "scala", "swift", "dart", "groovy"].map((ext) => [
|
|
14179
|
+
ext,
|
|
14180
|
+
{ ...C_LIKE, triple: true }
|
|
14181
|
+
]),
|
|
14182
|
+
["go", { ...C_LIKE, rawBacktick: true }],
|
|
14183
|
+
...["css", "scss", "less"].map((ext) => [
|
|
14184
|
+
ext,
|
|
14185
|
+
{ line: [], block: SLASH_BLOCK, quotes: `"'`, cssUrl: true }
|
|
14186
|
+
]),
|
|
14187
|
+
...["py", "pyi"].map((ext) => [
|
|
14188
|
+
ext,
|
|
14189
|
+
{ line: ["#"], block: [], quotes: `"'`, triple: true, indented: true, fstring: true }
|
|
14190
|
+
]),
|
|
14191
|
+
["toml", { line: ["#"], block: [], quotes: `"`, literal: "'", triple: true }],
|
|
14192
|
+
...["html", "htm", "xml", "svg", "xhtml"].map((ext) => [
|
|
14193
|
+
ext,
|
|
14194
|
+
{ line: [], block: [["<!--", "-->"]], quotes: "", indented: true }
|
|
14195
|
+
])
|
|
14196
|
+
]);
|
|
14197
|
+
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;
|
|
14198
|
+
var extensionOf = (file) => {
|
|
14199
|
+
const name = file.slice(file.lastIndexOf("/") + 1);
|
|
14200
|
+
const dot = name.lastIndexOf(".");
|
|
14201
|
+
return dot > 0 ? name.slice(dot + 1).toLowerCase() : "";
|
|
14202
|
+
};
|
|
14203
|
+
var CI_PATH = /^\.github\/|(?:^|\/)(?:\.gitlab-ci\.ya?ml|Jenkinsfile|azure-pipelines\.ya?ml|\.pre-commit-config\.ya?ml|\.circleci\/.*)$/u;
|
|
14204
|
+
var syntaxOf = (file) => CI_PATH.test(file) ? null : SYNTAXES[extensionOf(file)] ?? null;
|
|
14205
|
+
var SPACE = /[ \t\r\f\v]/u;
|
|
14206
|
+
var WORD = /[\w$]/u;
|
|
14207
|
+
var NO_SPACE_AFTER = "([{,;";
|
|
14208
|
+
var NO_SPACE_BEFORE = ")]},;";
|
|
14209
|
+
var REGEX_AFTER = "(,=:[!&|?{};+-*%<>~^";
|
|
14210
|
+
var REGEX_KEYWORDS = new Set([
|
|
14211
|
+
"return",
|
|
14212
|
+
"typeof",
|
|
14213
|
+
"instanceof",
|
|
14214
|
+
"case",
|
|
14215
|
+
"do",
|
|
14216
|
+
"else",
|
|
14217
|
+
"in",
|
|
14218
|
+
"of",
|
|
14219
|
+
"new",
|
|
14220
|
+
"delete",
|
|
14221
|
+
"void",
|
|
14222
|
+
"throw",
|
|
14223
|
+
"yield",
|
|
14224
|
+
"await"
|
|
14225
|
+
]);
|
|
14226
|
+
function substanceOf(file, text) {
|
|
14227
|
+
const syntax = syntaxOf(file);
|
|
14228
|
+
if (!syntax)
|
|
14229
|
+
return plainSubstance(text);
|
|
14230
|
+
const markup = syntax.block.some(([open]) => open === "<!--");
|
|
14231
|
+
if (markup && (/<(?:script|style)\b|<!\[CDATA\[|<\?/iu.test(text) || /[^>\s][ \t]*<!--/u.test(text)))
|
|
14232
|
+
return plainSubstance(text);
|
|
14233
|
+
return lex(syntax, text);
|
|
14234
|
+
}
|
|
14235
|
+
function plainSubstance(text) {
|
|
14236
|
+
return text.split(`
|
|
14237
|
+
`).map((line) => line.replace(/[ \t\r\f\v]+$/u, "")).filter(Boolean).join(`
|
|
14238
|
+
`);
|
|
14239
|
+
}
|
|
14240
|
+
function lex(syntax, text) {
|
|
14241
|
+
const collapse = !syntax.indented;
|
|
14242
|
+
let out = "";
|
|
14243
|
+
let lineStart = 0;
|
|
14244
|
+
let kept = 0;
|
|
14245
|
+
let pendingSpace = false;
|
|
14246
|
+
const frames = [{ kind: "code", braces: 0 }];
|
|
14247
|
+
let index = 0;
|
|
14248
|
+
if (text.startsWith("#!")) {
|
|
14249
|
+
const end = text.indexOf(`
|
|
14250
|
+
`);
|
|
14251
|
+
out = end < 0 ? text : text.slice(0, end);
|
|
14252
|
+
kept = out.length;
|
|
14253
|
+
index = end < 0 ? text.length : end;
|
|
14254
|
+
}
|
|
14255
|
+
const emitCode = (chunk) => {
|
|
14256
|
+
if (collapse && pendingSpace && out.length > lineStart) {
|
|
14257
|
+
const before = out[out.length - 1];
|
|
14258
|
+
if (!NO_SPACE_AFTER.includes(before) && !NO_SPACE_BEFORE.includes(chunk[0]))
|
|
14259
|
+
out += " ";
|
|
14260
|
+
}
|
|
14261
|
+
pendingSpace = false;
|
|
14262
|
+
out += chunk;
|
|
14263
|
+
};
|
|
14264
|
+
const emitVerbatim = (chunk) => {
|
|
14265
|
+
emitCode(chunk);
|
|
14266
|
+
kept = out.length;
|
|
14267
|
+
};
|
|
14268
|
+
const newline = () => {
|
|
14269
|
+
if (!collapse) {
|
|
14270
|
+
let end = out.length;
|
|
14271
|
+
while (end > Math.max(lineStart, kept) && SPACE.test(out[end - 1]))
|
|
14272
|
+
end -= 1;
|
|
14273
|
+
out = out.slice(0, end);
|
|
14274
|
+
}
|
|
14275
|
+
pendingSpace = false;
|
|
14276
|
+
if (out.length > lineStart) {
|
|
14277
|
+
out += `
|
|
14278
|
+
`;
|
|
14279
|
+
lineStart = out.length;
|
|
14280
|
+
}
|
|
14281
|
+
};
|
|
14282
|
+
const regexAllowed = () => {
|
|
14283
|
+
let end = out.length;
|
|
14284
|
+
while (end > 0 && /\s/u.test(out[end - 1]))
|
|
14285
|
+
end -= 1;
|
|
14286
|
+
if (end === 0 || REGEX_AFTER.includes(out[end - 1]))
|
|
14287
|
+
return true;
|
|
14288
|
+
let start = end;
|
|
14289
|
+
while (start > 0 && WORD.test(out[start - 1]))
|
|
14290
|
+
start -= 1;
|
|
14291
|
+
return REGEX_KEYWORDS.has(out.slice(start, end));
|
|
14292
|
+
};
|
|
14293
|
+
const scanString = (quote, escapes, multiline) => {
|
|
14294
|
+
const prefix = text.slice(Math.max(0, index - 2), index).match(/[a-z]*$/iu)?.[0] ?? "";
|
|
14295
|
+
const fstring = syntax.fstring === true && /f/iu.test(prefix);
|
|
14296
|
+
let end = index + quote.length;
|
|
14297
|
+
while (end < text.length) {
|
|
14298
|
+
const char = text[end];
|
|
14299
|
+
if (escapes && char === "\\") {
|
|
14300
|
+
end += 2;
|
|
14301
|
+
continue;
|
|
14302
|
+
}
|
|
14303
|
+
if (text.startsWith(quote, end)) {
|
|
14304
|
+
if (fstring && unbalanced(text.slice(index + quote.length, end)))
|
|
14305
|
+
return false;
|
|
14306
|
+
emitVerbatim(text.slice(index, end + quote.length));
|
|
14307
|
+
index = end + quote.length;
|
|
14308
|
+
return true;
|
|
14309
|
+
}
|
|
14310
|
+
if (char === `
|
|
14311
|
+
` && !multiline)
|
|
14312
|
+
return false;
|
|
14313
|
+
end += 1;
|
|
14314
|
+
}
|
|
14315
|
+
return false;
|
|
14316
|
+
};
|
|
14317
|
+
while (index < text.length) {
|
|
14318
|
+
const frame = frames[frames.length - 1];
|
|
14319
|
+
const char = text[index];
|
|
14320
|
+
if (frame.kind === "template") {
|
|
14321
|
+
if (char === "\\") {
|
|
14322
|
+
emitVerbatim(text.slice(index, index + 2));
|
|
14323
|
+
index += 2;
|
|
14324
|
+
} else if (char === "`") {
|
|
14325
|
+
emitVerbatim(char);
|
|
14326
|
+
frames.pop();
|
|
14327
|
+
index += 1;
|
|
14328
|
+
} else if (text.startsWith("${", index)) {
|
|
14329
|
+
emitVerbatim("${");
|
|
14330
|
+
frames.push({ kind: "code", braces: 0 });
|
|
14331
|
+
index += 2;
|
|
14332
|
+
} else {
|
|
14333
|
+
out += char;
|
|
14334
|
+
kept = out.length;
|
|
14335
|
+
index += 1;
|
|
14336
|
+
}
|
|
14337
|
+
continue;
|
|
14338
|
+
}
|
|
14339
|
+
if (char === `
|
|
14340
|
+
`) {
|
|
14341
|
+
newline();
|
|
14342
|
+
index += 1;
|
|
14343
|
+
continue;
|
|
14344
|
+
}
|
|
14345
|
+
if (SPACE.test(char)) {
|
|
14346
|
+
if (collapse)
|
|
14347
|
+
pendingSpace = true;
|
|
14348
|
+
else
|
|
14349
|
+
out += char;
|
|
14350
|
+
index += 1;
|
|
14351
|
+
continue;
|
|
14352
|
+
}
|
|
14353
|
+
const lineOpener = syntax.line.find((opener) => text.startsWith(opener, index));
|
|
14354
|
+
if (lineOpener) {
|
|
14355
|
+
const end = text.indexOf(`
|
|
14356
|
+
`, index);
|
|
14357
|
+
const comment = text.slice(index, end < 0 ? text.length : end);
|
|
14358
|
+
if (DIRECTIVE.test(comment.slice(lineOpener.length)))
|
|
14359
|
+
emitVerbatim(comment);
|
|
14360
|
+
else
|
|
14361
|
+
pendingSpace = true;
|
|
14362
|
+
index = end < 0 ? text.length : end;
|
|
14363
|
+
continue;
|
|
14364
|
+
}
|
|
14365
|
+
const block = syntax.block.find(([open]) => text.startsWith(open, index));
|
|
14366
|
+
if (block) {
|
|
14367
|
+
const end = text.indexOf(block[1], index + block[0].length);
|
|
14368
|
+
if (end < 0)
|
|
14369
|
+
return null;
|
|
14370
|
+
const comment = text.slice(index, end + block[1].length);
|
|
14371
|
+
if (DIRECTIVE.test(comment.slice(block[0].length, -block[1].length)))
|
|
14372
|
+
emitVerbatim(comment);
|
|
14373
|
+
else
|
|
14374
|
+
pendingSpace = true;
|
|
14375
|
+
if (comment.includes(`
|
|
14376
|
+
`))
|
|
14377
|
+
newline();
|
|
14378
|
+
index = end + block[1].length;
|
|
14379
|
+
continue;
|
|
14380
|
+
}
|
|
14381
|
+
if (syntax.triple && (char === '"' || char === "'") && text.startsWith(char.repeat(3), index)) {
|
|
14382
|
+
if (!scanString(char.repeat(3), !syntax.literal?.includes(char), true))
|
|
14383
|
+
return null;
|
|
14384
|
+
continue;
|
|
14385
|
+
}
|
|
14386
|
+
if (syntax.quotes.includes(char)) {
|
|
14387
|
+
if (!scanString(char, true, Boolean(syntax.multiline)))
|
|
14388
|
+
return null;
|
|
14389
|
+
continue;
|
|
14390
|
+
}
|
|
14391
|
+
if (syntax.literal?.includes(char)) {
|
|
14392
|
+
if (!scanString(char, false, false))
|
|
14393
|
+
return null;
|
|
14394
|
+
continue;
|
|
14395
|
+
}
|
|
14396
|
+
if (syntax.rawBacktick && char === "`") {
|
|
14397
|
+
if (!scanString("`", false, true))
|
|
14398
|
+
return null;
|
|
14399
|
+
continue;
|
|
14400
|
+
}
|
|
14401
|
+
if (syntax.js && char === "`") {
|
|
14402
|
+
emitVerbatim(char);
|
|
14403
|
+
frames.push({ kind: "template" });
|
|
14404
|
+
index += 1;
|
|
14405
|
+
continue;
|
|
14406
|
+
}
|
|
14407
|
+
if (syntax.cssUrl && /^url\(\s*[^"')\s]/iu.test(text.slice(index, index + 64)))
|
|
14408
|
+
return null;
|
|
14409
|
+
if (syntax.js && char === "/" && !regexAllowed() && out.trimEnd().endsWith(")")) {
|
|
14410
|
+
const lineEnd = text.indexOf(`
|
|
14411
|
+
`, index);
|
|
14412
|
+
if (text.slice(index + 1, lineEnd < 0 ? text.length : lineEnd).includes("/"))
|
|
14413
|
+
return null;
|
|
14414
|
+
}
|
|
14415
|
+
if (syntax.js && char === "/" && regexAllowed()) {
|
|
14416
|
+
let end = index + 1;
|
|
14417
|
+
let inClass = false;
|
|
14418
|
+
while (end < text.length && (inClass || text[end] !== "/")) {
|
|
14419
|
+
if (text[end] === `
|
|
14420
|
+
`)
|
|
14421
|
+
return null;
|
|
14422
|
+
if (text[end] === "\\")
|
|
14423
|
+
end += 1;
|
|
14424
|
+
else if (text[end] === "[")
|
|
14425
|
+
inClass = true;
|
|
14426
|
+
else if (text[end] === "]")
|
|
14427
|
+
inClass = false;
|
|
14428
|
+
end += 1;
|
|
14429
|
+
}
|
|
14430
|
+
if (end >= text.length)
|
|
14431
|
+
return null;
|
|
14432
|
+
emitVerbatim(text.slice(index, end + 1));
|
|
14433
|
+
index = end + 1;
|
|
14434
|
+
continue;
|
|
14435
|
+
}
|
|
14436
|
+
if (frames.length > 1 && frame.kind === "code") {
|
|
14437
|
+
if (char === "{")
|
|
14438
|
+
frame.braces += 1;
|
|
14439
|
+
else if (char === "}") {
|
|
14440
|
+
if (frame.braces === 0) {
|
|
14441
|
+
emitVerbatim(char);
|
|
14442
|
+
frames.pop();
|
|
14443
|
+
index += 1;
|
|
14444
|
+
continue;
|
|
14445
|
+
}
|
|
14446
|
+
frame.braces -= 1;
|
|
14447
|
+
}
|
|
14448
|
+
}
|
|
14449
|
+
emitCode(char);
|
|
14450
|
+
index += 1;
|
|
14451
|
+
}
|
|
14452
|
+
if (frames.length !== 1)
|
|
14453
|
+
return null;
|
|
14454
|
+
newline();
|
|
14455
|
+
return out;
|
|
14456
|
+
}
|
|
14457
|
+
function unbalanced(body) {
|
|
14458
|
+
let depth = 0;
|
|
14459
|
+
for (const part of body.replaceAll("{{", "").replaceAll("}}", "")) {
|
|
14460
|
+
if (part === "{")
|
|
14461
|
+
depth += 1;
|
|
14462
|
+
else if (part === "}")
|
|
14463
|
+
depth = Math.max(0, depth - 1);
|
|
14464
|
+
}
|
|
14465
|
+
return depth > 0;
|
|
14466
|
+
}
|
|
14467
|
+
function hasOpaqueLineBreak(text) {
|
|
14468
|
+
return /\r(?!\n)|[\u2028\u2029]/u.test(text);
|
|
14469
|
+
}
|
|
14470
|
+
var decoder = new TextDecoder("utf-8", { fatal: true });
|
|
14471
|
+
function textOf(bytes) {
|
|
14472
|
+
if (bytes.includes(0))
|
|
14473
|
+
return null;
|
|
14474
|
+
try {
|
|
14475
|
+
return decoder.decode(bytes);
|
|
14476
|
+
} catch {
|
|
14477
|
+
return null;
|
|
14478
|
+
}
|
|
14479
|
+
}
|
|
14480
|
+
function cosmeticChange(file, before, after) {
|
|
14481
|
+
const left = textOf(before);
|
|
14482
|
+
const right = textOf(after);
|
|
14483
|
+
if (left === null || right === null)
|
|
14484
|
+
return false;
|
|
14485
|
+
if (hasOpaqueLineBreak(left) || hasOpaqueLineBreak(right))
|
|
14486
|
+
return false;
|
|
14487
|
+
const a = substanceOf(file, left);
|
|
14488
|
+
return a !== null && a === substanceOf(file, right);
|
|
14489
|
+
}
|
|
14490
|
+
|
|
14491
|
+
// packages/workit-core/src/core/policy/lane.ts
|
|
14492
|
+
var LANE_THRESHOLDS = {
|
|
14493
|
+
selfMaxLines: 100,
|
|
14494
|
+
selfMaxFiles: 5,
|
|
14495
|
+
fullMinLines: 500
|
|
14496
|
+
};
|
|
14497
|
+
var RISK_TOKENS = new Set([
|
|
14498
|
+
"auth",
|
|
14499
|
+
"authn",
|
|
14500
|
+
"authz",
|
|
14501
|
+
"oauth",
|
|
14502
|
+
"security",
|
|
14503
|
+
"permission",
|
|
14504
|
+
"permissions",
|
|
14505
|
+
"secret",
|
|
14506
|
+
"secrets",
|
|
14507
|
+
"crypto",
|
|
14508
|
+
"gate",
|
|
14509
|
+
"gates",
|
|
14510
|
+
"policy",
|
|
14511
|
+
"policies",
|
|
14512
|
+
"hook",
|
|
14513
|
+
"hooks",
|
|
14514
|
+
"husky",
|
|
14515
|
+
"lefthook",
|
|
14516
|
+
"release",
|
|
14517
|
+
"releases",
|
|
14518
|
+
"migration",
|
|
14519
|
+
"migrations",
|
|
14520
|
+
"migrate",
|
|
14521
|
+
"workflows"
|
|
14522
|
+
]);
|
|
14523
|
+
var RISK_FILES = new Set([
|
|
14524
|
+
".gitlab-ci.yml",
|
|
14525
|
+
"Jenkinsfile",
|
|
14526
|
+
"azure-pipelines.yml",
|
|
14527
|
+
".pre-commit-config.yaml",
|
|
14528
|
+
".workit/risk-paths"
|
|
14529
|
+
]);
|
|
14530
|
+
var RISK_NAMES = new Set([".gitattributes"]);
|
|
14531
|
+
var RISK_PATHS_FILE = ".workit/risk-paths";
|
|
14532
|
+
var LOCKFILES = new Set([
|
|
14533
|
+
"bun.lock",
|
|
14534
|
+
"bun.lockb",
|
|
14535
|
+
"package-lock.json",
|
|
14536
|
+
"npm-shrinkwrap.json",
|
|
14537
|
+
"yarn.lock",
|
|
14538
|
+
"pnpm-lock.yaml",
|
|
14539
|
+
"Cargo.lock",
|
|
14540
|
+
"go.sum",
|
|
14541
|
+
"poetry.lock",
|
|
14542
|
+
"uv.lock",
|
|
14543
|
+
"Pipfile.lock",
|
|
14544
|
+
"Gemfile.lock",
|
|
14545
|
+
"composer.lock"
|
|
14546
|
+
]);
|
|
14547
|
+
var DOC_EXT = /\.(?:md|mdx|markdown|rst|adoc|txt)$/iu;
|
|
14548
|
+
var DOC_NAMES = /^(?:license|licence|notice|authors|changelog|changes|contributors)$/iu;
|
|
14549
|
+
var INSTRUCTIONS = /(?:^|\/)(?:AGENTS|CLAUDE|GEMINI|SKILL)\.md$|(?:^|\/)(?:skills|agents|commands|rules|prompts)\/[^/]+/iu;
|
|
14550
|
+
var TEST_PATH = /(?:^|\/)(?:test|tests|__tests__|spec|specs|fixtures)\/|\.(?:test|spec)\.[^/]+$/iu;
|
|
14551
|
+
var basename = (file) => file.slice(file.lastIndexOf("/") + 1);
|
|
14552
|
+
var isDoc = (file) => !INSTRUCTIONS.test(file) && (DOC_EXT.test(file) || DOC_NAMES.test(basename(file)));
|
|
14553
|
+
function kindOf(file) {
|
|
14554
|
+
if (file.binary)
|
|
14555
|
+
return "behavior";
|
|
14556
|
+
if (file.generated)
|
|
14557
|
+
return "generated";
|
|
14558
|
+
if (LOCKFILES.has(basename(file.path)))
|
|
14559
|
+
return "lockfile";
|
|
14560
|
+
if (isDoc(file.path))
|
|
14561
|
+
return "doc";
|
|
14562
|
+
if (file.cosmetic)
|
|
14563
|
+
return "cosmetic";
|
|
14564
|
+
return TEST_PATH.test(file.path) ? "test" : "behavior";
|
|
14565
|
+
}
|
|
14566
|
+
var globCache = new Map;
|
|
14567
|
+
function globMatch(pattern, file) {
|
|
14568
|
+
let regex = globCache.get(pattern);
|
|
14569
|
+
if (!regex) {
|
|
14570
|
+
let source = "";
|
|
14571
|
+
for (let index = 0;index < pattern.length; index += 1) {
|
|
14572
|
+
const char = pattern[index];
|
|
14573
|
+
if (char === "*" && pattern[index + 1] === "*") {
|
|
14574
|
+
source += pattern[index + 2] === "/" ? "(?:[^/]*/)*" : ".*";
|
|
14575
|
+
index += pattern[index + 2] === "/" ? 2 : 1;
|
|
14576
|
+
} else if (char === "*")
|
|
14577
|
+
source += "[^/]*";
|
|
14578
|
+
else if (char === "?")
|
|
14579
|
+
source += "[^/]";
|
|
14580
|
+
else
|
|
14581
|
+
source += char.replace(/[.+^${}()|[\]\\]/gu, "\\$&");
|
|
14582
|
+
}
|
|
14583
|
+
regex = new RegExp(`^${source}$`, "u");
|
|
14584
|
+
globCache.set(pattern, regex);
|
|
14585
|
+
}
|
|
14586
|
+
return regex.test(file) || file.startsWith(`${pattern.replace(/\/$/u, "")}/`);
|
|
14587
|
+
}
|
|
14588
|
+
function isRiskPath(file, extra = []) {
|
|
14589
|
+
if (extra.some((pattern) => globMatch(pattern, file)))
|
|
14590
|
+
return true;
|
|
14591
|
+
if (RISK_FILES.has(file) || RISK_NAMES.has(basename(file)))
|
|
14592
|
+
return true;
|
|
14593
|
+
if (TEST_PATH.test(file) || isDoc(file))
|
|
14594
|
+
return false;
|
|
14595
|
+
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)));
|
|
14596
|
+
}
|
|
14597
|
+
var parseRiskPaths = (text) => text.split(`
|
|
14598
|
+
`).map((line) => line.trim()).filter((line) => line && !line.startsWith("#")).map((line) => line.replace(/^\//u, ""));
|
|
14599
|
+
function computeLane(input) {
|
|
14600
|
+
const riskFiles = input.files.filter((file) => isRiskPath(file.path, input.riskPaths)).map((file) => file.path);
|
|
14601
|
+
const risky = new Set(riskFiles);
|
|
14602
|
+
const kinds = input.files.map((file) => ({
|
|
14603
|
+
file,
|
|
14604
|
+
kind: risky.has(file.path) ? "behavior" : kindOf(file)
|
|
14605
|
+
}));
|
|
14606
|
+
const behavior = kinds.filter((entry) => entry.kind === "behavior" || entry.kind === "test" || entry.kind === "lockfile");
|
|
14607
|
+
const lines = behavior.filter((entry) => entry.kind !== "lockfile").reduce((sum, entry) => sum + entry.file.added + entry.file.deleted, 0);
|
|
14608
|
+
const opaque = behavior.filter((entry) => entry.kind === "lockfile" || entry.file.binary);
|
|
14609
|
+
const result = (lane, reasons) => ({
|
|
14610
|
+
lane,
|
|
14611
|
+
reasons,
|
|
14612
|
+
lines,
|
|
14613
|
+
files: behavior.length,
|
|
14614
|
+
riskFiles
|
|
14615
|
+
});
|
|
14616
|
+
const { selfMaxLines, selfMaxFiles, fullMinLines } = LANE_THRESHOLDS;
|
|
14617
|
+
if (input.riskTier === "high")
|
|
14618
|
+
return result("full", ["the task judgment records high risk"]);
|
|
14619
|
+
if (riskFiles.length)
|
|
14620
|
+
return result("full", [
|
|
14621
|
+
`touches a risk path: ${riskFiles.slice(0, 3).join(", ")}${riskFiles.length > 3 ? ", …" : ""}`
|
|
14622
|
+
]);
|
|
14623
|
+
if (!behavior.length)
|
|
14624
|
+
return result("none", [
|
|
14625
|
+
input.files.length ? "only docs, comments, formatting or generated copies changed" : "no changes"
|
|
14626
|
+
]);
|
|
14627
|
+
if (lines >= fullMinLines)
|
|
14628
|
+
return result("full", [`large diff: ${lines} lines (≥ ${fullMinLines})`]);
|
|
14629
|
+
if (lines > selfMaxLines)
|
|
14630
|
+
return result("focused", [`${lines} lines changed (> ${selfMaxLines})`]);
|
|
14631
|
+
if (behavior.length > selfMaxFiles)
|
|
14632
|
+
return result("focused", [`${behavior.length} behavior files changed (> ${selfMaxFiles})`]);
|
|
14633
|
+
if (opaque.length)
|
|
14634
|
+
return result("focused", [
|
|
14635
|
+
`a lockfile or binary changed: ${opaque.slice(0, 3).map((entry) => entry.file.path).join(", ")}${opaque.length > 3 ? ", …" : ""}`
|
|
14636
|
+
]);
|
|
14637
|
+
return result("self", [
|
|
14638
|
+
`small change: ${lines} line${lines === 1 ? "" : "s"} in ${behavior.length} file${behavior.length === 1 ? "" : "s"}, no risk path`
|
|
14639
|
+
]);
|
|
14640
|
+
}
|
|
14641
|
+
function untrackedStat(cwd, file) {
|
|
14642
|
+
try {
|
|
14643
|
+
const bytes = readFileSync4(path17.join(cwd, file));
|
|
14644
|
+
if (bytes.includes(0))
|
|
14645
|
+
return { path: file, added: 0, deleted: 0, binary: true };
|
|
14646
|
+
const text = bytes.toString("utf8");
|
|
14647
|
+
if (hasOpaqueLineBreak(text))
|
|
14648
|
+
return { path: file, added: 0, deleted: 0, binary: true };
|
|
14649
|
+
const lines = text.split(`
|
|
14650
|
+
`).length - (text.endsWith(`
|
|
14651
|
+
`) ? 1 : 0);
|
|
14652
|
+
return { path: file, added: lines, deleted: 0 };
|
|
14653
|
+
} catch {
|
|
14654
|
+
return { path: file, added: 0, deleted: 0, binary: true };
|
|
14655
|
+
}
|
|
14656
|
+
}
|
|
14657
|
+
var REGULAR = new Set(["100644", "100755"]);
|
|
14658
|
+
var ABSENT = /^0+$/u;
|
|
14659
|
+
function cosmeticFiles(cwd, fork, head) {
|
|
14660
|
+
const raw = diffRaw(cwd, fork, head);
|
|
14661
|
+
if (!raw)
|
|
14662
|
+
return null;
|
|
14663
|
+
const candidates = raw.filter((entry) => REGULAR.has(entry.oldMode) && entry.oldMode === entry.newMode && !ABSENT.test(entry.oldBlob) && (head === null || !ABSENT.test(entry.newBlob)));
|
|
14664
|
+
const blobs = readBlobs(cwd, candidates.flatMap((entry) => [entry.oldBlob, entry.newBlob]));
|
|
14665
|
+
if (!blobs)
|
|
14666
|
+
return null;
|
|
14667
|
+
const out = new Set;
|
|
14668
|
+
const opaque = new Set;
|
|
14669
|
+
for (const entry of candidates) {
|
|
14670
|
+
const before = blobs.get(entry.oldBlob);
|
|
14671
|
+
let after = blobs.get(entry.newBlob);
|
|
14672
|
+
if (!after && head === null && ABSENT.test(entry.newBlob))
|
|
14673
|
+
try {
|
|
14674
|
+
after = readFileSync4(path17.join(cwd, entry.path));
|
|
14675
|
+
} catch {
|
|
14676
|
+
after = undefined;
|
|
14677
|
+
}
|
|
14678
|
+
if (before && after && cosmeticChange(entry.path, before, after))
|
|
14679
|
+
out.add(entry.path);
|
|
14680
|
+
else if ([before, after].some((bytes) => bytes && hasOpaqueLineBreak(bytes.toString("utf8"))))
|
|
14681
|
+
opaque.add(entry.path);
|
|
14682
|
+
}
|
|
14683
|
+
return { cosmetic: out, opaque };
|
|
14684
|
+
}
|
|
14685
|
+
function generatedAt(cwd, fork, paths) {
|
|
14686
|
+
const files = treeFilesNamed(cwd, fork, ".gitattributes");
|
|
14687
|
+
const blobs = files?.size ? readBlobs(cwd, [...files.values()]) : null;
|
|
14688
|
+
if (!files || !blobs)
|
|
14689
|
+
return new Set;
|
|
14690
|
+
const texts = new Map;
|
|
14691
|
+
for (const [file, id] of files) {
|
|
14692
|
+
const bytes = blobs.get(id);
|
|
14693
|
+
if (bytes)
|
|
14694
|
+
texts.set(file, bytes.toString("utf8"));
|
|
14695
|
+
}
|
|
14696
|
+
return generatedByAttributes(texts, paths);
|
|
14697
|
+
}
|
|
14698
|
+
var forkOf = (cwd, base, head) => mergeBase(cwd, base, head ?? "HEAD");
|
|
14699
|
+
function laneFiles(cwd, fork, head) {
|
|
14700
|
+
const stats = diffNumstat(cwd, fork, head);
|
|
14701
|
+
const cosmetic = stats ? cosmeticFiles(cwd, fork, head) : null;
|
|
14702
|
+
if (!stats || !cosmetic)
|
|
14703
|
+
return null;
|
|
14704
|
+
const files = [
|
|
14705
|
+
...stats.map((entry) => ({
|
|
14706
|
+
...entry,
|
|
14707
|
+
...cosmetic.opaque.has(entry.path) ? { binary: true } : {},
|
|
14708
|
+
cosmetic: !entry.binary && cosmetic.cosmetic.has(entry.path)
|
|
14709
|
+
})),
|
|
14710
|
+
...head === null ? untrackedFiles(cwd).map((file) => untrackedStat(cwd, file)) : []
|
|
14711
|
+
];
|
|
14712
|
+
const generated = generatedAt(cwd, fork, files.map((entry) => entry.path));
|
|
14713
|
+
return files.map((entry) => ({
|
|
14714
|
+
...entry,
|
|
14715
|
+
generated: generated.has(entry.path)
|
|
14716
|
+
}));
|
|
14717
|
+
}
|
|
14718
|
+
function branchLane(cwd, options) {
|
|
14719
|
+
const candidates = options.base ? [`origin/${options.base}`, options.base] : [];
|
|
14720
|
+
const base = candidates.find((ref) => resolveRef(cwd, ref) !== null) ?? defaultBase(cwd) ?? null;
|
|
14721
|
+
const head = options.workingTree ? null : options.head;
|
|
14722
|
+
const fork = base && (options.workingTree || options.head) ? forkOf(cwd, base, head) : null;
|
|
14723
|
+
const files = fork ? laneFiles(cwd, fork, head) : null;
|
|
14724
|
+
const riskTier = options.judgment?.riskTier ?? null;
|
|
14725
|
+
if (!fork || !files) {
|
|
14726
|
+
const high = riskTier === "high";
|
|
14727
|
+
return {
|
|
14728
|
+
lane: high ? "full" : "focused",
|
|
14729
|
+
reasons: [
|
|
14730
|
+
`the diff of ${options.head?.slice(0, 12) ?? "(no head)"} against ${base ?? "(no base)"} cannot be read`,
|
|
14731
|
+
...high ? ["the task judgment records high risk"] : []
|
|
14732
|
+
],
|
|
14733
|
+
lines: 0,
|
|
14734
|
+
files: 0,
|
|
14735
|
+
riskFiles: [],
|
|
14736
|
+
base,
|
|
14737
|
+
head: options.head
|
|
14738
|
+
};
|
|
14739
|
+
}
|
|
14740
|
+
const repoRisk = parseRiskPaths(fileAt(cwd, fork, RISK_PATHS_FILE) ?? "");
|
|
14741
|
+
return {
|
|
14742
|
+
...computeLane({
|
|
14743
|
+
files,
|
|
14744
|
+
riskTier,
|
|
14745
|
+
riskPaths: [...repoRisk, ...options.riskPaths ?? []]
|
|
14746
|
+
}),
|
|
14747
|
+
base,
|
|
14748
|
+
head: options.head
|
|
14749
|
+
};
|
|
14750
|
+
}
|
|
14751
|
+
var laneNeed = (lane) => lane === "none" ? "none" : lane === "self" ? "self" : "non-author";
|
|
14752
|
+
|
|
14753
|
+
// packages/workit-core/src/core/policy/derive.ts
|
|
14754
|
+
var RULES = {
|
|
14755
|
+
test: "check:test",
|
|
14756
|
+
self: "verdict:self",
|
|
14757
|
+
nonAuthor: "verdict:non-author",
|
|
14758
|
+
verified: "verdict:verified",
|
|
14759
|
+
lane: "verdict:lane",
|
|
14760
|
+
product: "decision:product",
|
|
14761
|
+
plan: "plan"
|
|
14762
|
+
};
|
|
14763
|
+
var derived2 = (spec) => ({
|
|
14764
|
+
...spec,
|
|
14765
|
+
id: requirementId({
|
|
14766
|
+
ruleId: spec.ruleId,
|
|
14767
|
+
scope: spec.scope,
|
|
14768
|
+
satisfaction: spec.satisfaction,
|
|
14769
|
+
before: spec.before,
|
|
14770
|
+
dependentAction: spec.dependentAction
|
|
14771
|
+
})
|
|
14772
|
+
});
|
|
14773
|
+
var sorted = (values) => [...new Set(values)].toSorted();
|
|
14774
|
+
var normalizedScope = (scope) => ({
|
|
14775
|
+
description: scope.description,
|
|
14776
|
+
paths: sorted(scope.paths),
|
|
14777
|
+
exclusions: sorted(scope.exclusions)
|
|
14778
|
+
});
|
|
14779
|
+
function deriveRequirements(judgment, scope, constraints, verification = "self") {
|
|
14780
|
+
const out = [];
|
|
14781
|
+
const base = { scope, dependentAction: null };
|
|
14782
|
+
const proportional = verification === "proportional";
|
|
14783
|
+
if (judgment.behaviorChange)
|
|
14784
|
+
out.push(derived2({
|
|
14785
|
+
...base,
|
|
14786
|
+
ruleId: RULES.test,
|
|
14787
|
+
dimension: "testing",
|
|
14788
|
+
reason: "Behavior changes: the configured test check must pass on the final tree.",
|
|
14789
|
+
satisfaction: "Run `workit check test` on the final tree.",
|
|
14790
|
+
before: "close",
|
|
14791
|
+
acceptanceAllowed: false
|
|
14792
|
+
}));
|
|
14793
|
+
if (proportional && judgment.riskTier === "normal")
|
|
14794
|
+
out.push(derived2({
|
|
14795
|
+
...base,
|
|
14796
|
+
ruleId: RULES.lane,
|
|
14797
|
+
dimension: "review",
|
|
14798
|
+
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.",
|
|
14799
|
+
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.",
|
|
14800
|
+
before: "close",
|
|
14801
|
+
acceptanceAllowed: false
|
|
14802
|
+
}));
|
|
14803
|
+
if (!proportional && judgment.behaviorChange && judgment.riskTier === "normal")
|
|
14804
|
+
out.push(verification === "independent" ? derived2({
|
|
14805
|
+
...base,
|
|
14806
|
+
ruleId: RULES.nonAuthor,
|
|
14807
|
+
dimension: "review",
|
|
14808
|
+
reason: "This workspace requires independent verification: a session that did not author the change records the verdict.",
|
|
14809
|
+
satisfaction: "An independent session records a passing verdict: `workit ledger verdict verified --how <what it exercised>`.",
|
|
14810
|
+
before: "close",
|
|
14811
|
+
acceptanceAllowed: false
|
|
14812
|
+
}) : derived2({
|
|
14813
|
+
...base,
|
|
14814
|
+
ruleId: RULES.self,
|
|
14815
|
+
dimension: "review",
|
|
14816
|
+
reason: "A behavior change needs a recorded verdict; the author's own is allowed and is shown as self-reviewed, never verified.",
|
|
14817
|
+
satisfaction: "After an observed passing `workit check test`, record `workit ledger verdict tests-verified --self --how <what you ran>` (or have a verifier record one).",
|
|
14818
|
+
before: "close",
|
|
14819
|
+
acceptanceAllowed: false
|
|
14820
|
+
}));
|
|
14821
|
+
if (judgment.riskTier === "high")
|
|
14822
|
+
out.push(derived2({
|
|
14823
|
+
...base,
|
|
14824
|
+
ruleId: RULES.verified,
|
|
14825
|
+
dimension: "review",
|
|
14826
|
+
reason: "High risk: an independent verifier must exercise the live surface.",
|
|
14827
|
+
satisfaction: "An independent verifier runs the real surface and records `workit ledger verdict verified --kind live --how <what it exercised>`.",
|
|
14828
|
+
before: "close",
|
|
14829
|
+
acceptanceAllowed: false
|
|
14830
|
+
}));
|
|
14831
|
+
if (judgment.productChoiceOpen)
|
|
14832
|
+
out.push(derived2({
|
|
14833
|
+
...base,
|
|
14834
|
+
ruleId: RULES.product,
|
|
14835
|
+
dimension: "decisions",
|
|
14836
|
+
reason: "A product or preference choice is open; the user decides it before code is written.",
|
|
14837
|
+
satisfaction: 'Ask the user (with your recommendation), then record the answer: `workit ledger decision "<choice>" --why "<reason>"`.',
|
|
14838
|
+
before: "write",
|
|
14839
|
+
acceptanceAllowed: false
|
|
14840
|
+
}));
|
|
14841
|
+
if (judgment.needsPlan || judgment.riskTier === "high")
|
|
14842
|
+
out.push(derived2({
|
|
14843
|
+
...base,
|
|
14844
|
+
ruleId: RULES.plan,
|
|
14845
|
+
dimension: "artifacts",
|
|
14846
|
+
reason: judgment.needsPlan ? "The work needs a written plan or spec before implementation." : "High-risk work needs a written plan before implementation.",
|
|
14847
|
+
satisfaction: "Write the plan or spec (e.g. docs/plans/<slug>.md; docs and Markdown writes stay allowed), then record it: `workit policy assess --ref <path>`.",
|
|
14848
|
+
before: "write",
|
|
14849
|
+
acceptanceAllowed: true
|
|
14850
|
+
}));
|
|
14851
|
+
for (const constraint of constraints)
|
|
14852
|
+
for (const obligation of constraint.requires)
|
|
14853
|
+
out.push(derived2({
|
|
14854
|
+
ruleId: `project-constraint:${constraint.id}:${obligation.kind}:${sha256({
|
|
14855
|
+
kind: obligation.kind,
|
|
14856
|
+
scope: normalizedScope(obligation.scope),
|
|
14857
|
+
refs: obligation.refs.map((ref) => JSON.stringify(ref)).toSorted(),
|
|
14858
|
+
method: obligation.method,
|
|
14859
|
+
before: obligation.before,
|
|
14860
|
+
dependentAction: obligation.dependentAction
|
|
14861
|
+
})}`,
|
|
14862
|
+
dimension: obligation.method ?? (obligation.kind === "check" ? "verification" : obligation.kind === "decision" ? "decisions" : "challenge"),
|
|
14863
|
+
scope: obligation.scope,
|
|
14864
|
+
reason: `${constraint.kind} constraint requires ${obligation.kind}: ${constraint.statement}`,
|
|
14865
|
+
satisfaction: `Satisfy the constraint obligation using its referenced ${obligation.kind}.`,
|
|
14866
|
+
before: obligation.before,
|
|
14867
|
+
dependentAction: obligation.dependentAction,
|
|
14868
|
+
acceptanceAllowed: constraint.acceptanceAllowed && obligation.kind !== "decision"
|
|
14869
|
+
}));
|
|
14870
|
+
const seen = new Set;
|
|
14871
|
+
return out.filter((item) => seen.has(item.id) ? false : (seen.add(item.id), true));
|
|
14872
|
+
}
|
|
14873
|
+
var latestJudgment = (task) => task.judgments?.at(-1)?.data ?? null;
|
|
14874
|
+
var ledgerView = (root) => {
|
|
14875
|
+
let branch;
|
|
14876
|
+
let rows;
|
|
14877
|
+
return {
|
|
14878
|
+
branch: () => branch === undefined ? branch = currentBranch(root) : branch,
|
|
14879
|
+
rows: () => {
|
|
14880
|
+
if (rows === undefined) {
|
|
14881
|
+
const read = readLedger(root);
|
|
14882
|
+
rows = read.ok ? read.value.rows : [];
|
|
14883
|
+
}
|
|
14884
|
+
return rows;
|
|
14885
|
+
}
|
|
14886
|
+
};
|
|
14887
|
+
};
|
|
14888
|
+
var productDecision = (task, ledger) => {
|
|
14889
|
+
const decisions = task.decisions.filter((entry) => entry.data.revoked === null && (entry.data.response === "approved" || entry.data.response === "stated") && entry.data.purpose !== "limitation");
|
|
14890
|
+
if (decisions.length)
|
|
14891
|
+
return {
|
|
14892
|
+
met: true,
|
|
14893
|
+
reason: "the user's decision is recorded on the task",
|
|
14894
|
+
decisionIds: decisions.map((entry) => entry.id)
|
|
14895
|
+
};
|
|
14896
|
+
const since = Date.parse(task.createdAt);
|
|
14897
|
+
const branch = ledger.branch();
|
|
14898
|
+
const row = ledger.rows().findLast((item) => item.type === "decision" && item.by !== "decider" && !item.superseded && (item.branch ?? null) === branch && Date.parse(item.at) >= since);
|
|
14899
|
+
return row ? { met: true, reason: `ledger decision ${row.id} records the choice`, decisionIds: [] } : {
|
|
14900
|
+
met: false,
|
|
14901
|
+
reason: 'open product choice: ask the user, then `workit ledger decision "<choice>" --why "<reason>"` (or, if it is not open, `workit policy assess --judge product-choice=no --why "<reason>"`)',
|
|
14902
|
+
decisionIds: []
|
|
14903
|
+
};
|
|
14904
|
+
};
|
|
14905
|
+
var planRecorded = (task, root) => {
|
|
14906
|
+
const cited = (latestJudgment(task)?.refs ?? []).find((ref) => ref.kind === "external" || ref.kind === "file" && existsSync5(path18.resolve(root, ref.path)));
|
|
14907
|
+
if (cited)
|
|
14908
|
+
return {
|
|
14909
|
+
met: true,
|
|
14910
|
+
reason: `plan recorded: ${cited.kind === "file" ? cited.path : cited.kind === "external" ? cited.url : ""}`,
|
|
14911
|
+
decisionIds: []
|
|
14912
|
+
};
|
|
14913
|
+
const artifact = task.evidence.some((entry) => entry.data.kind === "artifact" && entry.data.result === "passed");
|
|
14914
|
+
return artifact ? { met: true, reason: "a passing artifact records the plan", decisionIds: [] } : {
|
|
14915
|
+
met: false,
|
|
14077
14916
|
reason: "no plan recorded: write it (docs/ and Markdown writes stay allowed), then `workit policy assess --ref <path>`",
|
|
14078
14917
|
decisionIds: []
|
|
14079
14918
|
};
|
|
14080
14919
|
};
|
|
14081
|
-
var
|
|
14920
|
+
var workingLane = (task, root) => branchLane(root, { head: null, workingTree: true, judgment: latestJudgment(task) });
|
|
14921
|
+
var laneVerdict = (task, root, ledger) => {
|
|
14922
|
+
if (!ledger.branch() && latestJudgment(task)?.behaviorChange === false)
|
|
14923
|
+
return {
|
|
14924
|
+
met: true,
|
|
14925
|
+
reason: "no branch to read a lane from, and the judgment records no behavior change",
|
|
14926
|
+
decisionIds: []
|
|
14927
|
+
};
|
|
14928
|
+
const lane = workingLane(task, root);
|
|
14929
|
+
const label = `lane ${lane.lane} (${lane.reasons.join("; ")})`;
|
|
14930
|
+
const need = laneNeed(lane.lane);
|
|
14931
|
+
if (need === "none")
|
|
14932
|
+
return { met: true, reason: `${label}: no verdict needed`, decisionIds: [] };
|
|
14933
|
+
const status = verdict(need === "self" ? RULES.self : RULES.nonAuthor, root, ledger);
|
|
14934
|
+
return { ...status, reason: `${label}: ${status.reason}` };
|
|
14935
|
+
};
|
|
14936
|
+
var verdict = (ruleId, root, ledger) => {
|
|
14082
14937
|
const branch = ledger.branch();
|
|
14083
14938
|
if (!branch)
|
|
14084
14939
|
return { met: false, reason: "no branch is checked out to hold a verdict", decisionIds: [] };
|
|
14085
14940
|
const check = checkVerdicts(root, branch, ledger.rows());
|
|
14086
14941
|
const failing = check.accepted.reasons.includes("failing_verdict");
|
|
14087
14942
|
const strong = (entry) => STRONG_RESULTS.has(String(entry.verdict.result));
|
|
14088
|
-
const met = !failing && (
|
|
14943
|
+
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
14944
|
if (met)
|
|
14090
14945
|
return {
|
|
14091
14946
|
met: true,
|
|
@@ -14093,7 +14948,7 @@ var verdict = (requirement, root, ledger) => {
|
|
|
14093
14948
|
decisionIds: []
|
|
14094
14949
|
};
|
|
14095
14950
|
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 =
|
|
14951
|
+
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
14952
|
return { met: false, reason: `${why}: ${unblock}`, decisionIds: [] };
|
|
14098
14953
|
};
|
|
14099
14954
|
function derivedRuleStatus(task, requirement, root, ledger) {
|
|
@@ -14105,7 +14960,9 @@ function derivedRuleStatus(task, requirement, root, ledger) {
|
|
|
14105
14960
|
case RULES.self:
|
|
14106
14961
|
case RULES.nonAuthor:
|
|
14107
14962
|
case RULES.verified:
|
|
14108
|
-
return verdict(requirement, root, ledger);
|
|
14963
|
+
return verdict(requirement.ruleId, root, ledger);
|
|
14964
|
+
case RULES.lane:
|
|
14965
|
+
return laneVerdict(task, root, ledger);
|
|
14109
14966
|
default:
|
|
14110
14967
|
return null;
|
|
14111
14968
|
}
|
|
@@ -14124,14 +14981,14 @@ function writeBlockers(task, root) {
|
|
|
14124
14981
|
|
|
14125
14982
|
// packages/workit-core/src/core/task-evaluation.ts
|
|
14126
14983
|
var digestBytes = (value) => createHash12("sha256").update(value).digest("hex");
|
|
14127
|
-
var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${
|
|
14128
|
-
var relative = (root, candidate) =>
|
|
14984
|
+
var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path19.sep}`);
|
|
14985
|
+
var relative = (root, candidate) => path19.relative(root, candidate).split(path19.sep).join("/") || ".";
|
|
14129
14986
|
var canonicalPath = (value) => {
|
|
14130
14987
|
if (typeof value !== "string" || !value)
|
|
14131
14988
|
return null;
|
|
14132
14989
|
if (value.includes("\\") || value.split("/").includes(".."))
|
|
14133
14990
|
return null;
|
|
14134
|
-
const normalized =
|
|
14991
|
+
const normalized = path19.posix.normalize(value);
|
|
14135
14992
|
if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
|
|
14136
14993
|
return null;
|
|
14137
14994
|
return normalized === "." ? "." : normalized.replace(/\/$/, "");
|
|
@@ -14167,7 +15024,7 @@ var compareCodeUnits2 = (left, right) => {
|
|
|
14167
15024
|
return left.length - right.length;
|
|
14168
15025
|
};
|
|
14169
15026
|
var gitPaths = (root) => {
|
|
14170
|
-
const result =
|
|
15027
|
+
const result = spawnSync11("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
|
|
14171
15028
|
cwd: root,
|
|
14172
15029
|
encoding: "buffer"
|
|
14173
15030
|
});
|
|
@@ -14179,21 +15036,21 @@ var gitPaths = (root) => {
|
|
|
14179
15036
|
git: !/not a git repository/i.test(stderr)
|
|
14180
15037
|
};
|
|
14181
15038
|
}
|
|
14182
|
-
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(
|
|
14183
|
-
const stagedDeleted =
|
|
15039
|
+
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path19.sep).join("/")) : [];
|
|
15040
|
+
const stagedDeleted = spawnSync11("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
|
|
14184
15041
|
if (stagedDeleted.status !== 0) {
|
|
14185
15042
|
const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
|
|
14186
15043
|
if (!/not a git repository/i.test(stderr))
|
|
14187
15044
|
return { paths, uncertain: true, git: true };
|
|
14188
15045
|
} else if (stagedDeleted.stdout) {
|
|
14189
|
-
paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(
|
|
15046
|
+
paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path19.sep).join("/")));
|
|
14190
15047
|
}
|
|
14191
15048
|
return { paths: [...new Set(paths)], uncertain: false, git: true };
|
|
14192
15049
|
};
|
|
14193
15050
|
var walk = (root, directory, output) => {
|
|
14194
15051
|
let names;
|
|
14195
15052
|
try {
|
|
14196
|
-
names =
|
|
15053
|
+
names = fs15.readdirSync(directory);
|
|
14197
15054
|
} catch {
|
|
14198
15055
|
return true;
|
|
14199
15056
|
}
|
|
@@ -14201,10 +15058,10 @@ var walk = (root, directory, output) => {
|
|
|
14201
15058
|
for (const name of names) {
|
|
14202
15059
|
if (name === ".git" || name === ".workit")
|
|
14203
15060
|
continue;
|
|
14204
|
-
const target =
|
|
15061
|
+
const target = path19.join(directory, name);
|
|
14205
15062
|
let stat;
|
|
14206
15063
|
try {
|
|
14207
|
-
stat =
|
|
15064
|
+
stat = fs15.lstatSync(target);
|
|
14208
15065
|
} catch {
|
|
14209
15066
|
uncertain = true;
|
|
14210
15067
|
continue;
|
|
@@ -14219,14 +15076,14 @@ var walk = (root, directory, output) => {
|
|
|
14219
15076
|
var scopeRoots = (root, scope) => {
|
|
14220
15077
|
const roots = [];
|
|
14221
15078
|
for (const item of scope.paths.length ? scope.paths : ["."]) {
|
|
14222
|
-
const target =
|
|
15079
|
+
const target = path19.resolve(root, item);
|
|
14223
15080
|
if (!inside(root, target))
|
|
14224
15081
|
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
15082
|
let ancestor = target;
|
|
14226
|
-
while (!
|
|
14227
|
-
ancestor =
|
|
15083
|
+
while (!fs15.existsSync(ancestor) && ancestor !== root)
|
|
15084
|
+
ancestor = path19.dirname(ancestor);
|
|
14228
15085
|
try {
|
|
14229
|
-
if (!inside(root,
|
|
15086
|
+
if (!inside(root, fs15.realpathSync(ancestor)))
|
|
14230
15087
|
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` }] });
|
|
14231
15088
|
} catch {
|
|
14232
15089
|
return failure2("invalid_input", `candidate scope cannot be inspected: ${item}`);
|
|
@@ -14238,7 +15095,7 @@ var scopeRoots = (root, scope) => {
|
|
|
14238
15095
|
function captureCandidate(root, scope, environment = []) {
|
|
14239
15096
|
let checkout;
|
|
14240
15097
|
try {
|
|
14241
|
-
checkout =
|
|
15098
|
+
checkout = fs15.realpathSync(root);
|
|
14242
15099
|
} catch {
|
|
14243
15100
|
return failure2("invalid_input", "candidate root does not exist");
|
|
14244
15101
|
}
|
|
@@ -14256,11 +15113,11 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
14256
15113
|
if (item === ".git" || item === ".workit")
|
|
14257
15114
|
continue;
|
|
14258
15115
|
names.add(item);
|
|
14259
|
-
if (!
|
|
15116
|
+
if (!fs15.existsSync(target))
|
|
14260
15117
|
continue;
|
|
14261
15118
|
let stat;
|
|
14262
15119
|
try {
|
|
14263
|
-
stat =
|
|
15120
|
+
stat = fs15.lstatSync(target);
|
|
14264
15121
|
} catch {
|
|
14265
15122
|
uncertain = true;
|
|
14266
15123
|
continue;
|
|
@@ -14276,17 +15133,17 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
14276
15133
|
for (const item of [...names].toSorted()) {
|
|
14277
15134
|
if (!scopeMatches(item, normalizedScope))
|
|
14278
15135
|
continue;
|
|
14279
|
-
const target =
|
|
15136
|
+
const target = path19.join(checkout, item);
|
|
14280
15137
|
try {
|
|
14281
|
-
const stat =
|
|
15138
|
+
const stat = fs15.lstatSync(target);
|
|
14282
15139
|
if (stat.isSymbolicLink()) {
|
|
14283
|
-
const link =
|
|
15140
|
+
const link = fs15.readlinkSync(target);
|
|
14284
15141
|
files.push({ path: item, kind: "symlink", digest: digestBytes(link), executable: null });
|
|
14285
15142
|
} else if (stat.isFile()) {
|
|
14286
15143
|
files.push({
|
|
14287
15144
|
path: item,
|
|
14288
15145
|
kind: "file",
|
|
14289
|
-
digest: digestBytes(
|
|
15146
|
+
digest: digestBytes(fs15.readFileSync(target)),
|
|
14290
15147
|
executable: (stat.mode & 73) !== 0
|
|
14291
15148
|
});
|
|
14292
15149
|
} else if (!stat.isDirectory()) {
|
|
@@ -14311,7 +15168,7 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
14311
15168
|
const values = supplied.toSorted((left, right) => compareCodeUnits2(left.name, right.name)).map(({ name, value }) => ({ name, value, refs: [] }));
|
|
14312
15169
|
if (values.some(({ value }) => value === null))
|
|
14313
15170
|
completeness = "uncertain";
|
|
14314
|
-
const headResult =
|
|
15171
|
+
const headResult = spawnSync11("git", ["rev-parse", "HEAD"], { cwd: checkout, encoding: "utf8" });
|
|
14315
15172
|
if (headResult.status !== 0 && !/not a git repository/i.test(headResult.stderr ?? ""))
|
|
14316
15173
|
completeness = "uncertain";
|
|
14317
15174
|
const head = headResult.status === 0 ? headResult.stdout.trim() : null;
|
|
@@ -14451,7 +15308,7 @@ var findingVerificationPasses = (findingCandidateId, evidence) => evidence.statu
|
|
|
14451
15308
|
var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
14452
15309
|
let root;
|
|
14453
15310
|
try {
|
|
14454
|
-
root =
|
|
15311
|
+
root = fs15.realpathSync(checkoutRoot);
|
|
14455
15312
|
} catch {
|
|
14456
15313
|
return failure2("permission_denied", "checkout root is unavailable");
|
|
14457
15314
|
}
|
|
@@ -14460,23 +15317,23 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
|
14460
15317
|
continue;
|
|
14461
15318
|
if (!reference.digest)
|
|
14462
15319
|
return failure2("invalid_input", "document references require a byte digest");
|
|
14463
|
-
const target =
|
|
14464
|
-
if (target !== root && !target.startsWith(`${root}${
|
|
15320
|
+
const target = path19.resolve(root, reference.path);
|
|
15321
|
+
if (target !== root && !target.startsWith(`${root}${path19.sep}`))
|
|
14465
15322
|
return failure2("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
|
|
14466
15323
|
fields: [
|
|
14467
15324
|
{ path: "contentRefs.path", reason: `${reference.path} is outside the checkout` }
|
|
14468
15325
|
]
|
|
14469
15326
|
});
|
|
14470
15327
|
try {
|
|
14471
|
-
const below =
|
|
15328
|
+
const below = path19.relative(root, target);
|
|
14472
15329
|
let current = root;
|
|
14473
|
-
for (const segment of below.split(
|
|
15330
|
+
for (const segment of below.split(path19.sep)) {
|
|
14474
15331
|
if (!segment)
|
|
14475
15332
|
continue;
|
|
14476
|
-
current =
|
|
14477
|
-
const stat =
|
|
14478
|
-
const real =
|
|
14479
|
-
if (real !== root && !real.startsWith(`${root}${
|
|
15333
|
+
current = path19.join(current, segment);
|
|
15334
|
+
const stat = fs15.lstatSync(current);
|
|
15335
|
+
const real = fs15.realpathSync(current);
|
|
15336
|
+
if (real !== root && !real.startsWith(`${root}${path19.sep}`))
|
|
14480
15337
|
return failure2("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
|
|
14481
15338
|
fields: [
|
|
14482
15339
|
{
|
|
@@ -14492,7 +15349,7 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
|
14492
15349
|
if (current !== target && !stat.isDirectory())
|
|
14493
15350
|
return failure2("invalid_input", "document reference ancestor is not a directory");
|
|
14494
15351
|
}
|
|
14495
|
-
if (digestBytes(
|
|
15352
|
+
if (digestBytes(fs15.readFileSync(target)) !== reference.digest)
|
|
14496
15353
|
return failure2("permission_denied", "decision document bytes have changed");
|
|
14497
15354
|
} catch {
|
|
14498
15355
|
return failure2("invalid_input", "decision document is unavailable");
|
|
@@ -14913,9 +15770,9 @@ Autonomy contract:
|
|
|
14913
15770
|
Prose is a note: only a configured check the CLI ran (\`workit check <name>\`)
|
|
14914
15771
|
satisfies a gate; an ad-hoc \`workit check -- <cmd>\` never does.
|
|
14915
15772
|
- 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\`).
|
|
15773
|
+
self-reviewed, never verified. High risk, a focused or full lane and
|
|
15774
|
+
merge under \`verification: self|independent\` need a verifier with its
|
|
15775
|
+
own host-derived session (a subagent's \`<lead>:<child>\`); never hand-mint one (\`<lead>-v1\`).
|
|
14919
15776
|
- A local-commit endpoint does not imply PR readiness, and a local commit alone
|
|
14920
15777
|
is not evidence of a requested remote push. Before reporting delivery,
|
|
14921
15778
|
reconcile every requested item and observe it (\`workit verify-delivery\`).
|
|
@@ -15077,21 +15934,21 @@ var exportDigest = (bundle) => sha256({
|
|
|
15077
15934
|
task: bundle.task
|
|
15078
15935
|
});
|
|
15079
15936
|
// packages/workit-core/src/core/route-intent.ts
|
|
15080
|
-
import { spawnSync as
|
|
15081
|
-
import { existsSync as
|
|
15082
|
-
import
|
|
15937
|
+
import { spawnSync as spawnSync13 } from "node:child_process";
|
|
15938
|
+
import { existsSync as existsSync7 } from "node:fs";
|
|
15939
|
+
import path22 from "node:path";
|
|
15083
15940
|
|
|
15084
15941
|
// packages/workit-core/src/hooks/raw-git.ts
|
|
15085
|
-
import { spawnSync as
|
|
15942
|
+
import { spawnSync as spawnSync12 } from "node:child_process";
|
|
15086
15943
|
import { createHash as createHash14 } from "node:crypto";
|
|
15087
|
-
import
|
|
15944
|
+
import fs17 from "node:fs";
|
|
15088
15945
|
import os4 from "node:os";
|
|
15089
|
-
import
|
|
15946
|
+
import path21 from "node:path";
|
|
15090
15947
|
|
|
15091
15948
|
// packages/workit-core/src/hooks/session-marker.ts
|
|
15092
15949
|
import { createHash as createHash13 } from "node:crypto";
|
|
15093
|
-
import
|
|
15094
|
-
import
|
|
15950
|
+
import fs16 from "node:fs";
|
|
15951
|
+
import path20 from "node:path";
|
|
15095
15952
|
var sessionMarkerFile = (input, session, store) => {
|
|
15096
15953
|
let dir = store;
|
|
15097
15954
|
if (dir === undefined) {
|
|
@@ -15101,13 +15958,13 @@ var sessionMarkerFile = (input, session, store) => {
|
|
|
15101
15958
|
dir = location.dir;
|
|
15102
15959
|
}
|
|
15103
15960
|
const name = createHash13("sha256").update(`${input.host}\x00${session}`).digest("hex").slice(0, 32);
|
|
15104
|
-
return
|
|
15961
|
+
return path20.join(dir, "hooks", `skills-${name}.json`);
|
|
15105
15962
|
};
|
|
15106
15963
|
var list2 = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
15107
15964
|
var EMPTY = { nudged: [], loaded: [], raw: [], stops: [], judges: [] };
|
|
15108
15965
|
var readRaw = (file) => {
|
|
15109
15966
|
try {
|
|
15110
|
-
return
|
|
15967
|
+
return fs16.readFileSync(file, "utf8");
|
|
15111
15968
|
} catch {
|
|
15112
15969
|
return "";
|
|
15113
15970
|
}
|
|
@@ -15128,13 +15985,13 @@ var parseMarker = (text) => {
|
|
|
15128
15985
|
};
|
|
15129
15986
|
var readSessionMarker = (file) => parseMarker(readRaw(file));
|
|
15130
15987
|
var writeAtomic = (file, marker) => {
|
|
15131
|
-
|
|
15988
|
+
fs16.mkdirSync(path20.dirname(file), { recursive: true });
|
|
15132
15989
|
const temp = `${file}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
15133
15990
|
try {
|
|
15134
|
-
|
|
15135
|
-
|
|
15991
|
+
fs16.writeFileSync(temp, JSON.stringify(marker));
|
|
15992
|
+
fs16.renameSync(temp, file);
|
|
15136
15993
|
} catch (error) {
|
|
15137
|
-
|
|
15994
|
+
fs16.rmSync(temp, { force: true });
|
|
15138
15995
|
throw error;
|
|
15139
15996
|
}
|
|
15140
15997
|
};
|
|
@@ -15656,17 +16513,17 @@ var GIT_VALUE_OPTIONS = new Set([
|
|
|
15656
16513
|
]);
|
|
15657
16514
|
var GIT_CONFIG_OPTIONS = new Set(["-c", "--config-env", "--git-dir"]);
|
|
15658
16515
|
var GIT_CONFIG_ENV = /^GIT_(?:CONFIG\w*|DIR|COMMON_DIR|WORK_TREE)$/;
|
|
15659
|
-
var
|
|
16516
|
+
var ASSIGNMENT2 = /^([A-Za-z_]\w*)=([\s\S]*)$/;
|
|
15660
16517
|
var MAX_DEPTH = 4;
|
|
15661
16518
|
var MAX_EXPANSIONS = 16;
|
|
15662
|
-
var isAbsolute = (value) =>
|
|
16519
|
+
var isAbsolute = (value) => path21.isAbsolute(value) || /^[A-Za-z]:[\\/]/.test(value) || value.startsWith("\\\\");
|
|
15663
16520
|
var HERE = /^(?:\$PWD|\$\{PWD\}|\$\(\s*pwd\s*\)|`pwd`|\$\(\s*git\s+rev-parse\s+--show-toplevel\s*\))(?=$|[\\/])/;
|
|
15664
16521
|
var joinDir = (base, raw) => {
|
|
15665
16522
|
const next = raw.replace(HERE, ".");
|
|
15666
16523
|
if (base === undefined || /[$`*?]/.test(next) || next === "-")
|
|
15667
16524
|
return;
|
|
15668
|
-
const expanded = next === "~" || next.startsWith("~/") ?
|
|
15669
|
-
return base === null || isAbsolute(expanded) ? expanded :
|
|
16525
|
+
const expanded = next === "~" || next.startsWith("~/") ? path21.join(os4.homedir(), next.slice(1)) : next;
|
|
16526
|
+
return base === null || isAbsolute(expanded) ? expanded : path21.join(base, expanded);
|
|
15670
16527
|
};
|
|
15671
16528
|
var rebase = (base, inner) => inner === null ? base : inner === undefined ? undefined : joinDir(base, inner);
|
|
15672
16529
|
var cdTarget = (words) => {
|
|
@@ -15683,7 +16540,7 @@ var stripPrefixes = (input) => {
|
|
|
15683
16540
|
const first = words[0];
|
|
15684
16541
|
if (first === undefined)
|
|
15685
16542
|
return { words, env };
|
|
15686
|
-
const assignment =
|
|
16543
|
+
const assignment = ASSIGNMENT2.exec(first);
|
|
15687
16544
|
if (assignment) {
|
|
15688
16545
|
if (GIT_CONFIG_ENV.test(assignment[1]))
|
|
15689
16546
|
env[assignment[1]] = assignment[2];
|
|
@@ -15742,10 +16599,10 @@ var assignments = (words, vars) => {
|
|
|
15742
16599
|
while (rest[0]?.startsWith("-"))
|
|
15743
16600
|
rest = rest.slice(1);
|
|
15744
16601
|
}
|
|
15745
|
-
if (rest.length === 0 || !rest.every((word) =>
|
|
16602
|
+
if (rest.length === 0 || !rest.every((word) => ASSIGNMENT2.test(word)))
|
|
15746
16603
|
return null;
|
|
15747
16604
|
return rest.map((word) => {
|
|
15748
|
-
const [, name, value] =
|
|
16605
|
+
const [, name, value] = ASSIGNMENT2.exec(word);
|
|
15749
16606
|
return [name, expandWord(value, vars)];
|
|
15750
16607
|
});
|
|
15751
16608
|
};
|
|
@@ -15933,7 +16790,7 @@ var splitString = (line) => {
|
|
|
15933
16790
|
return line.slice(index).join(" ").slice(15);
|
|
15934
16791
|
if (/^-S./.test(word))
|
|
15935
16792
|
return [word.slice(2), ...line.slice(index + 1)].join(" ");
|
|
15936
|
-
if (!word.startsWith("-") && !
|
|
16793
|
+
if (!word.startsWith("-") && !ASSIGNMENT2.test(word))
|
|
15937
16794
|
return null;
|
|
15938
16795
|
}
|
|
15939
16796
|
return null;
|
|
@@ -16200,6 +17057,24 @@ function workitInvocations(command, dialect = "posix", depth = 0) {
|
|
|
16200
17057
|
}
|
|
16201
17058
|
return found;
|
|
16202
17059
|
}
|
|
17060
|
+
var WORKIT_GLOBAL = /^--(?:cwd|session|token|as)(=|$)/;
|
|
17061
|
+
function skipWorkitGlobals(args) {
|
|
17062
|
+
let cwd = null;
|
|
17063
|
+
let at = 0;
|
|
17064
|
+
for (;at < args.length; at++) {
|
|
17065
|
+
const match = WORKIT_GLOBAL.exec(args[at]);
|
|
17066
|
+
if (match?.[1] === "=") {
|
|
17067
|
+
if (args[at].startsWith("--cwd="))
|
|
17068
|
+
cwd = args[at].slice(6);
|
|
17069
|
+
} else if (match && args[at + 1] !== undefined) {
|
|
17070
|
+
if (args[at] === "--cwd")
|
|
17071
|
+
cwd = args[at + 1];
|
|
17072
|
+
at++;
|
|
17073
|
+
} else if (args[at] !== "--json")
|
|
17074
|
+
break;
|
|
17075
|
+
}
|
|
17076
|
+
return { rest: args.slice(at), cwd };
|
|
17077
|
+
}
|
|
16203
17078
|
var PUSH_VALUE_OPTIONS = new Set(["-o", "--push-option", "--receive-pack", "--exec", "--repo"]);
|
|
16204
17079
|
var CURRENT_BRANCH = /^\$\(\s*git\s+(?:branch\s+--show-current|rev-parse\s+--abbrev-ref\s+HEAD|symbolic-ref\s+--short\s+HEAD)\s*\)$/;
|
|
16205
17080
|
var pushDestination = (destination) => {
|
|
@@ -16478,7 +17353,7 @@ var repoAt = (dir) => {
|
|
|
16478
17353
|
return { location, dir, branch: resolveTaskKey(dir, location).branch };
|
|
16479
17354
|
};
|
|
16480
17355
|
var isWorkitWorkspace = (repo) => {
|
|
16481
|
-
if (
|
|
17356
|
+
if (fs17.existsSync(repo.location.dir))
|
|
16482
17357
|
return true;
|
|
16483
17358
|
try {
|
|
16484
17359
|
return resolveRuntimeWorkspaceVcs(repo.location.top ?? repo.dir) !== null;
|
|
@@ -16499,17 +17374,17 @@ var headSha2 = (repo) => {
|
|
|
16499
17374
|
if (!git || git.reftable)
|
|
16500
17375
|
return null;
|
|
16501
17376
|
try {
|
|
16502
|
-
const head =
|
|
17377
|
+
const head = fs17.readFileSync(path21.join(git.gitDir, "HEAD"), "utf8").trim();
|
|
16503
17378
|
if (/^[0-9a-f]{40,64}$/.test(head))
|
|
16504
17379
|
return head;
|
|
16505
17380
|
const ref = /^ref: (refs\/.+)$/.exec(head)?.[1];
|
|
16506
17381
|
if (!ref)
|
|
16507
17382
|
return null;
|
|
16508
|
-
const common =
|
|
17383
|
+
const common = path21.dirname(repo.location.dir);
|
|
16509
17384
|
try {
|
|
16510
|
-
return
|
|
17385
|
+
return fs17.readFileSync(path21.join(common, ref), "utf8").trim() || null;
|
|
16511
17386
|
} catch {}
|
|
16512
|
-
const packed =
|
|
17387
|
+
const packed = fs17.readFileSync(path21.join(common, "packed-refs"), "utf8");
|
|
16513
17388
|
return new RegExp(`^([0-9a-f]{40,64}) ${ref.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`, "m").exec(packed)?.[1] ?? null;
|
|
16514
17389
|
} catch {
|
|
16515
17390
|
return null;
|
|
@@ -16521,7 +17396,7 @@ var gitConfig = (dir, invocation, cache) => {
|
|
|
16521
17396
|
if (cached)
|
|
16522
17397
|
return cached;
|
|
16523
17398
|
const config = new Map;
|
|
16524
|
-
const listed =
|
|
17399
|
+
const listed = spawnSync12("git", [...invocation.config ?? [], "config", "-z", "--list"], {
|
|
16525
17400
|
cwd: dir,
|
|
16526
17401
|
encoding: "utf8",
|
|
16527
17402
|
timeout: 3000,
|
|
@@ -16597,8 +17472,8 @@ var expandGitAliases = (root, invocation, cache = new Map, depth = 0) => {
|
|
|
16597
17472
|
const sub = invocation.args[0];
|
|
16598
17473
|
if (invocation.tool !== "git" || invocation.dir === undefined || !sub || sub.startsWith("-") || GIT_COMMANDS.has(sub) || depth >= MAX_DEPTH)
|
|
16599
17474
|
return [invocation];
|
|
16600
|
-
const dir =
|
|
16601
|
-
if (!
|
|
17475
|
+
const dir = path21.resolve(root, invocation.dir ?? ".");
|
|
17476
|
+
if (!fs17.existsSync(dir))
|
|
16602
17477
|
return [invocation];
|
|
16603
17478
|
const config = gitConfig(dir, invocation, cache);
|
|
16604
17479
|
const alias = configValue(config, `alias.${sub.toLowerCase()}`);
|
|
@@ -16620,11 +17495,11 @@ var expandGitAliases = (root, invocation, cache = new Map, depth = 0) => {
|
|
|
16620
17495
|
var normalizedUrl = (url, dir) => {
|
|
16621
17496
|
const trimmed = url.trim();
|
|
16622
17497
|
if (/^file:\/\//i.test(trimmed))
|
|
16623
|
-
return
|
|
17498
|
+
return path21.resolve(dir, trimmed.slice(7)).replace(/\/+$/, "").replace(/\.git$/, "");
|
|
16624
17499
|
const scheme = /^[a-z][a-z0-9+.-]*:\/\//i.exec(trimmed);
|
|
16625
17500
|
const scp = !scheme && /^(?:[^@/\\]+@)?[^/\\:]{2,}:/.test(trimmed);
|
|
16626
17501
|
if (!scheme && !scp)
|
|
16627
|
-
return
|
|
17502
|
+
return path21.resolve(dir, trimmed).replace(/\/+$/, "").replace(/\.git$/, "");
|
|
16628
17503
|
const value = scheme ? trimmed.slice(scheme[0].length).replace(/^[^@/]*@/, "").replace(/^([^/:]+):\d+(?=\/)/, "$1") : trimmed.replace(/^[^@/]*@/, "").replace(":", "/");
|
|
16629
17504
|
return value.toLowerCase().replace(/\/{2,}/g, "/").replace(/^www\./, "").replace(/\/+$/, "").replace(/\.git$/, "");
|
|
16630
17505
|
};
|
|
@@ -16644,7 +17519,7 @@ var rewrittenUrl = (url, config) => {
|
|
|
16644
17519
|
}
|
|
16645
17520
|
return url;
|
|
16646
17521
|
};
|
|
16647
|
-
var
|
|
17522
|
+
var pushUrl2 = (config, remote, dir) => {
|
|
16648
17523
|
const configured = configValue(config, `remote.${remote}.pushurl`) ?? configValue(config, `remote.${remote}.url`);
|
|
16649
17524
|
return normalizedUrl(rewrittenUrl(configured ?? remote, config), dir);
|
|
16650
17525
|
};
|
|
@@ -16672,7 +17547,7 @@ var toPushRemote = (repo, remote, config) => {
|
|
|
16672
17547
|
const pushRemote = pushRemoteOf(config, repo.branch);
|
|
16673
17548
|
if (pushRemote === null || pushRemote === remote)
|
|
16674
17549
|
return true;
|
|
16675
|
-
return
|
|
17550
|
+
return pushUrl2(config, remote, repo.dir) === pushUrl2(config, pushRemote, repo.dir);
|
|
16676
17551
|
};
|
|
16677
17552
|
var quote = (text) => `\`${text}\``;
|
|
16678
17553
|
var refusal = (action, branch, dir) => {
|
|
@@ -16685,7 +17560,7 @@ var refusal = (action, branch, dir) => {
|
|
|
16685
17560
|
};
|
|
16686
17561
|
var refFacts = (repo, remote) => {
|
|
16687
17562
|
const facts = { heads: new Set, tags: new Set, remoteHeads: new Set };
|
|
16688
|
-
const listed =
|
|
17563
|
+
const listed = spawnSync12("git", ["for-each-ref", "--format=%(refname)", "refs/heads", "refs/tags", `refs/remotes/${remote}`], { cwd: repo.dir, encoding: "utf8", timeout: 5000 });
|
|
16689
17564
|
const remotePrefix = `refs/remotes/${remote}/`;
|
|
16690
17565
|
for (const ref of (listed.stdout ?? "").split(`
|
|
16691
17566
|
`)) {
|
|
@@ -16702,7 +17577,7 @@ var globBranches = (repo, glob) => {
|
|
|
16702
17577
|
const source = glob.source.startsWith("refs/") ? glob.source : `refs/heads/${glob.source}`;
|
|
16703
17578
|
const [sourcePrefix, sourceSuffix = ""] = source.split("*");
|
|
16704
17579
|
const [destinationPrefix, destinationSuffix = ""] = glob.destination.split("*");
|
|
16705
|
-
const listed =
|
|
17580
|
+
const listed = spawnSync12("git", [
|
|
16706
17581
|
"for-each-ref",
|
|
16707
17582
|
"--format=%(refname)",
|
|
16708
17583
|
sourcePrefix.slice(0, sourcePrefix.lastIndexOf("/") + 1)
|
|
@@ -16779,7 +17654,7 @@ var tagRefusal = (action, facts, dir) => {
|
|
|
16779
17654
|
return `workit: raw ${quote(`git push${action.allTags ? " --tags" : ""}`)} of ${tags.length ? `tag ${tags.join(", ")}` : "tags"} bypasses the release grant. Push a release tag with ${quote(`workit git push --release ${named}`)} (${shape} tags whose commit the production branch already has; a tag never moves), under the ${quote("release")} grant (if refused, ask the user to run ${quote("workit grant set <workspace> release=true")}).`;
|
|
16780
17655
|
};
|
|
16781
17656
|
var remoteHeads = (repo, remote) => {
|
|
16782
|
-
const listed =
|
|
17657
|
+
const listed = spawnSync12("git", ["ls-remote", "--heads", remote], {
|
|
16783
17658
|
cwd: repo.dir,
|
|
16784
17659
|
encoding: "utf8",
|
|
16785
17660
|
timeout: 3000,
|
|
@@ -16848,7 +17723,7 @@ var actionsIn = (cwd, command, dialect, cache = new Map) => {
|
|
|
16848
17723
|
const out = [];
|
|
16849
17724
|
const repos = new Map;
|
|
16850
17725
|
const repoFor = (relative) => {
|
|
16851
|
-
const dir =
|
|
17726
|
+
const dir = path21.resolve(cwd, relative ?? ".");
|
|
16852
17727
|
if (!repos.has(dir)) {
|
|
16853
17728
|
const repo = repoAt(dir);
|
|
16854
17729
|
repos.set(dir, repo && isWorkitWorkspace(repo) ? repo : null);
|
|
@@ -16934,7 +17809,7 @@ var analysisDenial = (error) => ({
|
|
|
16934
17809
|
});
|
|
16935
17810
|
var NEXT_COMMAND = "next";
|
|
16936
17811
|
var MARKER_TTL_SECONDS = 3600;
|
|
16937
|
-
var markerFile = (repo, session, key) =>
|
|
17812
|
+
var markerFile = (repo, session, key) => path21.join(repo.location.dir, "hooks", `commit-${createHash14("sha256").update(`${session}\x00${key}`).digest("hex").slice(0, 32)}.json`);
|
|
16938
17813
|
var callKey = (toolUseId, command) => toolUseId ?? `cmd:${createHash14("sha256").update(command).digest("hex")}`;
|
|
16939
17814
|
function noteRawCommit(input, command, key) {
|
|
16940
17815
|
try {
|
|
@@ -16945,14 +17820,14 @@ function noteRawCommit(input, command, key) {
|
|
|
16945
17820
|
if (action.kind !== "commit")
|
|
16946
17821
|
continue;
|
|
16947
17822
|
const file = markerFile(repo, input.session.id, key);
|
|
16948
|
-
|
|
16949
|
-
|
|
17823
|
+
fs17.mkdirSync(path21.dirname(file), { recursive: true });
|
|
17824
|
+
fs17.writeFileSync(file, `${JSON.stringify({ head: headSha2(repo), at })}
|
|
16950
17825
|
`);
|
|
16951
17826
|
}
|
|
16952
17827
|
} catch {}
|
|
16953
17828
|
}
|
|
16954
17829
|
var headCommit = (dir) => {
|
|
16955
|
-
const run =
|
|
17830
|
+
const run = spawnSync12("git", [
|
|
16956
17831
|
"log",
|
|
16957
17832
|
"-1",
|
|
16958
17833
|
"--format=%H%x1f%ct%x1f%s%x1f%(trailers:key=Workit-Session,valueonly,separator=%x2c)"
|
|
@@ -16969,11 +17844,11 @@ var headCommit = (dir) => {
|
|
|
16969
17844
|
var settle2 = (input, repo, file) => {
|
|
16970
17845
|
let marker;
|
|
16971
17846
|
try {
|
|
16972
|
-
marker = JSON.parse(
|
|
17847
|
+
marker = JSON.parse(fs17.readFileSync(file, "utf8"));
|
|
16973
17848
|
} catch {
|
|
16974
17849
|
return;
|
|
16975
17850
|
}
|
|
16976
|
-
|
|
17851
|
+
fs17.rmSync(file, { force: true });
|
|
16977
17852
|
const session = input.session.id;
|
|
16978
17853
|
const now = Date.now() / 1000;
|
|
16979
17854
|
if (typeof marker.at !== "number" || now - marker.at > MARKER_TTL_SECONDS)
|
|
@@ -17022,7 +17897,7 @@ function settlePendingCommit(input) {
|
|
|
17022
17897
|
if (!repo)
|
|
17023
17898
|
return;
|
|
17024
17899
|
const file = markerFile(repo, input.session.id, NEXT_COMMAND);
|
|
17025
|
-
if (
|
|
17900
|
+
if (fs17.existsSync(file))
|
|
17026
17901
|
settle2(input, repo, file);
|
|
17027
17902
|
} catch {}
|
|
17028
17903
|
}
|
|
@@ -17166,7 +18041,7 @@ var targetsOf = (invocations) => invocations.flatMap((invocation) => invocation.
|
|
|
17166
18041
|
branch,
|
|
17167
18042
|
dir: invocation.dir
|
|
17168
18043
|
})) : []);
|
|
17169
|
-
var localBranchExists = (dir, branch) =>
|
|
18044
|
+
var localBranchExists = (dir, branch) => spawnSync13("git", ["show-ref", "--verify", "--quiet", `refs/heads/${branch}`], {
|
|
17170
18045
|
cwd: dir,
|
|
17171
18046
|
stdio: "ignore",
|
|
17172
18047
|
timeout: 5000
|
|
@@ -17176,8 +18051,8 @@ var judgedDir = (root, dir) => {
|
|
|
17176
18051
|
return null;
|
|
17177
18052
|
if (dir === null)
|
|
17178
18053
|
return root;
|
|
17179
|
-
const resolved =
|
|
17180
|
-
return
|
|
18054
|
+
const resolved = path22.resolve(root, dir);
|
|
18055
|
+
return existsSync7(resolved) && inWorkitWorkspace(resolved) ? resolved : null;
|
|
17181
18056
|
};
|
|
17182
18057
|
var shellBranchPolicyViolation = (root, command, dialect = "posix") => {
|
|
17183
18058
|
let judged = false;
|
|
@@ -17412,7 +18287,7 @@ var judgeInputSummary = (raw) => {
|
|
|
17412
18287
|
|
|
17413
18288
|
// packages/workit-core/src/autonomy.ts
|
|
17414
18289
|
import os5 from "node:os";
|
|
17415
|
-
import
|
|
18290
|
+
import path23 from "node:path";
|
|
17416
18291
|
var accountHome = () => {
|
|
17417
18292
|
try {
|
|
17418
18293
|
return os5.userInfo().homedir;
|
|
@@ -17421,10 +18296,10 @@ var accountHome = () => {
|
|
|
17421
18296
|
}
|
|
17422
18297
|
};
|
|
17423
18298
|
var grantsHome = { resolve: accountHome };
|
|
17424
|
-
var grantsDir = () =>
|
|
18299
|
+
var grantsDir = () => path23.join(grantsHome.resolve(), ".config", "workit");
|
|
17425
18300
|
var grantsOverride = () => {
|
|
17426
|
-
const configured =
|
|
17427
|
-
return configured ===
|
|
18301
|
+
const configured = path23.resolve(resolveConfigDir());
|
|
18302
|
+
return configured === path23.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 ${path23.join(grantsDir(), "workspaces.json")}, so the D4 defaults apply`;
|
|
17428
18303
|
};
|
|
17429
18304
|
var GRANT_KINDS = ["push", "pr", "merge", "release", "rerun"];
|
|
17430
18305
|
var DEFAULT_GRANTS = Object.freeze({
|
|
@@ -17477,8 +18352,16 @@ function endpointView(entry, context) {
|
|
|
17477
18352
|
...issue ? { endpointIssue: issue } : {}
|
|
17478
18353
|
};
|
|
17479
18354
|
}
|
|
17480
|
-
var
|
|
17481
|
-
|
|
18355
|
+
var VERIFICATION_MODES = [
|
|
18356
|
+
"proportional",
|
|
18357
|
+
"self",
|
|
18358
|
+
"independent"
|
|
18359
|
+
];
|
|
18360
|
+
var DEFAULT_VERIFICATION = "proportional";
|
|
18361
|
+
var verificationOf = (entry) => {
|
|
18362
|
+
const value = entry?.verification;
|
|
18363
|
+
return VERIFICATION_MODES.includes(value) ? value : DEFAULT_VERIFICATION;
|
|
18364
|
+
};
|
|
17482
18365
|
var isGrantValue = (value) => value === true || value === false || value === "verified";
|
|
17483
18366
|
var normalized = (kind, value) => value === "verified" && kind !== "merge" ? true : value;
|
|
17484
18367
|
var fromAutoApprove = (value) => {
|
|
@@ -19225,9 +20108,9 @@ class WorkitCore {
|
|
|
19225
20108
|
}
|
|
19226
20109
|
}
|
|
19227
20110
|
// packages/workit-core/src/core/repo-context.ts
|
|
19228
|
-
import { spawnSync as
|
|
19229
|
-
import { existsSync as
|
|
19230
|
-
import
|
|
20111
|
+
import { spawnSync as spawnSync14 } from "node:child_process";
|
|
20112
|
+
import { existsSync as existsSync8, readFileSync as readFileSync6, readdirSync as readdirSync4 } from "node:fs";
|
|
20113
|
+
import path24 from "node:path";
|
|
19231
20114
|
var isSafeContextRange = (value) => value.length <= 4096 && (() => {
|
|
19232
20115
|
try {
|
|
19233
20116
|
gitRevisionParts(value);
|
|
@@ -19254,7 +20137,7 @@ var invalidRange = (cwd) => ({
|
|
|
19254
20137
|
cwd
|
|
19255
20138
|
});
|
|
19256
20139
|
var runGit = (cwd, args) => {
|
|
19257
|
-
const result =
|
|
20140
|
+
const result = spawnSync14("git", args, { cwd, encoding: "utf8" });
|
|
19258
20141
|
return {
|
|
19259
20142
|
stdout: (result.stdout ?? "").trimEnd(),
|
|
19260
20143
|
stderr: (result.stderr ?? "").trimEnd(),
|
|
@@ -19361,7 +20244,7 @@ var findPrTemplate = (cwd) => {
|
|
|
19361
20244
|
]) {
|
|
19362
20245
|
const parts = rel.split("/");
|
|
19363
20246
|
const base = parts[parts.length - 1];
|
|
19364
|
-
const dir = parts.length > 1 ?
|
|
20247
|
+
const dir = parts.length > 1 ? path24.join(cwd, ...parts.slice(0, -1)) : cwd;
|
|
19365
20248
|
const actual = probeCaseInsensitive(dir, base);
|
|
19366
20249
|
if (actual)
|
|
19367
20250
|
return [...parts.slice(0, -1), actual].join("/");
|
|
@@ -19392,11 +20275,11 @@ var diffStatForRange = (cwd, range) => {
|
|
|
19392
20275
|
return runGit(cwd, ["diff", "--stat", "--"]).stdout;
|
|
19393
20276
|
};
|
|
19394
20277
|
var packageScripts = (cwd, keys) => {
|
|
19395
|
-
const pkgPath =
|
|
19396
|
-
if (!
|
|
20278
|
+
const pkgPath = path24.join(cwd, "package.json");
|
|
20279
|
+
if (!existsSync8(pkgPath))
|
|
19397
20280
|
return [];
|
|
19398
20281
|
try {
|
|
19399
|
-
const pkg = JSON.parse(
|
|
20282
|
+
const pkg = JSON.parse(readFileSync6(pkgPath, "utf8"));
|
|
19400
20283
|
return keys.filter((k) => pkg.scripts && typeof pkg.scripts[k] === "string").map((k) => `${k}: ${pkg.scripts?.[k]}`);
|
|
19401
20284
|
} catch {
|
|
19402
20285
|
return [];
|
|
@@ -19418,9 +20301,9 @@ var documentationFiles = (cwd) => {
|
|
|
19418
20301
|
if (entry.isDirectory()) {
|
|
19419
20302
|
if (excluded.has(entry.name))
|
|
19420
20303
|
continue;
|
|
19421
|
-
walk(
|
|
20304
|
+
walk(path24.join(dir, entry.name), depth + 1);
|
|
19422
20305
|
} else if (entry.isFile() && (entry.name === "README.md" || entry.name.endsWith(".md"))) {
|
|
19423
|
-
matches.push("./" +
|
|
20306
|
+
matches.push("./" + path24.relative(cwd, path24.join(dir, entry.name)).replaceAll("\\", "/"));
|
|
19424
20307
|
}
|
|
19425
20308
|
}
|
|
19426
20309
|
};
|
|
@@ -19428,15 +20311,15 @@ var documentationFiles = (cwd) => {
|
|
|
19428
20311
|
return matches.toSorted().slice(0, 200);
|
|
19429
20312
|
};
|
|
19430
20313
|
var readTrimmed2 = (file, maxLines) => {
|
|
19431
|
-
if (!
|
|
20314
|
+
if (!existsSync8(file))
|
|
19432
20315
|
return "";
|
|
19433
|
-
const lines =
|
|
20316
|
+
const lines = readFileSync6(file, "utf8").split(`
|
|
19434
20317
|
`);
|
|
19435
20318
|
return lines.slice(0, maxLines).join(`
|
|
19436
20319
|
`);
|
|
19437
20320
|
};
|
|
19438
20321
|
function prReadyContext(root, range) {
|
|
19439
|
-
const cwd =
|
|
20322
|
+
const cwd = path24.resolve(repoRoot(root));
|
|
19440
20323
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
19441
20324
|
return invalidRange(cwd);
|
|
19442
20325
|
let stdout = "";
|
|
@@ -19494,7 +20377,7 @@ ${status.stderr}` : "");
|
|
|
19494
20377
|
stdout += `template_path: ${template}
|
|
19495
20378
|
|
|
19496
20379
|
`;
|
|
19497
|
-
stdout += readTrimmed2(
|
|
20380
|
+
stdout += readTrimmed2(path24.join(cwd, template), 220);
|
|
19498
20381
|
} else {
|
|
19499
20382
|
stdout += `template_path: none
|
|
19500
20383
|
|
|
@@ -19504,7 +20387,7 @@ ${status.stderr}` : "");
|
|
|
19504
20387
|
stdout += `
|
|
19505
20388
|
`;
|
|
19506
20389
|
stdout += printSection("Recent Validation Signals");
|
|
19507
|
-
if (
|
|
20390
|
+
if (existsSync8(path24.join(cwd, "package.json"))) {
|
|
19508
20391
|
stdout += `package.json detected. Common scripts:
|
|
19509
20392
|
`;
|
|
19510
20393
|
const found = packageScripts(cwd, ["lint", "format:check", "test", "build"]);
|
|
@@ -19513,7 +20396,7 @@ ${status.stderr}` : "");
|
|
|
19513
20396
|
`) + `
|
|
19514
20397
|
`;
|
|
19515
20398
|
}
|
|
19516
|
-
if (
|
|
20399
|
+
if (existsSync8(path24.join(cwd, "Cargo.toml")) || existsSync8(path24.join(cwd, "src-tauri/Cargo.toml"))) {
|
|
19517
20400
|
stdout += `Rust project detected.
|
|
19518
20401
|
`;
|
|
19519
20402
|
}
|
|
@@ -19546,7 +20429,7 @@ var CHANGELOG_RULES = `- Use an [Unreleased] section.
|
|
|
19546
20429
|
- Apply with the host-native editor, preserving the repository changelog conventions.
|
|
19547
20430
|
- If Unreleased already has duplicate category headings, normalize_only first.`;
|
|
19548
20431
|
function changelogContext(root, range) {
|
|
19549
|
-
const cwd =
|
|
20432
|
+
const cwd = path24.resolve(repoRoot(root));
|
|
19550
20433
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
19551
20434
|
return invalidRange(cwd);
|
|
19552
20435
|
const resolvedRange = rangeArgOrDefault(range, cwd);
|
|
@@ -19564,8 +20447,8 @@ function changelogContext(root, range) {
|
|
|
19564
20447
|
stdout += CHANGELOG_RULES + `
|
|
19565
20448
|
`;
|
|
19566
20449
|
stdout += printSection("Existing CHANGELOG.md");
|
|
19567
|
-
const changelogPath =
|
|
19568
|
-
if (
|
|
20450
|
+
const changelogPath = path24.join(cwd, "CHANGELOG.md");
|
|
20451
|
+
if (existsSync8(changelogPath)) {
|
|
19569
20452
|
stdout += readTrimmed2(changelogPath, 260) + `
|
|
19570
20453
|
`;
|
|
19571
20454
|
} else {
|
|
@@ -19584,7 +20467,7 @@ function changelogContext(root, range) {
|
|
|
19584
20467
|
return { stdout, stderr: "", exitCode: 0, cwd };
|
|
19585
20468
|
}
|
|
19586
20469
|
function docsRefreshContext(root, range) {
|
|
19587
|
-
const cwd =
|
|
20470
|
+
const cwd = path24.resolve(repoRoot(root));
|
|
19588
20471
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
19589
20472
|
return invalidRange(cwd);
|
|
19590
20473
|
const resolvedRange = rangeArgOrDefault(range, cwd);
|
|
@@ -19606,8 +20489,8 @@ function docsRefreshContext(root, range) {
|
|
|
19606
20489
|
`) + `
|
|
19607
20490
|
`;
|
|
19608
20491
|
stdout += printSection("README Preview");
|
|
19609
|
-
const readme =
|
|
19610
|
-
if (
|
|
20492
|
+
const readme = path24.join(cwd, "README.md");
|
|
20493
|
+
if (existsSync8(readme)) {
|
|
19611
20494
|
stdout += readTrimmed2(readme, 220) + `
|
|
19612
20495
|
`;
|
|
19613
20496
|
} else {
|
|
@@ -19615,10 +20498,10 @@ function docsRefreshContext(root, range) {
|
|
|
19615
20498
|
`;
|
|
19616
20499
|
}
|
|
19617
20500
|
stdout += printSection("Package Scripts");
|
|
19618
|
-
const pkgPath =
|
|
19619
|
-
if (
|
|
20501
|
+
const pkgPath = path24.join(cwd, "package.json");
|
|
20502
|
+
if (existsSync8(pkgPath)) {
|
|
19620
20503
|
try {
|
|
19621
|
-
const pkg = JSON.parse(
|
|
20504
|
+
const pkg = JSON.parse(readFileSync6(pkgPath, "utf8"));
|
|
19622
20505
|
stdout += JSON.stringify({ name: pkg.name, version: pkg.version, scripts: pkg.scripts }, null, 2) + `
|
|
19623
20506
|
`;
|
|
19624
20507
|
} catch {}
|
|
@@ -19629,7 +20512,7 @@ function docsRefreshContext(root, range) {
|
|
|
19629
20512
|
return { stdout, stderr: "", exitCode: 0, cwd };
|
|
19630
20513
|
}
|
|
19631
20514
|
function releaseNotesContext(root, rangeOrTag) {
|
|
19632
|
-
const cwd =
|
|
20515
|
+
const cwd = path24.resolve(repoRoot(root));
|
|
19633
20516
|
if (!rangeOrTag) {
|
|
19634
20517
|
return { stdout: "", stderr: `ERROR: release tag or range required
|
|
19635
20518
|
`, exitCode: 1, cwd };
|
|
@@ -19667,7 +20550,7 @@ function releaseNotesContext(root, rangeOrTag) {
|
|
|
19667
20550
|
`;
|
|
19668
20551
|
stdout += printSection("Existing Release Files");
|
|
19669
20552
|
for (const rel of ["CHANGELOG.md", "RELEASE_NOTES.md", ".github/releases.md"]) {
|
|
19670
|
-
if (
|
|
20553
|
+
if (existsSync8(path24.join(cwd, rel)))
|
|
19671
20554
|
stdout += rel + `
|
|
19672
20555
|
`;
|
|
19673
20556
|
}
|
|
@@ -19696,46 +20579,6 @@ function releaseNotesContext(root, rangeOrTag) {
|
|
|
19696
20579
|
|
|
19697
20580
|
// packages/workit-core/src/core/tracker-issues.ts
|
|
19698
20581
|
import { spawnSync as spawnSync15 } from "node:child_process";
|
|
19699
|
-
|
|
19700
|
-
// packages/workit-core/src/core/pr-create.ts
|
|
19701
|
-
import fs17 from "node:fs";
|
|
19702
|
-
import path23 from "node:path";
|
|
19703
|
-
import { spawnSync as spawnSync14 } from "node:child_process";
|
|
19704
|
-
// packages/workit-core/src/forge/pr-body.ts
|
|
19705
|
-
function parseGhIssue(value) {
|
|
19706
|
-
const m = /issues\/(\d+)/.exec(value);
|
|
19707
|
-
return m ? m[1] : String(value).trim().replace(/^#/, "");
|
|
19708
|
-
}
|
|
19709
|
-
|
|
19710
|
-
// packages/workit-core/src/core/pr-create.ts
|
|
19711
|
-
function whichOnPath(tool) {
|
|
19712
|
-
const names = process.platform === "win32" ? [tool, `${tool}.exe`, `${tool}.cmd`] : [tool];
|
|
19713
|
-
for (const dir of (process.env.PATH ?? "").split(path23.delimiter)) {
|
|
19714
|
-
if (!dir)
|
|
19715
|
-
continue;
|
|
19716
|
-
for (const name of names) {
|
|
19717
|
-
const candidate = path23.join(dir, name);
|
|
19718
|
-
try {
|
|
19719
|
-
fs17.accessSync(candidate, fs17.constants.X_OK);
|
|
19720
|
-
return candidate;
|
|
19721
|
-
} catch {}
|
|
19722
|
-
}
|
|
19723
|
-
}
|
|
19724
|
-
return null;
|
|
19725
|
-
}
|
|
19726
|
-
var hostingCliAvailable = (provider) => whichOnPath(provider === "gitlab" ? "glab" : "gh") !== null;
|
|
19727
|
-
var pushRemote = (cwd) => {
|
|
19728
|
-
const result = spawnSync14("git", ["remote", "get-url", "--push", "--all", "origin"], {
|
|
19729
|
-
cwd,
|
|
19730
|
-
encoding: "utf8"
|
|
19731
|
-
});
|
|
19732
|
-
if (result.status !== 0)
|
|
19733
|
-
return null;
|
|
19734
|
-
const urls = (result.stdout ?? "").split(/\r?\n/u).map((url) => url.trim()).filter(Boolean);
|
|
19735
|
-
return urls.length === 1 ? urls[0] : null;
|
|
19736
|
-
};
|
|
19737
|
-
|
|
19738
|
-
// packages/workit-core/src/core/tracker-issues.ts
|
|
19739
20582
|
var repoPathFromRemote = (remote) => {
|
|
19740
20583
|
let rest = (remote || "").trim().replace(/\/+$/, "");
|
|
19741
20584
|
if (!rest)
|
|
@@ -19835,7 +20678,7 @@ var fetchGitLabIssueBody = async (ref, root, deps = {}) => {
|
|
|
19835
20678
|
|
|
19836
20679
|
// packages/workit-core/src/core/youtrack.ts
|
|
19837
20680
|
import fs18 from "node:fs";
|
|
19838
|
-
import
|
|
20681
|
+
import path25 from "node:path";
|
|
19839
20682
|
|
|
19840
20683
|
// packages/workit-core/src/core/templates.ts
|
|
19841
20684
|
var repoRoot2 = assetRoot();
|
|
@@ -19848,8 +20691,8 @@ var resolveWorkspaceRoot = (explicit) => explicit || process.cwd();
|
|
|
19848
20691
|
// packages/workit-core/src/core/youtrack.ts
|
|
19849
20692
|
var ISSUE_RE = /^[A-Z]+-\d+$/;
|
|
19850
20693
|
var TOKEN_PLACEHOLDER = "YOUR_TOKEN_HERE";
|
|
19851
|
-
var youTrackConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ??
|
|
19852
|
-
var youTrackReadOnlyConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ??
|
|
20694
|
+
var youTrackConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path25.join(configDir(), "youtrack.json");
|
|
20695
|
+
var youTrackReadOnlyConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path25.join(resolveConfigDir(), "youtrack.json");
|
|
19853
20696
|
var youTrackTokenModeOk = (p) => {
|
|
19854
20697
|
if (process.platform === "win32")
|
|
19855
20698
|
return true;
|
|
@@ -19917,7 +20760,7 @@ var youTrackToken = () => {
|
|
|
19917
20760
|
if ("error" in loaded)
|
|
19918
20761
|
return loaded;
|
|
19919
20762
|
const tokenFile = String(loaded.config.tokenFile ?? "");
|
|
19920
|
-
const tokenPath = tokenFile ?
|
|
20763
|
+
const tokenPath = tokenFile ? path25.isAbsolute(tokenFile) ? path25.resolve(tokenFile) : path25.resolve(process.cwd(), tokenFile) : "";
|
|
19921
20764
|
if (!tokenPath || !fs18.existsSync(tokenPath))
|
|
19922
20765
|
return { error: "missing youtrack.token" };
|
|
19923
20766
|
if (!youTrackTokenModeOk(tokenPath))
|
|
@@ -20274,15 +21117,15 @@ function resolveGitRevision(root, value) {
|
|
|
20274
21117
|
}
|
|
20275
21118
|
function resolveInside(root, candidate) {
|
|
20276
21119
|
const base = realpathSync5(root);
|
|
20277
|
-
const target =
|
|
20278
|
-
if (target !== base && !target.startsWith(base +
|
|
21120
|
+
const target = path26.resolve(base, candidate);
|
|
21121
|
+
if (target !== base && !target.startsWith(base + path26.sep)) {
|
|
20279
21122
|
throw new Error("path must stay inside repository root");
|
|
20280
21123
|
}
|
|
20281
21124
|
let ancestor = target;
|
|
20282
|
-
while (!
|
|
20283
|
-
ancestor =
|
|
21125
|
+
while (!existsSync9(ancestor))
|
|
21126
|
+
ancestor = path26.dirname(ancestor);
|
|
20284
21127
|
const canonicalAncestor = realpathSync5(ancestor);
|
|
20285
|
-
if (canonicalAncestor !== base && !canonicalAncestor.startsWith(base +
|
|
21128
|
+
if (canonicalAncestor !== base && !canonicalAncestor.startsWith(base + path26.sep)) {
|
|
20286
21129
|
throw new Error("path must stay inside repository root");
|
|
20287
21130
|
}
|
|
20288
21131
|
return target;
|
|
@@ -20328,7 +21171,7 @@ var capabilitiesFor = (descriptor, observed = {}) => descriptor.capabilities.map
|
|
|
20328
21171
|
// packages/workit-core/src/hooks/context.ts
|
|
20329
21172
|
import { createHash as createHash15 } from "node:crypto";
|
|
20330
21173
|
import fs19 from "node:fs";
|
|
20331
|
-
import
|
|
21174
|
+
import path27 from "node:path";
|
|
20332
21175
|
var MIGRATION_PENDING_NOTE = "workit migration pending — run `workit task status`";
|
|
20333
21176
|
var migrationPending = (result) => !result.ok && result.code === "needs_input" && (result.error ?? "").startsWith(MIGRATION_PENDING);
|
|
20334
21177
|
var boundTo = (entry, session) => entry.sessions.some((item) => item.host === session.host && item.handle === session.handle);
|
|
@@ -20413,7 +21256,7 @@ function entryCompactContext(store, entry, session, context) {
|
|
|
20413
21256
|
return null;
|
|
20414
21257
|
const freshness = { mode: "signal", current: () => recentSignal(store.root) };
|
|
20415
21258
|
const observed = task?.data.evidence.some(isObservedCheck) ?? false;
|
|
20416
|
-
const files = (task?.data.decisions ?? []).flatMap((decision) => decision.data.binding.contentRefs.flatMap((ref) => ref.kind === "file" ? [
|
|
21259
|
+
const files = (task?.data.decisions ?? []).flatMap((decision) => decision.data.binding.contentRefs.flatMap((ref) => ref.kind === "file" ? [path27.resolve(store.root, ref.path)] : []));
|
|
20417
21260
|
const observedFiles = filesKey(files);
|
|
20418
21261
|
const compact = new WorkitCore(store, context).compactContext(entry?.id ?? null, freshness, origin && { origin: origin.origin, taskId: origin.entry.id });
|
|
20419
21262
|
if (!compact.ok || !compact.data)
|
|
@@ -20491,14 +21334,14 @@ var turnMarker = (input) => {
|
|
|
20491
21334
|
if (location instanceof Error)
|
|
20492
21335
|
return null;
|
|
20493
21336
|
const session = [input.host, input.session.id, input.session.agentId ?? ""].join("\x00");
|
|
20494
|
-
return
|
|
21337
|
+
return path27.join(location.dir, "hooks", `turn-${digestOf(session).slice(0, 32)}.json`);
|
|
20495
21338
|
} catch {
|
|
20496
21339
|
return null;
|
|
20497
21340
|
}
|
|
20498
21341
|
};
|
|
20499
21342
|
var writeTurnDigest = (file, digest) => {
|
|
20500
21343
|
try {
|
|
20501
|
-
fs19.mkdirSync(
|
|
21344
|
+
fs19.mkdirSync(path27.dirname(file), { recursive: true });
|
|
20502
21345
|
fs19.writeFileSync(file, `${JSON.stringify({ digest })}
|
|
20503
21346
|
`);
|
|
20504
21347
|
} catch {}
|
|
@@ -20513,7 +21356,7 @@ var pruneTurnDigests = (dir, now) => {
|
|
|
20513
21356
|
for (const name of fs19.readdirSync(dir)) {
|
|
20514
21357
|
if (!name.startsWith("turn-"))
|
|
20515
21358
|
continue;
|
|
20516
|
-
const file =
|
|
21359
|
+
const file = path27.join(dir, name);
|
|
20517
21360
|
try {
|
|
20518
21361
|
if (now - fs19.statSync(file).mtimeMs > TURN_TTL_MS)
|
|
20519
21362
|
fs19.rmSync(file, { force: true });
|
|
@@ -20543,7 +21386,7 @@ var seedTurnContext = (input, descriptor, text = turnContextText(input, descript
|
|
|
20543
21386
|
const file = turnMarker(input);
|
|
20544
21387
|
if (!file)
|
|
20545
21388
|
return;
|
|
20546
|
-
pruneTurnDigests(
|
|
21389
|
+
pruneTurnDigests(path27.dirname(file), Date.now());
|
|
20547
21390
|
if (text)
|
|
20548
21391
|
writeTurnDigest(file, digestOf(text));
|
|
20549
21392
|
else
|
|
@@ -20564,7 +21407,7 @@ var shellPolicy = (cwd, command, dialect = "posix") => {
|
|
|
20564
21407
|
// packages/workit-core/src/core/heavy-rules.ts
|
|
20565
21408
|
import fs20 from "node:fs";
|
|
20566
21409
|
import os6 from "node:os";
|
|
20567
|
-
import
|
|
21410
|
+
import path28 from "node:path";
|
|
20568
21411
|
var EMPTY2 = () => ({ deny: [], allow: [] });
|
|
20569
21412
|
var readText2 = (file) => {
|
|
20570
21413
|
try {
|
|
@@ -20605,19 +21448,19 @@ function parseJsonc(text) {
|
|
|
20605
21448
|
return JSON.parse(out.replace(/,(\s*[}\]])/gu, "$1"));
|
|
20606
21449
|
}
|
|
20607
21450
|
function gitTop(cwd) {
|
|
20608
|
-
for (let dir =
|
|
20609
|
-
if (fs20.existsSync(
|
|
21451
|
+
for (let dir = path28.resolve(cwd);; dir = path28.dirname(dir)) {
|
|
21452
|
+
if (fs20.existsSync(path28.join(dir, ".git")))
|
|
20610
21453
|
return dir;
|
|
20611
|
-
if (
|
|
21454
|
+
if (path28.dirname(dir) === dir)
|
|
20612
21455
|
return null;
|
|
20613
21456
|
}
|
|
20614
21457
|
}
|
|
20615
21458
|
var projectDirs = (cwd) => {
|
|
20616
21459
|
const top = gitTop(cwd);
|
|
20617
21460
|
const dirs = [];
|
|
20618
|
-
for (let dir =
|
|
21461
|
+
for (let dir = path28.resolve(cwd);; dir = path28.dirname(dir)) {
|
|
20619
21462
|
dirs.push(dir);
|
|
20620
|
-
if (!top || dir === top ||
|
|
21463
|
+
if (!top || dir === top || path28.dirname(dir) === dir)
|
|
20621
21464
|
return dirs;
|
|
20622
21465
|
}
|
|
20623
21466
|
};
|
|
@@ -20629,12 +21472,12 @@ var CLAUDE_MANAGED = {
|
|
|
20629
21472
|
};
|
|
20630
21473
|
function claudeSettingsFiles(cwd, env, platform = process.platform) {
|
|
20631
21474
|
const project = env.CLAUDE_PROJECT_DIR || gitTop(cwd) || cwd;
|
|
20632
|
-
const user = env.CLAUDE_CONFIG_DIR ||
|
|
21475
|
+
const user = env.CLAUDE_CONFIG_DIR || path28.join(homeOf(env), ".claude");
|
|
20633
21476
|
return [
|
|
20634
21477
|
...CLAUDE_MANAGED[platform] ? [CLAUDE_MANAGED[platform]] : [],
|
|
20635
|
-
|
|
20636
|
-
|
|
20637
|
-
|
|
21478
|
+
path28.join(user, "settings.json"),
|
|
21479
|
+
path28.join(project, ".claude", "settings.json"),
|
|
21480
|
+
path28.join(project, ".claude", "settings.local.json")
|
|
20638
21481
|
];
|
|
20639
21482
|
}
|
|
20640
21483
|
var bashPattern = (entry) => {
|
|
@@ -20724,10 +21567,10 @@ var opencodeConfig = (value, rules) => {
|
|
|
20724
21567
|
function opencodeRules(cwd, env = process.env) {
|
|
20725
21568
|
const rules = EMPTY2();
|
|
20726
21569
|
try {
|
|
20727
|
-
const config = env.XDG_CONFIG_HOME ||
|
|
21570
|
+
const config = env.XDG_CONFIG_HOME || path28.join(homeOf(env), ".config");
|
|
20728
21571
|
const files = [
|
|
20729
|
-
...OPENCODE_NAMES.map((name) =>
|
|
20730
|
-
...projectDirs(cwd).flatMap((dir) => OPENCODE_NAMES.flatMap((name) => [
|
|
21572
|
+
...OPENCODE_NAMES.map((name) => path28.join(config, "opencode", name)),
|
|
21573
|
+
...projectDirs(cwd).flatMap((dir) => OPENCODE_NAMES.flatMap((name) => [path28.join(dir, name), path28.join(dir, ".opencode", name)])),
|
|
20731
21574
|
...env.OPENCODE_CONFIG ? [env.OPENCODE_CONFIG] : []
|
|
20732
21575
|
];
|
|
20733
21576
|
for (const file of files) {
|
|
@@ -20771,9 +21614,9 @@ var codexRule = (body) => {
|
|
|
20771
21614
|
function codexRules(cwd, env = process.env) {
|
|
20772
21615
|
const rules = EMPTY2();
|
|
20773
21616
|
try {
|
|
20774
|
-
const home = env.CODEX_HOME ||
|
|
21617
|
+
const home = env.CODEX_HOME || path28.join(homeOf(env), ".codex");
|
|
20775
21618
|
const top = gitTop(cwd) ?? cwd;
|
|
20776
|
-
for (const dir of [
|
|
21619
|
+
for (const dir of [path28.join(home, "rules"), path28.join(top, ".codex", "rules")]) {
|
|
20777
21620
|
let files;
|
|
20778
21621
|
try {
|
|
20779
21622
|
files = fs20.readdirSync(dir).filter((name) => name.endsWith(".rules"));
|
|
@@ -20783,7 +21626,7 @@ function codexRules(cwd, env = process.env) {
|
|
|
20783
21626
|
return "unreadable";
|
|
20784
21627
|
}
|
|
20785
21628
|
for (const name of files) {
|
|
20786
|
-
const text = readText2(
|
|
21629
|
+
const text = readText2(path28.join(dir, name)) ?? "";
|
|
20787
21630
|
for (const part of text.split(/\bprefix_rule\s*\(/u).slice(1)) {
|
|
20788
21631
|
const rule = codexRule(part);
|
|
20789
21632
|
if (!rule)
|
|
@@ -20802,8 +21645,8 @@ function cursorRules(cwd, env = process.env) {
|
|
|
20802
21645
|
try {
|
|
20803
21646
|
const top = gitTop(cwd) ?? cwd;
|
|
20804
21647
|
for (const file of [
|
|
20805
|
-
|
|
20806
|
-
|
|
21648
|
+
path28.join(homeOf(env), ".cursor", "cli-config.json"),
|
|
21649
|
+
path28.join(top, ".cursor", "cli.json")
|
|
20807
21650
|
]) {
|
|
20808
21651
|
const text = readText2(file);
|
|
20809
21652
|
if (text === null)
|
|
@@ -20879,21 +21722,21 @@ function wrapAllowed(read, words, command, options = {}) {
|
|
|
20879
21722
|
// packages/workit-core/src/core/heavy.ts
|
|
20880
21723
|
import fs21 from "node:fs";
|
|
20881
21724
|
import os7 from "node:os";
|
|
20882
|
-
import
|
|
21725
|
+
import path29 from "node:path";
|
|
20883
21726
|
function stateDir(env, platform = process.platform) {
|
|
20884
21727
|
if (env.WORKFLOW_TOOLKIT_STATE)
|
|
20885
21728
|
return env.WORKFLOW_TOOLKIT_STATE;
|
|
20886
21729
|
if (env.XDG_STATE_HOME)
|
|
20887
|
-
return
|
|
21730
|
+
return path29.join(env.XDG_STATE_HOME, "workit");
|
|
20888
21731
|
const home = env.HOME || os7.homedir();
|
|
20889
21732
|
if (platform === "darwin")
|
|
20890
|
-
return
|
|
21733
|
+
return path29.join(home, "Library", "Application Support", "workit");
|
|
20891
21734
|
if (platform === "win32")
|
|
20892
|
-
return
|
|
20893
|
-
return
|
|
21735
|
+
return path29.join(env.LOCALAPPDATA ?? path29.join(home, "AppData", "Local"), "workit");
|
|
21736
|
+
return path29.join(home, ".local", "state", "workit");
|
|
20894
21737
|
}
|
|
20895
21738
|
var HEAVY_COMMANDS_FILE = "heavy-commands.json";
|
|
20896
|
-
var heavyCommandsFile = (env) =>
|
|
21739
|
+
var heavyCommandsFile = (env) => path29.join(stateDir(env), HEAVY_COMMANDS_FILE);
|
|
20897
21740
|
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;
|
|
20898
21741
|
var INFO_FLAGS = new Set(["--version", "-v", "-V", "--help", "-h", "help", "version"]);
|
|
20899
21742
|
var WATCH_FLAG = /^(?:--watch(?:All)?(?:=.*)?|-w|--serve)$/u;
|
|
@@ -21168,7 +22011,7 @@ function machinePatterns(env) {
|
|
|
21168
22011
|
return isRecord5(value) ? stringList(value.patterns) : [];
|
|
21169
22012
|
}
|
|
21170
22013
|
function repoPatterns(cwd) {
|
|
21171
|
-
return repoHeavyCommands(gitTop(cwd) ??
|
|
22014
|
+
return repoHeavyCommands(gitTop(cwd) ?? path29.resolve(cwd));
|
|
21172
22015
|
}
|
|
21173
22016
|
function heavyLines(lines, options) {
|
|
21174
22017
|
const found = [];
|
|
@@ -21246,12 +22089,12 @@ function heavyPlan(command, options) {
|
|
|
21246
22089
|
}
|
|
21247
22090
|
function workitOnPath2(env = process.env) {
|
|
21248
22091
|
const names = process.platform === "win32" ? ["workit.exe", "workit.cmd", "workit"] : ["workit"];
|
|
21249
|
-
for (const dir of (env.PATH ?? "").split(
|
|
22092
|
+
for (const dir of (env.PATH ?? "").split(path29.delimiter))
|
|
21250
22093
|
for (const name of names) {
|
|
21251
22094
|
if (!dir)
|
|
21252
22095
|
continue;
|
|
21253
22096
|
try {
|
|
21254
|
-
fs21.accessSync(
|
|
22097
|
+
fs21.accessSync(path29.join(dir, name), fs21.constants.X_OK);
|
|
21255
22098
|
return true;
|
|
21256
22099
|
} catch {}
|
|
21257
22100
|
}
|
|
@@ -21270,20 +22113,20 @@ function leaseRewrite(command) {
|
|
|
21270
22113
|
// packages/workit-core/src/core/heavy-words.ts
|
|
21271
22114
|
import fs22 from "node:fs";
|
|
21272
22115
|
import os8 from "node:os";
|
|
21273
|
-
import
|
|
22116
|
+
import path30 from "node:path";
|
|
21274
22117
|
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(" "));
|
|
21275
22118
|
var wordsOf = (command) => command.split(/[\s;&|()<>`'"]+/u).map((word) => (word.split(/[\\/]/u).at(-1) ?? "").toLowerCase().replace(/\.(?:exe|cmd|bat)$/u, "")).filter(Boolean);
|
|
21276
22119
|
var stateDir2 = (env) => {
|
|
21277
22120
|
if (env.WORKFLOW_TOOLKIT_STATE)
|
|
21278
22121
|
return env.WORKFLOW_TOOLKIT_STATE;
|
|
21279
22122
|
if (env.XDG_STATE_HOME)
|
|
21280
|
-
return
|
|
22123
|
+
return path30.join(env.XDG_STATE_HOME, "workit");
|
|
21281
22124
|
const home = env.HOME || os8.homedir();
|
|
21282
22125
|
if (process.platform === "darwin")
|
|
21283
|
-
return
|
|
22126
|
+
return path30.join(home, "Library", "Application Support", "workit");
|
|
21284
22127
|
if (process.platform === "win32")
|
|
21285
|
-
return
|
|
21286
|
-
return
|
|
22128
|
+
return path30.join(env.LOCALAPPDATA ?? path30.join(home, "AppData", "Local"), "workit");
|
|
22129
|
+
return path30.join(home, ".local", "state", "workit");
|
|
21287
22130
|
};
|
|
21288
22131
|
var readJson2 = (file) => {
|
|
21289
22132
|
try {
|
|
@@ -21294,15 +22137,15 @@ var readJson2 = (file) => {
|
|
|
21294
22137
|
};
|
|
21295
22138
|
var patternWords = (cwd, env) => {
|
|
21296
22139
|
const patterns = [];
|
|
21297
|
-
const machine = readJson2(
|
|
22140
|
+
const machine = readJson2(path30.join(stateDir2(env), "heavy-commands.json"));
|
|
21298
22141
|
if (Array.isArray(machine?.patterns))
|
|
21299
22142
|
patterns.push(...machine.patterns);
|
|
21300
|
-
for (let dir =
|
|
21301
|
-
if (fs22.existsSync(
|
|
22143
|
+
for (let dir = path30.resolve(cwd);; dir = path30.dirname(dir)) {
|
|
22144
|
+
if (fs22.existsSync(path30.join(dir, ".git"))) {
|
|
21302
22145
|
patterns.push(...repoHeavyCommands(dir));
|
|
21303
22146
|
break;
|
|
21304
22147
|
}
|
|
21305
|
-
if (
|
|
22148
|
+
if (path30.dirname(dir) === dir)
|
|
21306
22149
|
break;
|
|
21307
22150
|
}
|
|
21308
22151
|
const words = new Set;
|
|
@@ -21372,16 +22215,16 @@ var splitDenial = (plan) => ({
|
|
|
21372
22215
|
unblock: plan.unblock
|
|
21373
22216
|
});
|
|
21374
22217
|
// packages/workit-core/src/hooks/write-gate.ts
|
|
21375
|
-
import { realpathSync as realpathSync6 } from "node:fs";
|
|
22218
|
+
import { existsSync as existsSync10, realpathSync as realpathSync6 } from "node:fs";
|
|
21376
22219
|
import os10 from "node:os";
|
|
21377
|
-
import
|
|
22220
|
+
import path32 from "node:path";
|
|
21378
22221
|
|
|
21379
22222
|
// packages/workit-core/src/core/live-session.ts
|
|
21380
22223
|
import { spawnSync as spawnSync17 } from "node:child_process";
|
|
21381
22224
|
import { createHash as createHash16 } from "node:crypto";
|
|
21382
22225
|
import fs23 from "node:fs";
|
|
21383
22226
|
import os9 from "node:os";
|
|
21384
|
-
import
|
|
22227
|
+
import path31 from "node:path";
|
|
21385
22228
|
var LIVE_SESSION_TTL_MS = 15 * 60000;
|
|
21386
22229
|
var HEARTBEAT_EVERY_MS = 30000;
|
|
21387
22230
|
var liveProbe = {
|
|
@@ -21455,15 +22298,15 @@ var liveOwner = (input, transport, env = process.env) => {
|
|
|
21455
22298
|
};
|
|
21456
22299
|
};
|
|
21457
22300
|
var checkoutOf = (target) => {
|
|
21458
|
-
let dir =
|
|
21459
|
-
while (!fs23.existsSync(dir) &&
|
|
21460
|
-
dir =
|
|
22301
|
+
let dir = path31.resolve(target);
|
|
22302
|
+
while (!fs23.existsSync(dir) && path31.dirname(dir) !== dir)
|
|
22303
|
+
dir = path31.dirname(dir);
|
|
21461
22304
|
if (fs23.existsSync(dir) && !fs23.statSync(dir).isDirectory())
|
|
21462
|
-
dir =
|
|
21463
|
-
const parts = dir.split(
|
|
22305
|
+
dir = path31.dirname(dir);
|
|
22306
|
+
const parts = dir.split(path31.sep);
|
|
21464
22307
|
const dotGit = parts.lastIndexOf(".git");
|
|
21465
22308
|
if (dotGit > 0)
|
|
21466
|
-
dir = parts.slice(0, dotGit).join(
|
|
22309
|
+
dir = parts.slice(0, dotGit).join(path31.sep) || path31.sep;
|
|
21467
22310
|
const location = resolveStore(dir);
|
|
21468
22311
|
if (location instanceof Error || !location.shared || !location.top)
|
|
21469
22312
|
return null;
|
|
@@ -21473,9 +22316,9 @@ var checkoutOf = (target) => {
|
|
|
21473
22316
|
} catch {}
|
|
21474
22317
|
return top;
|
|
21475
22318
|
};
|
|
21476
|
-
var markerFile2 = (top) =>
|
|
22319
|
+
var markerFile2 = (top) => path31.join(resolveStateDir(), "live-sessions", `${createHash16("sha256").update(checkoutKey(top)).digest("hex").slice(0, 32)}.json`);
|
|
21477
22320
|
var checkoutKey = (top, platform = process.platform) => {
|
|
21478
|
-
let key =
|
|
22321
|
+
let key = path31.resolve(top);
|
|
21479
22322
|
try {
|
|
21480
22323
|
key = fs23.realpathSync.native(key);
|
|
21481
22324
|
} catch {}
|
|
@@ -21570,7 +22413,7 @@ var claimLiveSession = (target, owner, deps = {}) => {
|
|
|
21570
22413
|
checkout,
|
|
21571
22414
|
heartbeatAt: now.toISOString()
|
|
21572
22415
|
};
|
|
21573
|
-
fs23.mkdirSync(
|
|
22416
|
+
fs23.mkdirSync(path31.dirname(file), { recursive: true });
|
|
21574
22417
|
const temp = `${file}.${process.pid}.tmp`;
|
|
21575
22418
|
fs23.writeFileSync(temp, JSON.stringify(marker));
|
|
21576
22419
|
fs23.renameSync(temp, file);
|
|
@@ -21582,26 +22425,26 @@ var NONE3 = { kind: "none" };
|
|
|
21582
22425
|
var CASE_INSENSITIVE = process.platform === "darwin" || process.platform === "win32";
|
|
21583
22426
|
var canonical2 = (file) => {
|
|
21584
22427
|
let rest = "";
|
|
21585
|
-
for (let dir =
|
|
22428
|
+
for (let dir = path32.resolve(file);; dir = path32.dirname(dir)) {
|
|
21586
22429
|
try {
|
|
21587
|
-
return
|
|
22430
|
+
return path32.join(realpathSync6.native(dir), rest);
|
|
21588
22431
|
} catch {
|
|
21589
|
-
if (
|
|
21590
|
-
return
|
|
21591
|
-
rest = rest ?
|
|
22432
|
+
if (path32.dirname(dir) === dir)
|
|
22433
|
+
return path32.resolve(file);
|
|
22434
|
+
rest = rest ? path32.join(path32.basename(dir), rest) : path32.basename(dir);
|
|
21592
22435
|
}
|
|
21593
22436
|
}
|
|
21594
22437
|
};
|
|
21595
22438
|
var exempt = (cwd, root, file, plans) => {
|
|
21596
22439
|
const fold = (value) => CASE_INSENSITIVE ? value.toLowerCase() : value;
|
|
21597
|
-
const relative =
|
|
21598
|
-
if (relative.startsWith("..") ||
|
|
22440
|
+
const relative = path32.relative(fold(canonical2(root)), fold(canonical2(path32.resolve(cwd, file))));
|
|
22441
|
+
if (relative.startsWith("..") || path32.isAbsolute(relative))
|
|
21599
22442
|
return true;
|
|
21600
|
-
const posix = relative.split(
|
|
22443
|
+
const posix = relative.split(path32.sep).join("/");
|
|
21601
22444
|
const segments = posix.split("/");
|
|
21602
22445
|
return /\.mdx?$/i.test(posix) || segments[0] === "docs" || segments.includes("plans") || [...plans].some((plan) => fold(plan) === posix);
|
|
21603
22446
|
};
|
|
21604
|
-
var PREFIXES2 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "xargs"]);
|
|
22447
|
+
var PREFIXES2 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "xargs", "busybox"]);
|
|
21605
22448
|
var operands = (words) => words.filter((item) => !item.startsWith("-"));
|
|
21606
22449
|
var SCRIPT_SHELLS = new Set(["sh", "bash", "zsh", "dash", "ksh"]);
|
|
21607
22450
|
var wrappedScript = (input) => {
|
|
@@ -21613,9 +22456,9 @@ var wrappedScript = (input) => {
|
|
|
21613
22456
|
return null;
|
|
21614
22457
|
if ("script" in tail)
|
|
21615
22458
|
return tail.script;
|
|
21616
|
-
const
|
|
21617
|
-
if (SCRIPT_SHELLS.has((
|
|
21618
|
-
return script
|
|
22459
|
+
const script = shellScript2(tail.argv);
|
|
22460
|
+
if (SCRIPT_SHELLS.has((tail.argv[0]?.split(/[\\/]/).at(-1) ?? "").toLowerCase()) && script)
|
|
22461
|
+
return script;
|
|
21619
22462
|
return tail.argv.map(quoteWord2).join(" ");
|
|
21620
22463
|
};
|
|
21621
22464
|
var editsOf = (input) => {
|
|
@@ -21694,6 +22537,94 @@ var editsOf = (input) => {
|
|
|
21694
22537
|
return [];
|
|
21695
22538
|
}
|
|
21696
22539
|
};
|
|
22540
|
+
var shellScript2 = (words) => {
|
|
22541
|
+
if (!SCRIPT_SHELLS.has((words[0]?.split(/[\\/]/).at(-1) ?? "").toLowerCase()))
|
|
22542
|
+
return null;
|
|
22543
|
+
for (let index = 1;index < words.length; index++) {
|
|
22544
|
+
const word = words[index];
|
|
22545
|
+
if (/^-[a-z]*c$/.test(word))
|
|
22546
|
+
return words[index + 1] ?? null;
|
|
22547
|
+
if (/^[-+][oO]$/.test(word))
|
|
22548
|
+
index++;
|
|
22549
|
+
else if (!/^[-+]/.test(word))
|
|
22550
|
+
return null;
|
|
22551
|
+
}
|
|
22552
|
+
return null;
|
|
22553
|
+
};
|
|
22554
|
+
var XARGS_VALUE = /^-[IinLPdsEa]$/;
|
|
22555
|
+
var runsWriter = (words) => {
|
|
22556
|
+
const filled = [...words.filter((word) => word !== "{}" && word !== ";" && word !== "+"), "x"];
|
|
22557
|
+
const edits = editsOf(filled);
|
|
22558
|
+
return edits === null || edits.length > 0 || opaqueWrites(filled) !== null;
|
|
22559
|
+
};
|
|
22560
|
+
var opaqueWrites = (input) => {
|
|
22561
|
+
let words = input;
|
|
22562
|
+
while (words.length && (PREFIXES2.has(words[0]) || /^\w+=/.test(words[0]))) {
|
|
22563
|
+
if (words[0] === "xargs") {
|
|
22564
|
+
let at = 1;
|
|
22565
|
+
while (words[at]?.startsWith("-"))
|
|
22566
|
+
at += XARGS_VALUE.test(words[at]) ? 2 : 1;
|
|
22567
|
+
return runsWriter(words.slice(at)) ? ["."] : null;
|
|
22568
|
+
}
|
|
22569
|
+
words = words.slice(1);
|
|
22570
|
+
}
|
|
22571
|
+
const [verb, ...args] = words;
|
|
22572
|
+
const name = (verb?.split(/[\\/]/).at(-1) ?? "").toLowerCase();
|
|
22573
|
+
const files = operands(args);
|
|
22574
|
+
const has = (pattern) => args.some((arg) => pattern.test(arg));
|
|
22575
|
+
const listed = (named) => named.length ? named : ["."];
|
|
22576
|
+
if (name === "unlink" || name === "shred")
|
|
22577
|
+
return listed(files);
|
|
22578
|
+
if (/^(?:chmod|chown|chgrp|chattr)$/.test(name))
|
|
22579
|
+
return listed(has(/^--reference/) ? files : files.slice(1));
|
|
22580
|
+
if (name === "rsync") {
|
|
22581
|
+
const dest = files.at(-1);
|
|
22582
|
+
const remote = /^[\w.@-]*:/.test(dest ?? "") && !/^[A-Za-z]:[\\/]/.test(dest ?? "");
|
|
22583
|
+
return dest === undefined ? ["."] : remote ? [] : [dest];
|
|
22584
|
+
}
|
|
22585
|
+
if (name === "find") {
|
|
22586
|
+
const exec = args.findIndex((arg) => /^-(?:exec|execdir|ok|okdir)$/.test(arg));
|
|
22587
|
+
const writes = has(/^-(?:delete|fprint\w*|fls)$/) || exec >= 0 && runsWriter(args.slice(exec + 1));
|
|
22588
|
+
const end = args.findIndex((arg) => /^[-(!]/.test(arg));
|
|
22589
|
+
return writes ? listed(end < 0 ? args : args.slice(0, end)) : null;
|
|
22590
|
+
}
|
|
22591
|
+
if (/^g?awk$/.test(name)) {
|
|
22592
|
+
if (!has(/^-i|^--include/))
|
|
22593
|
+
return null;
|
|
22594
|
+
const named = [];
|
|
22595
|
+
let program = !has(/^-f$|^--file/);
|
|
22596
|
+
for (let index = 0;index < args.length; index++)
|
|
22597
|
+
if (/^(?:-[ifvF]|--(?:include|file|assign|field-separator))$/.test(args[index]))
|
|
22598
|
+
index++;
|
|
22599
|
+
else if (args[index].startsWith("-"))
|
|
22600
|
+
continue;
|
|
22601
|
+
else if (program)
|
|
22602
|
+
program = false;
|
|
22603
|
+
else
|
|
22604
|
+
named.push(args[index]);
|
|
22605
|
+
return listed(named);
|
|
22606
|
+
}
|
|
22607
|
+
if (name === "perl")
|
|
22608
|
+
return has(/^-[a-zA-Z]*[eE]/) ? ["."] : null;
|
|
22609
|
+
if (/^(?:python[\d.]*|node|bun|deno|ruby|php)$/.test(name))
|
|
22610
|
+
return has(/^-[a-zA-Z]*[ce]$|^--eval$|^-p$/) ? ["."] : null;
|
|
22611
|
+
return shellScript2(words) === null ? null : ["."];
|
|
22612
|
+
};
|
|
22613
|
+
var linkSources = (args, at) => {
|
|
22614
|
+
const names = operands([...args]);
|
|
22615
|
+
const into = args.indexOf("-t");
|
|
22616
|
+
const dest = into >= 0 ? args[into + 1] : names.length > 1 ? names.at(-1) : ".";
|
|
22617
|
+
const sources = names.filter((name, index) => into >= 0 ? name !== dest : index < names.length - 1 || names.length === 1);
|
|
22618
|
+
const target = dest === undefined ? null : at(dest);
|
|
22619
|
+
const symbolic = args.some((arg) => /^-[a-zA-Z]*s|^--symbolic$/.test(arg));
|
|
22620
|
+
const base = target === null ? null : into >= 0 || sources.length > 1 || existsSync10(target) || /[\\/]$/.test(dest ?? "") ? target : path32.dirname(target);
|
|
22621
|
+
return sources.flatMap((source) => {
|
|
22622
|
+
if (symbolic && base !== null && !path32.isAbsolute(homeExpanded(source)))
|
|
22623
|
+
return [path32.resolve(base, source)];
|
|
22624
|
+
const resolved = at(source);
|
|
22625
|
+
return resolved ? [resolved] : [];
|
|
22626
|
+
});
|
|
22627
|
+
};
|
|
21697
22628
|
var shellWrites = (command, depth = 0) => {
|
|
21698
22629
|
const targets = [];
|
|
21699
22630
|
let unknown = false;
|
|
@@ -21718,7 +22649,7 @@ var shellWrites = (command, depth = 0) => {
|
|
|
21718
22649
|
return { writes: true, targets: unknown ? null : targets };
|
|
21719
22650
|
};
|
|
21720
22651
|
var cache2 = new Map;
|
|
21721
|
-
var planKeyOf = (root, plans) => [...plans].map((file) => fileSignature(
|
|
22652
|
+
var planKeyOf = (root, plans) => [...plans].map((file) => fileSignature(path32.resolve(root, file)) ?? "-").join("\x00");
|
|
21722
22653
|
var gateFor = (cwd) => {
|
|
21723
22654
|
const store = new TaskStore(cwd);
|
|
21724
22655
|
const listed = store.listTaskIndex();
|
|
@@ -21797,32 +22728,36 @@ var gitMutationDirs = (cwd, command, dialect) => {
|
|
|
21797
22728
|
return [];
|
|
21798
22729
|
if (sub === "stash" && ["list", "show"].includes(rest[0] ?? ""))
|
|
21799
22730
|
return [];
|
|
21800
|
-
return [
|
|
22731
|
+
return [path32.resolve(cwd, invocation.dir ?? ".")];
|
|
21801
22732
|
});
|
|
21802
22733
|
};
|
|
21803
22734
|
var CD2 = new Set(["cd", "chdir", "set-location", "sl"]);
|
|
21804
22735
|
var PUSHD2 = new Set(["pushd", "push-location"]);
|
|
21805
22736
|
var POPD2 = new Set(["popd", "pop-location"]);
|
|
21806
|
-
var
|
|
22737
|
+
var UNKNOWN_DIR = path32.resolve(path32.parse(process.cwd()).root, ".workit-unknown-dir");
|
|
22738
|
+
var cdInto = (dir, words, strict = false) => {
|
|
22739
|
+
const lost = strict ? UNKNOWN_DIR : undefined;
|
|
21807
22740
|
const named = words.slice(1).find((word) => !word.startsWith("-") || word === "-");
|
|
21808
22741
|
const target = named === undefined ? undefined : homeExpanded(named);
|
|
21809
|
-
if (
|
|
21810
|
-
return;
|
|
21811
|
-
|
|
22742
|
+
if (target === "-" || target && /^~|[$`*?]/.test(target))
|
|
22743
|
+
return lost;
|
|
22744
|
+
if (dir === undefined)
|
|
22745
|
+
return lost;
|
|
22746
|
+
return target === undefined ? os10.homedir() : path32.resolve(dir, target);
|
|
21812
22747
|
};
|
|
21813
22748
|
var quoteWord2 = (word) => /^[\w@%+=:,./-]+$/.test(word) ? word : `'${word.replaceAll("'", "'\\''")}'`;
|
|
21814
22749
|
var homeExpanded = (file) => {
|
|
21815
22750
|
const match = /^(?:~|\$HOME|\$\{HOME\})(?=\/|$)/.exec(file);
|
|
21816
22751
|
return match ? (process.env.HOME || os10.homedir()) + file.slice(match[0].length) : file;
|
|
21817
22752
|
};
|
|
21818
|
-
var segmentTargets = (cwd, command, dialect, depth = 0) => {
|
|
22753
|
+
var segmentTargets = (cwd, command, dialect, depth = 0, strict = false) => {
|
|
21819
22754
|
const targets = [];
|
|
21820
22755
|
let dir = cwd;
|
|
21821
22756
|
let stack = [];
|
|
21822
22757
|
const groups = [];
|
|
21823
22758
|
const at = (file) => {
|
|
21824
22759
|
const home = homeExpanded(file);
|
|
21825
|
-
return
|
|
22760
|
+
return path32.isAbsolute(home) ? home : dir === undefined ? null : path32.resolve(dir, home);
|
|
21826
22761
|
};
|
|
21827
22762
|
for (const segment of segmentsOf(command, dialect)) {
|
|
21828
22763
|
if (segment.group === "open") {
|
|
@@ -21839,21 +22774,35 @@ var segmentTargets = (cwd, command, dialect, depth = 0) => {
|
|
|
21839
22774
|
const words = lead < 0 ? [] : segment.words.slice(lead);
|
|
21840
22775
|
const name = (words[0]?.split(/[\\/]/).at(-1) ?? "").toLowerCase();
|
|
21841
22776
|
if (CD2.has(name)) {
|
|
21842
|
-
dir = cdInto(dir, words);
|
|
22777
|
+
dir = cdInto(dir, words, strict);
|
|
21843
22778
|
continue;
|
|
21844
22779
|
}
|
|
21845
22780
|
if (PUSHD2.has(name)) {
|
|
21846
22781
|
stack.push(dir);
|
|
21847
|
-
dir = cdInto(dir, words);
|
|
22782
|
+
dir = cdInto(dir, words, strict);
|
|
21848
22783
|
continue;
|
|
21849
22784
|
}
|
|
21850
22785
|
if (POPD2.has(name)) {
|
|
21851
|
-
dir = stack.length ? stack.pop() : undefined;
|
|
22786
|
+
dir = stack.length ? stack.pop() : strict ? UNKNOWN_DIR : undefined;
|
|
21852
22787
|
continue;
|
|
21853
22788
|
}
|
|
21854
|
-
const inner = depth < 4 ? wrappedScript(words) : null;
|
|
22789
|
+
const inner = depth < 4 ? wrappedScript(words) ?? (strict ? shellScript2(words) : null) : null;
|
|
21855
22790
|
if (inner !== null && dir !== undefined)
|
|
21856
|
-
targets.push(...segmentTargets(dir, inner, "posix", depth + 1));
|
|
22791
|
+
targets.push(...segmentTargets(dir, inner, "posix", depth + 1, strict));
|
|
22792
|
+
if (strict && dir !== undefined) {
|
|
22793
|
+
const runIn = dir;
|
|
22794
|
+
for (const sub of depth < 4 ? segment.subs ?? [] : [])
|
|
22795
|
+
targets.push(...segmentTargets(runIn, sub, dialect, depth + 1, true));
|
|
22796
|
+
const opaque = segment.unanalyzable ? ["."] : inner === null ? opaqueWrites(words) : null;
|
|
22797
|
+
for (const file of opaque ?? []) {
|
|
22798
|
+
const resolved = at(file);
|
|
22799
|
+
if (resolved)
|
|
22800
|
+
targets.push(file === "." ? path32.join(resolved, DIR_TARGET) : resolved);
|
|
22801
|
+
}
|
|
22802
|
+
const ln = words.findIndex((word) => /^ln$/i.test(word.split(/[\\/]/).at(-1) ?? ""));
|
|
22803
|
+
if (ln >= 0 && words.slice(0, ln).every((word) => PREFIXES2.has(word)))
|
|
22804
|
+
targets.push(...linkSources(words.slice(ln + 1), at));
|
|
22805
|
+
}
|
|
21857
22806
|
for (const file of segment.redirects)
|
|
21858
22807
|
if (!/^\/dev\/(?:null|stdout|stderr|tty)$/.test(file)) {
|
|
21859
22808
|
const resolved = at(file);
|
|
@@ -21863,18 +22812,36 @@ var segmentTargets = (cwd, command, dialect, depth = 0) => {
|
|
|
21863
22812
|
const edits = editsOf(segment.words);
|
|
21864
22813
|
if (edits === null) {
|
|
21865
22814
|
if (dir !== undefined)
|
|
21866
|
-
targets.push(
|
|
22815
|
+
targets.push(path32.join(dir, DIR_TARGET));
|
|
21867
22816
|
} else
|
|
21868
22817
|
for (const file of edits) {
|
|
21869
22818
|
const resolved = at(file);
|
|
21870
22819
|
if (resolved)
|
|
21871
22820
|
targets.push(resolved);
|
|
21872
22821
|
}
|
|
21873
|
-
if (dir !== undefined
|
|
21874
|
-
|
|
22822
|
+
if (dir !== undefined)
|
|
22823
|
+
for (const invocation of workitInvocations(segment.words.map(quoteWord2).join(" "), "posix")) {
|
|
22824
|
+
const { at: runsIn, args } = workitCwd(dir, invocation);
|
|
22825
|
+
if (args[0] === "git" && MUTATING_WORKIT_GIT.has(args[1] ?? "") && !inert(args.slice(2), ""))
|
|
22826
|
+
targets.push(path32.join(runsIn, DIR_TARGET));
|
|
22827
|
+
}
|
|
21875
22828
|
}
|
|
21876
22829
|
return targets;
|
|
21877
22830
|
};
|
|
22831
|
+
var workitCwd = (dir, args) => {
|
|
22832
|
+
const { rest, cwd } = skipWorkitGlobals(args);
|
|
22833
|
+
return { at: cwd === null ? dir : path32.resolve(dir, homeExpanded(cwd)), args: rest };
|
|
22834
|
+
};
|
|
22835
|
+
var shellMutationTargets = (cwd, command, dialect = "posix", strict = false) => {
|
|
22836
|
+
try {
|
|
22837
|
+
return [
|
|
22838
|
+
...segmentTargets(cwd, command, dialect, 0, strict),
|
|
22839
|
+
...gitMutationDirs(cwd, command, dialect).map((dir) => path32.join(dir, DIR_TARGET))
|
|
22840
|
+
];
|
|
22841
|
+
} catch {
|
|
22842
|
+
return [];
|
|
22843
|
+
}
|
|
22844
|
+
};
|
|
21878
22845
|
var overridden = (command, dialect) => {
|
|
21879
22846
|
const segments = segmentsOf(command, dialect);
|
|
21880
22847
|
if (segments.length !== 1 || segments[0].group || segments[0].subs?.length)
|
|
@@ -21906,7 +22873,7 @@ var liveDeny = ({ marker, checkout, ageMs, file }) => {
|
|
|
21906
22873
|
var guarded = (targets) => {
|
|
21907
22874
|
const seen = new Set;
|
|
21908
22875
|
return targets.filter((target) => {
|
|
21909
|
-
const dir =
|
|
22876
|
+
const dir = path32.dirname(target);
|
|
21910
22877
|
if (seen.has(dir))
|
|
21911
22878
|
return false;
|
|
21912
22879
|
seen.add(dir);
|
|
@@ -21927,7 +22894,7 @@ var claimAll = (owner, targets, deps) => {
|
|
|
21927
22894
|
for (const target of guarded(targets))
|
|
21928
22895
|
claimLiveSession(target, owner, deps);
|
|
21929
22896
|
};
|
|
21930
|
-
var liveTargets = (cwd, paths) => paths?.length ? paths.map((file) =>
|
|
22897
|
+
var liveTargets = (cwd, paths) => paths?.length ? paths.map((file) => path32.resolve(cwd, file)) : [path32.join(cwd, DIR_TARGET)];
|
|
21931
22898
|
var issuedTarget = (cwd, targets) => {
|
|
21932
22899
|
const state = resolveStateDir();
|
|
21933
22900
|
const found = targets.find((target) => underIssued(state, cwd, homeExpanded(target), process.platform, canonical2));
|
|
@@ -21935,7 +22902,7 @@ var issuedTarget = (cwd, targets) => {
|
|
|
21935
22902
|
};
|
|
21936
22903
|
var underIssued = (state, cwd, target, platform = process.platform, real = (file) => file) => {
|
|
21937
22904
|
const win = platform === "win32";
|
|
21938
|
-
const p = win ?
|
|
22905
|
+
const p = win ? path32.win32 : path32.posix;
|
|
21939
22906
|
const fold = (value) => win || platform === "darwin" ? value.toLowerCase() : value;
|
|
21940
22907
|
const named = win ? target.replace(/^\/([a-zA-Z])(?=\/|$)/, "$1:") : target;
|
|
21941
22908
|
const relative = p.relative(fold(real(p.resolve(state))), fold(real(p.resolve(cwd, named))));
|
|
@@ -21980,16 +22947,8 @@ function shellGate(cwd, command, owner, dialect = "posix", deps) {
|
|
|
21980
22947
|
} catch {}
|
|
21981
22948
|
const writes = shellWrites(command);
|
|
21982
22949
|
let targets = [];
|
|
21983
|
-
if (owner)
|
|
21984
|
-
|
|
21985
|
-
targets = [
|
|
21986
|
-
...segmentTargets(cwd, command, dialect),
|
|
21987
|
-
...gitMutationDirs(cwd, command, dialect).map((dir) => path31.join(dir, DIR_TARGET))
|
|
21988
|
-
];
|
|
21989
|
-
} catch {
|
|
21990
|
-
targets = [];
|
|
21991
|
-
}
|
|
21992
|
-
}
|
|
22950
|
+
if (owner)
|
|
22951
|
+
targets = shellMutationTargets(cwd, command, dialect);
|
|
21993
22952
|
if (owner && targets.length) {
|
|
21994
22953
|
const live = liveGate(owner, targets, deps);
|
|
21995
22954
|
if (live.kind !== "none") {
|
|
@@ -22034,6 +22993,24 @@ function beforeWriteGate(cwd, paths) {
|
|
|
22034
22993
|
}
|
|
22035
22994
|
// packages/workit-core/src/hooks/stop.ts
|
|
22036
22995
|
import { spawnSync as spawnSync18 } from "node:child_process";
|
|
22996
|
+
|
|
22997
|
+
// packages/workit-core/src/core/policy/current-judgment.ts
|
|
22998
|
+
function currentJudgment(cwd) {
|
|
22999
|
+
try {
|
|
23000
|
+
const store = new TaskStore(cwd);
|
|
23001
|
+
const listed = store.listTaskIndex();
|
|
23002
|
+
const key = store.currentKey();
|
|
23003
|
+
if (!listed.ok || !key.ok)
|
|
23004
|
+
return null;
|
|
23005
|
+
const entry = listed.data.filter((item) => item.key === key.data.key && item.status !== "closed").toSorted((left, right) => right.updatedAt.localeCompare(left.updatedAt))[0];
|
|
23006
|
+
const task = entry ? store.readTask(entry.id) : null;
|
|
23007
|
+
return task?.ok === true ? latestJudgment(task.data) : null;
|
|
23008
|
+
} catch {
|
|
23009
|
+
return null;
|
|
23010
|
+
}
|
|
23011
|
+
}
|
|
23012
|
+
|
|
23013
|
+
// packages/workit-core/src/hooks/stop.ts
|
|
22037
23014
|
var NONE4 = { kind: "none" };
|
|
22038
23015
|
var QUESTION = /[??؟](?=[\s*_`"')\]>]*(?:\s|$))|¿/;
|
|
22039
23016
|
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;
|
|
@@ -22126,29 +23103,32 @@ var redChecks = (rows, pr, head, endpoint) => {
|
|
|
22126
23103
|
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)" : ""}.`
|
|
22127
23104
|
};
|
|
22128
23105
|
};
|
|
22129
|
-
var
|
|
22130
|
-
|
|
22131
|
-
|
|
22132
|
-
const listed = store.listTaskIndex();
|
|
22133
|
-
const key = store.currentKey();
|
|
22134
|
-
if (!listed.ok || !key.ok)
|
|
22135
|
-
return null;
|
|
22136
|
-
const entry = listed.data.filter((item) => item.key === key.data.key && item.status !== "closed").toSorted((left, right) => right.updatedAt.localeCompare(left.updatedAt))[0];
|
|
22137
|
-
const task = entry ? store.readTask(entry.id) : null;
|
|
22138
|
-
return task?.ok === true ? latestJudgment(task.data) : null;
|
|
22139
|
-
} catch {
|
|
22140
|
-
return null;
|
|
22141
|
-
}
|
|
23106
|
+
var prBase = (rows) => {
|
|
23107
|
+
const seen = rows.findLast((row) => (row.type === "pr.status" || row.type === "pr.created") && row.observer === "workit_cli" && typeof row.base === "string");
|
|
23108
|
+
return typeof seen?.base === "string" ? seen.base : null;
|
|
22142
23109
|
};
|
|
22143
|
-
var unverified = (cwd, branch, all, descriptor, verification) => {
|
|
23110
|
+
var unverified = (cwd, branch, all, descriptor, verification, head) => {
|
|
22144
23111
|
const check = checkVerdicts(cwd, branch, all);
|
|
22145
23112
|
if (check.accepted.accepted || check.accepted.reasons.includes("failing_verdict"))
|
|
22146
23113
|
return null;
|
|
22147
|
-
const judgment =
|
|
23114
|
+
const judgment = currentJudgment(cwd);
|
|
22148
23115
|
const high = judgment?.riskTier === "high";
|
|
22149
|
-
|
|
22150
|
-
|
|
22151
|
-
|
|
23116
|
+
let independent;
|
|
23117
|
+
if (verification === "proportional") {
|
|
23118
|
+
const lane = branchLane(cwd, {
|
|
23119
|
+
base: prBase(all.filter((row) => row.branch === branch)),
|
|
23120
|
+
head,
|
|
23121
|
+
judgment
|
|
23122
|
+
});
|
|
23123
|
+
const need = laneNeed(lane.lane);
|
|
23124
|
+
if (need === "none")
|
|
23125
|
+
return null;
|
|
23126
|
+
independent = need === "non-author";
|
|
23127
|
+
} else {
|
|
23128
|
+
if (judgment && !judgment.behaviorChange && !high)
|
|
23129
|
+
return null;
|
|
23130
|
+
independent = verification === "independent" || high;
|
|
23131
|
+
}
|
|
22152
23132
|
if (!independent && check.review === "self-reviewed")
|
|
22153
23133
|
return null;
|
|
22154
23134
|
const has = check.review === "self-reviewed" ? "only a self verdict" : "no current verdict";
|
|
@@ -22195,7 +23175,7 @@ function stopObligation(input, descriptor) {
|
|
|
22195
23175
|
}
|
|
22196
23176
|
if (!delivered || endpoint !== "green" && endpoint !== "merged")
|
|
22197
23177
|
return null;
|
|
22198
|
-
const left = unverified(input.cwd, branch, all, descriptor, autonomy.verification);
|
|
23178
|
+
const left = unverified(input.cwd, branch, all, descriptor, autonomy.verification, head);
|
|
22199
23179
|
return left ? { ...left, head } : null;
|
|
22200
23180
|
} catch {
|
|
22201
23181
|
return null;
|
|
@@ -22493,9 +23473,9 @@ var writePaths = (input) => {
|
|
|
22493
23473
|
};
|
|
22494
23474
|
// packages/workit-core/src/hooks/standing.ts
|
|
22495
23475
|
import fs24 from "node:fs";
|
|
22496
|
-
import
|
|
23476
|
+
import path33 from "node:path";
|
|
22497
23477
|
var readPlans = (store) => {
|
|
22498
|
-
const dir =
|
|
23478
|
+
const dir = path33.join(store, "fanouts");
|
|
22499
23479
|
let names;
|
|
22500
23480
|
try {
|
|
22501
23481
|
names = fs24.readdirSync(dir).filter((name) => name.endsWith(".json"));
|
|
@@ -22505,7 +23485,7 @@ var readPlans = (store) => {
|
|
|
22505
23485
|
const plans = [];
|
|
22506
23486
|
for (const name of names) {
|
|
22507
23487
|
try {
|
|
22508
|
-
const raw = JSON.parse(fs24.readFileSync(
|
|
23488
|
+
const raw = JSON.parse(fs24.readFileSync(path33.join(dir, name), "utf8"));
|
|
22509
23489
|
if (typeof raw.name !== "string" || !Array.isArray(raw.slices))
|
|
22510
23490
|
continue;
|
|
22511
23491
|
const texts = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string" && item !== "") : [];
|
|
@@ -22628,9 +23608,266 @@ var cwdRewrite = (command) => {
|
|
|
22628
23608
|
return `workit --cwd ${dir} ${rest.slice("workit".length).trimStart()}`.trimEnd();
|
|
22629
23609
|
};
|
|
22630
23610
|
|
|
23611
|
+
// packages/workit-core/src/hooks/worktree-confine.ts
|
|
23612
|
+
import { createHash as createHash17 } from "node:crypto";
|
|
23613
|
+
import fs25 from "node:fs";
|
|
23614
|
+
import path34 from "node:path";
|
|
23615
|
+
var FANOUT_TOKEN_FILE = "workit-fanout-token";
|
|
23616
|
+
var NONE5 = { kind: "none" };
|
|
23617
|
+
var CASE_INSENSITIVE2 = process.platform === "darwin" || process.platform === "win32";
|
|
23618
|
+
var canonical3 = (file) => {
|
|
23619
|
+
let rest = "";
|
|
23620
|
+
for (let dir = path34.resolve(file);; dir = path34.dirname(dir)) {
|
|
23621
|
+
try {
|
|
23622
|
+
return path34.join(fs25.realpathSync.native(dir), rest);
|
|
23623
|
+
} catch {
|
|
23624
|
+
if (path34.dirname(dir) === dir)
|
|
23625
|
+
return path34.resolve(file);
|
|
23626
|
+
rest = rest ? path34.join(path34.basename(dir), rest) : path34.basename(dir);
|
|
23627
|
+
}
|
|
23628
|
+
}
|
|
23629
|
+
};
|
|
23630
|
+
var within = (child, parent) => {
|
|
23631
|
+
const fold = (value) => CASE_INSENSITIVE2 ? value.toLowerCase() : value;
|
|
23632
|
+
const relative = path34.relative(fold(parent), fold(child));
|
|
23633
|
+
return relative === "" || !relative.startsWith("..") && !path34.isAbsolute(relative);
|
|
23634
|
+
};
|
|
23635
|
+
var checkoutOf2 = (file) => {
|
|
23636
|
+
for (let dir = canonical3(file);; dir = path34.dirname(dir)) {
|
|
23637
|
+
const dotGit = path34.join(dir, ".git");
|
|
23638
|
+
if (fs25.existsSync(dotGit)) {
|
|
23639
|
+
if (!fs25.statSync(dotGit).isFile())
|
|
23640
|
+
return { root: dir, workit: false };
|
|
23641
|
+
const admin = /^gitdir:\s*(.+)$/mu.exec(fs25.readFileSync(dotGit, "utf8"))?.[1]?.trim();
|
|
23642
|
+
return {
|
|
23643
|
+
root: dir,
|
|
23644
|
+
workit: Boolean(admin) && fs25.existsSync(path34.join(path34.resolve(dir, admin), FANOUT_TOKEN_FILE))
|
|
23645
|
+
};
|
|
23646
|
+
}
|
|
23647
|
+
if (path34.dirname(dir) === dir)
|
|
23648
|
+
return null;
|
|
23649
|
+
}
|
|
23650
|
+
};
|
|
23651
|
+
var workitWorktreeOf = (file) => {
|
|
23652
|
+
try {
|
|
23653
|
+
const checkout = checkoutOf2(file);
|
|
23654
|
+
return checkout?.workit ? checkout.root : null;
|
|
23655
|
+
} catch {
|
|
23656
|
+
return null;
|
|
23657
|
+
}
|
|
23658
|
+
};
|
|
23659
|
+
var bindingDir = () => path34.join(resolveStateDir(), "worktree-bind");
|
|
23660
|
+
var bindingFile = (session, agentId) => path34.join(bindingDir(), `${createHash17("sha256").update(`${session}\x00${agentId}`).digest("hex").slice(0, 32)}.json`);
|
|
23661
|
+
var readBinding = (file) => {
|
|
23662
|
+
try {
|
|
23663
|
+
const value = JSON.parse(fs25.readFileSync(file, "utf8"));
|
|
23664
|
+
return typeof value.worktree === "string" ? value : null;
|
|
23665
|
+
} catch {
|
|
23666
|
+
return null;
|
|
23667
|
+
}
|
|
23668
|
+
};
|
|
23669
|
+
var boundWorktree = (input) => {
|
|
23670
|
+
const agentId = input.session.agentId;
|
|
23671
|
+
if (!agentId)
|
|
23672
|
+
return null;
|
|
23673
|
+
const binding = readBinding(bindingFile(input.session.id, agentId));
|
|
23674
|
+
return binding && workitWorktreeOf(binding.worktree) === binding.worktree ? binding.worktree : null;
|
|
23675
|
+
};
|
|
23676
|
+
var bind = (input, worktree) => {
|
|
23677
|
+
const agentId = input.session.agentId;
|
|
23678
|
+
const file = bindingFile(input.session.id, agentId);
|
|
23679
|
+
const binding = {
|
|
23680
|
+
worktree,
|
|
23681
|
+
session: input.session.id,
|
|
23682
|
+
agentId,
|
|
23683
|
+
at: new Date().toISOString()
|
|
23684
|
+
};
|
|
23685
|
+
fs25.mkdirSync(path34.dirname(file), { recursive: true });
|
|
23686
|
+
fs25.writeFileSync(file, JSON.stringify(binding));
|
|
23687
|
+
try {
|
|
23688
|
+
if (inWorkitWorkspace(input.cwd))
|
|
23689
|
+
appendHookObserved(input.cwd, {
|
|
23690
|
+
type: "worktree.bound",
|
|
23691
|
+
actor: { host: input.host, session: input.session.id, agentId },
|
|
23692
|
+
agentType: input.session.agentType,
|
|
23693
|
+
worktree
|
|
23694
|
+
});
|
|
23695
|
+
} catch {}
|
|
23696
|
+
};
|
|
23697
|
+
var hostIsolated = (cwd, agentId) => {
|
|
23698
|
+
try {
|
|
23699
|
+
if (agentId === null)
|
|
23700
|
+
return false;
|
|
23701
|
+
const checkout = checkoutOf2(cwd);
|
|
23702
|
+
if (!checkout)
|
|
23703
|
+
return false;
|
|
23704
|
+
const dotGit = path34.join(checkout.root, ".git");
|
|
23705
|
+
if (!fs25.statSync(dotGit).isFile())
|
|
23706
|
+
return false;
|
|
23707
|
+
const admin = /^gitdir:\s*(.+)$/mu.exec(fs25.readFileSync(dotGit, "utf8"))?.[1]?.trim();
|
|
23708
|
+
if (!admin)
|
|
23709
|
+
return false;
|
|
23710
|
+
const adminDir = path34.resolve(checkout.root, admin);
|
|
23711
|
+
const back = fs25.readFileSync(path34.join(adminDir, "gitdir"), "utf8").trim();
|
|
23712
|
+
if (path34.relative(canonical3(path34.resolve(adminDir, back)), canonical3(dotGit)) !== "")
|
|
23713
|
+
return false;
|
|
23714
|
+
const common = path34.join(adminDir, "commondir");
|
|
23715
|
+
const gitDir = fs25.existsSync(common) ? path34.resolve(adminDir, fs25.readFileSync(common, "utf8").trim()) : adminDir;
|
|
23716
|
+
const expected = path34.join(path34.dirname(canonical3(gitDir)), ".claude", "worktrees", `agent-${agentId}`);
|
|
23717
|
+
return path34.relative(canonical3(expected), checkout.root) === "";
|
|
23718
|
+
} catch {
|
|
23719
|
+
return false;
|
|
23720
|
+
}
|
|
23721
|
+
};
|
|
23722
|
+
var shown = (target) => path34.basename(target) === ".workit-live-target" ? path34.dirname(target) : target;
|
|
23723
|
+
var unboundDeny = (input, target) => {
|
|
23724
|
+
const unblock = "Work only in the worktree your brief names, which the lead made with `workit fanout worktree create <slice>`: edit files under it and run one verb per call against it (`workit --cwd <worktree> git commit -m … -- <paths>`, `workit --cwd <worktree> check <name>`). If your brief names no worktree, stop and report that to the lead.";
|
|
23725
|
+
return {
|
|
23726
|
+
kind: "deny",
|
|
23727
|
+
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}`,
|
|
23728
|
+
unblock
|
|
23729
|
+
};
|
|
23730
|
+
};
|
|
23731
|
+
var outsideDeny = (input, worktree, target) => {
|
|
23732
|
+
const unblock = `Edit only under ${worktree} and run git there, one verb per call: \`workit --cwd ${worktree} git commit -m … -- <paths>\`, \`workit --cwd ${worktree} check <name>\` (or \`git -C ${worktree} …\`); a command whose writes Workit cannot see is judged by the directory it runs in. Anything outside it is a follow-up for your report.`;
|
|
23733
|
+
return {
|
|
23734
|
+
kind: "deny",
|
|
23735
|
+
reason: `workit worktree confinement: implementer ${input.session.agentId} is bound to the worktree ${worktree}, and ${shown(target)} is outside it. ${unblock}`,
|
|
23736
|
+
unblock
|
|
23737
|
+
};
|
|
23738
|
+
};
|
|
23739
|
+
var unknownDirDeny = (input, worktree) => {
|
|
23740
|
+
const wt = worktree ?? "<worktree>";
|
|
23741
|
+
const unblock = `Workit cannot resolve \`cd -\`, \`cd "$X"\` or \`popd\`: write by absolute path, \`git -C ${wt}\` or \`workit --cwd ${wt}\`.`;
|
|
23742
|
+
return {
|
|
23743
|
+
kind: "deny",
|
|
23744
|
+
reason: `workit worktree confinement: implementer ${input.session.agentId} writes after an unknown directory change. ${unblock}`,
|
|
23745
|
+
unblock
|
|
23746
|
+
};
|
|
23747
|
+
};
|
|
23748
|
+
function confineImplementer(input, targets) {
|
|
23749
|
+
try {
|
|
23750
|
+
if (!input.session.agentId || hostIsolated(input.cwd, input.session.agentId))
|
|
23751
|
+
return NONE5;
|
|
23752
|
+
if (targets.some((target) => within(path34.resolve(target), UNKNOWN_DIR)))
|
|
23753
|
+
return unknownDirDeny(input, boundWorktree(input));
|
|
23754
|
+
const bindings = canonical3(bindingDir());
|
|
23755
|
+
const judged = targets.map((named) => ({ named, real: canonical3(named) })).filter(({ real }) => within(real, bindings) || checkoutOf2(real) !== null);
|
|
23756
|
+
if (judged.length === 0)
|
|
23757
|
+
return NONE5;
|
|
23758
|
+
let worktree = boundWorktree(input);
|
|
23759
|
+
if (!worktree) {
|
|
23760
|
+
for (const { real } of judged) {
|
|
23761
|
+
worktree = within(real, bindings) ? null : workitWorktreeOf(real);
|
|
23762
|
+
if (worktree)
|
|
23763
|
+
break;
|
|
23764
|
+
}
|
|
23765
|
+
if (!worktree)
|
|
23766
|
+
return unboundDeny(input, judged[0].named);
|
|
23767
|
+
bind(input, worktree);
|
|
23768
|
+
}
|
|
23769
|
+
const root = canonical3(worktree);
|
|
23770
|
+
const outside = judged.find(({ real }) => !within(real, root) || within(real, bindings));
|
|
23771
|
+
return outside ? outsideDeny(input, worktree, outside.named) : NONE5;
|
|
23772
|
+
} catch {
|
|
23773
|
+
return NONE5;
|
|
23774
|
+
}
|
|
23775
|
+
}
|
|
23776
|
+
var MUTATING_REMOTE = new Set([
|
|
23777
|
+
"add",
|
|
23778
|
+
"remove",
|
|
23779
|
+
"rm",
|
|
23780
|
+
"rename",
|
|
23781
|
+
"set-url",
|
|
23782
|
+
"set-head",
|
|
23783
|
+
"set-branches",
|
|
23784
|
+
"prune"
|
|
23785
|
+
]);
|
|
23786
|
+
var CONFIG_READ = /^(?:--get(?:-all|-regexp|-urlmatch)?|--list|-l|get|list)$/;
|
|
23787
|
+
var CONFIG_WRITE = /^(?:--(?:unset(?:-all)?|add|replace-all|rename-section|remove-section|edit)|-e|set|unset|rename-section|remove-section|edit)$/;
|
|
23788
|
+
var CONFIG_VALUE = /^(?:--file|-f|--blob|--type|--default|--comment)$/;
|
|
23789
|
+
var leadOwnedGit = (args) => {
|
|
23790
|
+
const [sub = "", ...rest] = args;
|
|
23791
|
+
if (rest.some((arg) => arg === "--help" || arg === "-h"))
|
|
23792
|
+
return false;
|
|
23793
|
+
const names = rest.filter((arg) => !arg.startsWith("-"));
|
|
23794
|
+
const flag = (pattern) => rest.some((arg) => pattern.test(arg));
|
|
23795
|
+
switch (sub) {
|
|
23796
|
+
case "worktree":
|
|
23797
|
+
return names[0] !== undefined && names[0] !== "list";
|
|
23798
|
+
case "update-ref":
|
|
23799
|
+
return true;
|
|
23800
|
+
case "symbolic-ref":
|
|
23801
|
+
return names.length > 1 || flag(/^(?:-d|--delete)$/);
|
|
23802
|
+
case "branch":
|
|
23803
|
+
return flag(/^-[a-zA-Z]*[dDfmMcC]|^--(?:delete|force|move|copy)$/);
|
|
23804
|
+
case "tag":
|
|
23805
|
+
return flag(/^-[a-zA-Z]*[df]|^--(?:delete|force)$/);
|
|
23806
|
+
case "push": {
|
|
23807
|
+
if (flag(/^(?:-d|--delete|--mirror|--prune)$/))
|
|
23808
|
+
return true;
|
|
23809
|
+
const refspecs = names.slice(1);
|
|
23810
|
+
return refspecs.some((name) => name.includes(":") || name.startsWith("+")) || refspecs.length > 0 && flag(/^-[a-zA-Z]*f|^--force/);
|
|
23811
|
+
}
|
|
23812
|
+
case "replace":
|
|
23813
|
+
case "filter-branch":
|
|
23814
|
+
case "gc":
|
|
23815
|
+
case "prune":
|
|
23816
|
+
case "repack":
|
|
23817
|
+
case "sparse-checkout":
|
|
23818
|
+
return true;
|
|
23819
|
+
case "notes":
|
|
23820
|
+
case "reflog":
|
|
23821
|
+
case "submodule":
|
|
23822
|
+
return !/^(?:list|show|status|exists)$/.test(names[0] ?? "list");
|
|
23823
|
+
case "stash":
|
|
23824
|
+
return names[0] === "drop" || names[0] === "clear";
|
|
23825
|
+
case "remote":
|
|
23826
|
+
return MUTATING_REMOTE.has(names[0] ?? "");
|
|
23827
|
+
case "config": {
|
|
23828
|
+
if (rest.some((arg) => CONFIG_READ.test(arg)))
|
|
23829
|
+
return false;
|
|
23830
|
+
if (rest.some((arg) => CONFIG_WRITE.test(arg)))
|
|
23831
|
+
return true;
|
|
23832
|
+
let operands = 0;
|
|
23833
|
+
for (let index = 0;index < rest.length; index++)
|
|
23834
|
+
if (CONFIG_VALUE.test(rest[index]))
|
|
23835
|
+
index++;
|
|
23836
|
+
else if (!rest[index].startsWith("-"))
|
|
23837
|
+
operands++;
|
|
23838
|
+
return operands > 1;
|
|
23839
|
+
}
|
|
23840
|
+
default:
|
|
23841
|
+
return false;
|
|
23842
|
+
}
|
|
23843
|
+
};
|
|
23844
|
+
var leadOwnedWorkit = (args) => !args.includes("--help") && args.some((_, start) => {
|
|
23845
|
+
if (start > 0 && !/(?:^|[\\/])workit(?:\.exe|\.cmd)?$/.test(args[start - 1]))
|
|
23846
|
+
return false;
|
|
23847
|
+
const [verb, sub] = skipWorkitGlobals(args.slice(start)).rest;
|
|
23848
|
+
return verb === "fanout" && /^(?:plan|worktree)$/.test(sub ?? "");
|
|
23849
|
+
});
|
|
23850
|
+
var leadOwnedDeny = (input, command) => {
|
|
23851
|
+
const unblock = "Worktrees, branches other than your own, refs, tags, remotes, git config, the shared stash and the fan-out plan belong to the lead: report what you need changed as a follow-up instead.";
|
|
23852
|
+
return {
|
|
23853
|
+
kind: "deny",
|
|
23854
|
+
reason: `workit worktree confinement: implementer ${input.session.agentId} may not run \`${command.slice(0, 160)}\`. ${unblock}`,
|
|
23855
|
+
unblock
|
|
23856
|
+
};
|
|
23857
|
+
};
|
|
23858
|
+
function confineImplementerShell(input, command, dialect = "posix") {
|
|
23859
|
+
try {
|
|
23860
|
+
if (!input.session.agentId || hostIsolated(input.cwd, input.session.agentId))
|
|
23861
|
+
return NONE5;
|
|
23862
|
+
if (rawInvocations(command, dialect).some((invocation) => invocation.tool === "git" && leadOwnedGit(invocation.args)) || workitInvocations(command, dialect).some(leadOwnedWorkit))
|
|
23863
|
+
return leadOwnedDeny(input, command);
|
|
23864
|
+
} catch {}
|
|
23865
|
+
return confineImplementer(input, shellMutationTargets(input.cwd, command, dialect, true));
|
|
23866
|
+
}
|
|
23867
|
+
|
|
22631
23868
|
// packages/workit-core/src/hooks/handle.ts
|
|
22632
23869
|
var usable2 = (value) => value === "native" || value === "partial";
|
|
22633
|
-
var
|
|
23870
|
+
var NONE6 = { kind: "none" };
|
|
22634
23871
|
var resendsOnChange = (descriptor) => descriptor.context.turnResend === "on-change" && usable2(descriptor.context.perTurn);
|
|
22635
23872
|
var offered = new Set;
|
|
22636
23873
|
var ROLES = {
|
|
@@ -22643,6 +23880,7 @@ var workitRole = (descriptor, agentType) => {
|
|
|
22643
23880
|
const name = agentType.startsWith(prefix) ? agentType.slice(prefix.length) : "";
|
|
22644
23881
|
return Object.hasOwn(ROLES, name) ? ROLES[name] : null;
|
|
22645
23882
|
};
|
|
23883
|
+
var implementerAgent = (input, descriptor) => Boolean(input.session.agentId) && input.session.agentType !== null && workitRole(descriptor, input.session.agentType) === "implementer";
|
|
22646
23884
|
var subagentSession = (session, agentId) => [session, agentId].filter((part) => Boolean(part)).join(":").replace(/[^A-Za-z0-9_.:@/+-]/g, "-").slice(0, 128);
|
|
22647
23885
|
var CLAUDE_WORKTREE = /[\\/]\.claude[\\/]worktrees[\\/]/;
|
|
22648
23886
|
var isolatedGit = (cwd) => `Run git as plain, separate commands inside your worktree (\`git switch -c <branch> <base>\`, ${workspaceTrailers(cwd).enabled ? '`git commit --trailer "Workit-Session=<your session>"`, `git push`; `workit doctor --fix` can install a prepare-commit-msg hook that adds the trailer' : "`git commit` (this workspace sets trailers: false: add no Workit-Session trailer), `git push`"}; add Co-Authored-By with \`git commit --trailer\` in the same commit, never by amending), never \`workit git …\`: Claude Code's worktree isolation refuses a command that wraps git and cannot be turned off (https://code.claude.com/docs/en/worktrees#how-claude-code-enforces-isolation). Never dodge that check by renaming or re-wrapping git. Use workit for the non-git verbs (\`workit check\`, \`workit ledger\`, \`workit pr\`).`;
|
|
@@ -22650,7 +23888,7 @@ var subagentStartText = (input, descriptor, event) => {
|
|
|
22650
23888
|
const session = input.session.id ?? null;
|
|
22651
23889
|
const role = workitRole(descriptor, event.agentType);
|
|
22652
23890
|
const who = `${descriptor.label} subagent ${event.agentId} (${event.agentType})`;
|
|
22653
|
-
const isolated = descriptor.subagents.worktreeIsolation === "native" && (role === "implementer"
|
|
23891
|
+
const isolated = descriptor.subagents.worktreeIsolation === "native" && (role === "implementer" ? hostIsolated(input.cwd, event.agentId) : CLAUDE_WORKTREE.test(input.cwd));
|
|
22654
23892
|
const guide = role === "implementer" ? branchGuidance(input.cwd) : null;
|
|
22655
23893
|
if (role === "implementer" && isolated)
|
|
22656
23894
|
return `Workit observed ${who} working in its own git worktree: it may edit and commit there, within its brief's scope. ${isolatedGit(input.cwd)} Plain git is expected here, and the branch name is still policy-checked: a raw \`git switch -c\`, \`git checkout -b\`, \`git branch\` or \`git worktree add -b\` onto a protected or non-compliant name is denied, so use a policy-compliant branch.${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.`;
|
|
@@ -22658,7 +23896,7 @@ var subagentStartText = (input, descriptor, event) => {
|
|
|
22658
23896
|
return `${isolatedGit(input.cwd)}
|
|
22659
23897
|
${subagentStartText({ ...input, cwd: "" }, descriptor, event)}`;
|
|
22660
23898
|
if (role === "implementer")
|
|
22661
|
-
return `Workit observed ${who} as an implementer: it edits and commits only in its own git worktree (the lead makes
|
|
23899
|
+
return `Workit observed ${who} as an implementer: it edits and commits only in its own git worktree, the one its brief names (the lead makes it with \`workit fanout worktree create <slice>\` before spawning), never in the lead's checkout, within its brief's scope. Workit binds it to the first Workit worktree it changes: from then on an edit or a mutating git command outside that worktree is denied, and before then so is one outside every Workit worktree. Run Workit against the worktree one verb per call, never \`cd … &&\` chains or \`$VAR\` paths: \`workit --cwd <worktree> git commit -m "<message>" -- <paths>\`, \`workit --cwd <worktree> check <name>\`. The worktree is on the brief's branch once the lead made it; otherwise switch with \`workit --cwd <worktree> git branch <branch> --base <base>\` (or \`--kind <kind> --slug <slug>\`, which picks the base); branch policy hooks still deny protected or non-compliant branches, raw git included.${guide ? ` ${guide}` : " (e.g. feature/<slug>)"} Never record a verdict on your own work. Never push, open a PR, or merge unless the brief asks for it.`;
|
|
22662
23900
|
const readOnly = `Workit observed ${who} as read-only/agent-guided.`;
|
|
22663
23901
|
if (role !== "judge" || !event.agentId)
|
|
22664
23902
|
return readOnly;
|
|
@@ -22689,7 +23927,7 @@ function decide(input, deps) {
|
|
|
22689
23927
|
switch (event.kind) {
|
|
22690
23928
|
case "session.start": {
|
|
22691
23929
|
if (!usable2(descriptor.context.sessionStart))
|
|
22692
|
-
return
|
|
23930
|
+
return NONE6;
|
|
22693
23931
|
const key = input.session.id ? `${input.host}\x00${input.session.id}` : null;
|
|
22694
23932
|
const offer = event.source === "startup" && (key === null || !offered.has(key));
|
|
22695
23933
|
if (offer && key !== null)
|
|
@@ -22704,26 +23942,31 @@ function decide(input, deps) {
|
|
|
22704
23942
|
}
|
|
22705
23943
|
case "context.turn": {
|
|
22706
23944
|
if (!usable2(descriptor.context.perTurn))
|
|
22707
|
-
return
|
|
23945
|
+
return NONE6;
|
|
22708
23946
|
const current = turnContextText(input, descriptor);
|
|
22709
23947
|
const text = resendsOnChange(descriptor) ? changedTurnContext(input, current) : current;
|
|
22710
|
-
return withContextLine(text ? { kind: "context", text } :
|
|
23948
|
+
return withContextLine(text ? { kind: "context", text } : NONE6, promptNudge(input, event.prompt));
|
|
22711
23949
|
}
|
|
22712
23950
|
case "shell.pre": {
|
|
22713
23951
|
const skillRead = skillReadIn(event.command, event.dialect);
|
|
22714
23952
|
if (skillRead)
|
|
22715
23953
|
recordSkillLoad(input, skillRead, "read");
|
|
22716
23954
|
const canDeny = usable2(descriptor.shellPolicy.deny);
|
|
22717
|
-
const policy = canDeny ? shellPolicy(input.cwd, event.command, event.dialect) :
|
|
23955
|
+
const policy = canDeny ? shellPolicy(input.cwd, event.command, event.dialect) : NONE6;
|
|
22718
23956
|
if (policy.kind !== "none")
|
|
22719
23957
|
return policy;
|
|
22720
23958
|
const postTool = usable2(descriptor.events["shell.post"].support);
|
|
22721
23959
|
if (!postTool)
|
|
22722
23960
|
settlePendingCommit(input);
|
|
22723
|
-
const raw = canDeny ? rawGitPre(input, event.command, { nudge: false }) :
|
|
23961
|
+
const raw = canDeny ? rawGitPre(input, event.command, { nudge: false }) : NONE6;
|
|
22724
23962
|
if (raw.kind === "deny")
|
|
22725
23963
|
return raw;
|
|
22726
|
-
|
|
23964
|
+
if (canDeny && implementerAgent(input, descriptor)) {
|
|
23965
|
+
const confined = confineImplementerShell(input, event.command, event.dialect);
|
|
23966
|
+
if (confined.kind === "deny")
|
|
23967
|
+
return confined;
|
|
23968
|
+
}
|
|
23969
|
+
const gate = usable2(descriptor.events["write.pre"].support) ? shellGate(input.cwd, event.command, liveOwner(input, descriptor.transport), event.dialect) : NONE6;
|
|
22727
23970
|
if (gate.kind === "deny")
|
|
22728
23971
|
return gate;
|
|
22729
23972
|
const plan = heavyGate(input.cwd, event.command, {
|
|
@@ -22746,10 +23989,10 @@ function decide(input, deps) {
|
|
|
22746
23989
|
command: rewrittenTo,
|
|
22747
23990
|
input: event.toolInput ? { ...event.toolInput, command: rewrittenTo } : null,
|
|
22748
23991
|
text
|
|
22749
|
-
} :
|
|
23992
|
+
} : NONE6;
|
|
22750
23993
|
if (descriptor.transport !== "hook-process")
|
|
22751
23994
|
return rewritten(null);
|
|
22752
|
-
const nudged = withContextLine(withContextLine(canDeny ? rawGitPre(input, event.command) :
|
|
23995
|
+
const nudged = withContextLine(withContextLine(canDeny ? rawGitPre(input, event.command) : NONE6, aside), shipNudge(input, event.command));
|
|
22753
23996
|
if (!lease)
|
|
22754
23997
|
return cwdCommand && nudged.kind !== "deny" ? rewritten(nudged.kind === "context" ? nudged.text : null) : nudged;
|
|
22755
23998
|
return rewritten([rewriteNote(lease), nudged.kind === "context" ? nudged.text : null].filter(Boolean).join(`
|
|
@@ -22758,9 +24001,17 @@ function decide(input, deps) {
|
|
|
22758
24001
|
case "shell.post":
|
|
22759
24002
|
if (usable2(descriptor.events["shell.post"].support))
|
|
22760
24003
|
rawGitPost(input, event.command, callKey(event.toolUseId, event.command));
|
|
22761
|
-
return
|
|
22762
|
-
case "write.pre":
|
|
22763
|
-
|
|
24004
|
+
return NONE6;
|
|
24005
|
+
case "write.pre": {
|
|
24006
|
+
if (!usable2(descriptor.events["write.pre"].support))
|
|
24007
|
+
return NONE6;
|
|
24008
|
+
if (implementerAgent(input, descriptor)) {
|
|
24009
|
+
const confined = confineImplementer(input, liveTargets(input.cwd, event.paths));
|
|
24010
|
+
if (confined.kind === "deny")
|
|
24011
|
+
return confined;
|
|
24012
|
+
}
|
|
24013
|
+
return writeGate(input.cwd, event.paths, liveOwner(input, descriptor.transport));
|
|
24014
|
+
}
|
|
22764
24015
|
case "subagent.start":
|
|
22765
24016
|
if (workitRole(descriptor, event.agentType) === "judge")
|
|
22766
24017
|
trackJudge(input, event.agentId, true);
|
|
@@ -22777,14 +24028,14 @@ function decide(input, deps) {
|
|
|
22777
24028
|
case "tool.pre":
|
|
22778
24029
|
if (event.skill)
|
|
22779
24030
|
recordSkillLoad(input, event.skill, "tool");
|
|
22780
|
-
return
|
|
24031
|
+
return NONE6;
|
|
22781
24032
|
case "stop":
|
|
22782
|
-
return usable2(descriptor.events.stop.support) ? stopDecision(input, descriptor) :
|
|
24033
|
+
return usable2(descriptor.events.stop.support) ? stopDecision(input, descriptor) : NONE6;
|
|
22783
24034
|
case "subagent.stop":
|
|
22784
24035
|
trackJudge(input, event.agentId, false);
|
|
22785
|
-
return
|
|
24036
|
+
return NONE6;
|
|
22786
24037
|
case "prompt.submit":
|
|
22787
|
-
return
|
|
24038
|
+
return NONE6;
|
|
22788
24039
|
}
|
|
22789
24040
|
}
|
|
22790
24041
|
// packages/workit-core/src/hooks/deadline.ts
|
|
@@ -23091,14 +24342,14 @@ var observeToolResult = (event, ctx) => {
|
|
|
23091
24342
|
var skillPromptNudge = (prompt, ctx) => process.env.WORKIT_PI_WORKER_SESSION ? null : promptNudge(hookInput(ctx, { kind: "context.turn", prompt }), prompt);
|
|
23092
24343
|
|
|
23093
24344
|
// packages/workit-pi/src/launches.ts
|
|
23094
|
-
import { mkdirSync as mkdirSync3, readFileSync as
|
|
24345
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync7, readdirSync as readdirSync5, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "node:fs";
|
|
23095
24346
|
import os11 from "node:os";
|
|
23096
|
-
import
|
|
24347
|
+
import path35 from "node:path";
|
|
23097
24348
|
var launchDir = (root) => {
|
|
23098
24349
|
const location = resolveStore(root);
|
|
23099
|
-
return location instanceof Error ? null :
|
|
24350
|
+
return location instanceof Error ? null : path35.join(location.dir, "pi-launches");
|
|
23100
24351
|
};
|
|
23101
|
-
var fileFor = (dir, workerId) =>
|
|
24352
|
+
var fileFor = (dir, workerId) => path35.join(dir, `${workerId.replace(/[^A-Za-z0-9-]/g, "_")}.json`);
|
|
23102
24353
|
var recordLaunch = (root, attempt) => {
|
|
23103
24354
|
const dir = launchDir(root);
|
|
23104
24355
|
if (!dir)
|
|
@@ -23144,7 +24395,7 @@ var abandonedLaunches = (root) => {
|
|
|
23144
24395
|
const found = [];
|
|
23145
24396
|
for (const name of names) {
|
|
23146
24397
|
try {
|
|
23147
|
-
const attempt = JSON.parse(
|
|
24398
|
+
const attempt = JSON.parse(readFileSync7(path35.join(dir, name), "utf8"));
|
|
23148
24399
|
if (typeof attempt.workerId === "string" && typeof attempt.session === "string") {
|
|
23149
24400
|
if (!alive(attempt))
|
|
23150
24401
|
found.push(attempt);
|