@brainervirus/workit-codex 8.12.3 → 8.13.1
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/.codex-plugin/plugin.json +1 -1
- package/agents/workit-reviewer.toml +2 -2
- package/agents/workit-verifier.toml +2 -2
- package/dist/launch-mcp.js +641 -401
- package/dist/{workit-hook-2d3wce88.js → workit-hook-j1zd509p.js} +251 -49
- package/dist/workit-hook.js +1 -1
- package/package.json +4 -4
- package/skills/workit-fanout/references/orchestration.md +4 -3
- package/skills/workit-review/SKILL.md +1 -1
- package/skills/workit-ship/SKILL.md +20 -20
- package/skills/workit-ship/references/release.md +26 -0
|
@@ -79,11 +79,11 @@ what and whether, the CLI does how and records what it observed.
|
|
|
79
79
|
Native host allow/ask/deny, sandbox and org rules stay authoritative. Never
|
|
80
80
|
switch paths to evade a denial, never claim enforcement a host cannot provide,
|
|
81
81
|
and never fabricate an approval or verdict. A question answer is not
|
|
82
|
-
host permission, and imported decisions or handoffs
|
|
82
|
+
host permission, and imported decisions or handoffs grant no authority.
|
|
83
83
|
|
|
84
84
|
Autonomy contract:
|
|
85
85
|
- Continue to the requested endpoint (commit, push, PR, green CI, verified,
|
|
86
|
-
merged) without asking "continue?", running checks and repairs
|
|
86
|
+
merged) without asking "continue?", running checks and repairs.
|
|
87
87
|
Stop only for a new consequential choice, a host denial, a conflicting edit
|
|
88
88
|
or an unresolved blocker; report open gaps and uncertain workers.
|
|
89
89
|
- No endpoint named: stop at a local commit on a policy-compliant branch and
|
|
@@ -93,7 +93,8 @@ Autonomy contract:
|
|
|
93
93
|
never merging; \`merged\` also lands it with \`workit pr merge\`.
|
|
94
94
|
Merge and release need a workspace grant only the user raises from their
|
|
95
95
|
own terminal (a blocked verb names how); never edit workspaces.json or route
|
|
96
|
-
around
|
|
96
|
+
around grant_required. Release: \`workit release start <v>\`, commits,
|
|
97
|
+
\`finish\`; \`publish\` if asked.
|
|
97
98
|
- Ask only for a product or preference choice, or for authority you lack, with
|
|
98
99
|
your recommended answer. Facts are yours: read, run or prototype.
|
|
99
100
|
- Label claims measured (run and seen this session), inferred, or guess.
|
|
@@ -108,22 +109,22 @@ Autonomy contract:
|
|
|
108
109
|
reconcile every requested item and observe it (\`workit verify-delivery\`).
|
|
109
110
|
|
|
110
111
|
CLI first: if a step has one right answer, use the \`workit\` verb instead of
|
|
111
|
-
hand-running git, gh or glab (\`workit git|pr|ci|check\`;
|
|
112
|
-
|
|
112
|
+
hand-running git, gh or glab (\`workit git|pr|ci|check|help\`;
|
|
113
|
+
else \`npx -y @brainervirus/workit-cli\`).
|
|
113
114
|
Add Co-Authored-By via \`workit git commit --trailer "K: v" -- <paths>\`, never
|
|
114
115
|
an amend.
|
|
115
116
|
The task families (task, policy, evidence, finding, decision, worker, state)
|
|
116
117
|
are optional continuity for tracked work; each branch has one implicit task,
|
|
117
|
-
and a solo edit needs no task. A decision
|
|
118
|
-
|
|
118
|
+
and a solo edit needs no task. A decision records a user choice; it never
|
|
119
|
+
authorizes an effect. Judge work once with
|
|
119
120
|
\`workit policy assess --judge risk= behavior= product-choice= plan=\`.
|
|
120
|
-
Across repositories, bind
|
|
121
|
-
|
|
122
|
-
external effect before
|
|
121
|
+
Across repositories, bind items to checkout, branch and endpoint, resolve
|
|
122
|
+
competing targets before a mutation, and reconcile an uncertain
|
|
123
|
+
external effect before a retry.
|
|
123
124
|
|
|
124
125
|
Mid-task: never silently drop or resume an objective (workit-continue).
|
|
125
126
|
|
|
126
|
-
Skills: load the skill, never act from memory
|
|
127
|
+
Skills: load the skill, never act from memory (aliases /wk-<name>).
|
|
127
128
|
- brainstorm, plan, spec, grill, should we: workit-shape
|
|
128
129
|
- implement, build, add a feature: workit-implement
|
|
129
130
|
- review, blast radius: workit-review
|
|
@@ -9788,7 +9789,7 @@ function acquireFileLockSync(targetPath, options) {
|
|
|
9788
9789
|
}
|
|
9789
9790
|
}
|
|
9790
9791
|
// packages/workit-core/src/host-session.ts
|
|
9791
|
-
import { createHash as createHash4 } from "node:crypto";
|
|
9792
|
+
import { createHash as createHash4, randomBytes as randomBytes2, timingSafeEqual } from "node:crypto";
|
|
9792
9793
|
import fs7 from "node:fs";
|
|
9793
9794
|
import path7 from "node:path";
|
|
9794
9795
|
|
|
@@ -9880,9 +9881,10 @@ function hostSessionFromEnv(env) {
|
|
|
9880
9881
|
return inner ? { host: inner.host, session: inner.session } : { host: declared, session: null };
|
|
9881
9882
|
}
|
|
9882
9883
|
var ISSUED_PER_LEAD = 64;
|
|
9883
|
-
var ISSUED_SESSIONS_MARKER = "workit-issued-sessions/
|
|
9884
|
+
var ISSUED_SESSIONS_MARKER = "workit-issued-sessions/3";
|
|
9884
9885
|
var hash = (value) => createHash4("sha256").update(value).digest("hex").slice(0, 32);
|
|
9885
9886
|
var userIssuedRoot = () => path7.join(resolveStateDir(), "issued");
|
|
9887
|
+
var userTokenRoot = () => path7.join(resolveStateDir(), "issued-tokens");
|
|
9886
9888
|
function migrateRepoIssued(cwd) {
|
|
9887
9889
|
if (cwd === undefined)
|
|
9888
9890
|
return;
|
|
@@ -9905,7 +9907,12 @@ function migrateRepoIssued(cwd) {
|
|
|
9905
9907
|
const to = path7.join(userIssuedRoot(), lead);
|
|
9906
9908
|
fs7.mkdirSync(to, { recursive: true, mode: 448 });
|
|
9907
9909
|
for (const id of fs7.readdirSync(from)) {
|
|
9908
|
-
|
|
9910
|
+
try {
|
|
9911
|
+
fs7.copyFileSync(path7.join(from, id), path7.join(to, id), fs7.constants.COPYFILE_EXCL);
|
|
9912
|
+
} catch (error) {
|
|
9913
|
+
if (error.code !== "EEXIST")
|
|
9914
|
+
throw error;
|
|
9915
|
+
}
|
|
9909
9916
|
fs7.rmSync(path7.join(from, id), { force: true });
|
|
9910
9917
|
}
|
|
9911
9918
|
removeIfEmpty(from);
|
|
@@ -9925,21 +9932,71 @@ var removeIfEmpty = (dir) => {
|
|
|
9925
9932
|
fs7.rmdirSync(dir);
|
|
9926
9933
|
} catch {}
|
|
9927
9934
|
};
|
|
9935
|
+
var tokenHash = (token) => createHash4("sha256").update(token).digest("hex");
|
|
9928
9936
|
function recordIssuedSession(cwd, lead, issued) {
|
|
9937
|
+
const token = randomBytes2(18).toString("base64url");
|
|
9929
9938
|
try {
|
|
9930
9939
|
migrateRepoIssued(cwd);
|
|
9940
|
+
const own = hash(issued);
|
|
9941
|
+
const tokens = path7.join(userTokenRoot(), hash(lead));
|
|
9942
|
+
fs7.mkdirSync(tokens, { recursive: true, mode: 448 });
|
|
9943
|
+
fs7.writeFileSync(path7.join(tokens, own), `${tokenHash(token)}
|
|
9944
|
+
`, { mode: 384, flag: "wx" });
|
|
9931
9945
|
const dir = path7.join(userIssuedRoot(), hash(lead));
|
|
9932
9946
|
fs7.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
9933
|
-
|
|
9934
|
-
|
|
9947
|
+
fs7.writeFileSync(path7.join(dir, own), `${ISSUED_SESSIONS_MARKER}
|
|
9948
|
+
`, { mode: 384 });
|
|
9949
|
+
pruneIssued(dir, own, tokens);
|
|
9950
|
+
return token;
|
|
9951
|
+
} catch {
|
|
9952
|
+
return null;
|
|
9953
|
+
}
|
|
9954
|
+
}
|
|
9955
|
+
function pruneIssued(dir, own, tokens) {
|
|
9956
|
+
try {
|
|
9935
9957
|
const entries = fs7.readdirSync(dir);
|
|
9936
9958
|
if (entries.length <= ISSUED_PER_LEAD)
|
|
9937
9959
|
return;
|
|
9938
9960
|
const byAge = entries.filter((name) => name !== own).map((name) => ({ name, at: fs7.statSync(path7.join(dir, name)).mtimeMs })).toSorted((a, b) => a.at - b.at);
|
|
9939
|
-
for (const { name } of byAge.slice(0, entries.length - ISSUED_PER_LEAD))
|
|
9961
|
+
for (const { name } of byAge.slice(0, entries.length - ISSUED_PER_LEAD)) {
|
|
9940
9962
|
fs7.rmSync(path7.join(dir, name), { force: true });
|
|
9963
|
+
fs7.rmSync(path7.join(tokens, name), { force: true });
|
|
9964
|
+
}
|
|
9941
9965
|
} catch {}
|
|
9942
9966
|
}
|
|
9967
|
+
var issuedRecord = (cwd, claimed) => {
|
|
9968
|
+
const cut = claimed.lastIndexOf(":");
|
|
9969
|
+
if (cut <= 0)
|
|
9970
|
+
return null;
|
|
9971
|
+
migrateRepoIssued(cwd);
|
|
9972
|
+
try {
|
|
9973
|
+
return fs7.readFileSync(path7.join(userIssuedRoot(), hash(claimed.slice(0, cut)), hash(claimed)), "utf8");
|
|
9974
|
+
} catch {
|
|
9975
|
+
return null;
|
|
9976
|
+
}
|
|
9977
|
+
};
|
|
9978
|
+
var TOKEN_HASH = /^([0-9a-f]{64})$/mu;
|
|
9979
|
+
var issuedToken = (claimed) => {
|
|
9980
|
+
const cut = claimed.lastIndexOf(":");
|
|
9981
|
+
if (cut <= 0)
|
|
9982
|
+
return { stored: null, leadBound: false };
|
|
9983
|
+
const dir = path7.join(userTokenRoot(), hash(claimed.slice(0, cut)));
|
|
9984
|
+
try {
|
|
9985
|
+
const text = fs7.readFileSync(path7.join(dir, hash(claimed)), "utf8");
|
|
9986
|
+
return { stored: TOKEN_HASH.exec(text)?.[1] ?? null, leadBound: true };
|
|
9987
|
+
} catch {
|
|
9988
|
+
return { stored: null, leadBound: safeList(dir).length > 0 };
|
|
9989
|
+
}
|
|
9990
|
+
};
|
|
9991
|
+
function issuedBinding(cwd, claimed) {
|
|
9992
|
+
const record = issuedRecord(cwd, claimed);
|
|
9993
|
+
const { stored, leadBound } = issuedToken(claimed);
|
|
9994
|
+
if (stored)
|
|
9995
|
+
return "token";
|
|
9996
|
+
if (record === null)
|
|
9997
|
+
return null;
|
|
9998
|
+
return leadBound ? "token" : "legacy";
|
|
9999
|
+
}
|
|
9943
10000
|
var OWN_CHILD_SHELL = new Set(["opencode", "pi"]);
|
|
9944
10001
|
|
|
9945
10002
|
// packages/workit-core/src/core/package-root.ts
|
|
@@ -9970,8 +10027,10 @@ var metadataLockSchema = object({
|
|
|
9970
10027
|
var FOREIGN_LOCK_TTL_MS = 10 * 60000;
|
|
9971
10028
|
var UNREADABLE_LOCK_TTL_MS = 30000;
|
|
9972
10029
|
var RECLAIM_GUARD_TTL_MS = 30000;
|
|
9973
|
-
var
|
|
10030
|
+
var IN_PROCESS_LOCK_TIMEOUT_MS = 250;
|
|
10031
|
+
var defaultLockTimeoutMs = IN_PROCESS_LOCK_TIMEOUT_MS;
|
|
9974
10032
|
var defaultLockTimeout = () => defaultLockTimeoutMs;
|
|
10033
|
+
var hostAwareLockTimeout = (cliMs) => defaultLockTimeoutMs > IN_PROCESS_LOCK_TIMEOUT_MS ? cliMs : IN_PROCESS_LOCK_TIMEOUT_MS;
|
|
9975
10034
|
var parseMetadataLock = (raw) => {
|
|
9976
10035
|
let value;
|
|
9977
10036
|
try {
|
|
@@ -12244,7 +12303,7 @@ import path15 from "node:path";
|
|
|
12244
12303
|
|
|
12245
12304
|
// packages/workit-core/src/ledger.ts
|
|
12246
12305
|
import { spawnSync as spawnSync9 } from "node:child_process";
|
|
12247
|
-
import { createHash as createHash9, randomBytes as
|
|
12306
|
+
import { createHash as createHash9, randomBytes as randomBytes3 } from "node:crypto";
|
|
12248
12307
|
import fs12 from "node:fs";
|
|
12249
12308
|
import path14 from "node:path";
|
|
12250
12309
|
|
|
@@ -12524,6 +12583,15 @@ var repoBranchPreset = (workspaceRoot) => {
|
|
|
12524
12583
|
const detected = detectBranchPolicy(workspaceRoot);
|
|
12525
12584
|
return detected.developBranch || detected.allowed.length ? detected.preset : null;
|
|
12526
12585
|
};
|
|
12586
|
+
var releaseTagPrefixOf = (workspaceRoot, policyPrefix) => {
|
|
12587
|
+
if (typeof policyPrefix === "string")
|
|
12588
|
+
return policyPrefix.trim();
|
|
12589
|
+
const r = spawnSync5("git", ["config", "--get", "gitflow.prefix.versiontag"], {
|
|
12590
|
+
cwd: workspaceRoot,
|
|
12591
|
+
encoding: "utf8"
|
|
12592
|
+
});
|
|
12593
|
+
return r.status === 0 ? (r.stdout ?? "").trim() : "v";
|
|
12594
|
+
};
|
|
12527
12595
|
|
|
12528
12596
|
// packages/workit-core/src/core/workspaces.ts
|
|
12529
12597
|
var nonBlank = string2().refine((value) => value.trim().length > 0, "must not be blank");
|
|
@@ -13626,7 +13694,7 @@ function ledgerPath(cwd) {
|
|
|
13626
13694
|
const root = storeRoot(cwd);
|
|
13627
13695
|
return root.ok ? { ok: true, value: path14.join(root.value.root, "ledger", "ledger.jsonl") } : root;
|
|
13628
13696
|
}
|
|
13629
|
-
var newId2 = () => `${Date.now().toString(36)}-${
|
|
13697
|
+
var newId2 = () => `${Date.now().toString(36)}-${randomBytes3(6).toString("hex")}`;
|
|
13630
13698
|
var NETWORK_FS_TYPES = new Set([
|
|
13631
13699
|
26985,
|
|
13632
13700
|
20859,
|
|
@@ -13676,14 +13744,14 @@ var ledgerLock = {
|
|
|
13676
13744
|
return null;
|
|
13677
13745
|
throw error;
|
|
13678
13746
|
}
|
|
13679
|
-
const token = `${process.pid}-${
|
|
13747
|
+
const token = `${process.pid}-${randomBytes3(8).toString("hex")}`;
|
|
13680
13748
|
fs12.writeFileSync(path14.join(lock, LOCK_OWNER), token);
|
|
13681
13749
|
return token;
|
|
13682
13750
|
},
|
|
13683
13751
|
release(lock, token) {
|
|
13684
13752
|
if (lockToken(lock) !== token)
|
|
13685
13753
|
return false;
|
|
13686
|
-
const tomb = `${lock}.release-${
|
|
13754
|
+
const tomb = `${lock}.release-${randomBytes3(6).toString("hex")}`;
|
|
13687
13755
|
try {
|
|
13688
13756
|
fs12.renameSync(lock, tomb);
|
|
13689
13757
|
} catch {
|
|
@@ -13700,7 +13768,7 @@ var ledgerLock = {
|
|
|
13700
13768
|
},
|
|
13701
13769
|
token: lockToken,
|
|
13702
13770
|
reap(lock, observed) {
|
|
13703
|
-
const tomb = `${lock}.reap-${
|
|
13771
|
+
const tomb = `${lock}.reap-${randomBytes3(6).toString("hex")}`;
|
|
13704
13772
|
try {
|
|
13705
13773
|
fs12.renameSync(lock, tomb);
|
|
13706
13774
|
} catch {
|
|
@@ -13718,7 +13786,7 @@ var ledgerLock = {
|
|
|
13718
13786
|
return false;
|
|
13719
13787
|
}
|
|
13720
13788
|
};
|
|
13721
|
-
function withLedgerLock(file, fn, waitMs = LOCK_WAIT_MS) {
|
|
13789
|
+
function withLedgerLock(file, fn, waitMs = hostAwareLockTimeout(LOCK_WAIT_MS)) {
|
|
13722
13790
|
const lock = `${file}.lock`;
|
|
13723
13791
|
const deadline = Date.now() + waitMs;
|
|
13724
13792
|
let token = null;
|
|
@@ -13776,10 +13844,18 @@ var lastLine = (fd, size) => {
|
|
|
13776
13844
|
}
|
|
13777
13845
|
return null;
|
|
13778
13846
|
};
|
|
13847
|
+
var tighten = (fd) => {
|
|
13848
|
+
try {
|
|
13849
|
+
if ((fs12.fstatSync(fd).mode & 63) !== 0)
|
|
13850
|
+
fs12.fchmodSync(fd, 384);
|
|
13851
|
+
} catch {}
|
|
13852
|
+
};
|
|
13779
13853
|
function appendLine(file, build, options) {
|
|
13780
13854
|
const write = () => {
|
|
13781
|
-
const fd = fs12.openSync(file, "a+");
|
|
13855
|
+
const fd = fs12.openSync(file, "a+", 384);
|
|
13782
13856
|
try {
|
|
13857
|
+
if (process.platform !== "win32")
|
|
13858
|
+
tighten(fd);
|
|
13783
13859
|
const size = fs12.fstatSync(fd).size;
|
|
13784
13860
|
let fence = "";
|
|
13785
13861
|
if (size > 0) {
|
|
@@ -13874,7 +13950,9 @@ function readLedger(cwd, options = {}) {
|
|
|
13874
13950
|
if (!file.ok)
|
|
13875
13951
|
return file;
|
|
13876
13952
|
let text = "";
|
|
13953
|
+
let stat = null;
|
|
13877
13954
|
try {
|
|
13955
|
+
stat = fs12.statSync(file.value);
|
|
13878
13956
|
text = fs12.readFileSync(file.value, "utf8");
|
|
13879
13957
|
} catch (error) {
|
|
13880
13958
|
if (error.code === "ENOENT")
|
|
@@ -13894,6 +13972,7 @@ function readLedger(cwd, options = {}) {
|
|
|
13894
13972
|
const integrity = { chained: 0, legacy: 0, unverified: [] };
|
|
13895
13973
|
const earlier = new Set([null]);
|
|
13896
13974
|
let chainStarted = false;
|
|
13975
|
+
const ids = new Set;
|
|
13897
13976
|
for (const raw of text.split(`
|
|
13898
13977
|
`)) {
|
|
13899
13978
|
const trimmed = raw.trim();
|
|
@@ -13927,6 +14006,10 @@ function readLedger(cwd, options = {}) {
|
|
|
13927
14006
|
reason = "unsigned";
|
|
13928
14007
|
else
|
|
13929
14008
|
rowIntegrity = "legacy";
|
|
14009
|
+
if (options.integrity && !reason && ids.has(parsed.id))
|
|
14010
|
+
reason = "duplicate_id";
|
|
14011
|
+
if (options.integrity)
|
|
14012
|
+
ids.add(parsed.id);
|
|
13930
14013
|
if (reason)
|
|
13931
14014
|
rowIntegrity = "unverified";
|
|
13932
14015
|
const pr = typeof parsed.pr === "number" && Number.isInteger(parsed.pr) ? parsed.pr : undefined;
|
|
@@ -13950,7 +14033,8 @@ function readLedger(cwd, options = {}) {
|
|
|
13950
14033
|
seq: rows.length + 1,
|
|
13951
14034
|
superseded: false,
|
|
13952
14035
|
supersedeIgnored: false,
|
|
13953
|
-
...rowIntegrity ? { integrity: rowIntegrity } : {}
|
|
14036
|
+
...rowIntegrity ? { integrity: rowIntegrity } : {},
|
|
14037
|
+
...reason ? { integrityReason: reason } : {}
|
|
13954
14038
|
};
|
|
13955
14039
|
rows.push(row);
|
|
13956
14040
|
if (!options.integrity)
|
|
@@ -13978,18 +14062,48 @@ function readLedger(cwd, options = {}) {
|
|
|
13978
14062
|
const byId = new Map;
|
|
13979
14063
|
for (const row of rows) {
|
|
13980
14064
|
const target = row.supersedes ? byId.get(row.supersedes) : undefined;
|
|
13981
|
-
if (target && supersedeAllowed(row, target, renameAwareBranch(scopeOf, row.branch)) === null)
|
|
14065
|
+
if (target && row.integrity !== "unverified" && supersedeAllowed(row, target, renameAwareBranch(scopeOf, row.branch)) === null)
|
|
13982
14066
|
target.superseded = true;
|
|
13983
14067
|
else if (row.supersedes)
|
|
13984
14068
|
row.supersedeIgnored = true;
|
|
13985
14069
|
if (!byId.has(row.id))
|
|
13986
14070
|
byId.set(row.id, row);
|
|
13987
14071
|
}
|
|
14072
|
+
if (options.integrity && stat && Buffer.byteLength(text) === stat.size)
|
|
14073
|
+
tamperCache.set(file.value, {
|
|
14074
|
+
size: stat.size,
|
|
14075
|
+
stamp: fileStamp(stat),
|
|
14076
|
+
breaks: integrity.unverified.filter((row) => TAMPER_REASONS.has(row.reason))
|
|
14077
|
+
});
|
|
13988
14078
|
return {
|
|
13989
14079
|
ok: true,
|
|
13990
14080
|
value: { path: file.value, rows, skipped, integrity: options.integrity ? integrity : null }
|
|
13991
14081
|
};
|
|
13992
14082
|
}
|
|
14083
|
+
var TAMPER_REASONS = new Set(["hash_mismatch", "broken_link"]);
|
|
14084
|
+
var tamperCache = new Map;
|
|
14085
|
+
var fileStamp = (stat) => `${stat.ino}:${stat.mtimeMs}:${stat.ctimeMs}`;
|
|
14086
|
+
function ledgerTampering(cwd) {
|
|
14087
|
+
const file = ledgerPath(cwd);
|
|
14088
|
+
if (!file.ok)
|
|
14089
|
+
return [];
|
|
14090
|
+
try {
|
|
14091
|
+
const stat = fs12.statSync(file.value);
|
|
14092
|
+
const cached = tamperCache.get(file.value);
|
|
14093
|
+
if (cached && cached.size === stat.size && cached.stamp === fileStamp(stat))
|
|
14094
|
+
return cached.breaks;
|
|
14095
|
+
} catch {
|
|
14096
|
+
return [];
|
|
14097
|
+
}
|
|
14098
|
+
const read = readLedger(cwd, { integrity: true });
|
|
14099
|
+
return read.ok ? (read.value.integrity?.unverified ?? []).filter((row) => TAMPER_REASONS.has(row.reason)) : [];
|
|
14100
|
+
}
|
|
14101
|
+
function tamperedUnblock(cwd) {
|
|
14102
|
+
const file = ledgerPath(cwd);
|
|
14103
|
+
const where = file.ok ? file.value : "the ledger file";
|
|
14104
|
+
return `workit ledger verify-integrity # lists the edited or missing rows; if the edit is understood, move ${where} aside (mv ${where} ${where}.tampered-<date>) and record the verdicts again`;
|
|
14105
|
+
}
|
|
14106
|
+
var tamperedWarning = (cwd, breaks) => `the ledger was edited, or a row was removed or moved (${breaks.slice(0, 3).map((row) => `${row.reason} at row ${row.seq}`).join(", ")}${breaks.length > 3 ? ", …" : ""}): no verdict is accepted until it is repaired. ${tamperedUnblock(cwd)}`;
|
|
13993
14107
|
function verdictBasis(verdict, current) {
|
|
13994
14108
|
if (verdict.head && verdict.head === current.head)
|
|
13995
14109
|
return "fresh";
|
|
@@ -13997,7 +14111,7 @@ function verdictBasis(verdict, current) {
|
|
|
13997
14111
|
return "carried";
|
|
13998
14112
|
return "stale";
|
|
13999
14113
|
}
|
|
14000
|
-
var isLiveVerdict = (row) => row.type === "verdict" && typeof row.result === "string" && !row.
|
|
14114
|
+
var isLiveVerdict = (row, retired) => row.type === "verdict" && typeof row.result === "string" && !retired.has(row.id);
|
|
14001
14115
|
function trailerSessions(cwd, base, head) {
|
|
14002
14116
|
if (!base || !head || !safeRef(base) || !safeRef(head))
|
|
14003
14117
|
return [];
|
|
@@ -14078,6 +14192,56 @@ var authoredBy = (authors, session) => {
|
|
|
14078
14192
|
return true;
|
|
14079
14193
|
return false;
|
|
14080
14194
|
};
|
|
14195
|
+
var TRUST_REASONS = new Set([
|
|
14196
|
+
"unsigned",
|
|
14197
|
+
"hash_mismatch",
|
|
14198
|
+
"broken_link",
|
|
14199
|
+
"duplicate_id",
|
|
14200
|
+
"unissued_session",
|
|
14201
|
+
"unbound_session"
|
|
14202
|
+
]);
|
|
14203
|
+
var SHELL_VOUCHED_HOSTS = new Set([
|
|
14204
|
+
"cli",
|
|
14205
|
+
"opencode",
|
|
14206
|
+
"pi",
|
|
14207
|
+
"codex_cli",
|
|
14208
|
+
"codex_desktop"
|
|
14209
|
+
]);
|
|
14210
|
+
function sessionTrust(cwd, row) {
|
|
14211
|
+
const session = row.actor.session;
|
|
14212
|
+
if (!session || row.self === true)
|
|
14213
|
+
return null;
|
|
14214
|
+
const source = typeof row.sessionSource === "string" ? row.sessionSource : "env";
|
|
14215
|
+
const host = row.actor.host;
|
|
14216
|
+
const issuedOnly = source === "flag" ? host !== "cli" : source === "env" && session.includes(":") && !SHELL_VOUCHED_HOSTS.has(host);
|
|
14217
|
+
if (!issuedOnly)
|
|
14218
|
+
return null;
|
|
14219
|
+
const binding = issuedBinding(cwd, session);
|
|
14220
|
+
if (binding === null)
|
|
14221
|
+
return "unissued_session";
|
|
14222
|
+
return binding === "token" && row.sessionBound !== true ? "unbound_session" : null;
|
|
14223
|
+
}
|
|
14224
|
+
function trustReasons(cwd, row) {
|
|
14225
|
+
if (row.integrity === "unverified")
|
|
14226
|
+
return [row.integrityReason ?? "unsigned"];
|
|
14227
|
+
if (row.integrity !== "verified")
|
|
14228
|
+
return ["unsigned"];
|
|
14229
|
+
const session = sessionTrust(cwd, row);
|
|
14230
|
+
return session ? [session] : [];
|
|
14231
|
+
}
|
|
14232
|
+
function withIntegrity(cwd, rows) {
|
|
14233
|
+
if (rows.every((row) => row.integrity !== undefined))
|
|
14234
|
+
return rows;
|
|
14235
|
+
const read = readLedger(cwd, { integrity: true });
|
|
14236
|
+
const bySeq = new Map(read.ok ? read.value.rows.map((row) => [row.seq, row]) : []);
|
|
14237
|
+
return rows.map((row) => {
|
|
14238
|
+
if (row.integrity !== undefined)
|
|
14239
|
+
return row;
|
|
14240
|
+
const fresh = bySeq.get(row.seq);
|
|
14241
|
+
return fresh && fresh.id === row.id && fresh.at === row.at ? fresh : row;
|
|
14242
|
+
});
|
|
14243
|
+
}
|
|
14244
|
+
var untrusted = (reasons) => reasons.some((reason) => TRUST_REASONS.has(reason));
|
|
14081
14245
|
var independenceReasons = (row, authors) => {
|
|
14082
14246
|
const reasons = [];
|
|
14083
14247
|
if (!row.actor.session)
|
|
@@ -14088,15 +14252,17 @@ var independenceReasons = (row, authors) => {
|
|
|
14088
14252
|
reasons.push("self");
|
|
14089
14253
|
return reasons;
|
|
14090
14254
|
};
|
|
14091
|
-
function effectiveOf(rows,
|
|
14255
|
+
function effectiveOf(rows, reasonsOf, sameCode, isCurrent) {
|
|
14092
14256
|
let effective = null;
|
|
14093
14257
|
for (const row of rows) {
|
|
14094
14258
|
if (!effective) {
|
|
14095
14259
|
effective = row;
|
|
14096
14260
|
continue;
|
|
14097
14261
|
}
|
|
14098
|
-
|
|
14099
|
-
|
|
14262
|
+
if (untrusted(reasonsOf(row)) && !untrusted(reasonsOf(effective)))
|
|
14263
|
+
continue;
|
|
14264
|
+
const rowIndependent = reasonsOf(row).length === 0;
|
|
14265
|
+
const effIndependent = reasonsOf(effective).length === 0;
|
|
14100
14266
|
if (effIndependent && !rowIndependent && isCurrent(effective))
|
|
14101
14267
|
continue;
|
|
14102
14268
|
if (effIndependent && FAILING.has(String(effective.result)) && row.actor.session === effective.actor.session && sameCode(effective, row))
|
|
@@ -14110,7 +14276,7 @@ function restackCarries(rows, branch, from, to) {
|
|
|
14110
14276
|
return false;
|
|
14111
14277
|
const edges = new Map;
|
|
14112
14278
|
for (const row of rows)
|
|
14113
|
-
if (row.type === "stack.restacked" && row.observer === "workit_cli" && row.branch === branch && row.patchEqual === true && typeof row.fromHead === "string" && row.head)
|
|
14279
|
+
if (row.type === "stack.restacked" && row.observer === "workit_cli" && row.integrity === "verified" && row.branch === branch && row.patchEqual === true && typeof row.fromHead === "string" && row.head)
|
|
14114
14280
|
edges.set(row.fromHead, [...edges.get(row.fromHead) ?? [], row.head]);
|
|
14115
14281
|
const seen = new Set([from]);
|
|
14116
14282
|
const queue = [from];
|
|
@@ -14126,7 +14292,8 @@ function restackCarries(rows, branch, from, to) {
|
|
|
14126
14292
|
}
|
|
14127
14293
|
return false;
|
|
14128
14294
|
}
|
|
14129
|
-
function checkVerdicts(cwd, branch,
|
|
14295
|
+
function checkVerdicts(cwd, branch, ledgerRows) {
|
|
14296
|
+
const rows = withIntegrity(cwd, ledgerRows);
|
|
14130
14297
|
const head = branchHead(cwd, branch);
|
|
14131
14298
|
const fallbackBase = defaultBase(cwd);
|
|
14132
14299
|
const scope = branchScope(cwd, branch);
|
|
@@ -14156,21 +14323,47 @@ function checkVerdicts(cwd, branch, rows) {
|
|
|
14156
14323
|
return verdictBasis(failed, { head: row.head, ...keys }) !== "stale" || restackCarries(rows, branch, failed.head, row.head);
|
|
14157
14324
|
};
|
|
14158
14325
|
const authors = authorSessions(cwd, rows, branch, { base: fallbackBase, head }, scope);
|
|
14326
|
+
const failureAuthorCache = new Map;
|
|
14327
|
+
const failureAuthors = (row) => {
|
|
14328
|
+
let set = failureAuthorCache.get(row.seq);
|
|
14329
|
+
if (!set) {
|
|
14330
|
+
set = authorSessions(cwd, rows.filter((other) => other.integrity === "verified" && other.seq < row.seq), branch, { base: null, head: null }, scope);
|
|
14331
|
+
failureAuthorCache.set(row.seq, set);
|
|
14332
|
+
}
|
|
14333
|
+
return set;
|
|
14334
|
+
};
|
|
14335
|
+
const reasonCache = new Map;
|
|
14336
|
+
const reasonsOf = (row) => {
|
|
14337
|
+
let reasons = reasonCache.get(row);
|
|
14338
|
+
if (!reasons) {
|
|
14339
|
+
const failing = FAILING.has(String(row.result));
|
|
14340
|
+
reasons = [
|
|
14341
|
+
...trustReasons(cwd, row),
|
|
14342
|
+
...independenceReasons(row, failing ? failureAuthors(row) : authors)
|
|
14343
|
+
];
|
|
14344
|
+
reasonCache.set(row, reasons);
|
|
14345
|
+
}
|
|
14346
|
+
return reasons;
|
|
14347
|
+
};
|
|
14348
|
+
const retired = new Set;
|
|
14349
|
+
for (const row of rows)
|
|
14350
|
+
if (row.type === "verdict" && row.supersedes && !row.supersedeIgnored && trustReasons(cwd, row).length === 0)
|
|
14351
|
+
retired.add(row.supersedes);
|
|
14159
14352
|
const byKind = new Map;
|
|
14160
14353
|
for (const row of rows)
|
|
14161
|
-
if (isLiveVerdict(row) && scope(row)) {
|
|
14354
|
+
if (isLiveVerdict(row, retired) && scope(row)) {
|
|
14162
14355
|
const kind = typeof row.kind === "string" ? row.kind : "review";
|
|
14163
14356
|
byKind.set(kind, [...byKind.get(kind) ?? [], row]);
|
|
14164
14357
|
}
|
|
14165
14358
|
const verdicts = [];
|
|
14166
14359
|
const warnings = [];
|
|
14167
14360
|
for (const [kind, list] of byKind) {
|
|
14168
|
-
const row = effectiveOf(list,
|
|
14361
|
+
const row = effectiveOf(list, reasonsOf, sameCode, (candidate) => basisOf(candidate) !== "stale");
|
|
14169
14362
|
if (!row)
|
|
14170
14363
|
continue;
|
|
14171
14364
|
const basis = basisOf(row);
|
|
14172
14365
|
const current = basis !== "stale";
|
|
14173
|
-
const independence =
|
|
14366
|
+
const independence = reasonsOf(row);
|
|
14174
14367
|
const reasons = [
|
|
14175
14368
|
...current ? [] : ["stale"],
|
|
14176
14369
|
...PASSING.has(String(row.result)) ? [] : ["not_passing"],
|
|
@@ -14187,23 +14380,31 @@ function checkVerdicts(cwd, branch, rows) {
|
|
|
14187
14380
|
});
|
|
14188
14381
|
if (reasons.length === 0) {
|
|
14189
14382
|
for (const later of list)
|
|
14190
|
-
if (later.seq > row.seq &&
|
|
14383
|
+
if (later.seq > row.seq && reasonsOf(later).length > 0 && !untrusted(reasonsOf(later)) && FAILING.has(String(later.result)) && basisOf(later) !== "stale")
|
|
14191
14384
|
warnings.push(`the author's own (self) ${String(later.result)} ${kind} verdict ${later.id} by ${later.actor.session ?? "no session"} on the current code is newer than the accepted pass ${row.id}: read it before merging`);
|
|
14192
14385
|
}
|
|
14193
14386
|
}
|
|
14194
14387
|
for (const row of rows)
|
|
14195
14388
|
if (row.integrity === "unverified" && scope(row))
|
|
14196
14389
|
warnings.push(`ledger row ${row.id} (${row.type}) on ${row.branch ?? branch} is unverified: the workit CLI did not write it or it changed afterwards (workit ledger verify-integrity)`);
|
|
14390
|
+
const tampered = ledgerTampering(cwd);
|
|
14391
|
+
if (tampered.length) {
|
|
14392
|
+
warnings.push(tamperedWarning(cwd, tampered));
|
|
14393
|
+
for (const entry of verdicts) {
|
|
14394
|
+
entry.accepted = false;
|
|
14395
|
+
entry.reasons = [...entry.reasons, "ledger_tampered"];
|
|
14396
|
+
}
|
|
14397
|
+
}
|
|
14197
14398
|
verdicts.sort((a, b) => a.verdict.seq - b.verdict.seq);
|
|
14198
14399
|
const newestCurrent = verdicts.findLast((entry) => entry.current) ?? verdicts.at(-1) ?? null;
|
|
14199
14400
|
const failing = verdicts.find((entry) => entry.current && entry.independent && FAILING.has(String(entry.verdict.result)));
|
|
14200
14401
|
const newestAccepted = verdicts.findLast((entry) => entry.accepted && STRONG_RESULTS.has(String(entry.verdict.result))) ?? null;
|
|
14201
|
-
const accepted = newestAccepted && !failing ? { accepted: true, verdict: newestAccepted.verdict, reasons: [] } : {
|
|
14402
|
+
const accepted = tampered.length ? { accepted: false, verdict: null, reasons: ["ledger_tampered"] } : newestAccepted && !failing ? { accepted: true, verdict: newestAccepted.verdict, reasons: [] } : {
|
|
14202
14403
|
accepted: false,
|
|
14203
14404
|
verdict: failing?.verdict ?? null,
|
|
14204
14405
|
reasons: failing ? ["failing_verdict"] : !newestCurrent ? ["no_verdict"] : newestCurrent.reasons.length ? newestCurrent.reasons : ["type_check_only"]
|
|
14205
14406
|
};
|
|
14206
|
-
const selfEntry = failing ? undefined : verdicts.findLast((entry) => entry.current && !entry.independent && STRONG_RESULTS.has(String(entry.verdict.result)));
|
|
14407
|
+
const selfEntry = failing ? undefined : verdicts.findLast((entry) => entry.current && !entry.independent && !untrusted(entry.reasons) && STRONG_RESULTS.has(String(entry.verdict.result)));
|
|
14207
14408
|
return {
|
|
14208
14409
|
branch,
|
|
14209
14410
|
head,
|
|
@@ -19485,7 +19686,7 @@ var refusal = (action, branch, dir) => {
|
|
|
19485
19686
|
return `workit: raw ${quote(action.via === "api" ? `${action.forge} api` : action.forge === "gh" ? "gh pr merge" : "glab mr merge")}${action.via === "api" ? " merge" : ""} bypasses the merge grant and the verdict gate. Run ${quote("workit pr merge")} instead (it checks both; ${quote("workit help pr")}).`;
|
|
19486
19687
|
const plainPush = action.kind === "push" && !action.blindForce && !action.deletes;
|
|
19487
19688
|
if (dir && branch && plainPush && releaseBranch(dir, branch))
|
|
19488
|
-
return `workit: raw ${quote("git push")} onto ${branch}, a protected release branch, bypasses workit's protected-branch check. To finish a release or hotfix, push it with ${quote(`workit git push --release ${branch} [--release
|
|
19689
|
+
return `workit: raw ${quote("git push")} onto ${branch}, a protected release branch, bypasses workit's protected-branch check. To finish a release or hotfix, push it with ${quote(`workit git push --release ${branch} [--release ${releaseTagPrefixOf(dir)}<version>]`)}: it pushes ${branch} (fast-forward only) and the release tag, verifies the remote and records it, under the ${quote("release")} grant (if refused, ask the user to run ${quote("workit grant set <workspace> release=true")}). Feature work goes through ${quote("workit git push")} and ${quote("workit pr merge")}.`;
|
|
19489
19690
|
return `workit: raw ${quote("git push")}${action.kind === "push" && action.blindForce ? " --force" : ""} onto ${branch}, a protected branch or the default target, bypasses workit's protected-branch check. Push your feature branch with ${quote("workit git push")} (${quote("workit git push --force-with-lease")} to rewrite it) and land it with ${quote("workit pr merge")}.`;
|
|
19490
19691
|
};
|
|
19491
19692
|
var refFacts = (repo, remote) => {
|
|
@@ -19572,15 +19773,16 @@ var guardedTarget = (dir, target) => {
|
|
|
19572
19773
|
return null;
|
|
19573
19774
|
};
|
|
19574
19775
|
var isTag = (facts, name) => facts.tags.has(name) && !facts.heads.has(name);
|
|
19575
|
-
var tagRefusal = (action, facts) => {
|
|
19776
|
+
var tagRefusal = (action, facts, dir) => {
|
|
19576
19777
|
const tags = [
|
|
19577
19778
|
...action.tags ?? [],
|
|
19578
19779
|
...action.targets.filter((name) => name !== null && isTag(facts, name))
|
|
19579
19780
|
];
|
|
19580
19781
|
if (!tags.length && !action.allTags)
|
|
19581
19782
|
return null;
|
|
19582
|
-
const
|
|
19583
|
-
|
|
19783
|
+
const shape = `${releaseTagPrefixOf(dir)}<version>`;
|
|
19784
|
+
const named = tags.length ? tags.join(" --release ") : shape;
|
|
19785
|
+
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")}).`;
|
|
19584
19786
|
};
|
|
19585
19787
|
var remoteHeads = (repo, remote) => {
|
|
19586
19788
|
const listed = spawnSync11("git", ["ls-remote", "--heads", remote], {
|
|
@@ -19646,7 +19848,7 @@ var pushRefusal = (action, repo, config) => {
|
|
|
19646
19848
|
if (guarded)
|
|
19647
19849
|
return refusal(action, guarded, repo.dir);
|
|
19648
19850
|
}
|
|
19649
|
-
return tagRefusal(pushed, facts) ?? (action.deletes ? null : offPolicyPush(pushed, repo, facts, remote));
|
|
19851
|
+
return tagRefusal(pushed, facts, repo.dir) ?? (action.deletes ? null : offPolicyPush(pushed, repo, facts, remote));
|
|
19650
19852
|
};
|
|
19651
19853
|
var actionsIn = (cwd, command, dialect, cache = new Map) => {
|
|
19652
19854
|
const out = [];
|
|
@@ -20302,10 +20504,10 @@ function stopObligation(input, descriptor) {
|
|
|
20302
20504
|
const branch = currentBranch(input.cwd);
|
|
20303
20505
|
if (!branch || guardedBranch(input.cwd, branch))
|
|
20304
20506
|
return null;
|
|
20305
|
-
const ledger = readLedger(input.cwd);
|
|
20507
|
+
const ledger = readLedger(input.cwd, { integrity: true });
|
|
20306
20508
|
if (!ledger.ok)
|
|
20307
20509
|
return null;
|
|
20308
|
-
const all = ledger.value.rows;
|
|
20510
|
+
const all = ledger.value.rows.filter((row) => row.integrity !== "unverified");
|
|
20309
20511
|
const rows = all.filter((row) => row.branch === branch);
|
|
20310
20512
|
const mine = rows.filter((row) => sessionOf(row) === session);
|
|
20311
20513
|
const commits = new Set(mine.filter((row) => row.type === "commit.recorded").map((row) => typeof row.sha === "string" ? row.sha : row.head).filter((sha) => typeof sha === "string"));
|
|
@@ -20681,9 +20883,9 @@ ${subagentStartText({ ...input, cwd: "" }, descriptor, event)}`;
|
|
|
20681
20883
|
if (role !== "judge" || !event.agentId)
|
|
20682
20884
|
return readOnly;
|
|
20683
20885
|
const own = subagentSession(session, event.agentId);
|
|
20684
|
-
|
|
20685
|
-
|
|
20686
|
-
return `${readOnly} Its own Workit session is ${own}: record verdicts with \`workit ledger verdict <result>
|
|
20886
|
+
const token = session ? recordIssuedSession(input.cwd || process.cwd(), session, own) : null;
|
|
20887
|
+
const flags = token ? `--session ${own} --token ${token}` : `--session ${own}`;
|
|
20888
|
+
return `${readOnly} Its own Workit session is ${own}: record verdicts with \`workit ledger verdict <result> ${flags} ...\` so the ledger tells it apart from the author.${token ? " The token is this subagent's alone: never write it into a report, a file or a commit. --token needs a workit CLI at least as new as this plugin; if the CLI answers that --token is an unknown option, update the workit CLI (workit upgrade --cli) and record again." : ""}`;
|
|
20687
20889
|
};
|
|
20688
20890
|
function handleHook(input, deps) {
|
|
20689
20891
|
const { descriptor } = deps;
|
package/dist/workit-hook.js
CHANGED
|
@@ -37,6 +37,6 @@ if (quiet)
|
|
|
37
37
|
process.stdout.write(`${JSON.stringify({ hookSpecificOutput: { hookEventName: quiet } })}
|
|
38
38
|
`);
|
|
39
39
|
else {
|
|
40
|
-
const { runCodexHook } = await import("./workit-hook-
|
|
40
|
+
const { runCodexHook } = await import("./workit-hook-j1zd509p.js");
|
|
41
41
|
await runCodexHook([payload]);
|
|
42
42
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@brainervirus/workit-codex",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.13.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Workit Codex CLI/desktop plugin — shared MCP transport, hooks, method skills, and verifier/reviewer/implementer agents",
|
|
6
6
|
"keywords": [
|
|
@@ -38,9 +38,9 @@
|
|
|
38
38
|
"scripts": {
|
|
39
39
|
"build": "bun scripts/build.ts"
|
|
40
40
|
},
|
|
41
|
-
"
|
|
42
|
-
"@brainervirus/workit-core": "^8.
|
|
43
|
-
"@brainervirus/workit-mcp": "^8.
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@brainervirus/workit-core": "^8.13.1",
|
|
43
|
+
"@brainervirus/workit-mcp": "^8.13.1"
|
|
44
44
|
},
|
|
45
45
|
"engines": {
|
|
46
46
|
"node": ">=24"
|
|
@@ -72,11 +72,12 @@ Which session id a verdict carries, per host:
|
|
|
72
72
|
|
|
73
73
|
- Claude Code: subagents inherit your `WORKIT_SESSION_ID`, so the
|
|
74
74
|
SubagentStart hook names each `verifier` and `reviewer` its own session
|
|
75
|
-
(`<lead>:<agent id>`)
|
|
76
|
-
|
|
75
|
+
(`<lead>:<agent id>`) and a token only that subagent sees, passed exactly as
|
|
76
|
+
`--session` and `--token`: the hook records the ids it issues, and only
|
|
77
|
+
those, with their token, are accepted. An `implementer` is issued
|
|
77
78
|
none: it keeps your id in its env for its workit commands and commits.
|
|
78
79
|
- Codex: the `workit-verifier` and `workit-reviewer` agents get the same
|
|
79
|
-
issued `--session` from SubagentStart; any other verifier passes
|
|
80
|
+
issued `--session` and `--token` from SubagentStart; any other verifier passes
|
|
80
81
|
`--as verifier`.
|
|
81
82
|
- OpenCode: a child session's shell already has
|
|
82
83
|
`WORKIT_SESSION_ID=<lead>:<child>`; Pi: a supervised worker's shell has
|
|
@@ -8,7 +8,7 @@ description: Independent review of a diff, branch or PR - intent fidelity and st
|
|
|
8
8
|
Review the candidate, never the author's summary of it. A session that wrote a
|
|
9
9
|
commit on the branch cannot record a passing verdict: if that is you, hand the
|
|
10
10
|
review to a fresh agent with its own host-derived session: Claude Code and
|
|
11
|
-
Codex, the `reviewer` or `verifier` agent (`--session` from SubagentStart);
|
|
11
|
+
Codex, the `reviewer` or `verifier` agent (`--session` and `--token` from SubagentStart);
|
|
12
12
|
OpenCode, a child session; Pi, a supervised worker. Never hand-mint one such
|
|
13
13
|
as `<lead>-v1`: it is refused as `forged_session`.
|
|
14
14
|
An author's `--self` verdict reads self-reviewed and never counts as
|