specguard-mcp 0.1.0

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.
Files changed (38) hide show
  1. package/README.md +366 -0
  2. package/dist/bin/specguard-mcp.d.ts +2 -0
  3. package/dist/bin/specguard-mcp.js +35 -0
  4. package/dist/bin/specguard-mcp.js.map +1 -0
  5. package/dist/src/config.d.ts +108 -0
  6. package/dist/src/config.js +172 -0
  7. package/dist/src/config.js.map +1 -0
  8. package/dist/src/errors.d.ts +60 -0
  9. package/dist/src/errors.js +64 -0
  10. package/dist/src/errors.js.map +1 -0
  11. package/dist/src/index.d.ts +5 -0
  12. package/dist/src/index.js +5 -0
  13. package/dist/src/index.js.map +1 -0
  14. package/dist/src/server.d.ts +28 -0
  15. package/dist/src/server.js +113 -0
  16. package/dist/src/server.js.map +1 -0
  17. package/dist/src/support/run-command.d.ts +86 -0
  18. package/dist/src/support/run-command.js +322 -0
  19. package/dist/src/support/run-command.js.map +1 -0
  20. package/dist/src/support/specguard-api.d.ts +11 -0
  21. package/dist/src/support/specguard-api.js +157 -0
  22. package/dist/src/support/specguard-api.js.map +1 -0
  23. package/dist/src/tools/args.d.ts +48 -0
  24. package/dist/src/tools/args.js +66 -0
  25. package/dist/src/tools/args.js.map +1 -0
  26. package/dist/src/tools/index.d.ts +33 -0
  27. package/dist/src/tools/index.js +34 -0
  28. package/dist/src/tools/index.js.map +1 -0
  29. package/dist/src/tools/lint-intent-annotations.d.ts +45 -0
  30. package/dist/src/tools/lint-intent-annotations.js +342 -0
  31. package/dist/src/tools/lint-intent-annotations.js.map +1 -0
  32. package/dist/src/tools/repository-overview.d.ts +424 -0
  33. package/dist/src/tools/repository-overview.js +797 -0
  34. package/dist/src/tools/repository-overview.js.map +1 -0
  35. package/dist/src/tools/types.d.ts +111 -0
  36. package/dist/src/tools/types.js +2 -0
  37. package/dist/src/tools/types.js.map +1 -0
  38. package/package.json +44 -0
