@bongos/core 1.19.834 → 1.19.836
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/.bongos-core.json +48 -33
- package/.claude/skills/builder-claim/SKILL.md +2 -0
- package/.claude/skills/builder-release/SKILL.md +1 -1
- package/.claude/skills/builder-ship/SKILL.md +2 -0
- package/.claude/skills/builder-start/SKILL.md +1 -1
- package/docs/module-api-changelog.md +4 -0
- package/modules/lifecycle/db-claims.js +4 -2
- package/modules/lifecycle/db-ship.js +64 -0
- package/modules/lifecycle/publish-reconciler.js +23 -1
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/api.js +5 -1
- package/scripts/gds/build-cli-package.js +1 -0
- package/scripts/gds/claim.js +17 -2
- package/scripts/gds/cli-lib.js +11 -0
- package/scripts/gds/plain-cards.js +348 -0
- package/scripts/gds/provision-repo.js +53 -14
- package/scripts/gds/provision-teardown.js +1 -1
- package/scripts/gds/provision.js +3 -1
- package/scripts/gds/push-path-brief.js +2 -0
- package/scripts/gds/release.js +3 -0
- package/scripts/gds/ship-flow.js +4 -0
- package/scripts/gds/ship-grade-steps.js +5 -0
- package/scripts/gds/ship-land.js +14 -1
- package/scripts/gds/start.js +3 -0
- package/src/module-api.js +1 -1
- package/tests/claim_gate_rebase.mjs +54 -3
- package/tests/plain_cards.mjs +221 -0
- package/tests/provision.mjs +89 -1
- package/tests/rebase_gate_unprovable.mjs +134 -0
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
// scripts/gds/plain-cards.js — the plain-language card for every failure a builder can
|
|
2
|
+
// be parked behind (task 1003989).
|
|
3
|
+
//
|
|
4
|
+
// WHY. The CLIs explain a refusal in engineer prose, and the lifecycle skills told the
|
|
5
|
+
// agent to relay it verbatim — so a non-technical builder met `REBASE_REQUIRED` or
|
|
6
|
+
// `strand:pr_not_landed` raw, at the moment they were most stuck, whatever their
|
|
7
|
+
// interaction profile said. A card says the same thing four ways a person can use:
|
|
8
|
+
// what happened, what the agent will do next, whether a PERSON is needed (and who), and
|
|
9
|
+
// whether this is a rule rather than a fault. That last one is the expensive mistake:
|
|
10
|
+
// an agent that reads a deliberate restriction as breakage goes looking for a way round
|
|
11
|
+
// it (the box session that asked the owner for a personal access token).
|
|
12
|
+
//
|
|
13
|
+
// STATIC TEXT ONLY. A card interpolates nothing — no task title, no server message — so
|
|
14
|
+
// printing it adds no prompt-injection surface (the strand-watch / push-path-brief rule).
|
|
15
|
+
// The engineer text above it keeps the ids. tests/plain_cards.mjs fails when a CLI gains
|
|
16
|
+
// a failure code with no card, and when a card drifts back into the vocabulary it exists
|
|
17
|
+
// to translate (it reuses the accessibility scorer's jargon list, ADR 0291).
|
|
18
|
+
|
|
19
|
+
'use strict';
|
|
20
|
+
|
|
21
|
+
const { isTerminal } = require('./surface');
|
|
22
|
+
|
|
23
|
+
// The line a skill looks for. Agent-surface marker, same family as [otb-confirm].
|
|
24
|
+
const PLAIN_MARKER = '[otb-plain]';
|
|
25
|
+
|
|
26
|
+
const RULE = true; // deliberate: never look for a way round it
|
|
27
|
+
const FAULT = false; // something is actually off; diagnosing it is fine
|
|
28
|
+
|
|
29
|
+
const card = (happened, next, person, byDesign) => Object.freeze({ happened, next, person, by_design: byDesign });
|
|
30
|
+
|
|
31
|
+
const ONE_TASK_PER_SESSION = card(
|
|
32
|
+
'This chat session is already working on another task. Each session handles one task at a time, so finished work never gets mixed together.',
|
|
33
|
+
'I will finish or hand back the task we already have — or tell you how to open a second session for this one.',
|
|
34
|
+
null, RULE);
|
|
35
|
+
|
|
36
|
+
const CARDS = Object.freeze({
|
|
37
|
+
// ── starting a task ─────────────────────────────────────────────────────────
|
|
38
|
+
stage_required: card(
|
|
39
|
+
'There is a getting-started step to finish before this task can be started.',
|
|
40
|
+
'I will show you the step the server named and help you complete it.',
|
|
41
|
+
null, RULE),
|
|
42
|
+
session_already_has_active_claim: ONE_TASK_PER_SESSION,
|
|
43
|
+
session_claim_check_failed: ONE_TASK_PER_SESSION,
|
|
44
|
+
session_already_has_bound_claim: ONE_TASK_PER_SESSION,
|
|
45
|
+
newcomer_must_ship_first: card(
|
|
46
|
+
'New builders finish their first task before starting a second one.',
|
|
47
|
+
'I will help you finish the task you already have.',
|
|
48
|
+
null, RULE),
|
|
49
|
+
REBASE_REQUIRED: card(
|
|
50
|
+
'An earlier task of yours was approved but has not made it into the project yet, so starting new tasks is paused until it does.',
|
|
51
|
+
'I will run /strand-fix on the stuck task. That is the supported way to land it and it needs nothing technical from you.',
|
|
52
|
+
'Only if /strand-fix says it needs the owner.', RULE),
|
|
53
|
+
ALREADY_CLAIMED: card(
|
|
54
|
+
'Someone else started this task a moment before us.',
|
|
55
|
+
'I will pull a fresh list and suggest another task. Trying again would not help.',
|
|
56
|
+
null, RULE),
|
|
57
|
+
MODULE_CONFLICT: card(
|
|
58
|
+
'Another task in progress is changing the same part of the project.',
|
|
59
|
+
'I will suggest a task in a different area, or we wait for the other one to finish.',
|
|
60
|
+
null, RULE),
|
|
61
|
+
TOUCHES_CONFLICT: card(
|
|
62
|
+
'Another task in progress is changing some of the same files.',
|
|
63
|
+
'I will suggest a task that does not overlap, or we wait for the other one to finish.',
|
|
64
|
+
null, RULE),
|
|
65
|
+
TASK_NOT_READY: card(
|
|
66
|
+
'This task is not open for work yet — its status says so.',
|
|
67
|
+
'I will tell you its status and find a task that is ready.',
|
|
68
|
+
null, RULE),
|
|
69
|
+
TASK_NOT_FOUND: card(
|
|
70
|
+
'No task has that number.',
|
|
71
|
+
'I will list the tasks that exist so we can pick the right one.',
|
|
72
|
+
null, FAULT),
|
|
73
|
+
DEPS_NOT_SHIPPED: card(
|
|
74
|
+
'This task builds on other tasks that are not finished yet.',
|
|
75
|
+
'I will show which ones. If one of them was cancelled, the link between them needs a person to re-decide it.',
|
|
76
|
+
'Sometimes — whoever owns the goal, when a task it depends on was cancelled.', RULE),
|
|
77
|
+
OPEN_BLOCKER: card(
|
|
78
|
+
'This task is waiting on a decision or some input that was written down as a blocker.',
|
|
79
|
+
'I will show you the blocker. /blocker-solve can drive it to done.',
|
|
80
|
+
'Usually the owner, for the decision the blocker names.', RULE),
|
|
81
|
+
GOAL_MEMBERSHIP_REQUIRED: card(
|
|
82
|
+
'This task belongs to a goal you have not joined.',
|
|
83
|
+
'I will offer to join it for you — or apply, if it is private. I will not do either without your yes.',
|
|
84
|
+
'For a private goal, its owner approves the request.', RULE),
|
|
85
|
+
archon_approval_required: card(
|
|
86
|
+
'This goal covers a protected part of the project, so joining it needs an Archon to say yes.',
|
|
87
|
+
'I will file the request for you and we pick other work while it waits.',
|
|
88
|
+
'An Archon approves the request.', RULE),
|
|
89
|
+
goal_private: card(
|
|
90
|
+
'This goal is private, so joining it is by request.',
|
|
91
|
+
'I will file the request if you want, and we pick other work while it waits.',
|
|
92
|
+
'The goal owner approves the request.', RULE),
|
|
93
|
+
request_pending: card(
|
|
94
|
+
'Your request to join this goal is already waiting for an answer.',
|
|
95
|
+
'Nothing more to send. I will suggest other work in the meantime.',
|
|
96
|
+
'The goal owner, who has the request.', RULE),
|
|
97
|
+
BUILDER_INACTIVE: card(
|
|
98
|
+
'Your builder account is switched off, so it cannot start tasks.',
|
|
99
|
+
'I cannot change that from here. Ask an Archon to switch the account back on.',
|
|
100
|
+
'An Archon.', RULE),
|
|
101
|
+
INSUFFICIENT_RANK: card(
|
|
102
|
+
'This task needs a higher rank than yours.',
|
|
103
|
+
'I will find tasks open to your rank. I will not look for a way round the rank rule.',
|
|
104
|
+
'An Archon grants ranks.', RULE),
|
|
105
|
+
rank_too_low_for_task: card(
|
|
106
|
+
'Your rank cannot start this kind of task yet.',
|
|
107
|
+
'I will find tasks open to your rank. I will not look for a way round the rank rule.',
|
|
108
|
+
'An Archon grants ranks.', RULE),
|
|
109
|
+
BATCH_DUPLICATE_IDS: card(
|
|
110
|
+
'The same task was listed twice in one request.',
|
|
111
|
+
'I will send the list again with each task once.',
|
|
112
|
+
null, FAULT),
|
|
113
|
+
BATCH_EMPTY: card(
|
|
114
|
+
'The request to start several tasks had no tasks in it.',
|
|
115
|
+
'I will ask which tasks you meant.',
|
|
116
|
+
null, FAULT),
|
|
117
|
+
BATCH_FAILED: card(
|
|
118
|
+
'One of the tasks in the group could not be started, so none of them were.',
|
|
119
|
+
'I will show which one and why, then retry with the rest if you want.',
|
|
120
|
+
null, RULE),
|
|
121
|
+
main_checkout_claim: card(
|
|
122
|
+
'Tasks are worked on in their own separate copy of the project, and this session is standing in the main copy.',
|
|
123
|
+
'I will make the separate copy with one command and start the task from there.',
|
|
124
|
+
null, RULE),
|
|
125
|
+
|
|
126
|
+
board_unavailable: card(
|
|
127
|
+
'The list of tasks could not be loaded — the project answered with an error.',
|
|
128
|
+
'I will check that the project is reachable and that you are signed in, then try once more.',
|
|
129
|
+
null, FAULT),
|
|
130
|
+
|
|
131
|
+
// ── handing a task back ─────────────────────────────────────────────────────
|
|
132
|
+
CLAIM_SESSION_MISMATCH: card(
|
|
133
|
+
'This task was started from a different chat session, so this one is not allowed to hand it back by accident.',
|
|
134
|
+
'If you are sure it is yours to give up, I will repeat the command with the override it names.',
|
|
135
|
+
null, RULE),
|
|
136
|
+
no_active_claim: card(
|
|
137
|
+
'You are not working on that task right now, so there is nothing to hand back.',
|
|
138
|
+
'I will show what you do have in progress.',
|
|
139
|
+
null, FAULT),
|
|
140
|
+
|
|
141
|
+
// ── why an approved task has not landed ─────────────────────────────────────
|
|
142
|
+
'strand:pr_not_landed': card(
|
|
143
|
+
'Your work was approved and sent for the automatic checks, but it never made it into the project.',
|
|
144
|
+
'I will look up what stopped it and fix that, then land it.',
|
|
145
|
+
null, FAULT),
|
|
146
|
+
'strand:pr_closed_unmerged': card(
|
|
147
|
+
'The request to add your work was closed without being accepted, so it will never land by waiting.',
|
|
148
|
+
'I will send the work again. If a check failed, I fix that first.',
|
|
149
|
+
null, FAULT),
|
|
150
|
+
'strand:no_branch_no_tip': card(
|
|
151
|
+
'The task was approved, but its work never left the computer it was written on.',
|
|
152
|
+
'I will send the work from this computer. If it lives on another machine, it has to be sent from there.',
|
|
153
|
+
'Only if the work is on a machine I cannot reach.', FAULT),
|
|
154
|
+
'strand:land_not_proven': card(
|
|
155
|
+
'The project could not prove that your approved work actually arrived.',
|
|
156
|
+
'I will check what arrived and send the work again if it did not.',
|
|
157
|
+
null, FAULT),
|
|
158
|
+
'strand:unresolvable_conflict': card(
|
|
159
|
+
'Your work and newer work changed the same lines, and the automatic fix could not choose between them.',
|
|
160
|
+
'I will bring your work up to date by hand, keeping both changes, and send it again.',
|
|
161
|
+
null, FAULT),
|
|
162
|
+
'strand:branch_modifies_executed_code': card(
|
|
163
|
+
'Your work changes the tools that would be used to update it, so the automatic update was refused on purpose.',
|
|
164
|
+
'I will bring your work up to date on this computer instead, and send it again.',
|
|
165
|
+
null, RULE),
|
|
166
|
+
|
|
167
|
+
// ── why a request to add work has not been accepted ─────────────────────────
|
|
168
|
+
failing_check: card(
|
|
169
|
+
'An automatic test failed on your work, so it has not been added to the project.',
|
|
170
|
+
'I will run that test here, fix what it found, and send the work again. Sending the same work again would fail the same way.',
|
|
171
|
+
null, FAULT),
|
|
172
|
+
non_required_check_failing: card(
|
|
173
|
+
'An optional automatic test failed on your work.',
|
|
174
|
+
'I will look at what it found. It does not stop the work being added, but it is usually worth fixing.',
|
|
175
|
+
null, FAULT),
|
|
176
|
+
non_required_check_pending: card(
|
|
177
|
+
'An optional automatic test is still running on your work.',
|
|
178
|
+
'Nothing to do yet — I will check again shortly.',
|
|
179
|
+
null, RULE),
|
|
180
|
+
failing_check_unreadable: card(
|
|
181
|
+
'An automatic test failed on your work, but this project is not allowed to see which one.',
|
|
182
|
+
'I will run all the tests here to find it, fix it, and send the work again. Sending the same work again cannot help.',
|
|
183
|
+
'The owner can switch on the permission that shows which test failed.', FAULT),
|
|
184
|
+
blocked_required_review_or_pending_check: card(
|
|
185
|
+
'Your work is waiting on a test that has not finished, or on a review.',
|
|
186
|
+
'Nothing to do yet — I will check again shortly.',
|
|
187
|
+
null, RULE),
|
|
188
|
+
review_required: card(
|
|
189
|
+
'Your work needs a person to review it before it can be added.',
|
|
190
|
+
'I will tell you who can review it. I cannot approve my own work.',
|
|
191
|
+
'A reviewer with the right to approve.', RULE),
|
|
192
|
+
changes_requested: card(
|
|
193
|
+
'A reviewer asked for changes before your work can be added.',
|
|
194
|
+
'I will read what they asked for and make the changes.',
|
|
195
|
+
null, RULE),
|
|
196
|
+
merge_conflict: card(
|
|
197
|
+
'Your work and newer work changed the same lines, so they cannot be combined automatically.',
|
|
198
|
+
'I will bring your work up to date, keeping both changes, and send it again.',
|
|
199
|
+
null, FAULT),
|
|
200
|
+
branch_behind_base: card(
|
|
201
|
+
'The project moved on while your work was being checked, so your work needs refreshing first.',
|
|
202
|
+
'The server usually does this by itself. If it has not, I will refresh it and send it again.',
|
|
203
|
+
null, RULE),
|
|
204
|
+
draft_pr: card(
|
|
205
|
+
'Your work was sent as a draft, and drafts are not added to the project.',
|
|
206
|
+
'I will mark it ready.',
|
|
207
|
+
null, FAULT),
|
|
208
|
+
mergeability_computing: card(
|
|
209
|
+
'The project is still working out whether your work can be added.',
|
|
210
|
+
'Nothing to do yet — I will check again in a moment.',
|
|
211
|
+
null, RULE),
|
|
212
|
+
no_open_pr: card(
|
|
213
|
+
'There is no open request to add your work to the project.',
|
|
214
|
+
'I will check whether it was already added, and send it if it was not.',
|
|
215
|
+
null, FAULT),
|
|
216
|
+
unknown: card(
|
|
217
|
+
'The project did not say why your work has not been added.',
|
|
218
|
+
'I will check again, and look at it directly if the answer stays empty.',
|
|
219
|
+
null, FAULT),
|
|
220
|
+
|
|
221
|
+
// ── sending work from this computer ─────────────────────────────────────────
|
|
222
|
+
'ship:workflow_scope': card(
|
|
223
|
+
'Your work changes the project\'s automatic test setup, and the sign-in this computer uses is not allowed to send that kind of change.',
|
|
224
|
+
'Sending it again will fail the same way. I will show the one-time command that sends it another way.',
|
|
225
|
+
'You, once, to run that command or refresh the sign-in.', RULE),
|
|
226
|
+
'ship:protected_branch': card(
|
|
227
|
+
'Nobody is allowed to put work straight into the project\'s main copy — everything goes through the automatic checks first.',
|
|
228
|
+
'I will send your work through the checked route instead. I will not try to get round the protection.',
|
|
229
|
+
null, RULE),
|
|
230
|
+
'ship:auth': card(
|
|
231
|
+
'This computer\'s sign-in to the code host was refused.',
|
|
232
|
+
'I will show you how to sign in again. I will not ask anyone for a password or a key.',
|
|
233
|
+
'You, to sign in again.', RULE),
|
|
234
|
+
'ship:stale_branch': card(
|
|
235
|
+
'The project moved on since your work started, so it has to be refreshed before it can be sent.',
|
|
236
|
+
'I will refresh it and send it again.',
|
|
237
|
+
null, FAULT),
|
|
238
|
+
'ship:unknown': card(
|
|
239
|
+
'Sending your work failed for a reason I do not recognise.',
|
|
240
|
+
'I will read the message above, fix what it names, and try again once.',
|
|
241
|
+
null, FAULT),
|
|
242
|
+
'ship:main-checkout-on-branch': card(
|
|
243
|
+
'This task was worked on in the project\'s main copy instead of its own separate copy, so it cannot be added the usual way.',
|
|
244
|
+
'I will send it through the checked route instead. Next time I will start the task in its own copy.',
|
|
245
|
+
null, FAULT),
|
|
246
|
+
'ship:no-task-branch': card(
|
|
247
|
+
'I could not find the saved work for this task on this computer.',
|
|
248
|
+
'I will look for where it was saved. If it is on another machine, it has to be sent from there.',
|
|
249
|
+
'Only if the work is on a machine I cannot reach.', FAULT),
|
|
250
|
+
land_pending: card(
|
|
251
|
+
'Your work passed review and is waiting for the automatic tests. The server adds it to the project by itself when they pass.',
|
|
252
|
+
'Nothing to do. This is normal, not a failure.',
|
|
253
|
+
null, RULE),
|
|
254
|
+
reconcile_blocked: card(
|
|
255
|
+
'Your work was added to the project, but the server could not prove it was this task\'s, so the task is not marked finished yet.',
|
|
256
|
+
'The server re-checks every few minutes. If it stays stuck I will run the ship command again.',
|
|
257
|
+
null, FAULT),
|
|
258
|
+
smoke_failed: card(
|
|
259
|
+
'A quick health check of the project failed before your work was handed in.',
|
|
260
|
+
'I will find what the check tripped on and fix it before handing the work in.',
|
|
261
|
+
null, FAULT),
|
|
262
|
+
nothing_to_ship: card(
|
|
263
|
+
'There is no saved work for this task, so there is nothing to hand in.',
|
|
264
|
+
'I will check whether the work was saved. If the task really needed no changes, I will say so explicitly.',
|
|
265
|
+
null, RULE),
|
|
266
|
+
uncommitted_work: card(
|
|
267
|
+
'Some of your work has not been saved into the project\'s history yet, so handing it in now would leave it behind.',
|
|
268
|
+
'I will save it, then hand it in.',
|
|
269
|
+
null, RULE),
|
|
270
|
+
|
|
271
|
+
// ── the review of your work ─────────────────────────────────────────────────
|
|
272
|
+
grade_unavailable: card(
|
|
273
|
+
'The automatic reviewers could not run. Your work was not judged — this is an outage, not a rejection.',
|
|
274
|
+
'I will ask for the review again later. There is nothing in your work to fix.',
|
|
275
|
+
'Only if the outage lasts — an Archon can accept the work by hand.', FAULT),
|
|
276
|
+
grade_lens_unavailable: card(
|
|
277
|
+
'One of the automatic reviewers did not run; the others found nothing wrong. Your work was not rejected.',
|
|
278
|
+
'I will ask for the review again when the reviewers are healthy.',
|
|
279
|
+
'Only if it keeps happening — an Archon can accept the work by hand.', FAULT),
|
|
280
|
+
grade_refused_authorization: card(
|
|
281
|
+
'Your work touches a protected part of the project that your rank may not change, so the server would not accept it.',
|
|
282
|
+
'Asking again will be refused again. I will take the protected files out and hand in the rest, or we pass the task to someone with the rank.',
|
|
283
|
+
'A builder with a high enough rank, for the protected part.', RULE),
|
|
284
|
+
grade_failed: card(
|
|
285
|
+
'The automatic reviewers found problems in your work, so it is not accepted yet.',
|
|
286
|
+
'I will fix what they listed, save it, and ask for the review again. That costs nothing extra.',
|
|
287
|
+
null, FAULT),
|
|
288
|
+
|
|
289
|
+
// ── signing in ──────────────────────────────────────────────────────────────
|
|
290
|
+
no_session: card(
|
|
291
|
+
'This computer is not signed in to the project yet.',
|
|
292
|
+
'I will walk you through signing in — it happens in your browser.',
|
|
293
|
+
'You, to sign in.', RULE),
|
|
294
|
+
session_invalid: card(
|
|
295
|
+
'Your sign-in to the project has ended.',
|
|
296
|
+
'I will walk you through signing in again — Settings, then one copy and paste.',
|
|
297
|
+
'You, to sign in again.', RULE),
|
|
298
|
+
box_scope: card(
|
|
299
|
+
'This sign-in is limited on purpose: it can do the work, but not hand it in.',
|
|
300
|
+
'I will walk you through the full sign-in, then hand the work in.',
|
|
301
|
+
'You, to sign in again.', RULE),
|
|
302
|
+
instance_mismatch: card(
|
|
303
|
+
'This computer is signed in to a different project than the one this folder belongs to.',
|
|
304
|
+
'I will show which project each one is, and help you sign in to the right one.',
|
|
305
|
+
'You, to choose the project.', FAULT),
|
|
306
|
+
dev_box_unavailable: card(
|
|
307
|
+
'This project does not offer hosted computers to work from.',
|
|
308
|
+
'We can work from this computer instead.',
|
|
309
|
+
null, RULE),
|
|
310
|
+
no_push_credential: card(
|
|
311
|
+
'This computer is not allowed to send work to the code host itself. That is deliberate: the server sends it for you.',
|
|
312
|
+
'I will hand the work in with /builder-ship and read its progress from the server. I will never ask anyone for a password, key or sign-in to work round this.',
|
|
313
|
+
null, RULE),
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
// cardFor(code) → the card, or null. Reasons that carry a name or a file list after a
|
|
317
|
+
// colon (`failing_check:unit`, `strand:unresolvable_conflict:a.md,b.md`) resolve on
|
|
318
|
+
// their stem. `XENOS_RESTRICTED` is the legacy spelling of rank_too_low_for_task.
|
|
319
|
+
function cardFor(code) {
|
|
320
|
+
if (typeof code !== 'string' || !code) return null;
|
|
321
|
+
if (code === 'XENOS_RESTRICTED') return CARDS.rank_too_low_for_task;
|
|
322
|
+
if (Object.prototype.hasOwnProperty.call(CARDS, code)) return CARDS[code];
|
|
323
|
+
const parts = code.split(':');
|
|
324
|
+
for (let n = parts.length - 1; n >= 1; n--) {
|
|
325
|
+
const stem = parts.slice(0, n).join(':');
|
|
326
|
+
if (Object.prototype.hasOwnProperty.call(CARDS, stem)) return CARDS[stem];
|
|
327
|
+
}
|
|
328
|
+
return null;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// plainCardLines(code) → the printable card, or [] for a code with none. Never throws:
|
|
332
|
+
// a missing card must not be able to break the refusal it was meant to explain.
|
|
333
|
+
// AGENT SURFACE ONLY: a card is written in the agent's voice ("I will…") for a
|
|
334
|
+
// session to relay. A person running the `bongos` CLI reads the text above it directly,
|
|
335
|
+
// and several cards name slash commands that do not exist there (tests/cli_surface.mjs).
|
|
336
|
+
function plainCardLines(code, env = process.env) {
|
|
337
|
+
const c = isTerminal(env) ? null : cardFor(code);
|
|
338
|
+
if (!c) return [];
|
|
339
|
+
return [
|
|
340
|
+
` ${PLAIN_MARKER} in plain words`,
|
|
341
|
+
` What happened ${c.happened}`,
|
|
342
|
+
` What I will do ${c.next}`,
|
|
343
|
+
` Needs a person ${c.person || 'No.'}`,
|
|
344
|
+
` By design ${c.by_design ? 'Yes — this is a rule, not a fault. Do not look for a way round it.' : 'No — something is actually off, so looking into it is right.'}`,
|
|
345
|
+
];
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
module.exports = { CARDS, cardFor, plainCardLines, PLAIN_MARKER };
|
|
@@ -512,6 +512,8 @@ async function scaffoldStandaloneRepo(inst, deps) {
|
|
|
512
512
|
throw new Error(`standalone instance ${inst.slug}: target_ref "${inst.target_ref || '(unset)'}" is not an owner/repo — set it (e.g. <owner>/${inst.slug}) so the scaffold knows which repo to clone + push.`);
|
|
513
513
|
}
|
|
514
514
|
if (alreadyScaffolded(root, mode)) {
|
|
515
|
+
const resumed = deps.apply ? await resumeUnpushedScaffold(inst, deps, { root, repo, mode }) : null;
|
|
516
|
+
if (resumed) return resumed;
|
|
515
517
|
log(` [scaffold] ${root} already scaffolded (mode=${mode}) — skipping`);
|
|
516
518
|
return { already: true };
|
|
517
519
|
}
|
|
@@ -541,13 +543,8 @@ async function scaffoldStandaloneRepo(inst, deps) {
|
|
|
541
543
|
// command or makeExec's env (makeExec surfaces both), and is removed in a `finally` so a
|
|
542
544
|
// failed git op can't leave it on disk. No token (org-owned repo, or secret-box off) →
|
|
543
545
|
// fall back to the SSH `origin` remote.
|
|
544
|
-
const
|
|
545
|
-
|
|
546
|
-
// carries only the `x-access-token` username (no secret), so it is safe in a logged argv.
|
|
547
|
-
const credFile = ownerToken ? path.join(os.tmpdir(), `bongos-gitcred-${inst.slug}-${process.pid}`) : null;
|
|
548
|
-
const httpsUrl = `https://x-access-token@github.com/${repo.owner}/${repo.name}.git`;
|
|
549
|
-
const credOpt = credFile ? `-c credential.helper='store --file=${credFile}' ` : '';
|
|
550
|
-
if (credFile) writeFile(credFile, `https://x-access-token:${ownerToken}@github.com\n`, { mode: 0o600 });
|
|
546
|
+
const cred = await scaffoldCredential(inst, repo, deps, writeFile);
|
|
547
|
+
const { credFile, httpsUrl, credOpt } = cred;
|
|
551
548
|
|
|
552
549
|
try {
|
|
553
550
|
// 1. Materialize the working tree.
|
|
@@ -680,13 +677,7 @@ async function scaffoldStandaloneRepo(inst, deps) {
|
|
|
680
677
|
// destroying their work. Adopt's commit is a descendant of the tip it cloned, so an
|
|
681
678
|
// ordinary push fast-forwards; if the remote moved underneath us the rejection is the
|
|
682
679
|
// correct outcome and a re-run picks up the new tip.
|
|
683
|
-
|
|
684
|
-
exec(`git -C ${root} ${credOpt}push ${httpsUrl} HEAD:main`);
|
|
685
|
-
log(` [scaffold] ${mode === 'adopt' ? 'pushed the adopt layer' : 'pushed'} to ${repo.owner}/${repo.name}@main via the owner's GitHub token (task 1002317)`);
|
|
686
|
-
} else {
|
|
687
|
-
exec(`git -C ${root} push -u origin main`);
|
|
688
|
-
log(` [scaffold] pushed ${mode === 'adopt' ? 'the adopt layer' : 'initial scaffold'} to ${repo.owner}/${repo.name}@main (SSH origin)`);
|
|
689
|
-
}
|
|
680
|
+
pushScaffold({ exec, log, root, repo, mode, cred });
|
|
690
681
|
return { ok: true, scaffolded: true, mode };
|
|
691
682
|
} finally {
|
|
692
683
|
// Always remove the credential file — a failed clone/push must not leave it on disk.
|
|
@@ -694,6 +685,54 @@ async function scaffoldStandaloneRepo(inst, deps) {
|
|
|
694
685
|
}
|
|
695
686
|
}
|
|
696
687
|
|
|
688
|
+
// The owner-token credential the scaffold's git network ops ride (rationale at the call site in
|
|
689
|
+
// scaffoldStandaloneRepo). The URL carries only the `x-access-token` username, so it is safe in a
|
|
690
|
+
// logged argv. credFile null → no token → SSH origin fallback; the caller `rm -f`s it in a finally.
|
|
691
|
+
async function scaffoldCredential(inst, repo, deps, writeFile) {
|
|
692
|
+
const ownerToken = await resolveOwnerGithubToken(inst, deps, { tag: '[scaffold]' });
|
|
693
|
+
const credFile = ownerToken ? path.join(os.tmpdir(), `bongos-gitcred-${inst.slug}-${process.pid}`) : null;
|
|
694
|
+
const httpsUrl = `https://x-access-token@github.com/${repo.owner}/${repo.name}.git`;
|
|
695
|
+
const credOpt = credFile ? `-c credential.helper='store --file=${credFile}' ` : '';
|
|
696
|
+
if (credFile) writeFile(credFile, `https://x-access-token:${ownerToken}@github.com\n`, { mode: 0o600 });
|
|
697
|
+
return { credFile, httpsUrl, credOpt };
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
function pushScaffold({ exec, log, root, repo, mode, cred }) {
|
|
701
|
+
if (cred.credFile) {
|
|
702
|
+
exec(`git -C ${root} ${cred.credOpt}push ${cred.httpsUrl} HEAD:main`);
|
|
703
|
+
log(` [scaffold] ${mode === 'adopt' ? 'pushed the adopt layer' : 'pushed'} to ${repo.owner}/${repo.name}@main via the owner's GitHub token (task 1002317)`);
|
|
704
|
+
} else {
|
|
705
|
+
exec(`git -C ${root} push -u origin main`);
|
|
706
|
+
log(` [scaffold] pushed ${mode === 'adopt' ? 'the adopt layer' : 'initial scaffold'} to ${repo.owner}/${repo.name}@main (SSH origin)`);
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
// alreadyScaffolded is a LOCAL marker, and a scaffold whose push failed leaves it behind: the
|
|
711
|
+
// re-run skipped the never-done push, then wedged at step 2's `git pull --ff-only` against the
|
|
712
|
+
// still-empty remote (task 1002718, F12). Only a remote that ANSWERS with no main re-pushes — an
|
|
713
|
+
// unreachable one keeps the old skip, as it can't tell "never pushed" from "offline". Returns
|
|
714
|
+
// null when there is nothing to resume.
|
|
715
|
+
async function resumeUnpushedScaffold(inst, deps, { root, repo, mode }) {
|
|
716
|
+
const exec = deps.controlExec || deps.exec;
|
|
717
|
+
const writeFile = deps.controlWriteFile || deps.writeFile;
|
|
718
|
+
let cred = null;
|
|
719
|
+
let probe = { softFailed: true };
|
|
720
|
+
try {
|
|
721
|
+
// This runs on every re-provision of a LIVE instance, so a probe that cannot even start
|
|
722
|
+
// (token lookup, credential write) must keep the skip — never fail a healthy instance.
|
|
723
|
+
try {
|
|
724
|
+
cred = await scaffoldCredential(inst, repo, deps, writeFile);
|
|
725
|
+
probe = exec(`git -C ${root} ${cred.credOpt}ls-remote --heads ${cred.credFile ? cred.httpsUrl : 'origin'} main`, { allowFail: true });
|
|
726
|
+
} catch { deps.log(` [scaffold] could not probe ${repo.owner}/${repo.name} for main — keeping the skip`); }
|
|
727
|
+
if (probe.softFailed || String(probe.stdout || '').trim()) return null;
|
|
728
|
+
deps.log(` [scaffold] ${root} is scaffolded but ${repo.owner}/${repo.name} has no main — re-pushing the scaffold that never landed`);
|
|
729
|
+
pushScaffold({ exec, log: deps.log, root, repo, mode, cred });
|
|
730
|
+
return { ok: true, resumed: true, mode };
|
|
731
|
+
} finally {
|
|
732
|
+
if (cred && cred.credFile) exec(`rm -f ${cred.credFile}`, { allowFail: true });
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
|
|
697
736
|
// ── private-repo deploy key (ADR 0176, task 1002735; walk finding N4) ────────────
|
|
698
737
|
|
|
699
738
|
function deployKeyTitle(inst) { return `cloudbongos-deploy-${inst.slug}`; }
|
|
@@ -43,7 +43,7 @@ async function teardownInstance(inst, deps) {
|
|
|
43
43
|
// Left for an operator (data is precious), naming ONLY what this row created —
|
|
44
44
|
// teardown now takes 'requested'/'error'. `port` + `domain` stay held: ADR 0181.
|
|
45
45
|
if (inst.db_name) log(` (left for the operator: dropdb ${dbName(inst)}, prune ${CONFIG.backupDir}/${dbName(inst)}-*.sql.gz)`);
|
|
46
|
-
} else if (apply &&
|
|
46
|
+
} else if (apply && inst.droplet_id && deps.doApi) {
|
|
47
47
|
await deps.doApi.deleteDroplet(inst.droplet_id);
|
|
48
48
|
}
|
|
49
49
|
parkAddress(inst, { controlExec, log, apply });
|
package/scripts/gds/provision.js
CHANGED
|
@@ -1393,12 +1393,14 @@ async function main() {
|
|
|
1393
1393
|
if (CONFIG.remoteHost && !CONFIG.remoteCwd) { console.error('PROVISION_REMOTE_HOST is set but PROVISION_REMOTE_CHECKOUT is not — name the co-hosting box\'s checkout dir (the old hardcoded default was removed, task 1002756).'); process.exit(2); }
|
|
1394
1394
|
const exec = makeExec({ apply, remoteHost: CONFIG.remoteHost, remoteCwd: CONFIG.remoteCwd, sshOpts: CONFIG.sshOpts });
|
|
1395
1395
|
const controlExec = makeExec({ apply });
|
|
1396
|
+
let doApiClient = null;
|
|
1396
1397
|
const deps = {
|
|
1397
1398
|
db: pool, provisioning, apply, privileged: CONFIG.privileged, log: console.log,
|
|
1398
1399
|
exec, controlExec,
|
|
1399
1400
|
writeFile: makeWriteFile({ apply, privileged: CONFIG.privileged, exec, remoteHost: CONFIG.remoteHost }),
|
|
1400
1401
|
controlWriteFile: makeWriteFile({ apply, privileged: CONFIG.privileged, exec: controlExec }),
|
|
1401
|
-
|
|
1402
|
+
// Built on first READ (task 1002718 F6): an --apply tick whose intents never call DO needs no DO_API_TOKEN.
|
|
1403
|
+
get doApi() { return DoApi ? (doApiClient = doApiClient || new DoApi({ dryRun: !apply, token: CONFIG.doToken, log: (m) => console.log(' ' + m) })) : null; },
|
|
1402
1404
|
// Cross-site identity (ADR 0141 §4). Null on a non-hub control plane → federation
|
|
1403
1405
|
// dormant; shouldFederate() then skips and the instance owns its auth (as today).
|
|
1404
1406
|
platformIdentity, hubKeys,
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
// Detection override (tests / forcing): OTB_PUSH_VIA_SERVER=1 (server) / =0 (local).
|
|
34
34
|
|
|
35
35
|
const { hasFlag } = require('./cli-lib');
|
|
36
|
+
const { plainCardLines } = require('./plain-cards');
|
|
36
37
|
|
|
37
38
|
// ---- pure core ---------------------------------------------------------------
|
|
38
39
|
|
|
@@ -70,6 +71,7 @@ function report(via) {
|
|
|
70
71
|
if (via === 'server') {
|
|
71
72
|
console.log(' server-mediated (no authenticated gh — a dev box or credential-less checkout).');
|
|
72
73
|
console.log(` ${BRIEF}`);
|
|
74
|
+
for (const line of plainCardLines('no_push_credential')) console.log(line);
|
|
73
75
|
} else if (via === 'local') {
|
|
74
76
|
console.log(' local — this machine pushes with its own git + gh credentials.');
|
|
75
77
|
} else {
|
package/scripts/gds/release.js
CHANGED
|
@@ -10,6 +10,7 @@ const { execFileSync } = require('node:child_process');
|
|
|
10
10
|
const path = require('node:path');
|
|
11
11
|
const { cliClient, requireSession, arg } = require('./cli-lib');
|
|
12
12
|
const { parkedMainCheckoutWarning } = require('./worktree-claim-guard');
|
|
13
|
+
const { plainCardLines } = require('./plain-cards');
|
|
13
14
|
|
|
14
15
|
// gitAt(args, cwd) — run git with an ARGV ARRAY and no shell.
|
|
15
16
|
//
|
|
@@ -73,6 +74,7 @@ function mainCheckoutRoot() {
|
|
|
73
74
|
console.error(`Your active claims:`);
|
|
74
75
|
for (const c of claims) console.error(` #${c.task_id}: ${c.title}`);
|
|
75
76
|
}
|
|
77
|
+
for (const line of plainCardLines('no_active_claim')) console.error(line);
|
|
76
78
|
process.exit(1);
|
|
77
79
|
}
|
|
78
80
|
|
|
@@ -97,6 +99,7 @@ function mainCheckoutRoot() {
|
|
|
97
99
|
console.error(`This claim was created in a different session${wt ? ` (worktree: ${wt})` : ''}.`);
|
|
98
100
|
console.error('Only the original session can release it.');
|
|
99
101
|
console.error('If that session is gone, re-run with --force to override.');
|
|
102
|
+
for (const line of plainCardLines('CLAIM_SESSION_MISMATCH')) console.error(line);
|
|
100
103
|
} else {
|
|
101
104
|
console.error(`release failed (${r.status}):`, r.data);
|
|
102
105
|
}
|
package/scripts/gds/ship-flow.js
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
const { cliClient, requireSession, arg, detectWorktreeName, cardFlagsFromMe } = require('./cli-lib');
|
|
12
12
|
const { resolveProse, markShipProgress, smokeGateMessages, proseFlagPresent, regradeNotesRefusal, staleSummaryNotice } = require('./ship-honesty.js');
|
|
13
|
+
const { plainCardLines } = require('./plain-cards.js');
|
|
13
14
|
const { classifyEmptyShip } = require('../../modules/lifecycle/ship-preflight');
|
|
14
15
|
const { isModuleKey, scopeForModule, moduleDriftFiles: computeModuleDrift } = require('../../src/bongos/module-scope-map');
|
|
15
16
|
const { runShipGate } = require('./sandbox-stage');
|
|
@@ -251,12 +252,14 @@ async function shipMain() {
|
|
|
251
252
|
console.error('');
|
|
252
253
|
console.error('Your work was never committed — shipping now would mark the task "shipped" with NO artifact on main (the ledger would lie).');
|
|
253
254
|
console.error('Stage + commit your work (e.g. `git add -A && git commit`), then re-run /builder-ship. Your claim is still active.');
|
|
255
|
+
for (const line of plainCardLines('uncommitted_work')) console.error(line);
|
|
254
256
|
process.exit(1);
|
|
255
257
|
}
|
|
256
258
|
if (verdict.reason === 'nothing-to-ship') {
|
|
257
259
|
console.error(`Nothing to ship: the committed diff vs ${graderBaseline || 'baseline'} is empty and the working tree is clean.`);
|
|
258
260
|
console.error('The grader reads committed history, not the working tree — commit your work on this branch, then re-run /builder-ship. Your claim is still active.');
|
|
259
261
|
console.error('If this task legitimately has no code/doc artifact (a pure DB-only or no-op task), re-run with --allow-empty.');
|
|
262
|
+
for (const line of plainCardLines('nothing_to_ship')) console.error(line);
|
|
260
263
|
process.exit(1);
|
|
261
264
|
}
|
|
262
265
|
// verdict.reason === 'allow-empty': a deliberate no-artifact / DB-only ship.
|
|
@@ -425,6 +428,7 @@ async function shipMain() {
|
|
|
425
428
|
if (!passed && !allowSmokeFail) {
|
|
426
429
|
console.error('');
|
|
427
430
|
for (const line of smokeGateMessages(taskId).abort) console.error(line);
|
|
431
|
+
for (const line of plainCardLines('smoke_failed')) console.error(line);
|
|
428
432
|
process.exit(1);
|
|
429
433
|
}
|
|
430
434
|
|
|
@@ -14,6 +14,7 @@ const os = require('node:os');
|
|
|
14
14
|
const { cliClient, rewardPolicy } = require('./cli-lib');
|
|
15
15
|
const { gitOk } = require('./ship-git.js');
|
|
16
16
|
const { resolveInstanceBase } = require('./ship-deploy-target.js');
|
|
17
|
+
const { plainCardLines } = require('./plain-cards.js');
|
|
17
18
|
// task 1003313: the shared onboarding/doctor preflight — reused (not duplicated)
|
|
18
19
|
// so the grader outage explains itself with the same `claude CLI` verdict doctor
|
|
19
20
|
// gives, and the two can never drift.
|
|
@@ -294,6 +295,7 @@ function formatGradeOutcome(g, taskId, cliFinding = null) {
|
|
|
294
295
|
out.push(` node scripts/gds/ship.js ${taskId} --regrade`);
|
|
295
296
|
out.push('Or land it now via manual-confirm:');
|
|
296
297
|
out.push(` curl -X POST -H "Authorization: Bearer <token>" ${resolveInstanceBase() || '<instance-origin>'}/api/bongos/tasks/${taskId}/confirm`);
|
|
298
|
+
out.push(...plainCardLines('grade_unavailable'));
|
|
297
299
|
return out.join('\n');
|
|
298
300
|
}
|
|
299
301
|
|
|
@@ -324,6 +326,7 @@ function formatGradeOutcome(g, taskId, cliFinding = null) {
|
|
|
324
326
|
out.push(' - Split the protected files out of your diff and re-ship the rest, or');
|
|
325
327
|
out.push(' - hand this task to a builder at/above the floor the refusal names.');
|
|
326
328
|
out.push(`(--regrade re-runs the same server check and will refuse again unless the diff changes.)`);
|
|
329
|
+
out.push(...plainCardLines('grade_refused_authorization'));
|
|
327
330
|
return out.join('\n');
|
|
328
331
|
}
|
|
329
332
|
|
|
@@ -351,6 +354,7 @@ function formatGradeOutcome(g, taskId, cliFinding = null) {
|
|
|
351
354
|
out.push(` node scripts/gds/ship.js ${taskId} --regrade`);
|
|
352
355
|
out.push('Or land it now via manual-confirm:');
|
|
353
356
|
out.push(` curl -X POST -H "Authorization: Bearer <token>" ${resolveInstanceBase() || '<instance-origin>'}/api/bongos/tasks/${taskId}/confirm`);
|
|
357
|
+
out.push(...plainCardLines('grade_lens_unavailable'));
|
|
354
358
|
return out.join('\n');
|
|
355
359
|
}
|
|
356
360
|
|
|
@@ -378,6 +382,7 @@ function formatGradeOutcome(g, taskId, cliFinding = null) {
|
|
|
378
382
|
out.push(` node scripts/gds/ship.js ${taskId} --regrade`);
|
|
379
383
|
out.push('Last resort (only if the grade was wrong):');
|
|
380
384
|
out.push(` curl -X POST -H "Authorization: Bearer <token>" ${resolveInstanceBase() || '<instance-origin>'}/api/bongos/tasks/${taskId}/confirm`);
|
|
385
|
+
out.push(...plainCardLines('grade_failed'));
|
|
381
386
|
}
|
|
382
387
|
return out.join('\n');
|
|
383
388
|
}
|
package/scripts/gds/ship-land.js
CHANGED
|
@@ -15,6 +15,7 @@ const { cliClient, isBoxScope } = require('./cli-lib');
|
|
|
15
15
|
const { gitArgvOk, gitOk, sanitizeGitHubRefs, shellOk } = require('./ship-git.js');
|
|
16
16
|
const { buildPrCreateArgv, buildPrMergeArgv, deployRunSucceeded, deployedCommitStatus, fetchDeployedVersion, ghArgvOk, ghJson, pushVia, resolveInstanceBase } = require('./ship-deploy-target.js');
|
|
17
17
|
const { normalizeApiError } = require('./ship-io.js');
|
|
18
|
+
const { plainCardLines } = require('./plain-cards.js');
|
|
18
19
|
|
|
19
20
|
// ---------- the land outcome (task 1002541, part 1) ----------
|
|
20
21
|
//
|
|
@@ -311,6 +312,7 @@ function landPendingLines(taskId, { merged } = {}) {
|
|
|
311
312
|
function printLandPending(taskId, opts = {}) {
|
|
312
313
|
console.log('');
|
|
313
314
|
for (const line of landPendingLines(taskId, opts)) console.log(line);
|
|
315
|
+
for (const line of plainCardLines('land_pending')) console.log(line);
|
|
314
316
|
}
|
|
315
317
|
|
|
316
318
|
// task 1002645 — the honest close-out when the PR MERGED but the server refused the
|
|
@@ -349,6 +351,14 @@ function redPrLine(md) {
|
|
|
349
351
|
return named ? ` ci-land: the PR is RED — the \`${named[1]}\` check failed; ${fix}` : null;
|
|
350
352
|
}
|
|
351
353
|
|
|
354
|
+
// redPrLines — the red line followed by the same reason in plain words (task 1003989).
|
|
355
|
+
// The card is keyed on the merge reason; when the failing tests were readable that
|
|
356
|
+
// reason is `failing_check:<name>`, which resolves to the failing_check card.
|
|
357
|
+
function redPrLines(md) {
|
|
358
|
+
const red = redPrLine(md);
|
|
359
|
+
return red ? [red, ...plainCardLines((md && md.reason) || 'failing_check')] : [];
|
|
360
|
+
}
|
|
361
|
+
|
|
352
362
|
// One module-scope sleep command shared by both poll loops (task 1093 / C9). A
|
|
353
363
|
// child `node -e setTimeout` is used (not `await new Promise`) so it blocks
|
|
354
364
|
// identically in the SYNCHRONOUS pollCiDeploy and the async pollServerPublish —
|
|
@@ -377,7 +387,7 @@ async function pollServerPublish(taskId, branch) {
|
|
|
377
387
|
const red = redPrLine(r.data.merge_driver);
|
|
378
388
|
if (red && red !== redLine) {
|
|
379
389
|
redLine = red;
|
|
380
|
-
console.log(
|
|
390
|
+
for (const line of redPrLines(r.data.merge_driver)) console.log(line);
|
|
381
391
|
}
|
|
382
392
|
const outcome = classifyLandOutcome(r.data);
|
|
383
393
|
if (outcome === 'shipped') {
|
|
@@ -401,6 +411,7 @@ async function pollServerPublish(taskId, branch) {
|
|
|
401
411
|
if (blockedReason) {
|
|
402
412
|
console.log('');
|
|
403
413
|
for (const line of reconcileBlockedLines(taskId, blockedReason)) console.log(line);
|
|
414
|
+
for (const line of plainCardLines('reconcile_blocked')) console.log(line);
|
|
404
415
|
return landBailed(`the PR merged but the server could not prove the land is this task's: ${blockedReason}`, `node scripts/gds/ship.js ${taskId}`);
|
|
405
416
|
}
|
|
406
417
|
printLandPending(taskId);
|
|
@@ -562,6 +573,7 @@ async function ciLandLocal(taskId, branch, valueSummary) {
|
|
|
562
573
|
} else {
|
|
563
574
|
console.log(` ci-land: to retry — ${d.fix}`);
|
|
564
575
|
}
|
|
576
|
+
for (const line of plainCardLines(`ship:${d.id}`)) console.log(line);
|
|
565
577
|
return landBailed(
|
|
566
578
|
`could not push ${branch} to origin (${d.id})`,
|
|
567
579
|
d.terminal ? d.fix.split('\n')[0] : `node scripts/gds/ship.js ${taskId}`,
|
|
@@ -713,6 +725,7 @@ module.exports = {
|
|
|
713
725
|
pickStaleGateReviewRun,
|
|
714
726
|
reconcileBlockedLines,
|
|
715
727
|
redPrLine,
|
|
728
|
+
redPrLines,
|
|
716
729
|
reconciledCardStatus,
|
|
717
730
|
strandCommand,
|
|
718
731
|
strandNextStep,
|