@feigi/fleet-ctl 3.90.0 → 3.91.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/package.json +1 -1
- package/scripts/board-gather.mjs +5 -4
- package/scripts/ledger-grammar.mjs +23 -13
- package/scripts/ledger.mjs +108 -27
- package/scripts/phase0.mjs +13 -7
- package/skills/run-team/references/ledger.md +2 -2
- package/skills/run-team/references/phase-0.md +6 -1
- package/skills/run-team/references/pull.md +5 -1
package/package.json
CHANGED
package/scripts/board-gather.mjs
CHANGED
|
@@ -412,10 +412,11 @@ export function mapCi(ciJson, pr) {
|
|
|
412
412
|
// A receipt with no session to give is `{ error }`, which the panel shows:
|
|
413
413
|
// the board names no session rather than guessing one. So is a recorded
|
|
414
414
|
// session whose NAME is not an omp `<ISO>_<uuid>` session directory:
|
|
415
|
-
// phase0.mjs
|
|
416
|
-
//
|
|
417
|
-
//
|
|
418
|
-
//
|
|
415
|
+
// phase0.mjs refuses a directory with no `<dir>.jsonl` beside it, but a
|
|
416
|
+
// receipt it wrote before that refusal, or one written by hand, can record the
|
|
417
|
+
// encoded-cwd project dir instead of its own session child, and readOmpSpend
|
|
418
|
+
// would book every session under it — the controller's own main-session
|
|
419
|
+
// transcript included — as this run's agents.
|
|
419
420
|
// A recorded directory that does not exist yet is not an error — that is
|
|
420
421
|
// gatherSpend()'s quiet `null`, below.
|
|
421
422
|
export function receiptSpendDir(stateDir) {
|
|
@@ -429,6 +429,27 @@ export function rowWriteRefusal(before, after, oldLine, line, retired = []) {
|
|
|
429
429
|
return null;
|
|
430
430
|
}
|
|
431
431
|
|
|
432
|
+
/**
|
|
433
|
+
* The name of every member the ledger holds settled: a settled copy in
|
|
434
|
+
* `## Dispatched` or on a row settles it, except that a PR-bound member's
|
|
435
|
+
* settled copy counts only on its own PR's row. Malformed tokens are the
|
|
436
|
+
* caller's to have refused: one here is read as nothing.
|
|
437
|
+
*/
|
|
438
|
+
export function settledOnLedger(rows, dispatched) {
|
|
439
|
+
const settled = new Set();
|
|
440
|
+
for (const e of dispatched) {
|
|
441
|
+
const t = parseToken(e);
|
|
442
|
+
if (t !== null && t.error === null && t.outcome !== null) settled.add(t.name);
|
|
443
|
+
}
|
|
444
|
+
for (const text of rows) {
|
|
445
|
+
const { pr } = rowNums(text);
|
|
446
|
+
for (const t of memberTokens(text)) {
|
|
447
|
+
if (t.error === null && t.outcome !== null && (t.bound !== "pr" || t.number === pr)) settled.add(t.name);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
return settled;
|
|
451
|
+
}
|
|
452
|
+
|
|
432
453
|
/**
|
|
433
454
|
* What a ledger still holds live, and what must travel with it. `tokens` is
|
|
434
455
|
* every unsettled member token and every open review launch, once each, in
|
|
@@ -442,25 +463,14 @@ export function rowWriteRefusal(before, after, oldLine, line, retired = []) {
|
|
|
442
463
|
* keep a bare token from reading live again. `dispatched` is every
|
|
443
464
|
* `## Dispatched` entry whose member is unsettled, plus every settled entry
|
|
444
465
|
* naming a member a kept row carries, for the same reason. A member is
|
|
445
|
-
* settled
|
|
446
|
-
* PR-bound member's settled copy counts only on its own PR's row — the reading
|
|
466
|
+
* settled as settledOnLedger() reads it — the reading
|
|
447
467
|
* fleet-tick.mjs folds liveness by, so nothing that fold calls live is left
|
|
448
468
|
* out. A launch is open while unanswered and not `=failed`, paired across its
|
|
449
469
|
* PR's rows as `rowWriteRefusal` pairs them. Malformed tokens are the caller's
|
|
450
470
|
* to have refused: one here is read as nothing.
|
|
451
471
|
*/
|
|
452
472
|
export function liveOnLedger(rows, dispatched) {
|
|
453
|
-
const settled =
|
|
454
|
-
for (const e of dispatched) {
|
|
455
|
-
const t = parseToken(e);
|
|
456
|
-
if (t !== null && t.error === null && t.outcome !== null) settled.add(t.name);
|
|
457
|
-
}
|
|
458
|
-
for (const text of rows) {
|
|
459
|
-
const { pr } = rowNums(text);
|
|
460
|
-
for (const t of memberTokens(text)) {
|
|
461
|
-
if (t.error === null && t.outcome !== null && (t.bound !== "pr" || t.number === pr)) settled.add(t.name);
|
|
462
|
-
}
|
|
463
|
-
}
|
|
473
|
+
const settled = settledOnLedger(rows, dispatched);
|
|
464
474
|
const live = (t) => t !== null && t.error === null && t.outcome === null && !settled.has(t.name);
|
|
465
475
|
const open = new Map();
|
|
466
476
|
for (const [id, tokens] of streams(rows)) {
|
package/scripts/ledger.mjs
CHANGED
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
// that.
|
|
6
6
|
//
|
|
7
7
|
// Rows are rewritten in place, one per ticket: `row` replaces a whole line
|
|
8
|
-
// and `token` appends tokens to one
|
|
8
|
+
// and `token` appends tokens to one, or replaces its `tier=`, which it refuses
|
|
9
|
+
// while the row's implementer has not settled. `filed` and `ruled` are
|
|
9
10
|
// append-only, because their whole purpose is to outlive the reasoning that
|
|
10
11
|
// produced them; `filed` appends at most one row per issue.
|
|
11
12
|
//
|
|
@@ -55,7 +56,7 @@ import { fileURLToPath } from "node:url";
|
|
|
55
56
|
import { spawnSync, execFileSync } from "node:child_process";
|
|
56
57
|
import { makeDie, isFlagLike, hasEqualsForm, isDigits, writeAll } from "./arg.mjs";
|
|
57
58
|
import { fleetFile, FleetDirUnresolvable } from "./fleet-dir.mjs";
|
|
58
|
-
import { parseMember, parseToken, memberTokens, nextMergeBot, nextRetry, liveOnLedger, retiredOnLedger, retiredName, readRetired, agentDefinition, CELL, tierValues, rowPr, rowWriteRefusal, isReviewToken, addedReviewToken, newSplitRow, splitRowClause, added, premisesOf, MEMBER_FAMILIES, HALT_CAUSES, ROWS, DISPATCHED, FILED, RULED, DRAIN, RETIRED, RECEIPT, archiveStamp, receiptArchiveName, CLOSEOUT_RECEIPT, closeoutArchiveName, BRIEFS, briefsArchiveName, memberRole, parseDispatchedEntry, dispatchedEntry, headerRe, escapeText, unescapeText, nextReviewerName } from "./ledger-grammar.mjs";
|
|
59
|
+
import { parseMember, parseToken, memberTokens, nextMergeBot, nextRetry, liveOnLedger, settledOnLedger, retiredOnLedger, retiredName, readRetired, agentDefinition, expectedDefinition, CELL, POLICY_CELL, tierValues, rowPr, rowWriteRefusal, isReviewToken, addedReviewToken, newSplitRow, splitRowClause, added, premisesOf, MEMBER_FAMILIES, HALT_CAUSES, ROWS, DISPATCHED, FILED, RULED, DRAIN, RETIRED, RECEIPT, archiveStamp, receiptArchiveName, CLOSEOUT_RECEIPT, closeoutArchiveName, BRIEFS, briefsArchiveName, memberRole, parseDispatchedEntry, dispatchedEntry, headerRe, escapeText, unescapeText, nextReviewerName } from "./ledger-grammar.mjs";
|
|
59
60
|
import { deriveRun, LedgerError, labelOffMember, HELD, CONFLICT_HOLD, PARKED } from "./fleet-tick.mjs";
|
|
60
61
|
import { stateFileIn, readState, writeState, assessBeat, stallsAt } from "./fleet-state.mjs";
|
|
61
62
|
import { processSource, ancestry, controllerOf, judgeController, ProcUnreadable } from "./proc.mjs";
|
|
@@ -760,6 +761,13 @@ if (cmd === "read") {
|
|
|
760
761
|
// only be refused later, by `dispatch`, after the claim.
|
|
761
762
|
const badTier = tierValues(line).find((v) => !CELL.test(v));
|
|
762
763
|
if (badTier !== undefined) die(`row ${key}: 'tier=${badTier}' is not tier=<cell> — a cell is <slow|task|smol>-<level>`);
|
|
764
|
+
// A Pull's cell binds its implementer until that member settles; see
|
|
765
|
+
// cellChangeRefusal(). A new row binds nothing yet.
|
|
766
|
+
const i = data.rows.findIndex((r) => r.split(/\s/)[0] === key);
|
|
767
|
+
const created = i === -1;
|
|
768
|
+
const oldRow = created ? null : data.rows[i];
|
|
769
|
+
const cellWhy = cellChangeRefusal(oldRow, line);
|
|
770
|
+
if (cellWhy !== null) die(`row ${key}: ${cellWhy}`);
|
|
763
771
|
// `label-off=<attempt>` tells the tick a missing `ready-to-merge` is
|
|
764
772
|
// the controller's own removal, not a finisher's miss — so one naming no
|
|
765
773
|
// finisher-pr member this run has would mask a real miss behind a record of
|
|
@@ -768,9 +776,6 @@ if (cmd === "read") {
|
|
|
768
776
|
if (badOff !== undefined) {
|
|
769
777
|
die(`row ${key}: '${badOff}' ${LABEL_OFF_UNKNOWN}`);
|
|
770
778
|
}
|
|
771
|
-
const i = data.rows.findIndex((r) => r.split(/\s/)[0] === key);
|
|
772
|
-
const created = i === -1;
|
|
773
|
-
const oldRow = created ? null : data.rows[i];
|
|
774
779
|
const after = created ? [...data.rows, line] : data.rows.with(i, line);
|
|
775
780
|
// Every review token is `review`'s to write, onto the PR's row in write
|
|
776
781
|
// order, so `row` adds none; one the row already holds is carried.
|
|
@@ -2845,17 +2850,77 @@ function badLabelOff(words) {
|
|
|
2845
2850
|
});
|
|
2846
2851
|
}
|
|
2847
2852
|
|
|
2848
|
-
//
|
|
2849
|
-
//
|
|
2850
|
-
//
|
|
2851
|
-
//
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2853
|
+
// The cell a row's implementer runs at — its `tier=`, or POLICY_CELL on a row
|
|
2854
|
+
// with none — read the way `dispatch` reads it: null for a row whose `tier=`
|
|
2855
|
+
// values conflict or name no agents/<definition>.agent.md, which `dispatch`
|
|
2856
|
+
// refuses, so such a row has no draw to bind.
|
|
2857
|
+
function runnableCell(rowText) {
|
|
2858
|
+
let definition;
|
|
2859
|
+
try {
|
|
2860
|
+
definition = expectedDefinition(rowText);
|
|
2861
|
+
} catch {
|
|
2862
|
+
return null;
|
|
2863
|
+
}
|
|
2864
|
+
if (!existsSync(join(AGENTS_DIR, `${definition}.agent.md`))) return null;
|
|
2865
|
+
return tierValues(rowText)[0] ?? POLICY_CELL;
|
|
2866
|
+
}
|
|
2867
|
+
|
|
2868
|
+
// The member a row's cell binds, while it has not settled, else null: the
|
|
2869
|
+
// newest implementer for the row's ticket, on the row or in `## Dispatched`,
|
|
2870
|
+
// newest by retry suffix as nextRetry() orders them. That is the Pull's first
|
|
2871
|
+
// implementer on an ordinary row, and a resumed or re-Pulled row's latest.
|
|
2872
|
+
// Not settled is live and not yet dispatched alike: a bare token on the row
|
|
2873
|
+
// binds as a bare `## Dispatched` entry does.
|
|
2874
|
+
function unsettledImplementer(rowText) {
|
|
2875
|
+
const ticket = Number(rowKey(rowText).slice(1));
|
|
2876
|
+
const newest = [...memberTokens(rowText), ...data.dispatched.map(parseToken)]
|
|
2877
|
+
.filter((t) => t !== null && t.error === null && t.family === "impl" && t.number === ticket)
|
|
2878
|
+
.map((t) => ({ name: t.name, attempt: t.retry ?? "a" }))
|
|
2879
|
+
.sort((a, b) => (a.attempt < b.attempt ? -1 : a.attempt > b.attempt ? 1 : 0))
|
|
2880
|
+
.at(-1);
|
|
2881
|
+
if (newest === undefined || settledOnLedger(data.rows, data.dispatched).has(newest.name)) return null;
|
|
2882
|
+
return newest.name;
|
|
2883
|
+
}
|
|
2884
|
+
|
|
2885
|
+
// Why a write turning row `oldLine` into `line` may not land, or null when it
|
|
2886
|
+
// may: a Pull's cell binds its implementer, so a write that changes the
|
|
2887
|
+
// row's cell is refused until that member has settled, when a replacement
|
|
2888
|
+
// may run at another cell. A new row, a row with no runnable definition and
|
|
2889
|
+
// a write that leaves the cell as it was bind nothing.
|
|
2890
|
+
function cellChangeRefusal(oldLine, line) {
|
|
2891
|
+
if (oldLine === null) return null;
|
|
2892
|
+
const cell = runnableCell(oldLine);
|
|
2893
|
+
if (cell === null) return null;
|
|
2894
|
+
const tiers = [...new Set(tierValues(line))];
|
|
2895
|
+
const to = tiers.length === 0 ? POLICY_CELL : tiers.join(" and ");
|
|
2896
|
+
if (to === cell) return null;
|
|
2897
|
+
const member = unsettledImplementer(oldLine);
|
|
2898
|
+
if (member === null) return null;
|
|
2899
|
+
return `changes the Pull's cell from ${cell} to ${to} while ${member} has not settled — the cell binds its implementer; once ${member} has settled, \`ledger.mjs token ${rowKey(oldLine).slice(1)} tier=<cell>\` sets a replacement's cell`;
|
|
2900
|
+
}
|
|
2901
|
+
|
|
2902
|
+
// `line` with `tok` in place of its first `tier=` word, every other `tier=`
|
|
2903
|
+
// word dropped with the ` · ` before it.
|
|
2904
|
+
function withTier(line, tok) {
|
|
2905
|
+
let placed = false;
|
|
2906
|
+
return line.replace(/(\s+·)?\s+tier=\S*/g, (m) => {
|
|
2907
|
+
if (placed) return "";
|
|
2908
|
+
placed = true;
|
|
2909
|
+
return m.replace(/tier=\S*$/, () => tok);
|
|
2910
|
+
});
|
|
2911
|
+
}
|
|
2912
|
+
|
|
2913
|
+
// Why `token` may not write `tok`, or null when it may; `earlier` is the
|
|
2914
|
+
// call's tokens before it. A token is one word: an empty argument, one with
|
|
2915
|
+
// a space in it (prose, which would split into words the row's own words
|
|
2916
|
+
// then dedupe against), and the row's ` · ` separator are no token. A member
|
|
2917
|
+
// token is `dispatch`'s or `settle`'s, a review token `review`'s. A word
|
|
2918
|
+
// shaped like a member that the grammar cannot parse, a `tier=` that is not
|
|
2919
|
+
// a cell, a `tier=` naming a second tier in one call, a `held-behind`,
|
|
2920
|
+
// `conflict-hold` or `parked` token the tick cannot read, and a lone `→` — an
|
|
2921
|
+
// arrow with no PR beside it points at nothing — are refused. Every other
|
|
2922
|
+
// word is a freeform token.
|
|
2923
|
+
function tokenRefusal(tok, earlier) {
|
|
2859
2924
|
if (tok === "" || /\s/.test(tok)) {
|
|
2860
2925
|
return "is not one token — pass each token as its own argument, with no space in it";
|
|
2861
2926
|
}
|
|
@@ -2881,9 +2946,9 @@ function tokenRefusal(tok, rowText) {
|
|
|
2881
2946
|
const [tier] = tierValues(tok);
|
|
2882
2947
|
if (tier !== undefined) {
|
|
2883
2948
|
if (!CELL.test(tier)) return "is not tier=<cell> — a cell is <slow|task|smol>-<level>";
|
|
2884
|
-
const other = tierValues(
|
|
2949
|
+
const other = tierValues(earlier).find((v) => v !== tier);
|
|
2885
2950
|
if (other !== undefined) {
|
|
2886
|
-
return `names a second tier —
|
|
2951
|
+
return `names a second tier — this call already names tier=${other}, and a row names one`;
|
|
2887
2952
|
}
|
|
2888
2953
|
}
|
|
2889
2954
|
if (tok === "→") return "is the row's arrow, which is written with the PR it points at — ledger.mjs row writes the whole line";
|
|
@@ -2895,8 +2960,11 @@ function tokenRefusal(tok, rowText) {
|
|
|
2895
2960
|
// — the PR's row for a PR key, the ticket row once `settle impl-<N>=PR#M` has
|
|
2896
2961
|
// run, else `#M`, created when neither exists: `review`'s lookup — keeping
|
|
2897
2962
|
// every token already there, in order, as ` · <token>`. A token the row
|
|
2898
|
-
// already carries is a no-op.
|
|
2899
|
-
//
|
|
2963
|
+
// already carries is a no-op. A `tier=` replaces the row's: in place of its
|
|
2964
|
+
// first `tier=`, dropping any other, refused by cellChangeRefusal() while the
|
|
2965
|
+
// row's implementer has not settled; it is a no-op only on a row whose one
|
|
2966
|
+
// tier it already is. Every refusal is judged before anything is written,
|
|
2967
|
+
// whole call alike, under the lock.
|
|
2900
2968
|
function runToken() {
|
|
2901
2969
|
const usage = "usage: ledger.mjs token <ticket|pr> <token>…";
|
|
2902
2970
|
const [keyArg, ...tokens] = rest;
|
|
@@ -2905,20 +2973,23 @@ function runToken() {
|
|
|
2905
2973
|
const i = prRowIndex(num);
|
|
2906
2974
|
const created = i === -1;
|
|
2907
2975
|
const oldLine = created ? null : data.rows[i];
|
|
2908
|
-
let
|
|
2976
|
+
let earlier = "";
|
|
2909
2977
|
for (const tok of tokens) {
|
|
2910
|
-
const why = tokenRefusal(tok,
|
|
2978
|
+
const why = tokenRefusal(tok, earlier);
|
|
2911
2979
|
if (why !== null) die(`token #${num}: '${tok}' ${why}`);
|
|
2912
|
-
|
|
2980
|
+
earlier += ` ${tok}`;
|
|
2913
2981
|
}
|
|
2914
2982
|
const key = created ? `#${num}` : rowKey(oldLine);
|
|
2915
2983
|
const have = new Set(created ? [] : rowTokens(oldLine));
|
|
2984
|
+
let tiers = [...new Set(tierValues(oldLine))];
|
|
2916
2985
|
const appended = [];
|
|
2917
2986
|
for (const tok of tokens) {
|
|
2918
|
-
|
|
2987
|
+
const [tier] = tierValues(tok);
|
|
2988
|
+
if (tier === undefined ? have.has(tok) : tiers.length === 1 && tiers[0] === tier) {
|
|
2919
2989
|
console.error(` already present on row ${key}: ${tok}`);
|
|
2920
2990
|
} else {
|
|
2921
|
-
have.add(tok);
|
|
2991
|
+
if (tier === undefined) have.add(tok);
|
|
2992
|
+
else tiers = [tier];
|
|
2922
2993
|
appended.push(tok);
|
|
2923
2994
|
}
|
|
2924
2995
|
}
|
|
@@ -2926,7 +2997,16 @@ function runToken() {
|
|
|
2926
2997
|
console.log(JSON.stringify({ key, line: oldLine, added: appended }));
|
|
2927
2998
|
return;
|
|
2928
2999
|
}
|
|
2929
|
-
const
|
|
3000
|
+
const tierTok = appended.find((tok) => tierValues(tok).length > 0);
|
|
3001
|
+
const replaced = tierTok === undefined ? [] : tierValues(oldLine).map((v) => `tier=${v}`);
|
|
3002
|
+
// `appended` holds at most one `tier=`: tokenRefusal() refuses a second
|
|
3003
|
+
// tier in one call, and the loop above drops a repeat of it.
|
|
3004
|
+
const others = replaced.length > 0 ? appended.filter((tok) => tok !== tierTok) : appended;
|
|
3005
|
+
const line = created
|
|
3006
|
+
? `${key} ${appended.join(" · ")}`
|
|
3007
|
+
: [replaced.length > 0 ? withTier(oldLine, tierTok) : oldLine, ...others].join(" · ");
|
|
3008
|
+
const cellWhy = cellChangeRefusal(oldLine, line);
|
|
3009
|
+
if (cellWhy !== null) die(`token ${key}: ${cellWhy}`);
|
|
2930
3010
|
const after = created ? [...data.rows, line] : data.rows.with(i, line);
|
|
2931
3011
|
// A freeform token can still move the PR a row maps to (a `PR#<n>` mention
|
|
2932
3012
|
// on a row with no implementer), or split one PR across two rows.
|
|
@@ -2940,6 +3020,7 @@ function runToken() {
|
|
|
2940
3020
|
if (refusal !== null) die(`token ${key}: ${refusal}`);
|
|
2941
3021
|
data.rows = after;
|
|
2942
3022
|
save(data);
|
|
2943
|
-
console.error(`
|
|
3023
|
+
if (replaced.length > 0) console.error(` replaced ${replaced.join(" ")} with ${tierTok} on row ${key}`);
|
|
3024
|
+
if (others.length > 0) console.error(` appended ${others.join(" ")} to row ${key}`);
|
|
2944
3025
|
console.log(JSON.stringify({ key, line, added: appended }));
|
|
2945
3026
|
}
|
package/scripts/phase0.mjs
CHANGED
|
@@ -8,9 +8,11 @@
|
|
|
8
8
|
// The session directory is `--session`, else `PI_ARTIFACTS_DIR`, which omp
|
|
9
9
|
// exports to its Python eval kernel and the processes that kernel starts (the
|
|
10
10
|
// bash tool's environment does not carry it); with neither, exit 2. It is
|
|
11
|
-
// accepted when it is
|
|
12
|
-
//
|
|
13
|
-
// writes there.
|
|
11
|
+
// accepted when `<dir>.jsonl` exists beside it and the path is a directory or
|
|
12
|
+
// does not exist yet — omp creates the directory when the session's first
|
|
13
|
+
// subagent writes there. An existing directory with no `<dir>.jsonl` beside
|
|
14
|
+
// it names no session (the directory holding every session of a checkout is
|
|
15
|
+
// one): from either source it exits 2 naming the path, writing no receipt.
|
|
14
16
|
//
|
|
15
17
|
// Reached through the Resolver and run from the MAIN checkout (a linked
|
|
16
18
|
// worktree refuses: the checks below judge the checkout the controller reads
|
|
@@ -528,15 +530,19 @@ function main() {
|
|
|
528
530
|
}
|
|
529
531
|
const source = given === null ? "PI_ARTIFACTS_DIR" : "--session";
|
|
530
532
|
const session = resolve(given ?? fromEnv);
|
|
531
|
-
//
|
|
532
|
-
//
|
|
533
|
-
//
|
|
533
|
+
// A session is named by its transcript, `<session>.jsonl`, beside the
|
|
534
|
+
// directory: an existing directory with none beside it — the one holding
|
|
535
|
+
// every session of a checkout, say — names no session. The directory itself
|
|
536
|
+
// may not exist yet: omp creates it when the session's first subagent writes
|
|
537
|
+
// there.
|
|
538
|
+
const transcript = existsSync(`${session}.jsonl`);
|
|
534
539
|
try {
|
|
535
540
|
if (!statSync(session).isDirectory()) die(`${source} ${session} is not a directory`);
|
|
541
|
+
if (!transcript) die(`${source} ${session} has no ${session}.jsonl beside it, so it names no session`);
|
|
536
542
|
} catch (e) {
|
|
537
543
|
if (e.code === undefined) throw e;
|
|
538
544
|
if (e.code !== "ENOENT") die(`${source} ${session} is not readable (${e.code})`);
|
|
539
|
-
if (!
|
|
545
|
+
if (!transcript) die(`${source} ${session} does not exist, and no ${session}.jsonl beside it names a session`);
|
|
540
546
|
}
|
|
541
547
|
const positive = (name) => {
|
|
542
548
|
const n = F.numArg(name);
|
|
@@ -62,7 +62,7 @@ One git-ignored `.fleet/ledger.md` per run is updated at every state change thro
|
|
|
62
62
|
|
|
63
63
|
## One line per ticket
|
|
64
64
|
|
|
65
|
-
One line per ticket, rewritten in place (`ledger.mjs row <ticket> <text>`; `ledger.mjs token <key> <token>…` adds a token and keeps every token already on the row):
|
|
65
|
+
One line per ticket, rewritten in place (`ledger.mjs row <ticket> <text>`; `ledger.mjs token <key> <token>…` adds a token and keeps every token already on the row, and replaces the row's `tier=`, refused while its implementer has not settled):
|
|
66
66
|
|
|
67
67
|
```
|
|
68
68
|
#<t1> impl-<t1>=PR#<p1> → PR#<p1> → MERGED 73b356de
|
|
@@ -108,4 +108,4 @@ The ledger half reports its own readability as its own field, `ledger.ok`, the p
|
|
|
108
108
|
|
|
109
109
|
## Adding a token
|
|
110
110
|
|
|
111
|
-
Token additions go through `ledger.mjs token`. `ledger.mjs token <key> <token>…` adds a token and keeps every token already on the row. Re-typing the row with `row` replaces the whole line, so it drops the tokens you do not restate.
|
|
111
|
+
Token additions go through `ledger.mjs token`. `ledger.mjs token <key> <token>…` adds a token and keeps every token already on the row. A `tier=` replaces the row's `tier=` instead and keeps every other token; while the row's implementer has not settled, `token` and `row` both refuse a write that changes the row's cell. Re-typing the row with `row` replaces the whole line, so it drops the tokens you do not restate.
|
|
@@ -7,7 +7,12 @@ environment carries `PI_ARTIFACTS_DIR`, this run's own session directory,
|
|
|
7
7
|
which phase 0 records in its receipt; the bash tool's does not, so there
|
|
8
8
|
phase 0 exits 2. `expanduser` is required: a literal `~/…` fails with
|
|
9
9
|
`FileNotFoundError`. So is the `stderr` redirect: the cell never shows a
|
|
10
|
-
child's stderr. `--session <dir>` overrides the variable.
|
|
10
|
+
child's stderr. `--session <dir>` overrides the variable. Either way the
|
|
11
|
+
directory must have its transcript `<dir>.jsonl` beside it; the directory
|
|
12
|
+
itself may not exist yet. An existing directory with no `<dir>.jsonl` beside
|
|
13
|
+
it — `~/.omp/agent/sessions/<encoded-cwd>`, which holds every session of the
|
|
14
|
+
checkout — names no session: phase 0 exits 2 naming the path and where it
|
|
15
|
+
came from, and writes no receipt.
|
|
11
16
|
|
|
12
17
|
Exit 3 is a step that failed: its line carries the refusal, so fix the cause
|
|
13
18
|
and run it again.
|
|
@@ -434,7 +434,11 @@ true every Pull draws, over every cell, the policy cell included. An
|
|
|
434
434
|
Exploration Pull records `tier=<cell>` in the row, and a replacement inherits the row's tier —
|
|
435
435
|
written at the Pull's row step, ahead of `ledger.mjs dispatch`, which reads it
|
|
436
436
|
and prints `fleet-implementer-<cell>` as the `agent` to dispatch; a row with
|
|
437
|
-
no `tier=` runs the policy cell. The
|
|
437
|
+
no `tier=` runs the policy cell. The Pull's cell binds the row's implementer
|
|
438
|
+
(its newest, on a resumed or re-Pulled row):
|
|
439
|
+
`ledger.mjs row` and `ledger.mjs token` refuse a write that changes it until that implementer has
|
|
440
|
+
settled. Once it has, a replacement may run at another cell, set with
|
|
441
|
+
`ledger.mjs token <N> tier=<cell>`. The
|
|
438
442
|
assignment rolls to the next Pull when another open ticket sequences after
|
|
439
443
|
it — a ticket the rest of the run
|
|
440
444
|
depends on; `class` is not read. Do not tell the member it is a control: a member that
|