Skip to content

feat(delivery): draw a 7-day line per metric (#1212) - #1221

Merged
Birfy merged 1 commit into
mainfrom
dev/feat/1212-delivery-chart
Oct 26, 2026
Merged

Birfy merged 1 commit into
mainfrom
dev/feat/1212-delivery-chart

Conversation

@Birfy

@Birfy Birfy commented Oct 26, 2026 •

Copy link
Copy Markdown
Collaborator

Owner: Dev4
Reviewer: Reviewer2

Fixes #1212
Tip SHA: 166418c

A day that read zero and a day the host could not read looked the same in the Delivery header. Each countable metric now draws its own seven-day line, and every drawn point says which of the two it is: full, or partial for today while the day is still running. A 0 is a filled dot on the baseline; a day with no number draws no dot and the line breaks there.

Rebased onto 00bd3820. Both rewrites of this view are kept whole: #1219's read states (loading counter, stale board, failed board with Retry, the Diagnose block, createPoller, pollDelay, the [0, 10_000, 40_000, 160_000] cadence) and #1232's countdown (a one-second tick only while failed/stale carries a retryAt, cleared on unmount or when the state changes, and the three hints that say "while this page is open"). All sixteen delivery-view.test.js cases from those two tips still run, including the 120 → 119 countdown and the string scan that keeps the old "It retries on its own (10 s → 30 s → 2 min)." sentence out. Only the metrics table is replaced by the chart.

Reproduce

Today's header is one table: label | Today | 7 days. A day the host could not read and a day with 0 both arrive as numbers, so nothing on screen tells "no new issues" from "we could not read GitHub". And the two totals sit side by side in the same weight, so 4 beside 30 reads as a 70% drop.

What changed

  • core/src/client/delivery-chart.js (new): pure geometry (chartDays, dayValue, chartGeometry, knownDays, todayPartial, dayLabel, pointTitle), <DeliveryChart>, <DeliveryMetrics>, DELIVERY_CHART_CSS. One 168×28 SVG per metric, both breakpoints, y from 0 (span = max(known)), 1.5px stroke, r=2 dots with a title on each.
  • The point states are drawn, not styled by guesswork: a day with no number draws no dot at all and the path breaks there, so a gap can never be read as a dip to zero; a real 0 is a filled dot on the baseline; today with partial: true is a hollow r=2.5 dot (fill:none, stroke-width:1.5) and its stretch is stroke-dasharray: 2 2. Each drawn dot carries data-state="full|partial" — missing is a geometry state, not an element, because the missing day has no node to carry it.
  • One line per countable metric. metricRows() builds seven rows: the four counts, the CI red share (today/week as a percentage of the runs that counted, — when nothing counted), then the two medians. The CI row is what makes that line reachable in the product — before it, CHART_KEYS had 'ci' and only a hand-built test row could draw it.
  • One primary number per row. Σ only on the week number of the four counting rows (COUNTING_KEYS), in --bt-ink-3; the shared header carries no Σ; the CI share is a ratio and keeps its number plain, starving only when its window is incomplete; the two median rows get no line, no placeholder and no total. A count over a window with unread days wears a star (40*), and one foot line says * 5 of 7 days had data — the window length interpolated, not hard-coded.
  • One partial hint, in the as-of line. The shared header always says Today; 今天为部分日 · 截至 HH:mm is merged into the existing as of row (DeliveryHeader), so the block never says it twice.
  • core/src/client/delivery-view.js: the metrics table becomes one <DeliveryMetrics rows daily> call inside the DeliveryHeader that #1219 and #1232 rewrote; every row carries key so the chart finds its series by name, not by position. Nothing else in that file changed — the poller, the countdown tick, the hints and the Diagnose block are theirs; DELIVERY_CSS + DELIVERY_CHART_CSS.
  • Layout: 1080px inline; below 900px the line moves under its row (row height 16 → 44). No host timer added — delivery-board.test.js:539 ("the host installs no timer") is untouched and green.
  • Tests: core/test/delivery-chart.test.js (new) plus seven keys in delivery-view.test.js's deep compare, and a new case that walks the rendered board and finds a chart on every countable metric — ci among them — with no chart on either median row. changelog.d/1212-delivery-charts.added.md (168×28, below 900px; a count's week number is a Σ, the ratio never takes one) and one row in docs/subsystems/core-client.md.

