@feigi/fleet-ctl 3.81.6 → 3.82.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/package.json +1 -1
- package/scripts/pull.mjs +35 -7
- package/scripts/ticket-router.mjs +24 -1
- package/skills/run-team/references/pull.md +14 -0
package/package.json
CHANGED
package/scripts/pull.mjs
CHANGED
|
@@ -54,9 +54,16 @@
|
|
|
54
54
|
// 1 inflight `inflight.sh <N>`: anything but exit 0 is taken.
|
|
55
55
|
// 2 issue `gh issue view <N> --json title,body,comments,labels,createdAt`
|
|
56
56
|
// written to `<scratch>/impl-<N>/issue.json`. Without the
|
|
57
|
-
// `ready-for-agent` label the ticket is gone.
|
|
58
|
-
//
|
|
59
|
-
//
|
|
57
|
+
// `ready-for-agent` label the ticket is gone. A comment with a
|
|
58
|
+
// `## Ruling` or `## Triage` heading line and no `## Agent
|
|
59
|
+
// Brief` one, newer than the last comment that has one or on a
|
|
60
|
+
// ticket with none, is a stranded ruling: the newest is named,
|
|
61
|
+
// with its URL, in a comment, and the label replaces
|
|
62
|
+
// `ready-for-agent` with `ready-for-human`, as a Q2 cause does;
|
|
63
|
+
// a newest one `gh` printed with no URL is exit 2, nothing
|
|
64
|
+
// posted.
|
|
65
|
+
// Otherwise the distilled brief and the paths the router's
|
|
66
|
+
// extractor finds in it are printed.
|
|
60
67
|
// 3 Q1 staleness `gone` / `present` (each with `--path <p>` and the string
|
|
61
68
|
// as the text) run `staleness.mjs --path <p> --gone|--present --
|
|
62
69
|
// <string>`; `none <why>` and `done` end the question. Exit 1
|
|
@@ -127,7 +134,7 @@ import { gitEnv } from "./git-env.mjs";
|
|
|
127
134
|
import { isCLI } from "./is-cli.mjs";
|
|
128
135
|
import { memberTokens, premisesOf, tierValues } from "./ledger-grammar.mjs";
|
|
129
136
|
import { sessionFromReceipt } from "./phase0-receipt.mjs";
|
|
130
|
-
import { briefPaths, briefText, outOfScope } from "./ticket-router.mjs";
|
|
137
|
+
import { briefPaths, briefText, outOfScope, strandedRuling } from "./ticket-router.mjs";
|
|
131
138
|
|
|
132
139
|
const NAME = "pull";
|
|
133
140
|
const SCRIPTS = dirname(fileURLToPath(import.meta.url));
|
|
@@ -169,8 +176,18 @@ function handsParts(text) {
|
|
|
169
176
|
return { cause: null, line: "" };
|
|
170
177
|
}
|
|
171
178
|
|
|
172
|
-
/**
|
|
179
|
+
/**
|
|
180
|
+
* The comment, and the label that replaces `ready-for-agent`, for a Q2 answer
|
|
181
|
+
* other than `decided` — or, for `stranded-ruling`, for a ruling or triage
|
|
182
|
+
* comment newer than the brief, `text` naming its heading line and its URL.
|
|
183
|
+
*/
|
|
173
184
|
export function relabel(token, text) {
|
|
185
|
+
if (token === "stranded-ruling") {
|
|
186
|
+
return {
|
|
187
|
+
label: HUMAN,
|
|
188
|
+
comment: `${RELABELLED} a ruling or triage comment is newer than the brief, and the Pull does not read it: ${text}. To make it the brief, rename its heading to \`## Agent Brief — ruling\` and drop anything that is not decided scope, or delete it; then restore \`${READY}\`.`,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
174
191
|
if (token === "needs-triage") {
|
|
175
192
|
return {
|
|
176
193
|
label: TRIAGE,
|
|
@@ -802,6 +819,11 @@ class Pull {
|
|
|
802
819
|
return this.stop(`cannot write ${this.p.issue}: ${e.message}`);
|
|
803
820
|
}
|
|
804
821
|
if (!issue.labels.some((l) => l?.name === READY)) return this.leave("relabelled elsewhere");
|
|
822
|
+
const stranded = strandedRuling(issue);
|
|
823
|
+
if (stranded !== null) {
|
|
824
|
+
if (typeof stranded.url !== "string") return this.stop(`gh issue view printed a ruling comment with no url: "${stranded.heading}"`);
|
|
825
|
+
return this.relabelled("stranded-ruling", `"${stranded.heading}" ${stranded.url}`);
|
|
826
|
+
}
|
|
805
827
|
this.log.push(...this.brief());
|
|
806
828
|
st.stage = "staleness";
|
|
807
829
|
return null;
|
|
@@ -1078,9 +1100,15 @@ class Pull {
|
|
|
1078
1100
|
st.stage = "collision";
|
|
1079
1101
|
return null;
|
|
1080
1102
|
}
|
|
1103
|
+
return this.relabelled(token, text);
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
// Posts the cause's comment and swaps `ready-for-agent` for its label. One
|
|
1107
|
+
// comment per cause: an edit that failed after its comment landed is run
|
|
1108
|
+
// again without a second comment.
|
|
1109
|
+
relabelled(token, text) {
|
|
1110
|
+
const { st } = this;
|
|
1081
1111
|
const { label, comment } = relabel(token, text);
|
|
1082
|
-
// One comment per cause: an edit that failed after its comment landed is
|
|
1083
|
-
// answered again without a second comment.
|
|
1084
1112
|
if (st.commented !== comment) {
|
|
1085
1113
|
const c = this.gh("relabel", ["issue", "comment", String(st.ticket), "--body", comment]);
|
|
1086
1114
|
if (c.status !== 0) return this.stop(withText(`gh issue comment failed (${c.how})`, c));
|
|
@@ -80,16 +80,39 @@ const DAY_MS = 86_400_000;
|
|
|
80
80
|
// metrics — every one free, deterministic and known before dispatch
|
|
81
81
|
// ---------------------------------------------------------------------------
|
|
82
82
|
|
|
83
|
+
/** An `## Agent Brief` heading line: what marks the comment the brief is taken from. */
|
|
84
|
+
const AGENT_BRIEF = /^## Agent Brief/m;
|
|
85
|
+
|
|
83
86
|
/** The brief: the last comment carrying an `## Agent Brief` heading line, else the body. */
|
|
84
87
|
export function briefText(issue) {
|
|
85
88
|
const comments = Array.isArray(issue.comments) ? issue.comments : [];
|
|
86
89
|
for (let i = comments.length - 1; i >= 0; i--) {
|
|
87
90
|
const body = String(comments[i]?.body ?? "");
|
|
88
|
-
if (
|
|
91
|
+
if (AGENT_BRIEF.test(body)) return body;
|
|
89
92
|
}
|
|
90
93
|
return String(issue.body ?? "");
|
|
91
94
|
}
|
|
92
95
|
|
|
96
|
+
/** A ruling or triage heading line: what a decision posted outside an `## Agent Brief` comment is headed with. */
|
|
97
|
+
const RULING_HEADING = /^## (?:Ruling|Triage).*$/m;
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* The newest comment with a ruling or triage heading line and no `## Agent
|
|
101
|
+
* Brief` heading line that comes after the last comment `briefText` takes the
|
|
102
|
+
* brief from, or after none when there is no such comment, as its first ruling
|
|
103
|
+
* or triage heading line and its `url`; null when none is.
|
|
104
|
+
*/
|
|
105
|
+
export function strandedRuling(issue) {
|
|
106
|
+
const comments = Array.isArray(issue.comments) ? issue.comments : [];
|
|
107
|
+
for (let i = comments.length - 1; i >= 0; i--) {
|
|
108
|
+
const body = String(comments[i]?.body ?? "");
|
|
109
|
+
if (AGENT_BRIEF.test(body)) return null;
|
|
110
|
+
const heading = RULING_HEADING.exec(body);
|
|
111
|
+
if (heading !== null) return { heading: heading[0], url: comments[i].url };
|
|
112
|
+
}
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
|
|
93
116
|
/** The line span `[at, end)` of `lines`' `Out of scope` section, heading to the next heading of its level or above; null when there is none. */
|
|
94
117
|
function outOfScopeSpan(lines) {
|
|
95
118
|
const at = lines.findIndex((l) => /^#{1,6}\s*out of scope\b/i.test(l));
|
|
@@ -52,6 +52,20 @@ terminating in exactly one dispatch or an empty shortlist:
|
|
|
52
52
|
scope` sequencing wherever that section is found. Then judge **decided?**
|
|
53
53
|
— never size.
|
|
54
54
|
|
|
55
|
+
**A ruling the brief does not carry stops the Pull first.** The brief is
|
|
56
|
+
the last `## Agent Brief` comment, else the issue body, so a decision posted
|
|
57
|
+
under another heading never reaches any question the Pull asks. A comment
|
|
58
|
+
with a `## Ruling` or `## Triage` heading line and no `## Agent Brief`
|
|
59
|
+
heading line that is newer than the last `## Agent Brief` comment, or on a
|
|
60
|
+
ticket with none, is a stranded ruling. `pull.mjs` checks for one right
|
|
61
|
+
after the `ready-for-agent` label check, before printing the brief or
|
|
62
|
+
asking anything. On a hit it posts one comment naming the newest stranded
|
|
63
|
+
comment's heading line and URL, which says to rename that heading to
|
|
64
|
+
`## Agent Brief — ruling` and drop anything that is not decided scope, or
|
|
65
|
+
to delete the comment, then restore `ready-for-agent`. It then swaps
|
|
66
|
+
`ready-for-agent` for `ready-for-human` and leaves with `LEFT #N:
|
|
67
|
+
relabelled ready-for-human: stranded-ruling`.
|
|
68
|
+
|
|
55
69
|
**Still live?** A backlog ticket's claims rot. Before `decided?`, settle the
|
|
56
70
|
ticket's central claim against `origin/main` — never the working tree, never
|
|
57
71
|
the ticket's own line numbers, which drift. One `git show origin/main:<file>`
|