Repository navigation
Add a source-token ratchet gate: comment-stripped tokens over the authored surface (src minus translations/ and data/), only-down #1183
Description
Activity
- addedci/cdCI plumbing and the verification pipelineCI plumbing and the verification pipelinepm:queueReady for the PM dispatch loopReady for the PM dispatch looppm:dispatchedDispatched to a dev agent by /pm-dispatchDispatched to a dev agent by /pm-dispatchand removedpm:queueReady for the PM dispatch loopReady for the PM dispatch loop
on Aug 17, 2026 Claim: PM loop round 1
Session:session_01XeAz8tSjwAibjdSDn9niiZ
Branch:claude/issue-1183-source-token-ratchet
Worktree:hotcrm-issue-1183
Domain:ci/cd
File surface:scripts/(new check + possiblycheck-source-hygiene.mjs),package.json(script wiring),.github/workflows/(step wiring),test/(stop on breach; explain in the report). Must not editsrc/— the gate measures that tree, it does not change it.
Container & model:M,mode:subagent,model: opus
Serial constraints cleared: no in-flight PR or claim touchesscripts/or the workflows. Siblings #1180 (src/actions/) and #1181 (src/objects,src/flows) are disjoint. Note the read-coupling direction: this gate measuressrc/, so whatever #1180/#1181/#1182 land changes the measured value — the ceiling must be anchored from a fresh run at PR time, never from the numbers quoted in the issue body.
Generated by Claude Code
- added a commit that references this issue
on Aug 17, 2026 { "issue": 1183, "status": "done", "branch": "claude/issue-1183-source-token-ratchet", "pr": "https://github.com/objectstack-ai/hotcrm/pull/1190", "premise_still_valid": true, "summary": "New gate scripts/check-source-token-ratchet.mjs measures comment-stripped, blank-stripped src/**/*.ts minus translations/ and data/, prints business semantics / interaction layer / other / authored total on every run, and fails when a scope exceeds its committed shrink-only ceiling. Wired into pnpm verify as pnpm hygiene:tokens and into BOTH CI and Code Quality (CI has no paths: filter, so it always fires on PRs touching src/); test/source-token-ratchet.test.ts pins the measurement basis, the red paths and the wiring itself. Ceilings anchored from a real run on this branch 2026-08-17 02:11Z after merging main (main was still 2342811a; #1180/#1181 had not landed): business 80,411 -> 81,000, interaction 39,259 -> 40,000, total 133,533 -> 134,000 (round up to the next 1,000, under 1% headroom by construction). src/ untouched; the gate is green on today's tree. TWO THINGS FOR PM: (1) the script measures business semantics ~80.4k, not the issue table's ~78k — I reproduced the PM's line-oriented rule exactly (8,288 lines / ~77,629 tokens, matching the table to the line) and the entire 160-line / ~2.8k-token delta is action-body template literals in five src/actions/ files whose string contents carry their own // commentary; a line matcher deletes that, a grammar-correct scanner keeps it, and I kept it deliberately (stripping inside strings means deciding per string whether it is code — unstable in exactly the way a ratchet must not be). #1187 should therefore cite ~80k / ~39k, or the maintainer rules that sandbox-body commentary is excluded, which is a one-line change plus a re-anchor. (2) headroom is 589/741/467 tokens, so the next PR that adds a business object goes red — that is what only-down means, but #1180/#1181 are in flight against src/ and whichever merges first may meet it.", "tests": "All at c057891a (tip of the branch, = final commit; no edits after). `pnpm validate && pnpm typecheck && pnpm lint && pnpm lint:i18n-gate && pnpm hygiene && pnpm hygiene:tokens` -> exit 0, gate printed 'business semantics ~80,411 tokens (ceiling ~81,000; headroom ~589)' / 'interaction layer ... headroom ~741' / 'authored total ... headroom ~467' / 'source token ratchet clean'. `pnpm build && pnpm test` -> exit 0, 'Test Files 107 passed (107) / Tests 2619 passed | 1 skipped (2620)' — includes the 14 new cases. `pnpm changeset status --since=origin/main` -> exit 0. Reverse verification on the REAL tree from the committed state, business ceiling lowered 81,000 -> 80,000 (measured 80,411): 'business semantics is ~80,411 tokens; the ratchet ceiling is ~80,000 (over by ~411)' + 'source token ratchet failed: 1 scope(s) over ceiling', exit 1; restored with git checkout -> 'source token ratchet clean', exit 0. Direction observed = red, as expected. Stripper equivalence proved by hand over the whole tree with esbuild (minified output of original vs stripped must be byte-identical): '286 files, 0 divergent, 0 line-count drifts, 1495852 chars removed as comments' — recorded in the PR body, not automated, because esbuild is not a declared dependency (same reasoning test/lint-i18n-gate.test.ts gives for its own hand run). NOT run locally, all three run on the PR: test:coverage thresholds (scoped to src/objects/*.hook.ts, untouched), Playwright E2E, CodeQL. CI results not awaited, per the report-at-draft-PR contract.", "open_questions": [], "out_of_scope_findings": [ "filed as #1191 (sub-issue of #1187, unassigned): the GitHub repository DESCRIPTION still quotes the drifted '~170k tokens' claim — it is repo metadata, not a file, so #1187's `grep -rn \"18,000\\|170k\" README.md content/docs/ apps/docs/` re-check can pass green while the most-viewed copy of the number stays wrong", "not filed, decision input for #1187 instead: TypeScript 7's npm package exposes no compiler API at all (`import('typescript')` yields only { version, versionMajorMinor }), so any future in-repo tooling that wants to parse TS cannot borrow the compiler's scanner — this is why the gate's stripper is hand-written plus esbuild-proved rather than delegating" ] }
Generated by Claude Code
ACCEPT — PM review of #1190 (round 1). Verified against GitHub, not against the report.
All 9 checks green including
Build and Test (22.x)andCheck Changeset(the empty-frontmatter changeset is accepted here — tooling-only, nothing ships).The dev corrected the PM's measurement, and the correction is right. I verified it independently rather than taking it on trust: counting lines inside template literals in
src/actions/*.tsgives exactly 160, in exactly the five files named —global.actions.ts(82),opportunity.actions.ts(48),contact.actions.ts(15),lead.actions.ts(13),knowledge_article.actions.ts(2). Those lines are QuickJS action-body source, not comments; the PM's line-oriented stripper deleted them and undercounted. The honest figure for business semantics is ~80.4k, not the ~78k in this card's table. The card's table is hereby superseded by the script's output — which is precisely the failure mode this card existed to end, arriving one round early and from the right direction.Choosing a grammar-correct character scanner over the line matcher is also the right call for a ratchet specifically: stripping inside strings would require deciding per-string whether its contents are code, which is unstable in exactly the way a ratchet must not be.
What else I checked:
- Measurement basis matches the rulings.
src/translations/andsrc/data/are excluded from measurement entirely (not merely un-ceilinged), and the ruling is quoted verbatim in both the script header and the test that pins it — so a well-meaning "surely translations count too" edit fails a test rather than silently moving the headline number. - Comment-stripped by design, and defended. The script header and the failure message both state that deleting comments cannot move this number, so Comment slimming: 66% of comment mass is issue archaeology — delete it, migrate teaching prose to
description/docs, and file upstream cards for every hand-copied platform constraint #1184's work is not misdirected here and nobody gets sent comment-hunting by a red gate. - The wiring is asserted, not assumed.
Docs Appworkflow never runs for a docs-only PR: it compilescontent/docsbut triggers only onapps/docs/**#1169 is the live in-repo example of a workflow that cannot fire for what it checks; the test asserts the gate is invoked by both workflows and bypnpm verify, and thatci.ymlcarries nopaths:filter. That is the right lesson to have drawn from a sibling defect. - Empty read is red, not green — a gate that cannot find its input fails. Missing measured directories fail loudly too.
- Reverse verification on the real tree (ceiling lowered 81,000 → 80,000 against a measured 80,411, then restored), with the observed direction reported.
- Stripper equivalence proved by hand over all 286 first-party files via byte-identical esbuild minified output, recorded in the PR body and explicitly not automated because esbuild is not a declared dependency. Correct call, and the reasoning is stated rather than hidden.
Two items go to the maintainer rather than being resolved here (both raised by the dev, both correctly escalated):
- The README figure. The ruling that README headline numbers: replace the drifted "18,000 lines / 170k tokens" with the two gate-cited numbers — business semantics ~80k, interaction layer ~39k #1187 carries — 「我觉得只需要写 业务语义 ~78k 交互层 ~40k」 — was given against the PM's undercount. The measured pair is ~80k / ~39k.
- Headroom. Ceilings are anchored at the measured value rounded up to the next 1,000, leaving 589 / 741 / 467 tokens. That is an anchor, not a budget, and it means the next PR adding a business object goes red by design.
Landing: held at ready-but-unqueued pending item 1, since a change to the measurement basis would change this script. Nothing about the gate's existence or construction is in question.
Generated by Claude Code
- Measurement basis matches the rulings.
- added a commit that references this issue
on Aug 17, 2026 { "issue": 1183, "status": "done", "branch": "claude/issue-1183-source-token-ratchet", "pr": "https://github.com/objectstack-ai/hotcrm/pull/1190", "premise_still_valid": true, "summary": "Patch round on the same PR (no new PR; head is now 9d140ae2, 3 commits, base d038b957). Ruling 2 「给 5% 缓冲」 implemented: merged main first (#1189 had landed at d038b957, removing the account renewal fields, their view and seed values), re-measured at 2026-08-17 03:20Z — business 80,356, interaction 39,084, authored total 133,302 — and re-anchored each ceiling at measured x 1.05 rounded up to the next 1,000: 85,000 / 42,000 / 140,000, headroom 4,644 (5.8%) / 2,916 (7.5%) / 6,698 (5.0%). The arithmetic is now one exported function, anchor(tokens), so the header, the advisory and any future re-anchoring PR read it from one place instead of repeating a multiplication. Header prose rewritten: headroom is a deliberate 5% working buffer with the ruling quoted, not 'an anchor, not a growth budget'; the failure message now says the ceiling already carries the buffer, so a red run is growth past the buffer rather than routine drift. Ruling 1 「写实测值 ~80k / ~39k」 needed no code change — the scanner and the measurement basis are untouched, template-literal contents included — but the PR body's measurement section now records that it is settled rather than open. src/ still untouched. HEADROOM ADVISORY (item 3): re-pitched, not dropped, and this is the one judgment call in the round. A flat threshold cannot survive the ruling — 2k on a scope designed to carry 4.6k of headroom fires on every clean run and tells the author to undo the buffer — so the trigger is now relative and one whole buffer clear of the policy: it fires only when headroom exceeds twice the ruled buffer (10%), which means real shrinkage rather than sitting inside the allowance, and it names the ceiling anchor() would commit today so re-anchoring is a copy rather than a calculation. Kept rather than dropped because opportunistic tightening is the half of shrink-only the ruling did not touch; today's run prints no advisory on any scope, which is the intended quiet state.", "tests": "All at 9d140ae2 (tip of the branch, = final commit; the union was run after it, nothing edited since). `pnpm validate && pnpm typecheck && pnpm lint && pnpm lint:i18n-gate && pnpm hygiene && pnpm hygiene:tokens` -> exit 0; gate printed 'business semantics ~80,356 tokens (ceiling ~85,000; headroom ~4,644)', 'interaction layer ~39,084 (ceiling ~42,000; headroom ~2,916)', 'authored total ~133,302 (ceiling ~140,000; headroom ~6,698)', 'source token ratchet clean', and no advisory line on any scope. `pnpm build && pnpm test` -> exit 0, 'Test Files 108 passed (108) / Tests 2627 passed | 1 skipped (2628)' — 108/2627 not 107/2619 because the merge brought in #1189's suite and this round adds a 15th case to mine. `pnpm changeset status --since=origin/main` -> exit 0. anchor() arithmetic verified directly against the three readings: 80356->85000, 39084->42000, 133302->140000, all OK. Reverse verification RE-RUN at the new anchor from the committed state (business ceiling lowered 85,000 -> 80,000 against measured 80,356): 'business semantics is ~80,356 tokens; the ratchet ceiling is ~80,000 (over by ~356). That ceiling already carries the ruled 5% working buffer, so this is growth past the buffer, not routine drift.' + 'source token ratchet failed: 1 scope(s) over ceiling', exit 1; restored with git checkout -> 'source token ratchet clean', exit 0. Direction observed = red, as expected. New test case pins the advisory policy in both directions: a tiny tree nags and names 're-anchor this ceiling to ~1,000', and a scope sitting at 39,084+ tokens against the 42,000 ceiling (headroom ~1,990) gets a clean tick with no advisory on the following line. The over-ceiling fixture was updated to the committed 85,000 and now also asserts the message names the spent buffer. Control-byte self-scan over the three touched files — clean. NOT run locally, all three run on the PR: test:coverage thresholds (scoped to src/objects/*.hook.ts, untouched), Playwright E2E, CodeQL. CI results not awaited, per the report-at-draft-PR contract.", "open_questions": [], "out_of_scope_findings": [ "filed last round as #1191 (sub-issue of #1187, unassigned): the GitHub repository DESCRIPTION still quotes the drifted '~170k tokens' claim — it is repo metadata, not a file, so #1187's grep-based re-check can pass green while the most-viewed copy of the number stays wrong. Still open, unchanged by this round.", "flag for #1180 (do_not_call enforcement, in flight against src/actions/, inside business semantics): under the old sub-1% anchor it would very likely have gone red; with the ruled buffer it has 4,644 tokens of room and should pass untouched. If it lands before this PR, merge main and re-run — the script now prints the exact ceiling to commit." ] }
Generated by Claude Code
ACCEPT (patch round) — #1190 re-reviewed after the 5% ruling. Flipping to ready and queueing.
Maintainer ruling, 2026-08-17, verbatim: 「给 5% 缓冲」 — the sub-1% anchor was rejected because routine work should not be interrupted, only real growth should.
Verified against the branch and GitHub, not the report:
- Committed ceilings are what the report claims:
CEILINGSon the branch reads business 85,000 / interaction 42,000 / authored total 140,000, andanchor()is a real exported function —Math.ceil((tokens * (1 + BUFFER)) / 1000) * 1000— so the header, the advisory and any future re-anchoring PR read the arithmetic from one place rather than repeating it. - The re-anchor was taken on the right tree: base is
d038b957, i.e. after refactor(account): remove the dead account-level renewal model #1189 landed. Confirmed independently that both renewal field declarations are gone frommainand the one remainingrenewal_owneroccurrence inaccount.object.tsis the explanatory comment. So the reading is against today's tree, not the pre-merge one. - All 9 checks green,
Build and Test (22.x)included,mergeable_state: clean. - Path surface is
scripts/,.github/workflows/,package.json,test/,.changeset/— nodocs/adr/**and no skills root, so this takes the normal queue path rather than the human-merge fork.
On the advisory re-pitch — the one judgment call in this round, and it was the right one. The old flat 2k threshold would have fired on every clean run under a 4.6k buffer, telling authors to lower the ceiling the maintainer had just ruled into existence: a gate instructing people to undo a ruling. Making the trigger relative and setting it at twice the buffer keeps opportunistic tightening — the half of shrink-only the ruling did not speak to — while staying quiet inside the allowance. Today's run prints no advisory on any scope, which is the correct quiet state. Choosing to keep it rather than delete it is defensible and defended.
Two accepted rounding artifacts, both disclosed rather than smoothed over: interaction layer lands at 7.5% headroom and business at 5.8%, because ceilings round up to the next 1,000. A ceiling a reader can hold in their head is worth more than a few hundred tokens of precision on a
chars / 4estimate.Also correct: the failure message now distinguishes growth past the buffer from routine drift, so a red run cannot be misread as "the ratchet is too tight".
Numbers of record (from the gate,
main@d038b957): business semantics ~80,356 · interaction layer ~39,084 · authored total ~133,302. #1187 has been updated to cite ~80k / ~39k with the correction recorded.
Generated by Claude Code
- Committed ceilings are what the report claims:
- added a commit that references this issue
on Aug 17, 2026 - added a commit that references this issue
on Sep 10, 2026
Problem
The app's headline positioning claim ("the entire enterprise CRM fits in a single agent context window") has no gate, so it drifts. Measured on main @ 2342811:
translations/+data/Ruled measurement basis
Maintainer rulings, 2026-08-17, PM session chat (verbatim):
So the gate measures comment-stripped, blank-stripped
src/**/*.tsexcludingsrc/translations/andsrc/data/, and reports two headline layers: business semantics (objects+flows+actions+hooks) and interaction layer (views+pages+dashboards+apps). Translations and seed growth is healthy (a new locale must never compete with business logic for budget) and stays outside the ratchet. Because the measure is comment-stripped, the comment-slimming card (#1184) does not move these numbers — by design.Shape
scripts/(natural neighbor:check-source-hygiene.mjs, which already owns the 100KB per-file ceiling), wired into the same CI step.check-skill-line-ratchet.mjs). Raising it requires a maintainer ruling quoted in the PR.Mechanical card: the comment-slimming work is separate (#1184) and lands after — the ratchet only needs today's truth to start.