@bongos/core 1.19.319 → 1.19.320
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 +121 -36
- package/.claude/skills/merge-mode/SKILL.md +3 -1
- package/.devcontainer/init-firewall.sh +41 -5
- package/clients/bongos-client/examples/hello-world.mjs +11 -2
- package/docs/copy-inventory.md +20 -20
- package/docs/copy-registry.json +22 -22
- package/modules/hall-ui/public/builders.js +0 -1
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/audit-replay.sh +20 -3
- package/scripts/gds/box.js +79 -86
- package/scripts/gds/db-backup-nightly.sh +17 -4
- package/scripts/gds/db-backup-offsite.sh +23 -6
- package/scripts/gds/discipline-modes.json +1 -1
- package/scripts/gds/doc-cli-guard.js +34 -2
- package/scripts/gds/fitness.js +8 -4
- package/scripts/gds/gds-db-backup.service +15 -4
- package/scripts/gds/gds-db-backup.timer +12 -1
- package/scripts/gds/gds-db-offsite.service +16 -5
- package/scripts/gds/gds-db-offsite.timer +12 -1
- package/scripts/gds/instance-apex.js +28 -0
- package/scripts/gds/newcomer-floor.templates.json +2 -2
- package/scripts/gds/provision.js +7 -3
- package/scripts/gds/ship-deploy-target.js +388 -0
- package/scripts/gds/ship-finish.js +377 -0
- package/scripts/gds/ship-flow.js +814 -0
- package/scripts/gds/ship-git.js +100 -0
- package/scripts/gds/ship-grade-steps.js +376 -0
- package/scripts/gds/ship-grade.js +319 -0
- package/scripts/gds/ship-io.js +100 -0
- package/scripts/gds/ship-land.js +539 -0
- package/scripts/gds/ship-memory-sync.js +192 -0
- package/scripts/gds/ship-merge.js +297 -0
- package/scripts/gds/ship-preflight-steps.js +323 -0
- package/scripts/gds/ship-regen.js +286 -0
- package/scripts/gds/ship-resume.js +151 -0
- package/scripts/gds/ship-session-upload.js +216 -0
- package/scripts/gds/ship-state.js +73 -0
- package/scripts/gds/ship-surprise.js +84 -0
- package/scripts/gds/ship.js +105 -4195
- package/scripts/status-probe.mjs +30 -2
- package/src/module-api.js +3 -1
- package/tests/helpers.mjs +13 -0
- package/tests/provision.mjs +7 -1
- package/tests/regrade_eligibility.mjs +43 -0
- package/tests/ship_assurance.mjs +4 -2
- package/tests/ship_publish_error.mjs +43 -0
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
// scripts/gds/ship-finish.js — merge+deploy orchestration, post-ship sync/closeout and the completion card.
|
|
2
|
+
//
|
|
3
|
+
// Carved out of scripts/gds/ship.js by task 1003205 (goal 1000079, the R03
|
|
4
|
+
// de-monolith plan) — a STRUCTURAL move, not a rewrite: the code below is the
|
|
5
|
+
// original ship.js lines 3008-3352 unchanged. ship.js remains the facade that
|
|
6
|
+
// re-exports every name and owns the CLI entry point, so the ship contract and
|
|
7
|
+
// every flag are untouched, and the other scripts that import from it are too.
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const fs = require('node:fs');
|
|
12
|
+
const os = require('node:os');
|
|
13
|
+
const { cliClient, printOnboardingNudge, rewardPolicy } = require('./cli-lib');
|
|
14
|
+
const { closeoutPlan } = require('../../modules/lifecycle/ship-preflight');
|
|
15
|
+
const { syncMemoryOnShip } = require('./ship-memory-sync.js');
|
|
16
|
+
const { uploadSessionOnShip } = require('./ship-session-upload.js');
|
|
17
|
+
const { gitOk, shellOk } = require('./ship-git.js');
|
|
18
|
+
const { fetchDeployedVersion } = require('./ship-deploy-target.js');
|
|
19
|
+
const { landBailed, reconciledCardStatus } = require('./ship-land.js');
|
|
20
|
+
const { autoMerge } = require('./ship-merge.js');
|
|
21
|
+
const { normalizeApiError, playShipBell, postLandShipOutcome } = require('./ship-io.js');
|
|
22
|
+
const { getServerReward, noteServerReward } = require('./ship-state.js');
|
|
23
|
+
|
|
24
|
+
async function mergeAndDeploy({ taskId, summary, advanceToMerge, skipMerge, dbOnly = false }) {
|
|
25
|
+
if (advanceToMerge && skipMerge) {
|
|
26
|
+
console.log('');
|
|
27
|
+
console.log('--no-merge: stopping at confirmed. Run /merge-mode later to land it.');
|
|
28
|
+
return landBailed('--no-merge was passed, so the land was skipped', '/merge-mode');
|
|
29
|
+
}
|
|
30
|
+
// Grade failed: the card renders the 'failed' stage ('not deployed'), so there
|
|
31
|
+
// is no land outcome to report.
|
|
32
|
+
if (!advanceToMerge) return null;
|
|
33
|
+
|
|
34
|
+
console.log('');
|
|
35
|
+
console.log('Auto-merge chain (confirmed → shipped):');
|
|
36
|
+
const merged = await autoMerge(taskId, summary, { dbOnly });
|
|
37
|
+
// NOTE: `merged` is an OBJECT now — every value is truthy, so this MUST test
|
|
38
|
+
// .ok. A bare `if (!merged)` would fall through on a bail and POST /ship for a
|
|
39
|
+
// task that never landed.
|
|
40
|
+
if (!merged.ok) return merged;
|
|
41
|
+
|
|
42
|
+
const shipBody = merged.noArtifact ? { no_artifact: true, no_artifact_reason: merged.noArtifactReason || 'deliberate empty ship (--db-only)' } : {}; // ADR 0184
|
|
43
|
+
const ship = await (await cliClient()).tasks.postTasksIdShip({ id: taskId, body: shipBody });
|
|
44
|
+
if (!ship.ok) {
|
|
45
|
+
// task 1049 / 1002809: the server may have already flipped this task to
|
|
46
|
+
// shipped (reconcile-on-read, or the reconciler racing this call). Verify
|
|
47
|
+
// the LIVE status before treating the refusal as a failure — the old
|
|
48
|
+
// flat-shape string check never matched the R01 envelope, so this success
|
|
49
|
+
// path was dead and every reconciled land reported as a failed ship.
|
|
50
|
+
const { code, message } = normalizeApiError(ship.data);
|
|
51
|
+
let liveStatus = null;
|
|
52
|
+
try {
|
|
53
|
+
const t = await (await cliClient()).tasks.getTasksId({ id: taskId });
|
|
54
|
+
if (t.ok && t.data && t.data.task) liveStatus = t.data.task.status;
|
|
55
|
+
} catch (_) { /* verify is best-effort; fall back to the code-based call */ }
|
|
56
|
+
const outcome = postLandShipOutcome({ code, liveStatus });
|
|
57
|
+
if (outcome.alreadyShipped) {
|
|
58
|
+
console.log(`Task #${taskId} is now: shipped (${outcome.why}).`);
|
|
59
|
+
} else {
|
|
60
|
+
const e = message || code || `HTTP ${ship.status}`;
|
|
61
|
+
console.error(` /tasks/${taskId}/ship failed: ${e}${liveStatus ? ` (task is at '${liveStatus}')` : ''}`);
|
|
62
|
+
// The merge itself succeeded — only the status flip failed, so a re-run
|
|
63
|
+
// resumes cleanly (task 941). Report it rather than letting the card claim
|
|
64
|
+
// the land is in progress.
|
|
65
|
+
return landBailed(`merged, but the ship call failed — ${e}`, `node scripts/gds/ship.js ${taskId}`);
|
|
66
|
+
}
|
|
67
|
+
} else {
|
|
68
|
+
console.log(`Task #${taskId} is now: shipped`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// task 1442: the task-shipped bell used to play HERE, but this point is only
|
|
72
|
+
// reached on a SYNCHRONOUS merge+deploy. In ci/async landing mode (now the
|
|
73
|
+
// norm) ship.js exits at 'confirmed' and never gets here, so the bell never
|
|
74
|
+
// rang. It now plays at the confirmed/shipped success milestone via
|
|
75
|
+
// playShipBell() (finishShip + resumeMerge), which covers both paths.
|
|
76
|
+
|
|
77
|
+
// If the builder was promoted during this ship, write the promotion flag so
|
|
78
|
+
// the NEXT Claude turn plays the fanfare via the Stop hook.
|
|
79
|
+
const TMP_FLAGS = process.platform === 'win32'
|
|
80
|
+
? (process.env.TEMP || process.env.TMP || os.tmpdir())
|
|
81
|
+
: '/tmp';
|
|
82
|
+
if (ship.data?.task?.graduatedTo) {
|
|
83
|
+
try { fs.writeFileSync(require('path').join(TMP_FLAGS, 'otb-promotion'), ''); } catch (_) {}
|
|
84
|
+
console.log(` Rank promotion: ${ship.data.task.graduatedTo} — fanfare queued.`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
if (summary) {
|
|
89
|
+
// task 1749: the SERVER is now the SINGLE ship broadcaster. It emits
|
|
90
|
+
// task.shipped on the confirmed→shipped transition from every entry point
|
|
91
|
+
// (POST /ship, the publish-status reconcile-on-read, and the 5-min
|
|
92
|
+
// reconciler sweep — all idempotent via db.shipTask), and the discord
|
|
93
|
+
// module posts the #ship-feed line off that one event. This local post used
|
|
94
|
+
// to fire IN ADDITION to the server's, so a single ship announced FOUR
|
|
95
|
+
// messages (local #ship-news + #ship-feed, then the server's #ship-news +
|
|
96
|
+
// #ship-feed). Defer unconditionally so exactly one broadcast fires — from
|
|
97
|
+
// the server, after the deploy lands. (#ship-news itself is paused in
|
|
98
|
+
// chooseBroadcastTargets, so that one broadcast is the #ship-feed line —
|
|
99
|
+
// task 1749 / idea 458.) The security-sensitive fail-closed routing lives
|
|
100
|
+
// server-side in ship-broadcast.postShipBroadcast, so it is preserved.
|
|
101
|
+
console.log(' Discord broadcast: deferred to the server — it posts #ship-feed after the deploy lands.');
|
|
102
|
+
}
|
|
103
|
+
return merged;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Memory + session-digest push, gated on a successful land (confirmed/shipped).
|
|
107
|
+
// Both are best-effort — a failure here is a warning, never a ship failure.
|
|
108
|
+
async function postShipSync(taskId, postGradeStatus) {
|
|
109
|
+
if (postGradeStatus !== 'confirmed' && postGradeStatus !== 'shipped') return null;
|
|
110
|
+
console.log('');
|
|
111
|
+
console.log('Memory sync (best-effort):');
|
|
112
|
+
await syncMemoryOnShip();
|
|
113
|
+
console.log('');
|
|
114
|
+
console.log('Session upload (best-effort):');
|
|
115
|
+
// task 1270: the upload returns { totalTokens, modelUsage } (or null) so the
|
|
116
|
+
// completion card can show Claude usage + an estimated cost.
|
|
117
|
+
return await uploadSessionOnShip(taskId);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Post-ship onboarding nudge (V3.R13 #237). Best-effort — never let a stale /me
|
|
121
|
+
// read make the script exit non-zero after a successful ship.
|
|
122
|
+
async function onboardingNudgeBestEffort() {
|
|
123
|
+
try {
|
|
124
|
+
const meRes = noteServerReward(await (await cliClient()).me.getMe());
|
|
125
|
+
if (meRes.ok) {
|
|
126
|
+
console.log('');
|
|
127
|
+
printOnboardingNudge(meRes.data);
|
|
128
|
+
}
|
|
129
|
+
} catch (_) {
|
|
130
|
+
/* never block on a UX nudge */
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// finishShip — the confirmed→shipped tail shared by shipMain + regradeMain (task
|
|
135
|
+
// 1093 / C9): auto-merge + deploy, then the best-effort memory/session sync, the
|
|
136
|
+
// Done line, and the onboarding nudge. Only this UNIFORM tail is shared;
|
|
137
|
+
// printGradeOutcome + printCreditsAndAchievements stay caller-owned (their inputs
|
|
138
|
+
// differ between the two paths). `doneLine` is a param so each caller keeps its
|
|
139
|
+
// own wording ('Done.' vs the re-grade pass/fail message).
|
|
140
|
+
// task 932 (Lars 2026-06-12): deterministic, no-LLM post-ship working-tree
|
|
141
|
+
// close-out, folded into the delivery process (NOT a separate skill). After a
|
|
142
|
+
// ship lands, scratch often lingers uncommitted — untracked files the preflight
|
|
143
|
+
// lets through, generated artifacts, post-commit edits — making the session
|
|
144
|
+
// hard to archive cleanly. This reports what's left and stashes it under a
|
|
145
|
+
// labeled, RECOVERABLE stash so the tree is left clean + archive-ready and
|
|
146
|
+
// nothing is lost. STRICTLY fail-open: the ship has already succeeded by the
|
|
147
|
+
// time this runs, so nothing here may throw, prompt, or change the exit status —
|
|
148
|
+
// a messy tree (or a stash failure) must never surface as a ship failure. The
|
|
149
|
+
// caller gates this on a SUCCESSFUL land so a grade-failed 'completed' tree the
|
|
150
|
+
// builder still needs to fix is never stashed out from under them.
|
|
151
|
+
function postShipCloseout(taskId) {
|
|
152
|
+
try {
|
|
153
|
+
const out = gitOk('git status --porcelain');
|
|
154
|
+
if (out == null) return; // git unavailable / not a repo — nothing to reconcile
|
|
155
|
+
const plan = closeoutPlan(out, taskId);
|
|
156
|
+
if (plan.clean) {
|
|
157
|
+
console.log('Close-out: working tree clean — session is archive-ready.');
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
console.log('');
|
|
161
|
+
console.log(`Close-out: ${plan.leftover.length} uncommitted/untracked path(s) remain after the ship:`);
|
|
162
|
+
for (const f of plan.leftover.slice(0, 15)) console.log(` ${f}`);
|
|
163
|
+
if (plan.leftover.length > 15) console.log(` … and ${plan.leftover.length - 15} more`);
|
|
164
|
+
// --include-untracked so scratch the preflight waved through is captured too.
|
|
165
|
+
// Label is `otb-postship-<numericId>` — no shell metacharacters.
|
|
166
|
+
const stashed = shellOk(`git stash push --include-untracked -m "${plan.stashLabel}"`);
|
|
167
|
+
if (stashed) {
|
|
168
|
+
console.log(`Close-out: stashed the leftover as "${plan.stashLabel}" — tree is now clean + archive-ready.`);
|
|
169
|
+
console.log(' Recover anytime: git stash list → git stash pop');
|
|
170
|
+
} else {
|
|
171
|
+
console.log('Close-out: could not stash the leftover; left it in place (the ship already succeeded — tidy up manually when convenient).');
|
|
172
|
+
}
|
|
173
|
+
} catch (err) {
|
|
174
|
+
// Fail-open — a post-ship close-out problem must never look like a ship failure.
|
|
175
|
+
console.log(`Close-out: skipped (${err && err.message ? err.message : 'error'}).`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async function finishShip({ taskId, summary, advanceToMerge, skipMerge, postGradeStatus, doneLine, card, dbOnly = false }) {
|
|
180
|
+
// task 1002541: the land outcome carries WHY a land didn't happen. Without it
|
|
181
|
+
// the card had only the status to go on and printed 'landing automatically'
|
|
182
|
+
// over a merge that had already bailed.
|
|
183
|
+
const land = await mergeAndDeploy({ taskId, summary, advanceToMerge, skipMerge, dbOnly });
|
|
184
|
+
// task 1719: mergeAndDeploy may have landed the task to 'shipped' IN-SESSION — the
|
|
185
|
+
// enable-success poll (pollCiDeploy) or, since task 1718, the enable-failure server
|
|
186
|
+
// poll (pollServerPublish). finishShip was handed postGradeStatus from the CONFIRM
|
|
187
|
+
// step ('confirmed'), so without re-reading it the completion card renders the stale
|
|
188
|
+
// 'confirmed — landing' banner AND printCompletionCard writes a pending-land
|
|
189
|
+
// breadcrumb — the SHIPPED banner then only appears on a LATER prompt, in whatever
|
|
190
|
+
// session is next (the owner-reported confusion). Re-fetch the authoritative status
|
|
191
|
+
// (exactly as resumeMerge does, task 1275) so a real in-session land shows the
|
|
192
|
+
// SHIPPED banner here and writes no breadcrumb. Best-effort: any read failure keeps
|
|
193
|
+
// today's behavior; the grade-fail path (advanceToMerge=false) never re-fetches.
|
|
194
|
+
let landedStatus = postGradeStatus;
|
|
195
|
+
if (advanceToMerge && (postGradeStatus === 'confirmed' || postGradeStatus === 'shipped')) {
|
|
196
|
+
try {
|
|
197
|
+
const t = await (await cliClient()).tasks.getTasksId({ id: taskId });
|
|
198
|
+
const live = (t.ok && t.data && t.data.task) ? t.data.task.status : null;
|
|
199
|
+
landedStatus = reconciledCardStatus(live, postGradeStatus);
|
|
200
|
+
} catch (_) { /* keep postGradeStatus — the land above already awaited its outcome */ }
|
|
201
|
+
}
|
|
202
|
+
// Memory + session sync (#274 / 6D.1 / ADR 0027) — postShipSync itself gates on
|
|
203
|
+
// a successful land (confirmed/shipped); both legs are best-effort. It returns
|
|
204
|
+
// the session usage ({ totalTokens, modelUsage } | null) so the completion card
|
|
205
|
+
// can show Claude usage + an estimated cost (task 1270).
|
|
206
|
+
const usage = await postShipSync(taskId, landedStatus);
|
|
207
|
+
console.log('');
|
|
208
|
+
console.log(doneLine);
|
|
209
|
+
await onboardingNudgeBestEffort();
|
|
210
|
+
// task 1270: the ONE deterministic completion card — the same shape every ship.
|
|
211
|
+
// Replaces the agent's free-form prose recap (the builder-ship skill renders the
|
|
212
|
+
// rich in-feed card from the HTML this writes; the monospace card below is the
|
|
213
|
+
// always-printed fallback + the headless summary). Strictly best-effort.
|
|
214
|
+
// task 1719: pass the reconciled status so a just-landed ship shows SHIPPED (and
|
|
215
|
+
// writes no breadcrumb), not the stale pre-merge 'confirmed'.
|
|
216
|
+
await printCompletionCard({ taskId, postGradeStatus: landedStatus, card, usage, land });
|
|
217
|
+
// task 932: reconcile the working tree — ONLY on a successful land. A
|
|
218
|
+
// grade-failed ship stays at 'completed'; leave that tree untouched so the
|
|
219
|
+
// builder can fix findings + re-ship.
|
|
220
|
+
if (landedStatus === 'confirmed' || landedStatus === 'shipped') {
|
|
221
|
+
// task 1442: ring the ship bell at the success milestone. 'confirmed' counts —
|
|
222
|
+
// in ci/async mode the work is done + credited here and the deploy lands later
|
|
223
|
+
// server-side, so this is the moment the builder experiences as "shipped".
|
|
224
|
+
playShipBell();
|
|
225
|
+
postShipCloseout(taskId);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// task 1275: sum the committed diffstat (added/removed lines) over baseline...HEAD.
|
|
230
|
+
// Shared by the completion card and the in-review card. Best-effort: returns
|
|
231
|
+
// nulls when there's no baseline or git is unavailable.
|
|
232
|
+
function diffstatFor(baseline) {
|
|
233
|
+
if (!baseline) return { added: null, removed: null };
|
|
234
|
+
const numstat = gitOk(`git diff --numstat ${baseline}...HEAD`) || gitOk(`git diff --numstat ${baseline} HEAD`);
|
|
235
|
+
if (!numstat) return { added: null, removed: null };
|
|
236
|
+
let added = 0;
|
|
237
|
+
let removed = 0;
|
|
238
|
+
for (const line of numstat.split('\n')) {
|
|
239
|
+
const [a, r] = line.split('\t');
|
|
240
|
+
if (a !== '-' && a != null && a !== '') added += Number(a) || 0;
|
|
241
|
+
if (r !== '-' && r != null && r !== '') removed += Number(r) || 0;
|
|
242
|
+
}
|
|
243
|
+
return { added, removed };
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// task 1270: build + print the deterministic completion card. Prints the
|
|
247
|
+
// alignment-proof monospace card to stdout (always), then writes the branded
|
|
248
|
+
// in-feed HTML to a temp file and prints an [otb-card-html] marker the
|
|
249
|
+
// builder-ship skill picks up to render it in the chat feed. Every step is
|
|
250
|
+
// best-effort — a summary card must NEVER fail or undo a completed ship.
|
|
251
|
+
async function printCompletionCard({ taskId, postGradeStatus, card, usage, land = null }) {
|
|
252
|
+
if (!card) return;
|
|
253
|
+
try {
|
|
254
|
+
const shipCard = require('../../modules/lifecycle/ship-card');
|
|
255
|
+
|
|
256
|
+
// Diffstat from the committed range the grader used (baseline...HEAD).
|
|
257
|
+
const fileCount = Array.isArray(card.files) && card.files.length ? card.files.length : null;
|
|
258
|
+
const { added, removed } = diffstatFor(card.baseline);
|
|
259
|
+
|
|
260
|
+
// Deployed commit (shipped only): prefer prod /version, fall back to short HEAD.
|
|
261
|
+
let commit = null;
|
|
262
|
+
if (postGradeStatus === 'shipped') {
|
|
263
|
+
const v = fetchDeployedVersion();
|
|
264
|
+
commit = (v && v.commit) ? String(v.commit).slice(0, 7) : (gitOk('git rev-parse --short HEAD') || null);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Running drachmae total + the builder's render-widgets knob (task 1279),
|
|
268
|
+
// both from one /me fetch (best-effort).
|
|
269
|
+
let total = null;
|
|
270
|
+
let widgetsEnabled = true;
|
|
271
|
+
try {
|
|
272
|
+
const me = noteServerReward(await (await cliClient()).me.getMe());
|
|
273
|
+
if (me.ok && me.data) {
|
|
274
|
+
if (me.data.builder) total = me.data.builder.total_credits;
|
|
275
|
+
if (me.data.render) widgetsEnabled = me.data.render.widgets_enabled !== false;
|
|
276
|
+
}
|
|
277
|
+
} catch (_) { /* total + knob are optional; default widgets on */ }
|
|
278
|
+
|
|
279
|
+
// Token count + estimated cost from the session usage. Split the tokens into
|
|
280
|
+
// 'new' (fresh input + output + first-time cache writes) vs 'cached' re-reads
|
|
281
|
+
// (cache_read — the same large context re-read every turn) so the card can
|
|
282
|
+
// lead with cost and not with a single all-in count that cache re-reads make
|
|
283
|
+
// look alarmingly huge (task 1765). The per-model breakdown is already on hand
|
|
284
|
+
// via usage.modelUsage — no new server data needed.
|
|
285
|
+
let tokens = null;
|
|
286
|
+
let costUsd = null;
|
|
287
|
+
let newTokens = null;
|
|
288
|
+
let cachedTokens = null;
|
|
289
|
+
if (usage) {
|
|
290
|
+
tokens = usage.totalTokens;
|
|
291
|
+
const mu = usage.modelUsage || {};
|
|
292
|
+
let cache = 0;
|
|
293
|
+
let fresh = 0;
|
|
294
|
+
let sawUsage = false;
|
|
295
|
+
for (const u of Object.values(mu)) {
|
|
296
|
+
if (!u) continue;
|
|
297
|
+
sawUsage = true;
|
|
298
|
+
cache += Number(u.cache_read) || 0;
|
|
299
|
+
fresh += (Number(u.input) || 0) + (Number(u.output) || 0) + (Number(u.cache_creation) || 0);
|
|
300
|
+
}
|
|
301
|
+
if (sawUsage) { newTokens = fresh; cachedTokens = cache; }
|
|
302
|
+
try {
|
|
303
|
+
const { priceModelUsage } = require('../../src/bongos/llm-pricing');
|
|
304
|
+
costUsd = priceModelUsage(mu).true_cost_usd;
|
|
305
|
+
} catch (_) { /* cost is optional */ }
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const cardInputs = {
|
|
309
|
+
postGradeStatus,
|
|
310
|
+
taskId,
|
|
311
|
+
title: card.title,
|
|
312
|
+
valueSummary: card.valueSummary,
|
|
313
|
+
notes: card.notes,
|
|
314
|
+
fileCount,
|
|
315
|
+
added,
|
|
316
|
+
removed,
|
|
317
|
+
grade: card.grade || null,
|
|
318
|
+
credits: { awarded: card.creditsAwarded, total, perTaskCredit: rewardPolicy(getServerReward()).perTaskCredit },
|
|
319
|
+
usage: { tokens, costUsd, newTokens, cachedTokens },
|
|
320
|
+
deploy: { commit },
|
|
321
|
+
// task 1002541: absent (the default) ⇒ the card behaves exactly as before,
|
|
322
|
+
// which keeps every other caller — resumeMerge, the claim card, and
|
|
323
|
+
// land-watch replaying breadcrumbs written by older versions — unchanged.
|
|
324
|
+
land,
|
|
325
|
+
};
|
|
326
|
+
const model = shipCard.buildCardModel(cardInputs);
|
|
327
|
+
|
|
328
|
+
// Print the monospace card + write the branded HTML for the in-feed renderer
|
|
329
|
+
// (shared emitCard, task 1275). The skill reads the printed path and passes
|
|
330
|
+
// the contents to the visualization tool — the agent authors nothing, so the
|
|
331
|
+
// card is identical every ship. task 1279: when the builder has widgets off,
|
|
332
|
+
// emitCard prints only the monospace card (no HTML written or relayed).
|
|
333
|
+
shipCard.emitCard(model, taskId, { widgetsEnabled });
|
|
334
|
+
|
|
335
|
+
// task 1687: in CI deploy mode this card is 'confirmed — landing' and the real
|
|
336
|
+
// confirmed->shipped land happens minutes later, server-side — so the SHIPPED
|
|
337
|
+
// banner would never render in-session. Drop a pending-land breadcrumb; the
|
|
338
|
+
// land-watch hook renders the shipped card on a later prompt once it lands.
|
|
339
|
+
// Best-effort: a breadcrumb must never fail a ship (mirrors the card itself).
|
|
340
|
+
if (postGradeStatus === 'confirmed') {
|
|
341
|
+
try { require('./land-watch').writeBreadcrumb(taskId, { widgetsEnabled, cardInputs }); }
|
|
342
|
+
catch (_) { /* breadcrumb is best-effort */ }
|
|
343
|
+
}
|
|
344
|
+
} catch (_) {
|
|
345
|
+
/* a summary card must never fail a ship */
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// task 1002521: the card above says SHIPPED, which means "merged" — it does
|
|
349
|
+
// NOT mean users can see it. Publishing is manual and owner-gated, so a ship
|
|
350
|
+
// can land on main and change nothing live, and until now nothing said so.
|
|
351
|
+
// Blocker 1000097: 171 commits (a whole governance system) sat unreleased for
|
|
352
|
+
// ten days while every layer reported success. Warn, never block — a shipped
|
|
353
|
+
// task is still shipped, and an offline builder must not be punished for it.
|
|
354
|
+
try {
|
|
355
|
+
const { check, report } = require('./deploy-divergence');
|
|
356
|
+
const r = await check();
|
|
357
|
+
if (!r.undetermined && !r.ok) report(r);
|
|
358
|
+
} catch (_) {
|
|
359
|
+
/* a divergence warning must never fail a ship */
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Decide whether a builder may re-grade a task via `--regrade` (#678). Pure so
|
|
364
|
+
// it's unit-testable. A re-grade is allowed iff the task is stuck at `completed`
|
|
365
|
+
// (a ship whose grade failed) AND the caller either last held the claim or is an
|
|
366
|
+
// Archon. Every other status gets a tailored, actionable message. Note this is a
|
|
367
|
+
// client-side UX guard, not a trust boundary: /tasks/:id/grade is rank-open by
|
|
368
|
+
// design (the subagent grade is the authority, not builder identity).
|
|
369
|
+
|
|
370
|
+
module.exports = {
|
|
371
|
+
diffstatFor,
|
|
372
|
+
finishShip,
|
|
373
|
+
mergeAndDeploy,
|
|
374
|
+
onboardingNudgeBestEffort,
|
|
375
|
+
postShipSync,
|
|
376
|
+
printCompletionCard,
|
|
377
|
+
};
|