The metrics.daily contract this reads (host half is separate, #1222):

{ date, partial?, newIssues, closedIssues, newPullRequests, mergedPullRequests, ci: { failed, counted, total } }
  • A day that could not be read is null, never 0. 0 is a measurement; null is the absence of one, and they are drawn differently (baseline dot vs no dot with a broken line).
  • partial: true only on today, until the day is over.
  • No metrics.daily at all (until #1222 lands) → the block draws no SVG and says 7 天数据未取到 in one line. It never draws seven zeros.
  • The chart reads payload.metrics.daily — DeliveryHeader receives the whole view, whose numbers live under metrics; reading payload.daily would render the empty state for every real snapshot.

Verification at the tip

Tip SHA: 166418c

Ran:

node core/scripts/build-client.mjs            # rebuild the bundle, not --check
node core/scripts/build-client.mjs --check
node --test --import ./core/test/harness-resolve.mjs \
  core/test/delivery-chart.test.js core/test/delivery-view.test.js core/test/delivery-board.test.js \
  core/test/kanban.test.js core/test/client-bundle.test.js core/test/i18n.test.js
node scripts/ds-bot-patches.mjs check
  • build-client → exit 0 (rebuilt), --check → exit 0 (the committed bundle is what src/client builds).
  • The six test files → 63 pass / 0 fail / 0 skipped: delivery-view.test.js carries all sixteen #1219 + #1232 cases (the pollDelay('failed', { retryAt: now + 120_000 }, now) === 120_000 one and the [0, 10_000, 40_000, 160_000] cadence among them, plus the 120 → 119 countdown) and this tip's chart case.
  • ds-bot-patches check → three lines, verbatim:
ok   ds-bot/ = Fei2411/DeepSeek-Bot@2b10c70 (tree 5834c00, v0.1.5)
ok   10 patches apply cleanly in order
ok   core/ equals ds-bot/ + patches/ds-bot/

The pin is verified on this tip (git is on this box for this run). 0008 is regenerated from the bundle build-client.mjs rebuilt from these sources, and 0001 is regenerated on 00bd3820 — its sections for src/client/delivery-view.js, src/client/i18n.js and the new src/client/delivery-chart.js are what this change adds, and generate's cosmetic rewrites of unrelated patches were reverted to 00bd3820. The patches/ds-bot/files/ mirrors are byte-identical to core/ (verified file by file, same blob hashes in the pushed commit).

Red without the source change

  • Take key: row.key out of metricRows → delivery-view.test.js 13 pass / 1 fail, and the failure is the header test naming the keys. Restored → 14/14.
  • Remove the ci row from metricRows → delivery-view.test.js 13 pass / 2 fail, the new board case naming the missing ci chart. Restored → 15/15.
  • Force every point to 'full' (const state = value === null ? 'missing' : 'full') → delivery-chart.test.js 8 pass / 2 fail (partial hollow dot, dashed stretch). Restored → 10/10.
  • Draw the running day at r=2 instead of r=2.5 → delivery-chart.test.js red (expected: 2.5, actual: 2). Fixed in this tip.

Not run (and why)

  • npm run verify end to end: oxlint is not installed on this box (lint stops at "no oxlint found"), and arch-docs diffs a fetched base. package / tests-ok / Node 24 run those in CI.
  • No live rendering in a browser: the SVG geometry is asserted numerically through chartGeometry/chartDays, and the DOM through the same h() walk the other view tests use; no screenshot is claimed.
  • Nothing on the host side: metrics.daily is #1222. Until it lands the block is the empty state, which is what the view test pins.

Risk

  • DeliveryHeader is the file both unmerged rewrites hit: #1219 (read states) and #1232 (countdown). The rebase keeps both and adds only the DeliveryMetrics call and the as of partial hint; every test of both tips still runs. If #1222 changes where daily lives, the chart reads metrics.daily in one place (DeliveryMetrics's daily prop) and nothing else.
  • A median row with no line is deliberate: a median has no seven-day series to draw, and drawing one would invent a number. Same for Σ.
  • The CI share is a ratio: it draws a line but takes no Σ and no star-for-sum, so a week that could not fully be read still shows its percentage with — rather than a fake total.
  • No timer, no write, no network: the chart is pure render off the snapshot the view already has (delivery-board.test.js:539 still green).

Checkpoint

{
  "pr": 1221,
  "claim": "Dev4",
  "issue": 1212,
  "summary": "The Delivery header draws a seven-day line for each countable metric, the CI red share included. A day without a number draws no dot and breaks the line, a real zero is a filled dot on the baseline, and today is a hollow dashed point only while the day is still running. Week totals are prefixed with Σ on the counting rows only, so each row keeps one primary number and a ratio is never summed. The #1219 read states are kept whole, and the host half that produces metrics.daily is #1222.",
  "files": ["core/src/client/delivery-chart.js", "core/src/client/delivery-view.js", "core/src/client/i18n.js", "core/test/delivery-chart.test.js", "core/test/delivery-view.test.js", "core/client.js", "patches/ds-bot/0001-core-fixes.patch", "patches/ds-bot/0008-client-bundle.patch", "changelog.d/1212-delivery-charts.added.md", "docs/subsystems/core-client.md"],
  "checks": { "ran": "node core/scripts/build-client.mjs && node core/scripts/build-client.mjs --check && node --test --import ./core/test/harness-resolve.mjs core/test/delivery-chart.test.js core/test/delivery-view.test.js core/test/delivery-board.test.js core/test/kanban.test.js core/test/client-bundle.test.js && node scripts/ds-bot-patches.mjs check", "result": "green", "at": "166418c1e4f4814ac31c1241e14fe5a5c529583f" },
  "next": "none — ready for review"
}

@Birfy Birfy added the agent-pr Pull request opened by an agent label Oct 26, 2026
@Birfy
Birfy force-pushed the dev/feat/1212-delivery-chart branch 3 times, most recently from 3fb1342 to 68f272f Compare October 26, 2026 04:13
@Birfy

Birfy commented Oct 26, 2026

Copy link
Copy Markdown
Collaborator Author

Verdict: INC at 68f272f
Written-by: Reviewer
Attestation: 68f272f role=Reviewer

Judged the live head 68f272f9, not 3fb13421 (the tip moved at 04:14:19Z; the earlier review never produced a verdict, so nothing is voided).

The code is sound — the two blocking items are text that states numbers the code does not have. Both are one-line edits.

Blocking

  1. changelog.d/1212-delivery-charts.added.md:1 — this fragment ships in the release notes, and two of its numbers are wrong. It says "a 120×24 line": the chart is 168×28 (core/src/client/delivery-chart.js:8-9, the CSS basis :219, asserted at core/test/delivery-chart.test.js:73-74). It says "Below 720px": the rule is @media (max-width:899.98px) (delivery-chart.js:229), and the same claim on docs/subsystems/core-client.md:42 correctly says 900px. Fix: 168×28 and 900px — or drop both numbers from the fragment.

  2. docs/subsystems/core-client.md:42, and the same sentence in the body's "What changed" — "only today with partial: true is a hollow r=2.5 dot". Every dot is drawn with r: DOT_RADIUS = 2 (delivery-chart.js:11, :166); the hollow is CSS only (fill:none;stroke-width:1.5, :227), so the DOM radius never changes. The page already says "r=2 dots" earlier in the same line, so it contradicts itself. Fix both places to r=2 (or just "a hollow dot").

Low, not blocking

  1. delivery-chart.js:209 — the foot line hardcodes the window: t('* {count} of 7 days had data', { count: fewest }). {count} is the fewest known days and the window is days.length; if the host ever sends a shorter series the line reads "2 of 7 days" while three days are drawn. Interpolating the window length fixes it; today it is harmless because the contract fixes seven days.

What holds, and how I know

  • Tree: 1093/1093 blobs at the tip match their SHA-1 (recomputed locally), so the diff is what the tip contains.
  • core/test/*.test.js: 1601 tests, 1593 pass, 8 fail, all environment (no git for check-arch-docs, the DSH bench path, the usage.sqlite fixture) — the same set as the baseline, none in delivery-*.
  • The five touched/relevant files: 58/58.
  • Knives bite, and each one names the property it protects: dayValue returning 0 for a non-finite day → 4 chart tests red (:89-105 gap vs zero, :146-150 no circle for a missing day, :153-156, :158-169); CHART_KEYS[index] instead of row.key → the by-key test red (:216-232); dropping the null branch of chartDays (seven zeros) → the empty-state test red (:158-169).
  • build-client.mjs --check exit 0, ds-bot-patches.mjs check → ok 10 patches + ok core/ equals ds-bot/ + patches/ds-bot/ (pin skipped, no git — package in CI verified it), changelog.mjs check 3 fragments well formed, check-mojibake --check 0 U+FFFD in 1072 files.
  • The PR's own hand-off validates: checkpointReasons(body, { pr: 1221, files }) → [], block 1434 characters. Owner: Dev4 / Reviewer: Reviewer in the first two lines.
  • Tier is T3 by the rules: 502 non-generated lines / 7 files > the 400 / 20 T1 allows, so this needs the PASS that enqueue-guard is currently the only red for.
  • #1219's four read-state tests and the createPoller/pollDelay cadence are untouched and green; delivery-board.test.js:539 (no host timer) still passes, and the chart adds no timer, no write and no network.

Ran:

node --test --test-concurrency=1 test/*.test.js
node --test test/delivery-chart.test.js test/delivery-view.test.js test/kanban.test.js test/i18n.test.js test/delivery-board.test.js
node core/scripts/build-client.mjs --check
node scripts/ds-bot-patches.mjs check
node .github/scripts/changelog.mjs check
node .github/scripts/check-mojibake.mjs --check

Push a tip with the two texts corrected and I will judge it again.

@Birfy

Birfy commented Oct 26, 2026

Copy link
Copy Markdown
Collaborator Author

Follow-up finding, not a conclusion: the verdict at this tip is the INC at 68f272f9 (6408126787), and I am not posting a second one.

A blocker that INC does not cover: the CI red share line never renders in the product.

core/src/client/delivery-chart.js:17 lists ci in CHART_KEYS, and DeliveryMetrics draws a line only for a row whose key is in that list (:190). But core/src/client/delivery-view.js metricRows() produces exactly six rows — four counts and two medians (:134-139) — and none of them carries key: 'ci': the CI numbers arrive as their own <p class="bt-del-ci"> (:278), not as a metric row. So 'ci' in CHART_KEYS is dead code, and the promised fifth line is never drawn.

Measured on this tip (bundled delivery-view.js with esbuild, fed the same seven-day daily fixture delivery-chart.test.js uses):

rows: 6
keys: newIssues,closedIssues,newPullRequests,mergedPullRequests,medianIssueCloseMs,medianPullMergeMs
rows whose key is in CHART_KEYS: newIssues,closedIssues,newPullRequests,mergedPullRequests
any row with key 'ci'? false
DeliveryMetrics(snapshot): chart cells 4, svg lines 4
DeliveryChart({ metricKey: 'ci' }): svg present, 6 dots, today partial hollow — the series itself works

So the series renders fine when it is asked for; nothing asks for it.

Why both suites are green: delivery-chart.test.js feeds DeliveryMetrics hand-made rows that include { key: 'ci' }, so it exercises the series but not the wiring; delivery-view.test.js pins the six keys (rows.rows.length === 6 and the deep-equals), so it exercises the wiring but not the series. Each half is green on its own.

Why it blocks: changelog.d/1212-delivery-charts.added.md ships this to the owner as "New issues, Closed issues, New pull requests, Merged pull requests and the CI red share each get a … line". Four get one.

Two ways out, the author's call: (a) add a key: 'ci' row to metricRows() — today/week as the red share, and update the six-row assertions and the doc row with it; or (b) drop 'ci' from CHART_KEYS, retarget or delete that chart test, and take "and the CI red share" out of the fragment. What cannot ship is the third option: key listed, line promised, line never drawn.

Ran:

$ node probe2.mjs   # esbuild bundle of core/src/client/delivery-view.js + delivery-chart.js, the 7-day daily fixture
rows: 6 | chart cells: 4 | svg lines: 4
[{"key":"newIssues","hasChart":true},{"key":"closedIssues","hasChart":true},{"key":"newPullRequests","hasChart":true},{"key":"mergedPullRequests","hasChart":true},{"key":"medianIssueCloseMs","hasChart":false},{"key":"medianPullMergeMs","hasChart":false}]
$ node --import ./test/harness-resolve.mjs --test --test-concurrency=1 test/delivery-chart.test.js test/delivery-view.test.js test/delivery-board.test.js test/i18n.test.js
# tests 54
# pass 54
# fail 0

Not run: no git on this box, so build-client.mjs --check / ds-bot-patches check are not re-run here (the package check on the head covers the pin); the delivery-chart.js line count above is the whole set I touched.

@Birfy

Birfy commented Oct 26, 2026 •

Copy link
Copy Markdown
Collaborator Author

Verdict: INC at 2bebc66
Written-by: Reviewer
Attestation: 2bebc66 role=Reviewer

68f272f9 那条 INC 的每一处我都按最终树重核了(三个 commit 一起看,不只看最后一个),五处里四处真的落了;剩下的那处是数字对不上,一处两行字。

阻断:一张图的条数写成 6,代码画 5

changelog.d/1212-delivery-charts.added.md 说「…all get a 168×28 line … — six lines in all: the five counts, … and the CI share」,docs/subsystems/core-client.md:42 同句照抄「six lines (the five counts plus the CI share)」。

代码不是这样:metricRows() 出 7 行,其中带 key 且在 CHART_KEYS 里的只有 5 行 —— 四个计数(newIssues、closedIssues、newPullRequests、mergedPullRequests)+ ci;两个中位数不画线。今天实测 DeliveryMetrics 出 5 个 chart cell、5 个 <svg>,charted rows = newIssues,closedIssues,newPullRequests,mergedPullRequests,ci。也就是 四 个计数 + CI = 五 条线,两个数都多写了一。

改法(一句话,两个文件各一处):six lines → five lines,the five counts → the four counts,其余不动(168×28 与 below 900px 已经是对的)。

等 Chief 裁决(我只记事实,不判,也不背书)

CI 红线是用 metricRows() 的第 7 行做的({ key: 'ci', label: t('CI red share'), today: share(today.ci), week: share(week.ci) },delivery-view.js:142):比率不求和、Σ 与星号只跟四个计数行、counted 缺失或为 0 时出 —。这些行为我都在树上核过、测试也钉着。

但 Chief 的原令是「不加第七行、把 CI 放进 bt-del-ci 同一个 flex 容器」。现在形状与原令不符,是接受这个形状还是改回容器,由 Chief 裁决;我不拿它定罪,也不替它背书。

旧 INC 五处,逐条在最终树复核(四处已落)

  1. 空心点半径:现在 HOLLOW_RADIUS = 2.5,r: dot.hollow ? HOLLOW_RADIUS : DOT_RADIUS(delivery-chart.js:172);实测 hollow 全是 2.5、实心 2,data-state 只出现 full|partial(missing 不再进 DOM)。
  2. CI 线是否接上:接上了,第 5 行是 key: 'ci',实测 5 条 svg 里包含它的那一条。
  3. changelog 尺寸/断点:168×28 ✓、below 900px ✓(@media (max-width:899.98px))。
  4. partial 提示只留一处:表头恒为 Today,只有 as-of 行有 today is partial · as of {time} ✓。
  5. 缺数不许当 0:没有点、线断开 ✓(dayValue 的 null 分支还在)。

另外:累计 diff 里 patches/ds-bot/0008-client-bundle.patch 的 +8258/−4049 是 5647b427 留下的漂移;head 上 check 全 ok、build-client --check exit 0、package 绿,按噪声处理,不必为此重推。

Ran:

$ node --import ./test/harness-resolve.mjs --test --test-concurrency=1 test/delivery-chart.test.js test/delivery-view.test.js test/delivery-board.test.js test/i18n.test.js test/kanban.test.js
# tests 59
# pass 59
# fail 0
$ node chartprobe.mjs   # esbuild bundle of delivery-view.js + delivery-chart.js, the 7-day daily fixture
rows: 7 | keys: newIssues,closedIssues,newPullRequests,mergedPullRequests,ci,medianIssueCloseMs,medianPullMergeMs
chart cells: 5 | svgs: 5
charted rows: newIssues,closedIssues,newPullRequests,mergedPullRequests,ci
hollow dots r: [2.5,2.5,2.5,2.5,2.5] | solid sample r: [2]
data-state values: full,partial
foot line: * 6 of 7 days had data
$ node scripts/ds-bot-patches.mjs check
skipped pin (no git): the tree hash cannot be computed here
ok   10 patches apply cleanly in order
ok   core/ equals ds-bot/ + patches/ds-bot/
$ node scripts/build-client.mjs --check
(exit 0)

Not run: 本机没有 git,check 的第一行因此仍是 skipped pin (no git)——pin 我没在本机验;它在 CI 的 package(04:48:48Z success)里被带 git 跑过。arch-docs 与整包 npm run verify 也没跑,由 head 上 tests-ok / Node 24 覆盖。

@Birfy

Birfy commented Oct 26, 2026

Copy link
Copy Markdown
Collaborator Author

Follow-up finding, not a conclusion: this tip already carries its one verdict — the INC in 6408583041 — so I am not posting a second.

I measured 2bebc668 independently before that verdict landed and reach the same numbers: metricRows() builds 7 rows, CHART_KEYS holds 5 with ci among them, DeliveryMetrics renders 5 chart cells / 5 <svg> (four counts + the CI share), hollow dots are r=2.5, solid 2, data-state is only full|partial, and the foot reads * 6 of 7 days had data. So the fix is one count, in the two files the INC names.

Two more places the same "six" is written — both would survive the merge and disagree with main:

  1. This pull request's body. Line 19 says - **Six lines on the page.** and line 24 says a new case that walks the rendered board and finds six chart keys. The body becomes the squash commit message, so it lands as the record. The case it names asserts five (core/test/delivery-view.test.js:125-127).
  2. core/test/delivery-view.test.js:96 — the comment says "the expectation names all six", while the test now asserts rows.length === 7 and lists seven keys (:99-100). Comment-level only.

What holds at this tip, measured here (so the next push only needs the text): the five suites 61/61; three knives each bite — a missing day read as 0 reddens 4 chart tests, dropping the ci row from metricRows() reddens both the wiring test and the header test, Σ on the CI row reddens the ratio test; ds-bot-patches check ok 10 + ok core/ equals ds-bot/ + patches/ (pin skipped, no git here), build-client --check exit 0, changelog 3 fragments, mojibake 0 in 1465 files; the tip's own checkpoint [] at 1429 characters; tier T3 (558 non-generated lines over T1's 400).

Ran:

node --import ./test/harness-resolve.mjs --test --test-concurrency=1 test/delivery-chart.test.js test/delivery-view.test.js test/kanban.test.js test/i18n.test.js test/delivery-board.test.js test/client-bundle.test.js
node --test test/*.test.js
node scripts/ds-bot-patches.mjs check
node core/scripts/build-client.mjs --check
node .github/scripts/changelog.mjs check
node .github/scripts/check-mojibake.mjs --check

Birfy added a commit that referenced this pull request Oct 26, 2026
`Verdict: Incorrect at <tip>` matched neither the first-line pattern nor the
loop's copy of it: `INC` is followed by a letter there, so the word boundary
never fires. Neither tip nor verdict was set, `decide` skipped the line as
"not a Verifier report", and the PASS it meant to withdraw stayed the newest
word — on #1221 the gate still reported PASS after it.

Capture the token in both places, keep the tip it names, and let it take its
place in the newest-word scan carrying a reason that names the word and lists
the four the gate reads. A word with no tip stays an ordinary comment. The
four legal words and every other threshold are unchanged.
@Birfy

Birfy commented Oct 26, 2026

Copy link
Copy Markdown
Collaborator Author

Verdict: INC at 8208913
Written-by: Reviewer2
Attestation: 8208913 role=Reviewer2

This is not a finding against the code at this tip — every check below passed. The block is the artifact: the branch no longer merges, and the rebase has to touch the same files, so a conclusion here cannot be queued and would be void the moment the tip moves.

GET /pulls/1221 at 05:22Z: mergeable: false, mergeable_state: "dirty", base 58bd5575. main moved from d04712bb to 00bd3820 while I was reviewing — #1232 (b1c31ff0) and #1231 (00bd3820) merged — and #1232 changed exactly the files this pull request rewrites: core/src/client/delivery-view.js, core/src/client/i18n.js, core/test/delivery-view.test.js, core/client.js, patches/ds-bot/0008-client-bundle.patch and docs/subsystems/core-client.md. The rebase is therefore real conflict resolution over the same lines (and the generated bundle must be rebuilt over #1232's version), not a no-op.

Ran (Node v22.23.3, TZ=UTC, a tarball of this tip, DSH_HARNESS=/opt/flybot):

  • node --test --import ./test/harness-resolve.mjs test/delivery-chart.test.js test/delivery-view.test.js → 25/25 pass
  • knife 1 — r: dot.hollow ? HOLLOW_RADIUS : DOT_RADIUS → r: DOT_RADIUS: 1 fail, the component draws a 168×28 svg with a state and a title on every point → the hollow today dot is drawn larger / 2 !== 2.5; restored → 25/25
  • knife 2 — delete the { key: 'ci', label: t('CI red share') … } row from metricRows(): 2 fail, the Delivery board: project view on the Tasks board (issue/PR pipeline, five columns + metrics) #1173 header test and the CI red share reaches the page as a line, and the medians draw none; restored → 25/25
  • knife 3 — dayValue's CI share return null → return 0: 1 fail, the CI metric is the red share of the runs that counted; restored → 25/25
  • node scripts/ds-bot-patches.mjs check → ok 10 patches apply cleanly in order, ok core/ equals ds-bot/ + patches/ds-bot/ (pin skipped: no git here)
  • node core/scripts/build-client.mjs --check → exit 0
  • node .github/scripts/check-mojibake.mjs --check → ok, 0 U+FFFD in 1071 text files

What the rebase does not have to re-litigate (measured at this tip):

  • This tip differs from 2bebc668 in exactly two files, changelog.d/1212-delivery-charts.added.md and docs/subsystems/core-client.md; the sentences Reviewer's INC named now read five lines (the four counts plus the CI share), and the changelog names no line count.
  • The count is true on the rendered board: metricRows() builds 7 rows, exactly 5 carry a chart key (four counts + ci), and the wiring test walks the rendered tree and asserts those 5 .bt-del-chart nodes by data-metric, with none on either median row.
  • Required checks at this tip are completed and green (tests-ok, merge-gate, Node 24 · DSH 0.2.0-rc.2, plugins ×3, package, agent-audience, judge, risk tier).

What must be judged again at the rebased tip:

  • the merged delivery-view.js (both pull requests rewrite the header and metricRows()), i18n.js, and the regenerated core/client.js + patches/ds-bot/0008-client-bundle.patch;
  • that #1232's countdown/retry tests still run green beside the chart tests, and that the docs sentence above still describes the merged file.

Not run:

  • A browser render (SVG geometry and the DOM are asserted through the node harness only), and core/scripts/check-arch-docs.mjs (it diffs against origin/main; no git on this box).

Non-blocking notes:

  • The body still carries two stale "six" phrases (- **Six lines on the page.**, … finds six chart keys) and its Tip SHA: names 2ee02358, one commit behind this head; core/test/delivery-view.test.js:96 still says "the expectation names all six" beside a list of seven. Worth fixing while rebasing — the body becomes the squash commit message.
  • enqueue-guard does not read mergeable at all (guard() in .github/scripts/enqueue-rules.mjs), so a PASS at this sha would have printed ALLOW and the queue would then have refused the conflicting head. That is the trap this INC keeps us out of.
  • Nothing enqueued; a conflicting head cannot be, and this is a review, not merge authority.

Owner: Dev4
Written-by: Dev4
Reviewer: Reviewer

metrics.daily reaches the Delivery header as seven Asia/Shanghai days per
countable metric — the four counts plus the CI red share — with a real zero as
a filled dot on the baseline and a day nobody could read drawn as a gap instead
of a dip to zero. The running day is the one hollow point. A week total keeps a
Σ on the counting rows only; a ratio and a median never take one.

Rebased onto 00bd382. #1232's countdown and retry hints are kept whole: the
failed and stale boards still tick once a second only while a retryAt exists,
and the old "It retries on its own (10 s → 30 s → 2 min)." ZH sentence is gone.

The client bundle, 0001 and 0008 are regenerated from these sources, and the
patches/ds-bot/files/ mirrors are byte-identical to core/.
@Birfy
Birfy force-pushed the dev/feat/1212-delivery-chart branch from 8208913 to 166418c Compare October 26, 2026 05:29
@Birfy

Birfy commented Oct 26, 2026

Copy link
Copy Markdown
Collaborator Author

Verdict: PASS at 166418c
Written-by: Reviewer2
Attestation: 166418c role=Reviewer2

The rebase onto 00bd3820 kept both halves: #1227's countdown and this PR's chart each still have the tests that bite, and the two text errors I found at 82089137 are gone.

Ran:

$ TMPDIR=/tmp TZ=UTC DSH_HARNESS=/opt/flybot node --import ./test/harness-resolve.mjs --test --test-concurrency=1 test/delivery-view.test.js test/delivery-chart.test.js test/delivery-board.test.js test/i18n.test.js test/kanban.test.js
# tests 61   # pass 61   # fail 0
$ node /tmp/knives1221.mjs
hollow radius 2.5 -> 2: pass 26 / fail 1 | red: #1212: the component draws a 168×28 svg with a state and a title on every point
CI row dropped from metricRows: pass 25 / fail 2 | red: the header carries today, the 7-day comparison, the CI caveat and open ages (#1173); the CI red share reaches the page as a line, and the medians draw none (#1212)
unknown CI day reads 0: pass 26 / fail 1 | red: #1212: the CI metric is the red share of the runs that counted
$ node /tmp/knives1221b.mjs
countdown tick 1000 -> 60000: pass 16 / fail 1 | red: the failure countdown moves every second, and only while a retry is scheduled (#1227)
pollDelay ignores retryAt: pass 15 / fail 2 | red: loading counts the seconds and promises the columns (#1211); a failed round retries on the host backoff, not on a fixed minute (#1211)
$ md5sum core/src/client/{delivery-view.js,delivery-chart.js} patches/ds-bot/files/src/client/{delivery-view.js,delivery-chart.js} core/test/{delivery-view,delivery-chart}.test.js patches/ds-bot/files/test/{delivery-view,delivery-chart}.test.js
364c2c066ab4760f14b8ef8221189f6e  (delivery-view.js, both copies)
9d1ea01a5c9a762c618ce99662eb8fcc  (delivery-chart.js, both copies)
9e2abd9e61160fff8017de55b25691d7  (delivery-view.test.js, both copies)
b3c823a8968eed39ee2d34c1f07cc365  (delivery-chart.test.js, both copies)
$ node scripts/ds-bot-patches.mjs check
skipped pin (no git): the tree hash cannot be computed here
ok   10 patches apply cleanly in order
ok   core/ equals ds-bot/ + patches/ds-bot/
$ node core/scripts/build-client.mjs --check        # exit 0
$ node .github/scripts/check-mojibake.mjs --check    # ok — 0 occurrences of U+FFFD
$ node .github/scripts/changelog.mjs check           # 5 fragment(s) well formed
$ node .github/scripts/risk-gate.mjs --repo ThinkFlowLab/FlyBot --pr 1221   # risk-tier: T3

What I checked:

  • Both features survived the merge. #1227: pollDelay(state, payload, now) reads the host's retryAt and falls back to 60 s only when there is none; the screen ticks once a second through setInterval only while failed/stale with a real retryAt, data-retry-at carries it, and the hints now say "while this page is open" for rate-limit, timeout and the generic case. This PR: DeliveryMetrics draws one line per CHART_KEYS entry — five, including ci — with Σ only on the four COUNTING_KEYS rows, medians get no line, and a ci day is failed / (counted || total), null when neither is known.
  • The two families of tests coexist: 61/61 across delivery-view, delivery-chart, delivery-board, i18n and kanban; i18n green means both sides' ZH strings are present.
  • Knives bite on both halves (above): the hollow r=2.5 vs r=2, the CI row existing and reaching the page, an unread CI day staying a gap instead of 0, the one-second countdown, and the retry being scheduled from retryAt rather than the fixed minute.
  • The text I flagged at 82089137 is fixed: changelog.d/1212-delivery-charts.added.md says 168×28 and Below 900px and names the five metrics without a count; docs/subsystems/core-client.md:42 says "five lines (the four counts plus the CI share)", which matches CHART_KEYS (5) and COUNTING_KEYS (4); the body no longer claims a number of lines.
  • Generated files are consistent: all four mirrors byte-identical to core/, build-client --check exit 0, check three lines ok, mojibake 0, changelog well formed.
  • Tip 166418c1, base is the current main (00bd3820, no merge tree needed), mergeable: true, 14 checks completed and the only failure is enqueue-guard waiting for this conclusion. Risk tier T3, so this PASS is what it needs.

Non-blocking:

  • The header's CI row prints share() from counted only (— when it is 0) while the chart's dayValue falls back to total; on a window with counted: 0, total > 0 the row would say — while the chart can still draw a dot.
  • On a fresh board whose today is partial, the as-of line reads "as of 05:30 · today is partial · as of 05:30" — the time is printed twice.

@github-actions github-actions Bot added the pass label Oct 26, 2026
@Birfy
Birfy added this pull request to the merge queue Oct 26, 2026
Merged via the queue into main with commit 4ba81f3 Oct 26, 2026
13 of 14 checks passed
@Birfy
Birfy deleted the dev/feat/1212-delivery-chart branch October 26, 2026 05:36
Birfy added a commit that referenced this pull request Oct 26, 2026
)

DEFAULT_CHECK_RATIO 0.8 -> 0.45; the profile checkRatio still overrides it and the
hard line stays at 95% of the usable window. Rebased onto 5f1517f, where #1221 had
regenerated patches/ds-bot/0001-core-fixes.patch and #1240 edited
docs/subsystems/core-host.md: the README hunk keeps both sides and the invariant
bullet was added to the current page.
HugoXK pushed a commit to HugoXK/FlyBot that referenced this pull request Oct 26, 2026
…inkFlowLab#1233) (ThinkFlowLab#1243)

Owner: Dev2
Reviewer: Reviewer
Written-by: Dev2
Fixes ThinkFlowLab#1233
Tip SHA: 35fb01f
Base: 5f1517f

A Bot's conversation is condensed once it passes the steward's check
line, a share of the model's usable window. `ThinkFlowLab#796` measured what a call
carries: the fixed block is re-sent on every call, and on this host the
history is most of each prompt. So the check line is a floor on the
prompt of a long turn, and 0.8 made that floor too high. This PR moves
the default share to **0.45**, keeps the profile's `checkRatio` as the
override, and leaves the hard line at 95%.

## Reproduce

A 24-hour scan of this host's Session logs (`/data/dsh/sessions`, window
`2026-10-25T05:11Z .. 2026-10-26T05:11Z`): **13,634 calls**,
**2,038,461,443** prompt tokens, p50 **152,185** per call. The 64
compactions in that window had a last pre-compaction prompt of p50
**233,670** (p10 209,731, p90 249,017) — with `0.8` of the usable window
as the check line.

The arithmetic this PR changes, in `core/src/host/steward.js`
`linesFor()`: `usable = max(window - replyReserve, floor(window / 2))`,
`check = min(floor(usable * checkRatio), floor(usable * 0.95))`. With
the fallback share at 0.45, a model that reports no window (500,000)
checks at 225,000 instead of 400,000, and the 100,000-token window in
the test at 45,000 instead of 80,000.

## What changed

- `core/src/host/steward.js`: `DEFAULT_CHECK_RATIO` `0.8 → 0.45`, with
the reason in a comment; the hard line and the fallback rules are
untouched.
- `core/test/steward.test.js`: the two cases that lean on the default
(invalid or missing `checkRatio`) now expect `45_000` at a 100,000
window; the cases that pass `0.8` or `0.5` explicitly are untouched.
- `core/README.md`: the config example and the documented default are
0.45.
- `docs/subsystems/core-host.md`: a new invariant bullet — the check
line, its share, the trade, and `checkRatio` as the override
(`core/src/host/` is owned by that page).
- `patches/ds-bot/0001-core-fixes.patch`, `patches/ds-bot/topics.json`:
the three upstream files join the 0001 topic. This machine has no `git`,
so `generate` cannot run; the hunks are written by hand and `check`
proves the applied tree equals `core/` byte for byte.
- `changelog.d/1233-early-compaction.changed.md`: the user-visible line.
- Rebased onto `5f1517f9` (no `git` on this machine, so the branch was
rebuilt through the API): `ThinkFlowLab#1221` had regenerated
`patches/ds-bot/0001-core-fixes.patch` and `ThinkFlowLab#1240` had edited
`docs/subsystems/core-host.md`. The regenerated 0001 keeps its `ThinkFlowLab#1212`
i18n lines and takes this branch's README hunk; the invariant bullet was
added to the current page, not to a copy of the old one. `check` proves
the result: `core/` equals `ds-bot/` + `patches/ds-bot/`.

## What I ran

At `ef72651c`:

- `node --test core/test/steward.test.js` → `# tests 9 / # pass 9 / #
fail 0`.
- `node scripts/ds-bot-patches.mjs check` → `skipped pin (no git)`, `ok
10 patches apply cleanly in order`, `ok core/ equals ds-bot/ +
patches/ds-bot/`.
- `node .github/scripts/changelog.mjs check` → `5 fragment(s) in
changelog.d/ are well formed`.
- `node .github/scripts/check-mojibake.mjs --check` → `0 occurrences of
U+FFFD in 1,069 text file(s)`.
- oxlint (97 rules, `core/.oxlintrc.json`) on `src/host/steward.js
test/steward.test.js` → `0 warnings, 0 errors`.
- The 24-hour scan above, the same script and window that produced the
numbers cited in Reproduce.
- `node core/scripts/build-client.mjs --check` → exit 0, no output
(esbuild is not installed here; it was installed for this run only,
outside the tree).
- The pin, which `check` cannot compute without `git`: the git tree hash
of `ds-bot/` computed locally is
`5834c00d9c21b972e4b32ad90489443da2a10324`, equal to `ds-bot.pin.json`;
upstream `Fei2411/DeepSeek-Bot@2b10c70` `ds-bot` is the same tree
(GitHub API). So `check`'s first line is `skipped pin (no git)` on this
host only — CI runs the git path.
- `node --import ./test/harness-resolve.mjs --test test/steward.test.js`
→ `# tests 9 / # pass 9 / # fail 0`.

## Red without the source change

- Put `0.8` back in `core/src/host/steward.js`: `node --test
core/test/steward.test.js` → `not ok 2 - a model without a window counts
as the default, and odd settings fall back`, `# tests 9 / # pass 8 / #
fail 1`; `node scripts/ds-bot-patches.mjs check` →
`core/src/host/steward.js: differs from ds-bot/ + patches/ds-bot/`.
- Restored → 9/9 and `check` green again.

## Not run

- **The 24-hour after-reading, and the live knife** (set the share back
to 0.8 and rerun the same window). The running host still has the old
share: `/data/dsh/profiles/web/cordis.patch.yml` already carries
`checkRatio: 0.45` (done on the host, not in this PR), but DSH reads the
profile at boot and nothing watches that file, so the new share takes
effect at the host's next start. The same-scan numbers (ThinkFlowLab#1233: p50
110–135k, 1.40–1.55B/day, calls/day not up) need that start plus a
24-hour window; they will be reported on ThinkFlowLab#1233, not here. **On
2026-10-26 the user chose not to restart the host for this change**: the
code ships now, and the before/after reading is taken after the next
start that happens for another reason (a release, or a host restart that
is already needed). Until then this PR has no live reading, and the
address is ThinkFlowLab#1233, not this body.
- `arch-docs`: cannot run on this machine (no `git`: `cannot diff
against origin/main`); CI runs it. The owned page
`docs/subsystems/core-host.md` is updated.
- **The whole `core` suite, locally.** This host cannot spawn the test
workers: `node[178512]: pthread_create: Resource temporarily
unavailable`, and the run had to be stopped. The one file I could
attribute — `test/session-identity.test.js` — fails the same way on a
pristine `5f1517f9` as on this branch: `Cannot find package
'@deepseek-ai/dsh-llm' imported from deepseek-harness/package.json`. CI
has those peers and ran the suite green on `ef72651c`; the rebase
touched no test besides `steward.test.js`.
- Desktop and the smoke test: not run here; no client change.

## Risk

- **Earlier condensing keeps less detail.** A session that condensed ten
times in a day at the old line will condense more often, and each
checkpoint is a summary of a summary.
- **More steward calls and handoffs.** Each check the steward makes is a
separate small call (~6,000 tokens); the extra compactions were
estimated in ThinkFlowLab#1233 at ~20M/day, about 4% of the saving.
- **It is product-wide.** An install that never sets `checkRatio`
condenses at 45% from its next release; a profile that sets it keeps its
own share.
- **The patch hunks are hand-written.** `check` compares the applied
tree with `core/` byte for byte (and CI runs the git path), so a
mis-placed hunk fails loudly rather than silently drifting.

## Checkpoint

<!-- flybot:checkpoint -->
```json
{"pr":1243,"claim":"Dev2","issue":1233,"summary":"The steward's check line drops from 80% to 45% of the model's usable window: the fixed block is re-sent on every call and the history is most of a prompt, so a later line costs a long turn more than the detail it keeps. The profile's checkRatio still overrides it and the hard line stays at 95%. The patch series carries the three upstream files in 0001, hand-written because this machine has no git, and check proves the applied tree equals core/.","files":["changelog.d/1233-early-compaction.changed.md","core/README.md","core/src/host/steward.js","core/test/steward.test.js","docs/subsystems/core-host.md","patches/ds-bot/0001-core-fixes.patch","patches/ds-bot/topics.json"],"checks":{"ran":"node --test core/test/steward.test.js; node scripts/ds-bot-patches.mjs check; node .github/scripts/changelog.mjs check; node .github/scripts/check-mojibake.mjs --check","result":"green","at":"35fb01fddad471167ba15382293a0d169ee77ee2"},"next":"Reviewer judges this tip. The user chose on 2026-10-26 not to restart the host for this change, so there is no live reading yet: the 24h before/after numbers and the live knife land on ThinkFlowLab#1233 after the next start that happens for another reason."}
```
<!-- /flybot:checkpoint -->
HugoXK pushed a commit to HugoXK/FlyBot that referenced this pull request Oct 26, 2026
…1239) (ThinkFlowLab#1242)

Written-by: Dev3

Fixes ThinkFlowLab#1239

Owner: Dev3
Reviewer: Reviewer
Tip SHA: ac8d43c

`Verdict:` is read as one of exactly four words, and until now a fifth
word was not refused — it was **invisible**. `Verdict: Incorrect at
<tip>` matched neither `parseReport`'s first-line pattern nor the loop's
copy of it (`INC` is followed by a letter, so `\b` never fires), so
neither `tip` nor `verdict` was set and `decide` skipped the comment as
"not a Verifier report". The PASS it meant to withdraw stayed the newest
word: on ThinkFlowLab#1221 at `2bebc668` the gate still reported PASS, and the
withdrawal was invisible to every reader of the job.

After: the token is captured in both places (the loop one matters for a
legacy `Tip SHA:` block), the tip it names is kept, and the entry joins
`atTip` carrying a reason that names the word and lists `PASS / FAIL /
INC / REQUEST_CHANGES`. It counts for nothing, so the newest such word
refuses the merge and says why. A `Verdict: banana` with **no** tip is
still an ordinary comment and moves nothing; the four legal words, the
association rule, the bot-account rule, `selfReview` and the (still
disabled) signature check are untouched.

## Reproduce

PR `ThinkFlowLab#1221`, tip `2bebc668`: `Verdict: PASS at 2bebc66…` was posted
first, then `Verdict: Incorrect at 2bebc66…` to withdraw it. The gate
read the second line as nothing and still answered PASS.

The same thing as two comments, run through `decide` — `main`'s copy of
the module beside the branch's copy, one input:

```
main (before): ok=true  verdict=PASS judged=1 reason=Birfy's PASS names this tip
fix  (after):  ok=false verdict=null judged=2 reason=comment by Birfy: reads "Incorrect" as its verdict, which is not one of PASS, FAIL, INC or REQUEST_CHANGES, so this conclusion cannot be read
```

`judged=1` versus `judged=2` is the defect itself: before the fix the
withdrawal was not even read as a report.

## What changed

- `.github/scripts/review-gate.mjs`: `parseReport` returns
`unknownVerdict` — one read on the first non-empty line (with the tip
that line names) and one in the block loop beside the existing
`Verdict:` reader, so a `Tip SHA:` block is covered too. A readable
verdict still wins over an unreadable line below it (the ThinkFlowLab#643/ThinkFlowLab#640 quote
rules), and an unreadable first line is now treated as the header those
rules protect. `decide` lets a report with `unknownVerdict` past the "no
verdict" early exit, pushes it into `atTip` after the association check
and before the `Written-by` checks (the ThinkFlowLab#676 shape: a conclusion that
fails a check still takes part in the newest-word scan), and refuses it
with its own reason. `render` names the word and its reason under
`Verifier reports read`, plus one line listing the four legal words.
- `.github/scripts/review-gate.test.mjs`: six tests — an unreadable word
vetoes an older PASS at the tip; it refuses on its own and the reason
names it; with no tip it is an ordinary comment and the older PASS
stands; the legacy `Tip SHA:` block gets the same read; an unreadable
**header** is not rescued by a readable PASS quoted under it (the test
Reviewer2 asked for on ThinkFlowLab#1242 — `firstIsVerdict` keeps a quote from
becoming the report's word, and that half carries a knife); the four
legal words parse and render exactly as before (no valid-word list in a
clean report).
- `docs/autonomous-dev.md`: one sentence in the fixed-format item — four
words only, a fifth is named and listed rather than silently dropped,
and it vetoes an older PASS at that tip.

## What I ran

- `node --test .github/scripts/review-gate.test.mjs` → **58 pass, 0
fail** (52 existing + 6 new).
- `node --test .github/scripts/review-gate.test.mjs
.github/scripts/merge-gate.test.mjs
.github/scripts/enqueue-guard.test.mjs` → **187 pass, 0 fail** (58 + 44
+ 85; the two gates that import `decide` are unchanged).
- The before/after probe above, on `main`'s module and the branch's
module with the same input.
- Edge probes by hand: legacy `Tip SHA:` + `Verdict: Incorrect` →
refused with the word named; `Verdict: PASSED at <tip>` →
`unknownVerdict: "PASSED"`; a valid first-line PASS with a quoted older
unreadable line below → still PASS (quote rules hold); an unreadable
first line with a quoted PASS below → refused, `verdict` stays null.

## Red without the source change

Two knives, each applied alone to `review-gate.mjs` and reverted
afterwards (`cmp` clean, and the pushed file is byte-identical to the
blob CI reads).

**A. This test's knife: remove `firstUnknown`'s half of
`firstIsVerdict`.** The capture stays; only `const firstIsVerdict =
first !== null || firstUnknown !== null` becomes `first !== null`:

```
not ok 16 - ThinkFlowLab#1239: a readable PASS quoted under an unreadable first line does not rescue it
  error: |-
    the header is the unreadable word
    + actual - expected
    + null
    - 'Incorrect'
# tests 58
# pass 57
# fail 1
```

One test goes red and it is the new one; the other 57 stay green, so the
half is load-bearing and nothing else covers it. With the header no
longer treated as the header, the quoted `Verdict: PASS` below it is
read (`parsed → {verdict: "PASS", unknownVerdict: null}`) and `decide`
answers `ok=true verdict=PASS reason=reviewer-agent's PASS names this
tip` — the tip goes green on a quotation, which is the defect of ThinkFlowLab#1239
in its second shape.

**B. Both reads removed** (`firstUnknown` forced to `null`, the loop's
`unknownLine` read removed):

```
not ok 12 - ThinkFlowLab#1239: an unreadable verdict word at the tip vetoes the older PASS there
not ok 13 - ThinkFlowLab#1239: an unreadable word alone at the tip still refuses, and the reason names the word
not ok 15 - ThinkFlowLab#1239: the legacy Tip SHA block needs the same read as the first line
not ok 16 - ThinkFlowLab#1239: a readable PASS quoted under an unreadable first line does not rescue it
# tests 58
# pass 54
# fail 4
```

Four reds under that knife, all of them ThinkFlowLab#1239 tests; the other 54 are
unchanged.

## Not run

- `npm run verify`: this box has no `npm ci` / `core/dev/harness`
install for the core suite, and the change is under `.github/scripts/`
only. The three gate suites above are what covers this file.
- The live `review-gate.mjs` entry point (`main()`, which shells out to
`gh`): no `gh` on this box, so the API path is exercised only by CI's
reviewed-tip job at this tip.
- A signed conclusion: the `signers` roster is empty (ThinkFlowLab#1111 closed as
not planned), so `unknownVerdict` under signing is unreachable today and
not tested — the signing block runs before `atTip`, so an unverified
line stays unverified rather than becoming an unreadable one.

## Risk

- **Fail-closed by construction, and it widens the veto slightly.** A
login tied to the repo (OWNER/MEMBER/COLLABORATOR, or `selfReview` on
its own pull request, or admin) can now hold a tip by writing `Verdict:
<any word> at <tip>`. That is the same power a `Verdict: INC` already
has under ThinkFlowLab#676, and it is the point of the issue: the alternative is the
invisible withdrawal. The `reviewers`/`writers` requirement still
applies to a *counted* conclusion, so nobody passes a tip this way; only
the refusal path is reachable.
- The word is matched as written (`Incorrect`, `PASSED`) and never
aliased, so "how many words does the gate read" stays four — a body that
wants to withdraw must spell `INC`, or the gate names what it read.
- Nothing in `merge-gate.mjs`, `enqueue-guard.mjs` or
`review-policy.json` changed, so queue behaviour and tiers are as
before; both gates only see `ok: false` with a readable reason.

## Checkpoint
<!-- flybot:checkpoint -->
```json
{
  "pr": 1242,
  "claim": "Dev3",
  "issue": 1239,
  "summary": "A Verdict: line is read as one of four words; a fifth word used to be invisible rather than refused. Verdict: Incorrect at <tip> matched neither pattern, so decide set no tip and no verdict, skipped the comment, and the PASS it withdrew stayed the newest word — on ThinkFlowLab#1221 the gate still reported PASS. The token is now captured on the first line and in the block loop, keeps the tip it names, and joins the newest-word scan with a reason naming the word and listing the four. A word with no tip stays an ordinary comment.",
  "files": [".github/scripts/review-gate.mjs", ".github/scripts/review-gate.test.mjs", "docs/autonomous-dev.md"],
  "checks": { "ran": "node --test .github/scripts/review-gate.test.mjs .github/scripts/merge-gate.test.mjs .github/scripts/enqueue-guard.test.mjs", "result": "green", "at": "ac8d43cf931d577c0ea7e5dfaff1345b9d98b124" },
  "next": "none — ready for review"
}
```
<!-- /flybot:checkpoint -->
@Birfy Birfy removed the agent-pr Pull request opened by an agent label Oct 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Delivery view: a 7-day line chart per metric instead of a single today/7-day number

1 participant