@bongos/core 1.19.284 → 1.19.285
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 +12 -12
- package/.claude/skills/builder-claim/SKILL.md +1 -1
- package/.claude/skills/builder-ship/SKILL.md +7 -5
- package/modules/lifecycle/ship-card.js +55 -1
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/src/module-api.js +3 -1
- package/tests/ship_card.mjs +79 -0
package/.bongos-core.json
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
"artifact": "bongos-core",
|
|
3
3
|
"manifest_schema": 1,
|
|
4
4
|
"generator": "scripts/gds/package-core.js",
|
|
5
|
-
"core_version": "1.19.
|
|
6
|
-
"core_contract": "1.19.
|
|
7
|
-
"source_commit": "
|
|
5
|
+
"core_version": "1.19.285",
|
|
6
|
+
"core_contract": "1.19.285",
|
|
7
|
+
"source_commit": "81e2e3e6b0bcdad21011218f05f70fd10f3743e4",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-08-21T22:
|
|
9
|
+
"built_at": "2026-08-21T22:12:55.612Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
12
|
"docs_redacted": 319,
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
19
|
"file_count": 1723,
|
|
20
|
-
"tree_sha256": "
|
|
20
|
+
"tree_sha256": "c9778ab2cefac816ecfa4b07061af10f283ac6c42db6bc8b76c7321acc447baa",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/blocker-review/SKILL.md",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
{
|
|
43
43
|
"path": ".claude/skills/builder-claim/SKILL.md",
|
|
44
44
|
"mode": "0000644",
|
|
45
|
-
"sha256": "
|
|
45
|
+
"sha256": "4e6aeafd02b609df2a7651e11238a1b4c2f45b61f8a8f4122200c530aae81b25"
|
|
46
46
|
},
|
|
47
47
|
{
|
|
48
48
|
"path": ".claude/skills/builder-connect/SKILL.md",
|
|
@@ -97,7 +97,7 @@
|
|
|
97
97
|
{
|
|
98
98
|
"path": ".claude/skills/builder-ship/SKILL.md",
|
|
99
99
|
"mode": "0000644",
|
|
100
|
-
"sha256": "
|
|
100
|
+
"sha256": "d3ef4970dd1d3806dc43cd21a0d66c758cb364b538437ae365542d8ad2be9d96"
|
|
101
101
|
},
|
|
102
102
|
{
|
|
103
103
|
"path": ".claude/skills/builder-stage/SKILL.md",
|
|
@@ -3892,7 +3892,7 @@
|
|
|
3892
3892
|
{
|
|
3893
3893
|
"path": "modules/lifecycle/ship-card.js",
|
|
3894
3894
|
"mode": "0000644",
|
|
3895
|
-
"sha256": "
|
|
3895
|
+
"sha256": "2a0485ae7a846e8cb4673873fc1879f7ae354e5375af01f6a30f4bd7e6882f91"
|
|
3896
3896
|
},
|
|
3897
3897
|
{
|
|
3898
3898
|
"path": "modules/lifecycle/ship-preflight.js",
|
|
@@ -4507,12 +4507,12 @@
|
|
|
4507
4507
|
{
|
|
4508
4508
|
"path": "package-lock.json",
|
|
4509
4509
|
"mode": "0000644",
|
|
4510
|
-
"sha256": "
|
|
4510
|
+
"sha256": "d9c3039ff1ab07335c611517b4baaf896c77f88b1d5d1ddd72a2c0f62cb6efff"
|
|
4511
4511
|
},
|
|
4512
4512
|
{
|
|
4513
4513
|
"path": "package.json",
|
|
4514
4514
|
"mode": "0000644",
|
|
4515
|
-
"sha256": "
|
|
4515
|
+
"sha256": "ddea3b764a2952a78e139d4c6d1439a6b1972623ffa166bcc977dcf808e2c606"
|
|
4516
4516
|
},
|
|
4517
4517
|
{
|
|
4518
4518
|
"path": "public-docs/index.html",
|
|
@@ -6157,7 +6157,7 @@
|
|
|
6157
6157
|
{
|
|
6158
6158
|
"path": "src/module-api.js",
|
|
6159
6159
|
"mode": "0000644",
|
|
6160
|
-
"sha256": "
|
|
6160
|
+
"sha256": "fa343e5d3af72e2687d2bcf563cb33d1e374f8e36d9a89539831b1cc8cc3f6fa"
|
|
6161
6161
|
},
|
|
6162
6162
|
{
|
|
6163
6163
|
"path": "src/module-loader/catalog.js",
|
|
@@ -8332,7 +8332,7 @@
|
|
|
8332
8332
|
{
|
|
8333
8333
|
"path": "tests/ship_card.mjs",
|
|
8334
8334
|
"mode": "0000644",
|
|
8335
|
-
"sha256": "
|
|
8335
|
+
"sha256": "9fa0cd147650022ada782721f5a79ca19f9b24810c7ac16b2c11a46b585f0caf"
|
|
8336
8336
|
},
|
|
8337
8337
|
{
|
|
8338
8338
|
"path": "tests/ship_ci_deploy.mjs",
|
|
@@ -50,7 +50,7 @@ The server now **enforces** the one-claim half (task 1642): `POST /api/gds/claim
|
|
|
50
50
|
|
|
51
51
|
4. **On success**, relay the claim id, task title, version, priority, credit reward, and the `touches[]` to the user. Treat `touches[]` as an **advisory hint** at the expected blast radius — NOT an editing fence. Code legitimately wanders into files nobody predicted; that is fine and no longer ship-blocking (the grader reads the real committed diff, and `git merge` is the authoritative collision detector). If the claim returned an `overlap_warning`, relay it too — it just means another active claim declares overlapping files; the user self-serializes only if it's genuinely the same code.
|
|
52
52
|
|
|
53
|
-
**Render the claimed card (task 1275).** _As of task 1288 the lifecycle-card `PostToolUse` hook (`.claude/hooks/lifecycle-card.js`) reads this card and injects a one-step render directive right after `claim.js` runs — normally just follow that injected directive and render **once**; the detail below is the fallback if no directive appears._ `claim.js` prints the deterministic lifecycle card for the `claimed` stage — a monospace card plus, when the builder has widgets on (the default), an `[otb-card-html] <path>` marker. If that marker is present **and** an in-feed visualization tool is available (e.g. `show_widget`), **Read that file and pass its exact contents** to the tool as `widget_code` (title like `task_<id>_claimed`); it shows the scope (priority / estimate / touches), the brief in its dropdown, and an "Open the work" action routed to the task's discipline session. **Author nothing yourself and don't restate the claim in prose** — the card is the summary. If instead you see an `[otb-card] widgets are off …` line (the builder disabled widgets via Settings → Rendering, task 1279) — or no visualization tool is available — the monospace card already printed IS the summary; relay it as-is and do not call show_widget.
|
|
53
|
+
**Render the claimed card (task 1275).** _As of task 1288 the lifecycle-card `PostToolUse` hook (`.claude/hooks/lifecycle-card.js`) reads this card and injects a one-step render directive right after `claim.js` runs — normally just follow that injected directive and render **once**; the detail below is the fallback if no directive appears._ `claim.js` prints the deterministic lifecycle card for the `claimed` stage — a monospace card plus, when the builder has widgets on (the default), an `[otb-card-html] <path>` marker. If that marker is present **and** an in-feed visualization tool is available (e.g. `show_widget`), **Read that file and pass its exact contents** to the tool as `widget_code` (title like `task_<id>_claimed`); it shows the scope (priority / estimate / touches), the brief in its dropdown, and an "Open the work" action routed to the task's discipline session. **Author nothing yourself and don't restate the claim in prose** — the card is the summary. If instead you see an `[otb-card] widgets are off …` line (the builder disabled widgets via Settings → Rendering, task 1279) — or no visualization tool is available — the monospace card already printed IS the summary; relay it as-is and do not call show_widget. **One exception, always: relay the `[otb-confirm]` line as text** (task 1001730). `show_widget` reports success whether or not the client actually painted, and CLI stdout is not reliably shown to the user either — so that single line is the only confirmation channel that cannot silently vanish. Verbatim, one line, in addition to the card.
|
|
54
54
|
|
|
55
55
|
5. **On `ALREADY_CLAIMED`**: another session beat us to it. Run `/builder-start` again to get a fresh list and let the user pick something else.
|
|
56
56
|
|
|
@@ -65,16 +65,18 @@ The CLI drives a **three-state lifecycle** with a subagent grader between states
|
|
|
65
65
|
**Never merge a task branch to main before running `ship.js`.** `ship.js` diffs the claim branch against `main`; an already-merged branch yields an empty diff, and the only exits are `--allow-empty` (dead-ends the task at `completed`, requiring an Archon-only confirm) or reverting the merge. Ship from the branch, then let `ship.js` land it. (This trap was hit on tasks 1002417 and 1002419, producing blockers 1000081/1000082 and an ~8.5-hour stall.)
|
|
66
66
|
|
|
67
67
|
5. **Render the completion card — it IS the summary; do NOT write a prose recap (task 1270).**
|
|
68
|
-
|
|
68
|
+
_(Task 1288's lifecycle-card `PostToolUse` hook is no longer part of the core — `.claude/hooks/` ships only `secret-gate.js`, verified task 1001730. If an instance installs such a hook and it injects a render directive, follow it and render **once**; otherwise the steps below ARE the procedure, not a fallback.)_
|
|
69
69
|
On every ship, `ship.js` prints ONE deterministic completion card — the same shape every time, only the data changes. It always prints a monospace card to stdout, and for a shipped / confirmed / grade-failed outcome it ALSO writes a branded HTML card to a temp file and prints two marker lines:
|
|
70
70
|
```
|
|
71
|
+
[otb-confirm] shipped: task <id> · commit <sha> · credits +N — <title>
|
|
71
72
|
[otb-card-html] /tmp/otb-ship-card-<id>.html
|
|
72
|
-
[otb-card] builder-ship: render the HTML ...
|
|
73
|
+
[otb-card] builder-ship: render the HTML ... AND relay this one line as text ...
|
|
73
74
|
```
|
|
74
|
-
-
|
|
75
|
+
- **ALWAYS relay the `[otb-confirm]` line as text in your message** (task 1001730). This is the *guaranteed* confirmation and it is not optional. `show_widget` returns "Content rendered and shown to the user" whether or not the client actually painted the frame — so a widget can silently fail to appear and neither you nor the tool can tell. The owner then gets **no** ship confirmation at all and has to ask "did it land?" (owner, 2026-07-02: *"why didn't it show the shipped card then?"*). One line of text cannot be silently dropped, so it is the floor beneath the card. Relay it verbatim; do not reword or expand it.
|
|
76
|
+
- If the `[otb-card-html] <path>` line is present **and** an in-feed visualization tool is available (e.g. `show_widget`): **Read that file and pass its exact contents** to the tool as `widget_code` (title like `task_<id>_shipped`), *in addition to* the `[otb-confirm]` line. The card renders in the chat feed. **Author nothing yourself** — the HTML is generated, so the card is byte-identical every ship; that determinism is the whole point.
|
|
75
77
|
- If instead you see an **`[otb-card] widgets are off …`** line (the builder disabled widgets via Settings → Rendering — task 1279), there is no HTML file by design: **relay the monospace card as plain text and do NOT call show_widget.** Don't try to "restore" the widget — the absence is the saving.
|
|
76
|
-
- If no visualization tool is available at all (headless / cron / a session without it), the **monospace card already printed IS the summary** — stop there.
|
|
77
|
-
- **
|
|
78
|
+
- If no visualization tool is available at all (headless / cron / a session without it), the **monospace card already printed IS the summary** — relay the `[otb-confirm]` line and stop there.
|
|
79
|
+
- **Beyond that one line: do NOT add a free-form recap** of what shipped, credits, files, cost, or next steps. The card already carries all of it, and its built-in action ("Start next task" / "Land it" / "Fix & re-grade") is the next-step prompt — you don't need to restate it. This replaces the old per-ship prose summary the owner asked us to retire. (The `[otb-confirm]` line is the deliberate exception, and it is *one line* — that is what keeps the redundancy trivial when the card does paint.)
|
|
78
80
|
- The ONLY thing you may add is a single **required** action the card can't express — e.g. a sandbox `🔍 REVIEW REQUIRED` URL from a review pause (see step 1), or a blocker that needs the owner. One line, then stop.
|
|
79
81
|
- A Hero sound + Discord broadcast may still fire on a milestone (CLAUDE.md §2); that's automatic — no need to narrate it.
|
|
80
82
|
|
|
@@ -577,11 +577,57 @@ function renderCardHtml(model) {
|
|
|
577
577
|
// and NOT call show_widget.
|
|
578
578
|
//
|
|
579
579
|
// Strictly best-effort — a summary card must never fail or undo a CLI action.
|
|
580
|
+
// task 1001730 — THE GUARANTEED CONFIRMATION. One line, plain text, always
|
|
581
|
+
// emitted, carrying the facts the owner actually asked for ("did it land?"):
|
|
582
|
+
// stage, task id, and the stage's own key facts (commit / credits / grade).
|
|
583
|
+
//
|
|
584
|
+
// WHY a line and not the card: show_widget is best-effort in a direction the
|
|
585
|
+
// agent cannot see. The tool answers "Content rendered and shown to the user"
|
|
586
|
+
// whether or not the client actually painted the frame, so no amount of care on
|
|
587
|
+
// this side can make the WIDGET deterministic. Determinism has to come from a
|
|
588
|
+
// channel that cannot silently drop — the conversation text itself. The card
|
|
589
|
+
// stays the rich confirmation; this line is the floor beneath it.
|
|
590
|
+
//
|
|
591
|
+
// Deliberately ONE line: it is redundant with the card on every successful
|
|
592
|
+
// paint, so it has to be cheap enough that the redundancy costs nothing.
|
|
593
|
+
// Pure + exported so every stage's wording is pinned by a test.
|
|
594
|
+
function confirmLine(model) {
|
|
595
|
+
const m = model || {};
|
|
596
|
+
const id = m.taskId != null ? `task ${m.taskId}` : 'task';
|
|
597
|
+
const word = (m.cfg && m.cfg.statusWord) ? String(m.cfg.statusWord).toLowerCase() : String(m.stage || 'done');
|
|
598
|
+
// The stage's own rows already hold exactly the facts that stage cares about
|
|
599
|
+
// (buildCardModel picks them per stage); reuse them rather than re-deriving,
|
|
600
|
+
// so a new stage fact shows up here for free instead of drifting. The live row
|
|
601
|
+
// keys are Changed · Grade · Credits · Usage · Deploy — `Deploy` is where the
|
|
602
|
+
// commit rides ("live · ea509d5"), which is why there is no Commit key to keep.
|
|
603
|
+
// Usage (token spend) is deliberately dropped: it is not part of "did it land".
|
|
604
|
+
const keep = ['Changed', 'Grade', 'Credits', 'Deploy', 'Status'];
|
|
605
|
+
const byKey = new Map((Array.isArray(m.rows) ? m.rows : []).filter((r) => r && r.k).map((r) => [r.k, r.v]));
|
|
606
|
+
const bits = [];
|
|
607
|
+
for (const k of keep) {
|
|
608
|
+
if (!byKey.has(k)) continue;
|
|
609
|
+
const v = String(byKey.get(k));
|
|
610
|
+
// Skip the key prefix when the value already says the word ("+38 credits"),
|
|
611
|
+
// so the line never reads "credits +38 credits".
|
|
612
|
+
bits.push(v.toLowerCase().includes(k.toLowerCase()) ? v : `${k.toLowerCase()} ${v}`);
|
|
613
|
+
}
|
|
614
|
+
const title = m.title ? ` — ${String(m.title).slice(0, 60)}` : '';
|
|
615
|
+
return `${word}: ${id}${bits.length ? ' · ' + bits.join(' · ') : ''}${title}`;
|
|
616
|
+
}
|
|
617
|
+
|
|
580
618
|
function emitCard(model, taskId, { log = console.log, widgetsEnabled = true } = {}) {
|
|
581
619
|
try {
|
|
582
620
|
log('');
|
|
583
621
|
log(renderMonospaceCard(model));
|
|
584
622
|
} catch (_) { return null; }
|
|
623
|
+
// task 1001730: emitted BEFORE the widget branch and on EVERY path, so the
|
|
624
|
+
// owner has a confirmation even if everything downstream of here fails.
|
|
625
|
+
let confirm = null;
|
|
626
|
+
try {
|
|
627
|
+
confirm = confirmLine(model);
|
|
628
|
+
log('');
|
|
629
|
+
log(`[otb-confirm] ${confirm}`);
|
|
630
|
+
} catch (_) { /* best-effort; never break a CLI action over a summary */ }
|
|
585
631
|
if (!widgetsEnabled) {
|
|
586
632
|
try {
|
|
587
633
|
log('');
|
|
@@ -594,7 +640,13 @@ function emitCard(model, taskId, { log = console.log, widgetsEnabled = true } =
|
|
|
594
640
|
fs.writeFileSync(file, renderCardHtml(model), 'utf8');
|
|
595
641
|
log('');
|
|
596
642
|
log(`[otb-card-html] ${file}`);
|
|
597
|
-
|
|
643
|
+
// task 1001730: the old directive ended "then stop — no prose recap", which
|
|
644
|
+
// is precisely what made a silent non-paint fatal — the agent was told to
|
|
645
|
+
// say nothing, so a dropped widget frame left the owner with NO confirmation
|
|
646
|
+
// at all (owner, 2026-07-02: "why didn't it show the shipped card then?").
|
|
647
|
+
// Now the [otb-confirm] line above is ALWAYS relayed in text as well. Still
|
|
648
|
+
// no long recap — one line, so the redundancy is trivial when the card paints.
|
|
649
|
+
log(`[otb-card] render the HTML at the path above in the chat feed via the visualization tool (show_widget) AND relay this one line as text so the confirmation cannot be lost if the widget does not paint: "${confirm || ''}". No other prose recap. If the tool is unavailable, the card above IS the summary.`);
|
|
598
650
|
return file;
|
|
599
651
|
} catch (_) {
|
|
600
652
|
return null;
|
|
@@ -603,6 +655,8 @@ function emitCard(model, taskId, { log = console.log, widgetsEnabled = true } =
|
|
|
603
655
|
|
|
604
656
|
module.exports = {
|
|
605
657
|
STAGES, TONE, stageFor, buildCardModel, renderMonospaceCard, renderCardHtml, emitCard,
|
|
658
|
+
// task 1001730 — the guaranteed one-line confirmation (pure; every stage pinned).
|
|
659
|
+
confirmLine,
|
|
606
660
|
// exported for unit tests:
|
|
607
661
|
fmtTokens, fmtUsd, fmtInt, fmtMins, nextActionFor, esc, wrap, changedText,
|
|
608
662
|
// task 1003150 — the session-boundary nudge, pure so every branch is pinned.
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.285",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.19.
|
|
9
|
+
"version": "1.19.285",
|
|
10
10
|
"license": "AGPL-3.0-or-later",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"express": "^4.21.2",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.285",
|
|
4
4
|
"description": "Cloud Bongos — the AI-first build platform core (GDS + platform surfaces + module system), installed as a versioned dependency (ADR 0108).",
|
|
5
5
|
"license": "AGPL-3.0-or-later",
|
|
6
6
|
"main": "src/platform-server.js",
|
package/src/module-api.js
CHANGED
|
@@ -995,8 +995,10 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
|
|
|
995
995
|
// landed since 1.19.282 with no explicit bump. run 32527313879. (task 1002620)
|
|
996
996
|
// 1.19.284 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
997
997
|
// landed since 1.19.283 with no explicit bump. run 32531111199. (task 1002620)
|
|
998
|
+
// 1.19.285 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
999
|
+
// landed since 1.19.284 with no explicit bump. run 32531963073. (task 1002620)
|
|
998
1000
|
// ---------------------------------------------------------------------------
|
|
999
|
-
const CORE_VERSION = '1.19.
|
|
1001
|
+
const CORE_VERSION = '1.19.285'; // CI auto-patch carrier (ADR 0161); see changelog above
|
|
1000
1002
|
|
|
1001
1003
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
1002
1004
|
// Usage: const log = api.logger('dev-box'); log.info('mounted');
|
package/tests/ship_card.mjs
CHANGED
|
@@ -342,5 +342,84 @@ test('html ribbon: confirmed shows shipping bold-current + confirmed checked-don
|
|
|
342
342
|
assert.match(html, /ti-check[^<]*<\/i> completed<\/span>/); // a done step, checked
|
|
343
343
|
});
|
|
344
344
|
|
|
345
|
+
// ---- task 1001730: the GUARANTEED one-line confirmation ----------------------
|
|
346
|
+
// show_widget answers "Content rendered and shown to the user" whether or not the
|
|
347
|
+
// client actually painted, and CLI stdout is not reliably shown to the user
|
|
348
|
+
// either — so neither the widget nor the monospace card is a deterministic
|
|
349
|
+
// confirmation. The [otb-confirm] line, relayed as message text, is the floor.
|
|
350
|
+
// Owner-reported 2026-07-02: a ship whose card silently failed to paint left them
|
|
351
|
+
// asking "did it land?" on work that was already shipped + merged.
|
|
352
|
+
|
|
353
|
+
test('confirmLine: every stage yields one line naming the stage and the task', () => {
|
|
354
|
+
for (const stage of ['claimed', 'review', 'confirmed', 'shipped', 'failed']) {
|
|
355
|
+
const line = card.confirmLine(card.buildCardModel({ ...shippedInput, stage, postGradeStatus: undefined }));
|
|
356
|
+
assert.ok(!line.includes('\n'), `${stage} must be exactly one line`);
|
|
357
|
+
assert.match(line, /task 1266/, `${stage} names the task id`);
|
|
358
|
+
assert.ok(line.length > 10 && line.length < 260, `${stage} stays a glanceable line (got ${line.length})`);
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
test('confirmLine: a shipped line carries the facts the owner asks for ("did it land?")', () => {
|
|
363
|
+
const line = card.confirmLine(card.buildCardModel(shippedInput));
|
|
364
|
+
assert.match(line, /^shipped: task 1266/, 'leads with the outcome, not chrome');
|
|
365
|
+
assert.match(line, /deploy live/, 'says it actually deployed — the commit rides in this row');
|
|
366
|
+
assert.match(line, /credits/, 'carries the credit outcome');
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
test('confirmLine: never repeats a word already inside the value ("credits +38 credits")', () => {
|
|
370
|
+
const line = card.confirmLine(card.buildCardModel(shippedInput));
|
|
371
|
+
assert.ok(!/credits\s+\+?\d[^·]*credits/.test(line), `duplicated key word: ${line}`);
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
test('confirmLine: token usage is deliberately excluded — it is not part of did-it-land', () => {
|
|
375
|
+
const line = card.confirmLine(card.buildCardModel(shippedInput));
|
|
376
|
+
assert.ok(!/usage|\$/i.test(line), `spend must not ride the confirmation line: ${line}`);
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
test('confirmLine: a failed grade is stated as such, never dressed up as success', () => {
|
|
380
|
+
const line = card.confirmLine(card.buildCardModel({ ...shippedInput, postGradeStatus: 'completed' }));
|
|
381
|
+
assert.match(line, /needs fixes/);
|
|
382
|
+
assert.ok(!/^shipped/.test(line));
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
test('confirmLine: degenerate input never throws (a summary must not break a CLI action)', () => {
|
|
386
|
+
assert.equal(typeof card.confirmLine(null), 'string');
|
|
387
|
+
assert.equal(typeof card.confirmLine({}), 'string');
|
|
388
|
+
assert.equal(typeof card.confirmLine({ rows: null, taskId: 5 }), 'string');
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
test('emitCard: ALWAYS emits [otb-confirm] — widgets on, widgets off, and before the widget', () => {
|
|
392
|
+
const model = card.buildCardModel(shippedInput);
|
|
393
|
+
for (const widgetsEnabled of [true, false]) {
|
|
394
|
+
const lines = [];
|
|
395
|
+
card.emitCard(model, 1266, { log: (s) => lines.push(String(s)), widgetsEnabled });
|
|
396
|
+
const out = lines.join('\n');
|
|
397
|
+
assert.match(out, /\[otb-confirm\] shipped: task 1266/, `widgetsEnabled=${widgetsEnabled} must still confirm`);
|
|
398
|
+
if (widgetsEnabled) {
|
|
399
|
+
const ci = out.indexOf('[otb-confirm]');
|
|
400
|
+
const hi = out.indexOf('[otb-card-html]');
|
|
401
|
+
assert.ok(ci >= 0 && hi >= 0 && ci < hi, 'the confirmation is emitted BEFORE the widget marker, so a later failure cannot lose it');
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
test('emitCard: the widget directive no longer tells the agent to go silent', () => {
|
|
407
|
+
const lines = [];
|
|
408
|
+
card.emitCard(card.buildCardModel(shippedInput), 1266, { log: (s) => lines.push(String(s)) });
|
|
409
|
+
const directive = lines.find((l) => l.startsWith('[otb-card] render'));
|
|
410
|
+
assert.ok(directive, 'the render directive is still emitted');
|
|
411
|
+
assert.ok(!/then stop — no prose recap/.test(directive),
|
|
412
|
+
'THE REGRESSION: "then stop — no prose recap" is what made a silent non-paint leave the owner with nothing');
|
|
413
|
+
assert.match(directive, /AND relay this one line as text/, 'it now requires the text confirmation too');
|
|
414
|
+
assert.match(directive, /No other prose recap/, 'while still forbidding a full recap');
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
test('the ship + claim skills both instruct relaying [otb-confirm]', () => {
|
|
418
|
+
for (const p of ['../.claude/skills/builder-ship/SKILL.md', '../.claude/skills/builder-claim/SKILL.md']) {
|
|
419
|
+
const md = fs.readFileSync(new URL(p, import.meta.url), 'utf8');
|
|
420
|
+
assert.match(md, /otb-confirm/, `${p} must teach the guaranteed line`);
|
|
421
|
+
}
|
|
422
|
+
});
|
|
423
|
+
|
|
345
424
|
console.log(`\nship_card: ${passed} passed, ${failed} failed`);
|
|
346
425
|
process.exit(failed ? 1 : 0);
|