Skip to content

Commit 8b8ec1f

Browse files
dmealingclaude
andcommitted
docs(cli): --limit is per printed LIST, which is what it always was
Its doc said "per SECTION, never shared across them". The anti-pattern tier prints TWO separately-headed lists — the hand-rolling findings and the F52 missing-baseUrl findings — while being ONE section everywhere else: one structured list keyed by `rule`, one --no-antipatterns muting both. So `--limit 3` can print six advisory lines from a section the payload calls one, which reads like a defect. It is not. The separate budgets exist for the same reason the caps are per-section at all: a shared budget lets the larger list push the smaller one off the end. A project with 200 money-float sites would never see its baseUrl advisory — the one that silently drops the API prefix from every generated hook — and starving the small list is the worse failure. No behaviour changed. The doc states the rule the code follows, and two tests pin it: that each list caps independently and reports its own withheld count, and that the structured payload still carries both lists in one uncapped section. A decision on the record rather than an accident nobody had measured. cli: 917 pass, 0 fail. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NTcEKXTQMYt84fAjuw5A2M
1 parent e529a61 commit 8b8ec1f

3 files changed

Lines changed: 84 additions & 6 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,21 @@ integration cases generated with an explicit `--out <dir>/docs` while `verify --
7272
the config default, and passed only because the two coincided. Sixteen cases failed the moment
7373
they stopped. They now take the config path, which is what the gate reads.
7474

75+
### Changed — `--limit` is documented as per printed LIST, which is what it always was
76+
77+
Its doc said "per SECTION, never shared across them". The anti-pattern tier prints TWO
78+
separately-headed lists — the hand-rolling findings and the F52 missing-`baseUrl` findings —
79+
while being ONE section everywhere else: one structured list keyed by `rule`, one
80+
`--no-antipatterns` muting both. So `--limit 3` can print six advisory lines from a section
81+
the payload calls one, and reads like a defect.
82+
83+
It is not. The separate budgets exist for the same reason the caps are per-section at all: a
84+
shared budget lets the larger list push the smaller one off the end entirely. A project with
85+
200 money-float sites would never see its `baseUrl` advisory — the one that silently drops
86+
the API prefix from every generated hook — and starving the small list is the worse failure.
87+
No behaviour changed; the doc now states the rule the code follows, and a test pins it, so it
88+
is a decision on the record rather than an accident nobody had measured.
89+
7590
### Fixed — the Python nudge was blind to the 1.0 cut it exists to catch
7691

7792
`agent_context_staleness` on the Python port dropped the leading MAJOR from both of its

‎server/typescript/packages/cli/src/lib/args.ts‎

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -310,11 +310,19 @@ export interface VerifyFlags {
310310
*/
311311
lax: boolean;
312312
/**
313-
* How many advisory lines TEXT output prints per SECTION before truncating
314-
* (`DEFAULT_ADVISORY_LIMIT`, or Infinity for `--limit all`). Per-section, never
315-
* shared across them — the reason the two caps were separate in the first place
316-
* is that one shared budget lets the authoring lint push every gate warning off
317-
* the end. It never applies to a structured payload, which carries everything.
313+
* How many advisory lines TEXT output prints per printed LIST before truncating
314+
* (`DEFAULT_ADVISORY_LIMIT`, or Infinity for `--limit all`). Never shared across
315+
* lists — one shared budget lets the biggest list push every other finding off the
316+
* end. It never applies to a structured payload, which carries everything.
317+
*
318+
* Per LIST, not per section, and the distinction is real rather than pedantic: the
319+
* anti-pattern tier prints TWO separately-headed lists (the hand-rolling findings and
320+
* the F52 missing-`baseUrl` findings) while being ONE section everywhere else — one
321+
* structured list keyed by `rule`, one `--no-antipatterns` muting both. So that
322+
* section's text output can reach a MULTIPLE of the cap. Deliberate: a project with 200
323+
* money-float sites would otherwise never see its `baseUrl` advisory, which is the one
324+
* that silently drops the API prefix from every generated hook, and starving the small
325+
* list is the worse failure. Pinned in advisory-structured-output.test.ts.
318326
*/
319327
limit: number;
320328
}

‎server/typescript/packages/cli/test/advisory-structured-output.test.ts‎

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,22 @@ const ENTITY_JSON = JSON.stringify({
5858
* sites — each a money word plus minor-unit math on one line, which is the
5959
* scanner's high-precision `money-float` rule.
6060
*/
61-
function project(extraConfig = ""): string {
61+
function project(extraConfig = "", baseUrlSites = 0): string {
6262
const dir = mkdtempSync(join(tmpdir(), "mo-advisory-"));
6363
mkdirSync(join(dir, "metaobjects"), { recursive: true });
6464
mkdirSync(join(dir, ".metaobjects"), { recursive: true });
6565
mkdirSync(join(dir, "src"), { recursive: true });
6666
writeFileSync(join(dir, "metaobjects", "meta.shop.json"), ENTITY_JSON);
67+
// F52 sites: an entity-fetcher provider mounted with no baseUrl. A SECOND advisory list
68+
// inside the same section as the anti-patterns above — see the two-list cap test.
69+
if (baseUrlSites > 0) {
70+
for (let i = 0; i < baseUrlSites; i++) {
71+
writeFileSync(
72+
join(dir, "src", `mount${i}.tsx`),
73+
`<EntityFetcherProvider fetcher={fetcher}>\n <App />\n</EntityFetcherProvider>\n`,
74+
);
75+
}
76+
}
6777
writeFileSync(join(dir, ".metaobjects", "config.json"), JSON.stringify({ schema_version: 1, sources: [] }));
6878
writeFileSync(
6979
join(dir, "src", "checkout.ts"),
@@ -277,6 +287,51 @@ describe("text mode caps, --limit raises it, and every site honors one constant"
277287
}
278288
}, TIMEOUT_MS);
279289

290+
test("the cap is per printed LIST, so a two-list section can print 2x it", async () => {
291+
// A DECISION, pinned, because it reads like a defect: `--limit 3` yields up to SIX
292+
// advisory lines here. The anti-pattern tier prints two separately-headed lists — the
293+
// hand-rolling findings and the F52 missing-baseUrl findings — and they are one SECTION
294+
// everywhere else: one structured list keyed by `rule`, and one `--no-antipatterns`
295+
// suppressing both.
296+
//
297+
// They keep separate caps for exactly the reason the caps are per-section at all: a
298+
// shared budget lets the larger list push the smaller one off the end entirely. A
299+
// project with 200 money-float sites would never see its baseUrl advisory, which is the
300+
// one that silently drops the API prefix from every generated hook. Starving the small
301+
// list is the worse failure, so each list gets its own budget and the section total can
302+
// reach a multiple of the cap.
303+
const dir = project(`\n apiPrefix: "/api",`, 5);
304+
try {
305+
const { exit, err } = await capture(["verify", "--format", "text", "--limit", "3", "--cwd", dir]);
306+
expect(exit).toBe(0);
307+
// Both lists are present and each is capped at 3 — neither starved the other.
308+
expect(adviceLines(err)).toHaveLength(3);
309+
// "src/mount", not "mount" — the section HEADER says "mounted with no baseUrl".
310+
const baseUrlLines = err.split("\n").filter((l) => l.includes("src/mount"));
311+
expect(baseUrlLines).toHaveLength(3);
312+
// ...and each list says how many IT held back, rather than one shared tail.
313+
expect(err).toContain(`…and ${FINDINGS - 3} more`);
314+
expect(err).toContain("…and 2 more");
315+
} finally {
316+
rmSync(dir, { recursive: true, force: true });
317+
}
318+
}, TIMEOUT_MS);
319+
320+
test("...and the structured payload still carries both lists in ONE section, uncapped", async () => {
321+
const dir = project(`\n apiPrefix: "/api",`, 5);
322+
try {
323+
const { exit, out } = await capture(["verify", "--format", "json", "--limit", "1", "--cwd", dir]);
324+
expect(exit).toBe(0);
325+
const doc = JSON.parse(out) as { antiPatterns?: { total?: number; rows?: FindingRow[] } };
326+
const rows = doc.antiPatterns?.rows ?? [];
327+
expect(rows.filter((r) => r.rule === "missing-base-url")).toHaveLength(5);
328+
expect(rows.filter((r) => r.rule !== "missing-base-url")).toHaveLength(FINDINGS);
329+
expect(doc.antiPatterns?.total).toBe(FINDINGS + 5);
330+
} finally {
331+
rmSync(dir, { recursive: true, force: true });
332+
}
333+
}, TIMEOUT_MS);
334+
280335
test("--limit never truncates a structured payload, whatever it is set to", async () => {
281336
const dir = project();
282337
try {

0 commit comments

Comments
 (0)