@feigi/fleet-ctl 3.82.18 → 3.82.20
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/agents/fleet-merge-bot.agent.md +6 -6
- package/package.json +1 -1
- package/scripts/ledger.mjs +38 -8
- package/skills/run-team/SKILL.md +177 -579
- package/skills/run-team/references/ci-and-staleness.md +47 -0
- package/skills/run-team/references/ci-watcher.md +138 -3
- package/skills/run-team/references/close-out.md +28 -0
- package/skills/run-team/references/failure-handling.md +43 -0
- package/skills/run-team/references/finisher.md +185 -0
- package/skills/run-team/references/instruments.md +20 -0
- package/skills/run-team/references/isolation.md +10 -0
- package/skills/run-team/references/ledger.md +4 -0
- package/skills/run-team/references/member-lifecycle.md +18 -0
- package/skills/run-team/references/phase-0.md +50 -0
- package/skills/run-team/references/pull.md +42 -2
- package/skills/run-team/references/reviewers.md +81 -0
- package/skills/run-team/references/shell-traps.md +10 -9
- package/skills/run-team/references/tick-rows.md +35 -0
|
@@ -18,9 +18,9 @@ Your report is the one file you write outside it: `<scratch>/reports/merge-bot-<
|
|
|
18
18
|
the path your brief names, holding every PR you merged in `merged`. Your final
|
|
19
19
|
message is the one line your brief gives, `merge-bot-<n>: done <path>`.
|
|
20
20
|
|
|
21
|
-
The shell traps below are copied from the controller's skill
|
|
22
|
-
`
|
|
23
|
-
|
|
21
|
+
The shell traps below are copied from the controller's skill, `run-team`. In
|
|
22
|
+
them `references/ci-watcher.md` is that skill's file on the controller's CI
|
|
23
|
+
watcher, which you do not run; each trap applies to every shell
|
|
24
24
|
command and loop you run.
|
|
25
25
|
|
|
26
26
|
**zsh word-splits an unquoted command substitution's result; it does NOT split
|
|
@@ -54,7 +54,7 @@ EOF
|
|
|
54
54
|
```
|
|
55
55
|
|
|
56
56
|
The inline `$(printf '%s\n' "$prs")` form splits correctly too and is what the
|
|
57
|
-
watcher block
|
|
57
|
+
watcher block in `references/ci-watcher.md` uses, but it splits on *every* whitespace character, so it is safe only
|
|
58
58
|
for values that cannot contain one — PR numbers cannot, branch names and paths
|
|
59
59
|
can. Measured on a two-line list whose first value is `two words`: three
|
|
60
60
|
iterations through `$(printf …)`, two through `while IFS= read -r`. On an empty
|
|
@@ -70,8 +70,8 @@ word-split bug, so a correct heredoc fix missing this half looks exactly like no
|
|
|
70
70
|
fix at all, and reads as one.
|
|
71
71
|
|
|
72
72
|
**A probe that could not look emits an event; it never `continue`s.** Stated in
|
|
73
|
-
full at *A probe that cannot read is a transition event, not silence*
|
|
74
|
-
and it binds every watcher
|
|
73
|
+
full at *A probe that cannot read is a transition event, not silence* in `references/ci-watcher.md`,
|
|
74
|
+
and it binds every watcher rather than that one block: the third
|
|
75
75
|
value beside pass and fail is what turns a broken loop into a loud failure
|
|
76
76
|
instead of a quiet one. A guard that drops an unreadable probe — `[ -n "$json" ]
|
|
77
77
|
|| continue` — makes *could not look* indistinguishable from *nothing to report*.
|
package/package.json
CHANGED
package/scripts/ledger.mjs
CHANGED
|
@@ -13,7 +13,9 @@
|
|
|
13
13
|
// every liveness count from this file alone, rather than from row text a
|
|
14
14
|
// controller typed by hand: `dispatch` writes a member's live token onto its
|
|
15
15
|
// row and appends it to `## Dispatched` with `brief=<hash>`, the brief it
|
|
16
|
-
// refuses to dispatch without
|
|
16
|
+
// refuses to dispatch without; when that brief's sidecar names an absolute
|
|
17
|
+
// scratch, it also clears the member's report file there, so a prior run's
|
|
18
|
+
// report is not read as this run's; `settle` rewrites that token to
|
|
17
19
|
// `<member>=<outcome>` in both places (an implementer's settle to `PR#M` also
|
|
18
20
|
// folds the PR's own `#M` row into its ticket's) — with `--from-report`, the
|
|
19
21
|
// outcome derived from the member's report file and checked against git and
|
|
@@ -46,7 +48,7 @@
|
|
|
46
48
|
|
|
47
49
|
import { readFileSync, writeFileSync, mkdirSync, existsSync, renameSync, unlinkSync, linkSync } from "node:fs";
|
|
48
50
|
import { createHash } from "node:crypto";
|
|
49
|
-
import { dirname, resolve, join, basename } from "node:path";
|
|
51
|
+
import { dirname, resolve, join, basename, isAbsolute } from "node:path";
|
|
50
52
|
import { fileURLToPath } from "node:url";
|
|
51
53
|
import { spawnSync, execFileSync } from "node:child_process";
|
|
52
54
|
import { makeDie, isFlagLike, hasEqualsForm, isDigits, writeAll } from "./arg.mjs";
|
|
@@ -56,7 +58,7 @@ import { deriveRun, LedgerError, labelOffMember, HELD, CONFLICT_HOLD } from "./f
|
|
|
56
58
|
import { stateFileIn, readState, writeState, assessBeat, stallsAt } from "./fleet-state.mjs";
|
|
57
59
|
import { processSource, ancestry, controllerOf, judgeController, ProcUnreadable } from "./proc.mjs";
|
|
58
60
|
import { attemptLock, releaseLockFile, reapPathOf } from "./pid-lock.mjs";
|
|
59
|
-
import { readCheckRecordIn, readReportIn, reportsDir, reportFamily, defaultScratch } from "./report-files.mjs";
|
|
61
|
+
import { readCheckRecordIn, readReportIn, reportsDir, reportFamily, defaultScratch, reportPath } from "./report-files.mjs";
|
|
60
62
|
|
|
61
63
|
const NAME = "ledger";
|
|
62
64
|
// The plugin's own agent definitions — the directory tier-check.mjs reads
|
|
@@ -2032,7 +2034,12 @@ function runDispatch() {
|
|
|
2032
2034
|
|
|
2033
2035
|
// Last, so every refusal above still names its own cause first, and before
|
|
2034
2036
|
// anything is written.
|
|
2035
|
-
const brief = renderedBrief(member.name, memberRole(member.family));
|
|
2037
|
+
const { brief, scratch } = renderedBrief(member.name, memberRole(member.family));
|
|
2038
|
+
|
|
2039
|
+
// The report a prior run left for this member is that run's last claim.
|
|
2040
|
+
// Cleared here, after every refusal above, so settle --from-report and the
|
|
2041
|
+
// tick's SETTLE row cannot read it as this run's.
|
|
2042
|
+
clearReport(scratch, member.name);
|
|
2036
2043
|
|
|
2037
2044
|
let line = null;
|
|
2038
2045
|
const created = member.bound !== null && i === -1;
|
|
@@ -2059,9 +2066,11 @@ function runDispatch() {
|
|
|
2059
2066
|
// --member <name>` left it in the briefs directory beside the ledger: the
|
|
2060
2067
|
// sidecar `<name>.json` must exist, name `role` — the template of the
|
|
2061
2068
|
// member's family — and carry the brief's sha256, and the brief file beside
|
|
2062
|
-
// it, `<name>.md`, must exist and hash to it. `{path, sha256}
|
|
2063
|
-
// `path` that file, so the controller hands the `task`
|
|
2064
|
-
// the path printed
|
|
2069
|
+
// it, `<name>.md`, must exist and hash to it. `{brief: {path, sha256},
|
|
2070
|
+
// scratch}` comes back, `path` that file, so the controller hands the `task`
|
|
2071
|
+
// call its content from the path printed, and `scratch` the sidecar's
|
|
2072
|
+
// `args.scratch` when it is an absolute path, else null. Refused, nothing is
|
|
2073
|
+
// written.
|
|
2065
2074
|
function renderedBrief(name, role) {
|
|
2066
2075
|
const dir = resolve(dirname(file), BRIEFS);
|
|
2067
2076
|
const sidecar = join(dir, `${name}.json`);
|
|
@@ -2098,7 +2107,28 @@ function renderedBrief(name, role) {
|
|
|
2098
2107
|
if (actual !== s.sha256) {
|
|
2099
2108
|
die(`${path} hashes to ${actual}, but ${sidecar} carries ${s.sha256} — ${render}`);
|
|
2100
2109
|
}
|
|
2101
|
-
|
|
2110
|
+
const scratch = typeof s.args?.scratch === "string" && isAbsolute(s.args.scratch) ? s.args.scratch : null;
|
|
2111
|
+
return { brief: { path, sha256: s.sha256 }, scratch };
|
|
2112
|
+
}
|
|
2113
|
+
|
|
2114
|
+
// Removes `name`'s report under `scratch`, the last run's claim about the
|
|
2115
|
+
// member, if one is there. A null `scratch` — the brief's sidecar named none —
|
|
2116
|
+
// clears nothing and says so, since the report's location is then unknown.
|
|
2117
|
+
function clearReport(scratch, name) {
|
|
2118
|
+
if (scratch === null) {
|
|
2119
|
+
console.error(`${NAME}: WARNING ${name}'s brief sidecar names no scratch, so its prior report was not cleared`);
|
|
2120
|
+
return;
|
|
2121
|
+
}
|
|
2122
|
+
const path = reportPath(scratch, name);
|
|
2123
|
+
try {
|
|
2124
|
+
unlinkSync(path);
|
|
2125
|
+
} catch (e) {
|
|
2126
|
+
// ENOTDIR: something other than a directory sits where `reports` would
|
|
2127
|
+
// be, so no report is there either — readReportIn and the tick's
|
|
2128
|
+
// readReports read it as absent too.
|
|
2129
|
+
if (e.code === "ENOENT" || e.code === "ENOTDIR") return;
|
|
2130
|
+
die(`cannot clear ${path}: ${e.message} — not dispatching ${name}`);
|
|
2131
|
+
}
|
|
2102
2132
|
}
|
|
2103
2133
|
|
|
2104
2134
|
// Whether `outcome` is one whose claim a member's report carries and `settle
|