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.
- package/README.md +366 -0
- package/dist/bin/specguard-mcp.d.ts +2 -0
- package/dist/bin/specguard-mcp.js +35 -0
- package/dist/bin/specguard-mcp.js.map +1 -0
- package/dist/src/config.d.ts +108 -0
- package/dist/src/config.js +172 -0
- package/dist/src/config.js.map +1 -0
- package/dist/src/errors.d.ts +60 -0
- package/dist/src/errors.js +64 -0
- package/dist/src/errors.js.map +1 -0
- package/dist/src/index.d.ts +5 -0
- package/dist/src/index.js +5 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/server.d.ts +28 -0
- package/dist/src/server.js +113 -0
- package/dist/src/server.js.map +1 -0
- package/dist/src/support/run-command.d.ts +86 -0
- package/dist/src/support/run-command.js +322 -0
- package/dist/src/support/run-command.js.map +1 -0
- package/dist/src/support/specguard-api.d.ts +11 -0
- package/dist/src/support/specguard-api.js +157 -0
- package/dist/src/support/specguard-api.js.map +1 -0
- package/dist/src/tools/args.d.ts +48 -0
- package/dist/src/tools/args.js +66 -0
- package/dist/src/tools/args.js.map +1 -0
- package/dist/src/tools/index.d.ts +33 -0
- package/dist/src/tools/index.js +34 -0
- package/dist/src/tools/index.js.map +1 -0
- package/dist/src/tools/lint-intent-annotations.d.ts +45 -0
- package/dist/src/tools/lint-intent-annotations.js +342 -0
- package/dist/src/tools/lint-intent-annotations.js.map +1 -0
- package/dist/src/tools/repository-overview.d.ts +424 -0
- package/dist/src/tools/repository-overview.js +797 -0
- package/dist/src/tools/repository-overview.js.map +1 -0
- package/dist/src/tools/types.d.ts +111 -0
- package/dist/src/tools/types.js +2 -0
- package/dist/src/tools/types.js.map +1 -0
- 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;
|