@@ -0,0 +1,424 @@
1
+ import type { ToolDefinition } from "./types.js";
2
+ /**
3
+ * `GET /api/v1/repository` as a tool — shipped today in the platform
4
+ * (`specguard/config/routes.rb`, `Api::V1::RepositoriesController`).
5
+ *
6
+ * == Why this endpoint is the right second tool
7
+ *
8
+ * It is the agent-readable half of the repository dashboard, and its controller
9
+ * says why it exists in the first line of its own comment: *without* it "an
10
+ * agent can learn the suite's size only by running the suite and POSTing it — it
11
+ * cannot ask". That is the cold-start requirement in Project Goals (SPGD-1) and
12
+ * it is already met server-side; what was missing is a way for an agent to reach
13
+ * it without writing HTTP and Bearer plumbing into its prompt. This tool is
14
+ * exactly that gap and nothing more.
15
+ *
16
+ * One request answers what the suite is, what the last CI run cost, where the
17
+ * time went (by file, by directory, by individual example), and how the suite
18
+ * has grown — so the tool is described in those terms rather than as "get
19
+ * repository", which is not a question anybody asks.
20
+ *
21
+ * == The response is passed through, not re-modelled
22
+ *
23
+ * Every figure in that body is annotated in the controller with the reason for
24
+ * its exact shape, and several of those reasons are about honesty rather than
25
+ * convenience: `null` where a value was not measured (never a zero that would
26
+ * read as a measurement), counts served beside the figures they are the
27
+ * denominator of, `tie_break_served: false` admitting the array's order is not
28
+ * reproducible from the fields served. Any reshaping here — flattening,
29
+ * defaulting a null to 0, re-sorting a list — would discard the distinction the
30
+ * controller spent that care preserving. So the body goes back as it arrived.
31
+ *
32
+ * == `branch` narrows the history, and only the history
33
+ *
34
+ * That asymmetry is a documented property of the endpoint rather than a
35
+ * surprise, and it is stated in the schema because an agent that has not read
36
+ * the controller would otherwise read `latest_run` as belonging to the branch it
37
+ * asked for.
38
+ *
39
+ * == `spec_directory` opens an area the ranking only names
40
+ *
41
+ * `latest_run.spec_directories` ranks the heaviest directories and is served
42
+ * unconditionally, so an agent can already see WHERE the time went — but the
43
+ * ranking is at the area grain and cannot say which files inside the area spent
44
+ * it. `?spec_directory=` is the endpoint's answer to that, and the server has
45
+ * served it since the controller took `RequestedSpecDirectoryParam`: the key
46
+ * `latest_run.spec_directory_files` opens from `null` to a populated object the
47
+ * moment the parameter is sent. This bridge withheld it by not offering the
48
+ * parameter, which made the ranking a dead end for every agent that reached it
49
+ * through MCP.
50
+ *
51
+ * The parameter is forwarded and nothing about it is interpreted here. The
52
+ * server owns the whole meaning of the answer — `null` for "you did not ask",
53
+ * `rows: []` for "asked, matched nothing" (a renamed or deleted directory is an
54
+ * ordinary way to arrive, not an error), and a non-String shape read as no ask
55
+ * at all. A blank one sends no parameter, exactly as `branch` does and for the
56
+ * same reason: `getJson` omits an `undefined` value, so `optionalString` is the
57
+ * whole of the blank-handling in both cases.
58
+ *
59
+ * == `spec_file` and `repeated_description` are the same argument, twice more
60
+ *
61
+ * At that point the endpoint served a FOUR-rung drill-down ladder and this
62
+ * bridge forwarded two of them. The sentence above — "this bridge withheld it
63
+ * by not offering the parameter, which made the ranking a dead end for every
64
+ * agent that reached it through MCP" — was true verbatim of two further rungs,
65
+ * and the block was doubled: `additionalProperties: false` REJECTED the
66
+ * argument before the call, and `run()` would have dropped it anyway. Both
67
+ * rankings are served unconditionally to every caller, so the agent was shown
68
+ * the door and denied the handle:
69
+ *
70
+ * `latest_run.spec_files` → `spec_file_examples` (`spec_file`)
71
+ * `latest_run.repeated_descriptions` → `repeated_description_examples` (`repeated_description`)
72
+ *
73
+ * Both follow `spec_directory` exactly — one schema property, one
74
+ * `optionalString` call, one query key — because the pass-through above means
75
+ * the drilled keys populate the moment the parameter is forwarded, with no
76
+ * serializer or rendering work anywhere on this side.
77
+ *
78
+ * The fourth rung is worth naming separately: `repeated_description_examples`
79
+ * is reachable from NO other key. `slowest_examples` is the run-wide top ten
80
+ * and a group's members are usually absent from it entirely, and walking
81
+ * `spec_file_examples` over each path in the group's `files_seen` is N
82
+ * unrelated lists each cut at 50 BY DURATION, with no guarantee any of the
83
+ * group's members survive the cut in any of them. Withholding this one
84
+ * parameter withheld the capability, not merely a shortcut to it.
85
+ *
86
+ * == `unstable_test` is the fifth rung, and the same argument once more
87
+ *
88
+ * `unstable_tests.rows` → `unstable_tests.unstable_test_runs` (`unstable_test`)
89
+ *
90
+ * A flakiness row says `run_count: 30`, `failed_run_count: 4`,
91
+ * `outcome_words: ["failed", "passed"]`. Those three figures are IDENTICAL for
92
+ * two windows that call for opposite work: four failures in runs 27–30 is a
93
+ * REGRESSION, and the work is to find the commit between run 26 and run 27;
94
+ * four failures in runs 3, 11, 19 and 26 is genuine FLAKINESS, where there is
95
+ * no culprit commit and the work is quarantine or shared state. The RUN
96
+ * SEQUENCE is the only thing that separates them, and it is derivable from
97
+ * nothing else this bridge returns — `history` has no per-test grain, and both
98
+ * example drill-ins carry `outcome` for the LATEST RUN alone. So this is the
99
+ * fourth rung's argument again: withholding the parameter withheld the
100
+ * capability rather than a shortcut to it, and an agent told to "fix the flaky
101
+ * tests" hunts nondeterminism in tests that fail deterministically.
102
+ *
103
+ * TWO THINGS DIFFER FROM ITS FOUR SIBLINGS, and both are stated in the schema
104
+ * because neither is guessable from the ladder. The answer lands INSIDE the
105
+ * flakiness block — `unstable_tests.unstable_test_runs`, not under
106
+ * `latest_run.*` where the two example drill-ins live — and `branch` is a hard
107
+ * PREREQUISITE rather than a suggestion: `unstable_tests` is served only for a
108
+ * branch-narrowed window, so this parameter sent alone yields no block at all
109
+ * to drill into. Every sibling works on a plain call; this one does not.
110
+ *
111
+ * THE SEQUENCE RUNS NEWEST RUN FIRST, and that is stated in the schema too,
112
+ * because this is the one list on this tool where the direction IS the payload.
113
+ * The window is `Repository#recent_test_runs`, ordered `created_at: :desc`, and
114
+ * `SpecObservation.outcome_sequence_in` PRESERVES that order rather than
115
+ * re-sorting it. Read front-to-back as run 1 → run N, the regression above
116
+ * reads as four failures at the START of the window that have passed since —
117
+ * a fixed flake, the exact inversion of the truth, with no error anywhere to
118
+ * signal it. The consequence worth stating outright: the run a failure STARTED
119
+ * at is the LAST row of the leading failed block, not the first. The 200 cap
120
+ * takes rows off the OLD end for the same reason, so a truncated sequence is
121
+ * still the recent runs.
122
+ *
123
+ * The run a row belongs to is read off its `commit_sha` / `test_run_id` and
124
+ * NEVER off its index. `rows.length` is not the window's `run_count`: a run
125
+ * that recorded nothing under the description contributes no row, and a
126
+ * description carried by two examples in one run contributes two. Same
127
+ * direction as `history` is not same index into it.
128
+ *
129
+ * == `commit_sha` is not a sixth rung — it moves the ladder
130
+ *
131
+ * The five parameters above narrow what is served ABOUT a run that
132
+ * `Api::V1::RepositoriesController#latest_test_run` had already chosen. This
133
+ * one CHOOSES THAT RUN, which the controller states in those terms: `?branch=`
134
+ * asks about a SERIES, this asks WHICH RUN. It is read once, in that memo, so
135
+ * every run-grain block moves together — `latest_run` and its rollups, the
136
+ * four RUN-GRAIN drill-ins (`spec_directory_files`, `spec_file_examples`,
137
+ * `repeated_description_examples` and `unannotated_examples`, the flag-style
138
+ * rung documented below), `shards`, both growth windows and
139
+ * `previous_test_run`.
140
+ *
141
+ * That is four of the FIVE drill-ins on this tool, and the excluded one is
142
+ * worth naming because it is the composition an agent will actually try:
143
+ * `unstable_test_runs` is read over the BRANCH WINDOW (`history_runs`), not off
144
+ * the anchored run, so it does not move with this parameter. Sent together,
145
+ * `?commit_sha=` and `?unstable_test=` answer about different things on
146
+ * purpose — one run, and the window that run sits in.
147
+ *
148
+ * Withholding it here withheld a capability that the tool was ALREADY
149
+ * DISCLOSING THE NEED FOR. `branch`'s own description names the failure:
150
+ * "`latest_run` always names the repository's newest run, which on a busy repo
151
+ * may be on another branch". And `unstable_test`'s teaches `commit_sha` as the
152
+ * canonical run handle — read the run off each row's `commit_sha`, never off
153
+ * its index — while every `unstable_test_runs` row carries one. The bridge
154
+ * handed shas out and accepted none back, with `additionalProperties: false`
155
+ * refusing the argument before a request was made, so the agent that most needs
156
+ * it — one that edits tests, pushes, waits for CI and re-reads SpecGuard to
157
+ * check its own work — could not work around it. On a repository where anything
158
+ * else pushed in between, it was silently answered about another commit.
159
+ *
160
+ * `renderText` is the whole body verbatim, so this bridge has been SERVING the
161
+ * `run_anchor` block since the API shipped it — with its only informative state
162
+ * structurally unreachable. `requested_commit_sha` was always `nil` through the
163
+ * bridge, so every MCP call read `source: "default"`, `resolved: true`. A
164
+ * disclosure block cannot disclose a fallback to a client that cannot make the
165
+ * ask that falls back.
166
+ *
167
+ * TWO THINGS ARE STATED IN THE SCHEMA because neither is guessable from the
168
+ * ladder. `history` is NOT re-anchored — it stays the recent runs, narrowed
169
+ * only by `branch` — so the `history[0] == latest_run` identity holds on a
170
+ * default call and is NOT expected to hold under an explicit ask; that is the
171
+ * contract, and a client needing the identity back omits the parameter. And an
172
+ * unknown sha DOES NOT 404: a stale bookmark, a pruned run and a commit whose
173
+ * CI never reported are ordinary ways to arrive, so the endpoint falls back to
174
+ * the newest run and SAYS SO (`source: "requested"`, `resolved: false`, the raw
175
+ * ask kept in `requested_commit_sha`, `commit_sha`/`branch` naming what was
176
+ * actually served). Nothing else about the response looks unusual, which is why
177
+ * the schema tells the agent to read `run_anchor.resolved`.
178
+ *
179
+ * == `unannotated_examples` is the one that is a FLAG rather than a name
180
+ *
181
+ * `latest_run.total_specs` − `annotated_specs` → `latest_run.unannotated_examples`
182
+ *
183
+ * The argument for forwarding it is the ladder's again — the parameter was
184
+ * withheld by not being offered, `additionalProperties: false` refused it before
185
+ * a request was made, and `renderText` has therefore been serving
186
+ * `unannotated_examples: null` (the server's "you did not ask" spelling) to a
187
+ * client structurally incapable of asking. What is new is WHOSE question it
188
+ * answers. This is the adoption metric of Project Goals (SPGD-1): an agent told
189
+ * to raise annotation coverage was served `annotated_ratio` and a `null`, so it
190
+ * learned how far it had to go and could not name a single test to annotate. A
191
+ * plain `curl` user could.
192
+ *
193
+ * ONE THING DIFFERS FROM ALL SIX SIBLINGS, and it is the reason this forward is
194
+ * not a copy of the previous five. Every parameter above names a WHICH — which
195
+ * branch, which commit, which area, which file, which description, which test —
196
+ * because each opens the rows behind a LINE of a ranking the client had already
197
+ * read. This one opens a POPULATION rather than a pick: the figure it drills out
198
+ * of is a SUBTRACTION on the run itself, and a subtraction has no rows to have
199
+ * keys, so there is nothing for the ask itself to NAME. So the server reads only
200
+ * whether the parameter was NAMED, which `RequestedUnannotatedExamplesParam`
201
+ * states outright — the value is not read, and THAT INCLUDES `false`:
202
+ * `?unannotated_examples=false` opens the block exactly as `=true` does.
203
+ *
204
+ * That is a hazard on this side rather than a curiosity, and it is why the
205
+ * argument is a BOOLEAN coerced with `optionalBoolean` and the query key is
206
+ * built rather than stringified. `getJson` omits only `undefined`, so
207
+ * `String(false)` would put `unannotated_examples=false` on the URL and open a
208
+ * hundred-row block for the one caller who asked explicitly for it NOT to be
209
+ * opened — the exact misreading the server's guard file exists to prevent, made
210
+ * on the other side of the wire. The key is sent as `"true"` on an affirmative
211
+ * ask and is `undefined` otherwise, so declining and omitting are the same wire
212
+ * request. That matches how every other parameter here is declined: none of them
213
+ * has an "off" value either.
214
+ *
215
+ * WHICH POPULATION IT OPENS IS NOT FIXED, and that is the half this file first
216
+ * got wrong. `specguard` `55e3a09` made `?spec_file=` and `?spec_directory=`
217
+ * narrow this block when either rides along with the flag — the same two
218
+ * parameters that open their own drill-ins beside it — so the ask has FOUR
219
+ * shapes rather than one: the whole run, one file, one area, or the AND of a
220
+ * file and an area. The flag still names nothing, because the narrowing is named
221
+ * by those two parameters and not by this one; what changed is that "the
222
+ * population" is no longer a definite article. `SpecObservation.unannotated_in`
223
+ * appends both predicates to the WHERE that the `COUNT(*) OVER ()` window of
224
+ * `UNANNOTATED_POPULATION_COUNTS` rides, so `recorded_count` counts the NARROWED
225
+ * population rather than the run's — and the controller echoes `spec_file` and
226
+ * `spec_directory` back INSIDE the block, as the server read them and `null`
227
+ * when not sent, for exactly that reason: `recorded_count` is the one figure
228
+ * here a client reconciles against `total_specs - annotated_specs`, and a
229
+ * silently narrowed count breaks that reconciliation. The echo is what makes the
230
+ * count's population readable.
231
+ *
232
+ * ⭐ AND THE ONE ASK NOW OPENS TWO BLOCKS, THE SECOND OF WHICH IS A RANKING.
233
+ * `specguard` `9df1b3d` added `latest_run.unannotated_directories` under this
234
+ * SAME flag — no new parameter, no new value, nothing extra for a client to send
235
+ * — so every call that already asks for the worklist is already being served the
236
+ * map beside it. It answers what the worklist cannot: the worklist is WHICH
237
+ * TESTS to go and annotate, and the map is WHERE THE DEBT IS, rolled up by area,
238
+ * which is what a reader picks the next `?spec_directory=` narrowing FROM. That
239
+ * is the same shape `spec_directory`'s own description states one parameter
240
+ * over — one ask, several blocks, each in its own grain — so it is said here in
241
+ * that form rather than in a new one.
242
+ *
243
+ * TWO CAPS UNDER ONE ASK, and the difference is the KIND of list rather than the
244
+ * grain. `UNANNOTATED_EXAMPLES_LIMIT` is 100 and `UNANNOTATED_DIRECTORIES_LIMIT`
245
+ * is 10, and the server's constant says why: the hundred caps a WORKLIST, sized
246
+ * for a batch somebody opens, annotates and re-delivers in one sitting; the ten
247
+ * caps a RANKING, which exists only to name where the debt is concentrated, and
248
+ * a reader who cannot pick from ten areas is not helped by eighty. The ORDERS
249
+ * differ for the same reason — the worklist is file-navigable, and the map is
250
+ * `unannotated_count DESC, path ASC`: ranked by debt, with path as a tiebreak
251
+ * only. A fully-annotated area is a REAL ROW here with `unannotated_count: 0`
252
+ * against its real `recorded_count`, never an omission. Those rows sort last
253
+ * COLLECTIVELY, so on a run with more areas than the cap they are cut and never
254
+ * seen, but on a run inside the cap they ARE LISTED and listed is correct. So
255
+ * `rows.size` is not a count of areas WITH debt — read each row's
256
+ * `unannotated_count` for that; and `directory_count` counts EVERY area the run
257
+ * touched, not every area with debt, and never `rows.size` either.
258
+ *
259
+ * ⭐ THE TWO KEYS OF THIS ONE BLOCK DISAGREE IN TWO PLACES, ON PURPOSE, and both
260
+ * are counting traps rather than curiosities. `serialized_unannotated_directories`
261
+ * discloses both at unusual length precisely because the machine-readable
262
+ * consumer is the one that would otherwise discover them by arithmetic — and
263
+ * this bridge IS that consumer.
264
+ *
265
+ * (a) SCOPE. `unannotated_examples.recorded_count` NARROWS with `?spec_file=` /
266
+ * `?spec_directory=`; `unannotated_directories` stays WHOLE-RUN under both. So
267
+ * under a narrowing the worklist's `recorded_count` is NOT the sum of the map's
268
+ * `unannotated_count`s, AND NEITHER FIGURE IS WRONG: the first counts the one
269
+ * area or file you named, the second ranks the whole run. The map is whole-run
270
+ * BY DESIGN, because it is the thing a client picks a narrowing FROM and a map
271
+ * that narrowed to the area you had already picked would answer nothing — one
272
+ * row, echoing the parameter back. The sum is short of the run's total whenever
273
+ * `directory_count > rows.size` besides, narrowing or no narrowing. This is why
274
+ * the reconciliation rule above is scoped to the WORKLIST's count and to that
275
+ * count alone.
276
+ *
277
+ * (b) NULL VERSUS EMPTY. On a run that recorded no per-example rows at all, with
278
+ * the flag sent, `unannotated_examples` is a PRESENT block with `rows: []` and
279
+ * `recorded_count: 0`, while `unannotated_directories` is `null`. That is not an
280
+ * inconsistency to iron out. The sibling's zero is ambiguous by construction —
281
+ * "fully annotated" and "recorded nothing at all" reach the same
282
+ * `recorded_count: 0` there — and this key is how a client tells them apart: a
283
+ * PRESENT map beside that zero means the run has a per-area grain and the zero
284
+ * is the SUCCESS state; a `null` map means the run recorded nothing and the zero
285
+ * is an ABSENCE of data. Serving `rows: []` here instead would spend a
286
+ * distinction a client has no other way to make.
287
+ *
288
+ * THE `commit_sha` ROSTERS above and in README.md CORRECTLY STAY AT FOUR, and
289
+ * the reason is the roster's UNIT, not anything about this block's shape. That
290
+ * roster carries ONE REPRESENTATIVE KEY PER DRILL-IN PARAMETER, not one entry
291
+ * per response key: `spec_directory` opens THREE blocks (see its own
292
+ * description below), yet only `spec_directory_files` is on the roster —
293
+ * `directory_run_file_growth` and `directory_runtime_file_growth` are absent
294
+ * from it for exactly this reason, and the guard in
295
+ * `test/tools/repository-overview.test.ts` enforces it that way, deriving the
296
+ * obligation from the schema's PARAMETERS and mapping each to the single key it
297
+ * represents. `unannotated_directories` is a SECOND BLOCK OF AN EXISTING
298
+ * PARAMETER'S ASK and adds no parameter, so it is not a roster entry. It is at
299
+ * run grain and does re-anchor, and is covered there by "`latest_run` and its
300
+ * rollups".
301
+ *
302
+ * FOUR THINGS ARE STATED IN THE SCHEMA. It is at RUN GRAIN, so it moves with
303
+ * `commit_sha` like everything else under `latest_run` — unlike `unstable_test`,
304
+ * which does not. A FULLY-ANNOTATED run answers `rows: []` /
305
+ * `recorded_count: 0` with 200, never a 404 and never the no-ask `null`: that is
306
+ * the state the metric exists to reach, so an agent walking a repository to
307
+ * completion must see the block go empty rather than watch it vanish at the
308
+ * moment it succeeded and be unable to tell that from its own parameter having
309
+ * been dropped. The pair above — that `spec_file`/`spec_directory` narrow this
310
+ * population when they ride along, and are echoed back so the client can tell
311
+ * which population `recorded_count` is of. And the second block this one ask
312
+ * opens, with its own cap, its own ranking order and both of the disagreements
313
+ * above, because a pass-through `renderText` puts that key in front of every
314
+ * agent whether or not anything here has named it.
315
+ *
316
+ * == `delivery_health` and `credential_health` are that rule applied to the
317
+ * == blocks that say WHETHER TO BELIEVE THE REST
318
+ *
319
+ * The sentence directly above is the whole argument, and until now it was
320
+ * unapplied at the top level of the very same body. `Api::V1::RepositoriesController`
321
+ * serves both blocks UNCONDITIONALLY — it says "SERVED ON EVERY RESPONSE" in
322
+ * capitals at both sites — so `renderText` has been handing them to every MCP
323
+ * agent since they shipped, while this description enumerated the response in
324
+ * exhaustive detail and named neither.
325
+ *
326
+ * NOTHING HERE OPENS THEM, which is exactly why nothing here caught the
327
+ * omission. Every other block this file discusses arrived attached to a
328
+ * parameter, and the roster guard in `test/tools/repository-overview.test.ts`
329
+ * derives its obligation from `inputSchema.properties` — so a block that adds no
330
+ * property is structurally invisible to it, as `unannotated_directories` was one
331
+ * section up. The only other check on this string is a `length >= 80` floor. The
332
+ * schema is UNTOUCHED by this change for that reason: the two are response
333
+ * blocks, not asks, and a reader must not be able to infer a flag that does not
334
+ * exist.
335
+ *
336
+ * WHAT THEY ANSWER IS "WHY IS THIS DATA LYING TO ME", which is the one question
337
+ * an agent cannot answer from any other key here. `delivery_health` is the
338
+ * staleness verdict — `refusing?`, `last_rejection_at`, and the endpoint's own
339
+ * refusal reasons per retained delivery — and without it a `latest_run` that is
340
+ * days old reads as a suite nobody ran rather than a suite the platform stopped
341
+ * accepting. `credential_health` covers the one failure `delivery_health`
342
+ * structurally cannot: a 401 resolves no repository and writes no
343
+ * `IngestRejection` row, so an auth-broken pipeline leaves every rejection
344
+ * figure at zero. It reports the state anyway because it need not observe the
345
+ * 401 — it owns the key row and stamped the instant the token was retired.
346
+ *
347
+ * A QUIET ANSWER IS A POSITIVE FINDING, and that is stated outright rather than
348
+ * left to be inferred, on the controller's own reasoning at both sites: an agent
349
+ * that is served `refusing: false` must be able to tell "nothing was refused"
350
+ * from "SpecGuard does not track that", and the difference is not visible in the
351
+ * value. A human reads the dashboard panels for this; an agent reads only what
352
+ * this string told it to look for.
353
+ *
354
+ * TWO FURTHER KEYS ARE NAMED HERE FOR THE SAME REASON, both found by taking the
355
+ * membership question as a GREP over the endpoint's top-level keys rather than
356
+ * as a reading of this file. `api_key.last_used_at` is the claim the two health
357
+ * blocks exist to CORRECT — it is stamped on the way in, before the payload is
358
+ * looked at, so a repository whose every delivery is refused serves its freshest
359
+ * timestamp beside its stalest run, and the controller answers that with
360
+ * `acceptance_reported_by` / `rotation_reported_by` naming the keys that answer
361
+ * what it cannot. Naming the correction and not the claim would have been half a
362
+ * sentence. And the truncation contract, which is NOT the uniform family it looks like from the
363
+ * key names: only eight lists have a `*_window` sibling at all, MOST lists under `latest_run`
364
+ * carry an inline `limit` beside `rows` instead, four of those windows serve no bound of their
365
+ * own, `rejections_window` serves a bound and no order, and the lists this census found carrying
366
+ * no bound anywhere are `credential_health.keys` and BOTH `latest_run.shards` lists (`rows`,
367
+ * ranked slowest-first off `TestRun#shard_durations`, and `per_shard`, in delivery order off
368
+ * `#shard_reports`), each complete by construction. So the rule is stated in the direction that
369
+ * stays true as the endpoint grows, and whose correctness does NOT depend on that list being
370
+ * exhaustive: a bound BESIDE a list means a page, and no bound means the whole set. Quantifying
371
+ * over the capped cases instead — "every ranking is capped, except..." — is what put a false
372
+ * universal here twice, because the census that produced it counted lists that HAVE a bound and
373
+ * never asked how many have none. It sends an agent looking for a disclosure that does not exist
374
+ * and leaves it unable to tell "complete by construction" from "silently cut", which is the exact
375
+ * misreading the two blocks above were named to prevent.
376
+ *
377
+ * == `suite_size_measured`, `shard_count` and `timed_shard_count` are that rule
378
+ * == applied a FOURTH time, to the keys that say WHETHER TWO ROWS MAY BE
379
+ * == DIFFERENCED AT ALL
380
+ *
381
+ * The same argument, the same blind spot, the same remedy — and the remaining
382
+ * unapplied case. `serialized_history_row` in `Api::V1::RepositoriesController`
383
+ * puts all three on EVERY `history[]` row, and `suite_size_measured` is served a
384
+ * SECOND time on `latest_run`, deliberately from the same predicate so that a
385
+ * single response body cannot describe one row twice and disagree with itself
386
+ * (in the unfiltered window `history[0]` IS `latest_run`). Until now this string
387
+ * named none of the three, while selling history differencing outright: the
388
+ * `branch` parameter below tells an agent that consecutive all-branch rows "must
389
+ * not be differenced", which teaches the differencing and names only the one
390
+ * hazard that happens to be expressible as a parameter.
391
+ *
392
+ * THEY ARE ONE BLOCK BECAUSE THEY ARE ONE QUESTION. `suite_size_measured` says
393
+ * whether a row is a measurement at all; `shard_count` is the denominator of
394
+ * `total_specs` (a SUM over the shards RECORDED, and what `TestRun#assembled_like?`
395
+ * reads to decide differenceability); `timed_shard_count` is the denominator of
396
+ * `duration_seconds` (a MAX over the shards that REPORTED, whose absence lets a
397
+ * client report the controller's "70% speedup produced entirely by telemetry
398
+ * loss"). An agent that differences two rows without all three gets a number
399
+ * wearing a SHA and a timestamp that make it read as a checked fact.
400
+ *
401
+ * THE LAST SENTENCE OF THE DESCRIPTION WAS ALSO WRONG IN ITS REACH, not merely
402
+ * silent. "A null is 'not measured', never zero" routes a reader to NULLNESS as
403
+ * the measured/not-measured signal, but `TestRun#suite_size_measured?` is
404
+ * `total_specs_count.to_i.positive?` — so a run that reported zero tests serves a
405
+ * NON-NULL `total_specs: 0` beside `suite_size_measured: false`. A reader obeying
406
+ * the string's own stated rule reads that row as "measured, 0 tests" where the
407
+ * server says "not a measurement". The sentence's true content about nulls is
408
+ * kept; what is added is the bound, that the rule does not run backwards. The
409
+ * controller serialized the boolean rather than leaving the client to re-derive
410
+ * it from `total_specs` precisely so the two could not drift — and this bridge's
411
+ * silence was forcing every MCP agent into exactly that re-derivation.
412
+ *
413
+ * THE SCHEMA IS UNTOUCHED, for the reason stated one section up: these are
414
+ * RESPONSE keys, not asks, and a reader must not be able to infer a flag that
415
+ * does not exist. And NO GUARD CAN CATCH A REGRESSION OF THIS CHANGE — every
416
+ * roster guard in `test/tools/repository-overview.test.ts` opens with
417
+ * `inputSchema.properties` and derives its obligation from a PARAMETER, so a
418
+ * block that adds none is invisible to them by construction, and the only other
419
+ * check on this string is a `length >= 80` floor. That is why the reasoning is
420
+ * recorded here at this length: this comment is the only thing standing between
421
+ * these three keys and a silent re-wording that drops them again.
422
+ */
423
+ declare const getRepositoryOverview: ToolDefinition;
424
+ export default getRepositoryOverview;