specguard-mcp 0.1.13 → 0.1.14
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 +25 -6
- package/dist/src/tools/index.d.ts +19 -16
- package/dist/src/tools/index.js +19 -16
- package/dist/src/tools/index.js.map +1 -1
- package/dist/src/tools/list-repositories.d.ts +45 -5
- package/dist/src/tools/list-repositories.js +60 -6
- package/dist/src/tools/list-repositories.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -367,16 +367,35 @@ blank value is no ask: passing an empty string and omitting the argument make th
|
|
|
367
367
|
| `sort` | `"stale"` — re-order stalest-first: repositories CI has never ingested a run for first, then least-recently-ingested, with `full_name` breaking ties so two calls agree element for element. The same entries, a different order; the default order stays `full_name` ascending |
|
|
368
368
|
|
|
369
369
|
The body comes back as SpecGuard serves it — `{"repositories": […]}`, each entry carrying `id`,
|
|
370
|
-
`full_name`, `name`, `registered_at` and `
|
|
371
|
-
asks otherwise. The
|
|
372
|
-
|
|
373
|
-
|
|
370
|
+
`full_name`, `name`, `registered_at`, `role`, `delivery_health` and `latest_run`, ordered by
|
|
371
|
+
`full_name` ascending unless `sort` asks otherwise. The identity fields are deliberately the same
|
|
372
|
+
fields, under the same names, that `get_repository_overview` serves in its own `repository` block,
|
|
373
|
+
so a client that reads one reads the other.
|
|
374
|
+
|
|
375
|
+
`delivery_health` is the entry's delivery verdict — `refusing` beside `last_rejection_at` — served
|
|
376
|
+
on **every** entry, so this one call triages ingest-pipeline health across the whole reachable set
|
|
377
|
+
instead of paying one `get_repository_overview` call per repository. It is the coarse sibling of
|
|
378
|
+
the overview's fuller per-repository block. A quiet verdict is a finding, not a gap:
|
|
379
|
+
`refusing: false` means nothing was refused, never that delivery is untracked.
|
|
380
|
+
|
|
381
|
+
`latest_run` is the entry's newest run — when CI last reported, on what branch and commit, how big
|
|
382
|
+
the suite is, how much of it SpecGuard can read, and the run-level cost scalars. `null` means CI
|
|
383
|
+
has **never reported** for that repository — never a run that found an empty suite.
|
|
384
|
+
|
|
385
|
+
`role` has one value per
|
|
374
386
|
credential kind: under a **user** key (`sgu_…`) it is `owner` or `member` — the list mixes
|
|
375
387
|
repositories this person owns with repositories somebody shared with them, and nothing else tells
|
|
376
388
|
them apart — while under an **agent** key (`sga_…`) every entry is `agent`, the value that says the
|
|
377
389
|
ownership question does not apply because the key speaks for nobody (branching on owner/member
|
|
378
|
-
correctly reads false for both). Read it before assuming a repository is one you may administer.
|
|
379
|
-
|
|
390
|
+
correctly reads false for both). Read it before assuming a repository is one you may administer.
|
|
391
|
+
|
|
392
|
+
Under an **agent** key the top level also carries `credential.capabilities` — the calling key's own
|
|
393
|
+
grant, read through the server's policy, so an agent key learns what it was granted here instead of
|
|
394
|
+
discovering its permissions by hitting refusals; under a **user** key the block is absent, not
|
|
395
|
+
`null` — a person key has no mint-time permission set, and a null would assert one exists and is
|
|
396
|
+
empty.
|
|
397
|
+
|
|
398
|
+
An empty list means no access, not an error.
|
|
380
399
|
|
|
381
400
|
**It reads a different key from `get_repository_overview` — either of two, whichever is set.**
|
|
382
401
|
`SPECGUARD_AGENT_API_KEY` (`sga_…`) when it is set: the answer is then the repository set granted
|
|
@@ -20,7 +20,7 @@ import type { ToolDefinition } from "./types.js";
|
|
|
20
20
|
* the two halves of the surface — one local subprocess, one authenticated HTTP
|
|
21
21
|
* call — so the shape is proven on both kinds of capability rather than on one.
|
|
22
22
|
*
|
|
23
|
-
* == The
|
|
23
|
+
* == The first tool that reads the OTHER credential
|
|
24
24
|
*
|
|
25
25
|
* - `list_repositories` wraps `GET /api/v1/repositories` (shipped:
|
|
26
26
|
* `specguard/config/routes.rb`, `Api::V1::UserRepositoriesController`).
|
|
@@ -43,15 +43,7 @@ import type { ToolDefinition } from "./types.js";
|
|
|
43
43
|
* not offering the tool, because the agent has already committed to a plan by
|
|
44
44
|
* the time it finds out.
|
|
45
45
|
*
|
|
46
|
-
*
|
|
47
|
-
* rule — no tool may wrap what has not shipped. That half retired when the
|
|
48
|
-
* platform moved: SPGD-703 (`specguard` `c43dc19`, 2026-08-28) shipped
|
|
49
|
-
* `GET /api/v1/repository?near_duplicates=`, serving
|
|
50
|
-
* `RepositoryOverview#serialized_near_duplicates` behind an opt-in ask, and
|
|
51
|
-
* `near_duplicate_clusters` below wraps it. What moved was the platform, not
|
|
52
|
-
* the bar.
|
|
53
|
-
*
|
|
54
|
-
* == The fourth: the first tool that WRITES
|
|
46
|
+
* == The first tool that WRITES
|
|
55
47
|
*
|
|
56
48
|
* - `add_repository` wraps `POST /api/v1/repositories` (shipped:
|
|
57
49
|
* `specguard/config/routes.rb`, `Api::V1::UserRepositoriesController#create`).
|
|
@@ -69,10 +61,10 @@ import type { ToolDefinition } from "./types.js";
|
|
|
69
61
|
* The standing rule is unchanged and still binding. What once kept `DELETE
|
|
70
62
|
* /api/v1/repositories/:id` and the API-key endpoints out under it — "not on
|
|
71
63
|
* `origin/main`, so they may not be wrapped" — stopped being true when SPGD-754
|
|
72
|
-
* shipped them, and the
|
|
64
|
+
* shipped them, and the removal-and-key-lifecycle section below records their
|
|
73
65
|
* wrapping. What moved was the platform, not the bar.
|
|
74
66
|
*
|
|
75
|
-
* == The
|
|
67
|
+
* == The read half of the registration gate
|
|
76
68
|
*
|
|
77
69
|
* - `registrable_repositories` wraps `GET /api/v1/repositories/registrable`
|
|
78
70
|
* (shipped: `specguard/config/routes.rb:117`,
|
|
@@ -93,7 +85,7 @@ import type { ToolDefinition } from "./types.js";
|
|
|
93
85
|
* NOT on `origin/main`, so they may not be wrapped here however useful a tool
|
|
94
86
|
* for them would be. What moved was the platform, not the bar.
|
|
95
87
|
*
|
|
96
|
-
* ==
|
|
88
|
+
* == Removal and the key lifecycle
|
|
97
89
|
*
|
|
98
90
|
* - `remove_repository` wraps `DELETE /api/v1/repositories/:id`, and
|
|
99
91
|
* `create_repository_api_key` / `revoke_repository_api_key` wrap the two
|
|
@@ -113,7 +105,18 @@ import type { ToolDefinition } from "./types.js";
|
|
|
113
105
|
* as a comment only). A tool advertised in `tools/list` remains a promise an
|
|
114
106
|
* agent will act on.
|
|
115
107
|
*
|
|
116
|
-
* ==
|
|
108
|
+
* == Near-duplicate clusters: the forbid that retired
|
|
109
|
+
*
|
|
110
|
+
* - `near_duplicate_clusters` wraps `GET /api/v1/repository?near_duplicates=`
|
|
111
|
+
* (shipped: SPGD-703, `specguard` `c43dc19`, 2026-08-28), serving
|
|
112
|
+
* `RepositoryOverview#serialized_near_duplicates` behind an opt-in ask.
|
|
113
|
+
*
|
|
114
|
+
* Duplicate clustering was once under the same forbid that still keeps
|
|
115
|
+
* `/check-intent` out — the standing rule: no tool may wrap what has not
|
|
116
|
+
* shipped. That half retired when the platform moved and this entry became
|
|
117
|
+
* wrappable. What moved was the platform, not the bar.
|
|
118
|
+
*
|
|
119
|
+
* == Member management
|
|
117
120
|
*
|
|
118
121
|
* - `list_repository_members`, `add_repository_member`,
|
|
119
122
|
* `update_repository_member_permissions` and `remove_repository_member`
|
|
@@ -132,7 +135,7 @@ import type { ToolDefinition } from "./types.js";
|
|
|
132
135
|
* stays scoped to the repository (a foreign id is refused 404), and there is
|
|
133
136
|
* no name-based lookup.
|
|
134
137
|
*
|
|
135
|
-
* ==
|
|
138
|
+
* == Rename
|
|
136
139
|
*
|
|
137
140
|
* - `rename_repository` wraps `PATCH /api/v1/repositories/:id` (shipped:
|
|
138
141
|
* SPGD-878, `specguard@origin/main` e026793, PR #266).
|
|
@@ -148,7 +151,7 @@ import type { ToolDefinition } from "./types.js";
|
|
|
148
151
|
* remove-and-re-register, which destroys every key, run and intent; this one
|
|
149
152
|
* keeps them.
|
|
150
153
|
*
|
|
151
|
-
* == The
|
|
154
|
+
* == The agent-key family
|
|
152
155
|
*
|
|
153
156
|
* - `list_repository_agent_keys` and `revoke_repository_agent_key` wrap the
|
|
154
157
|
* inventory and revoke of `user_repository_agent_keys` (shipped: SPGD-1004,
|
package/dist/src/tools/index.js
CHANGED
|
@@ -36,7 +36,7 @@ import updateRepositoryMemberPermissions from "./update-repository-member-permis
|
|
|
36
36
|
* the two halves of the surface — one local subprocess, one authenticated HTTP
|
|
37
37
|
* call — so the shape is proven on both kinds of capability rather than on one.
|
|
38
38
|
*
|
|
39
|
-
* == The
|
|
39
|
+
* == The first tool that reads the OTHER credential
|
|
40
40
|
*
|
|
41
41
|
* - `list_repositories` wraps `GET /api/v1/repositories` (shipped:
|
|
42
42
|
* `specguard/config/routes.rb`, `Api::V1::UserRepositoriesController`).
|
|
@@ -59,15 +59,7 @@ import updateRepositoryMemberPermissions from "./update-repository-member-permis
|
|
|
59
59
|
* not offering the tool, because the agent has already committed to a plan by
|
|
60
60
|
* the time it finds out.
|
|
61
61
|
*
|
|
62
|
-
*
|
|
63
|
-
* rule — no tool may wrap what has not shipped. That half retired when the
|
|
64
|
-
* platform moved: SPGD-703 (`specguard` `c43dc19`, 2026-08-28) shipped
|
|
65
|
-
* `GET /api/v1/repository?near_duplicates=`, serving
|
|
66
|
-
* `RepositoryOverview#serialized_near_duplicates` behind an opt-in ask, and
|
|
67
|
-
* `near_duplicate_clusters` below wraps it. What moved was the platform, not
|
|
68
|
-
* the bar.
|
|
69
|
-
*
|
|
70
|
-
* == The fourth: the first tool that WRITES
|
|
62
|
+
* == The first tool that WRITES
|
|
71
63
|
*
|
|
72
64
|
* - `add_repository` wraps `POST /api/v1/repositories` (shipped:
|
|
73
65
|
* `specguard/config/routes.rb`, `Api::V1::UserRepositoriesController#create`).
|
|
@@ -85,10 +77,10 @@ import updateRepositoryMemberPermissions from "./update-repository-member-permis
|
|
|
85
77
|
* The standing rule is unchanged and still binding. What once kept `DELETE
|
|
86
78
|
* /api/v1/repositories/:id` and the API-key endpoints out under it — "not on
|
|
87
79
|
* `origin/main`, so they may not be wrapped" — stopped being true when SPGD-754
|
|
88
|
-
* shipped them, and the
|
|
80
|
+
* shipped them, and the removal-and-key-lifecycle section below records their
|
|
89
81
|
* wrapping. What moved was the platform, not the bar.
|
|
90
82
|
*
|
|
91
|
-
* == The
|
|
83
|
+
* == The read half of the registration gate
|
|
92
84
|
*
|
|
93
85
|
* - `registrable_repositories` wraps `GET /api/v1/repositories/registrable`
|
|
94
86
|
* (shipped: `specguard/config/routes.rb:117`,
|
|
@@ -109,7 +101,7 @@ import updateRepositoryMemberPermissions from "./update-repository-member-permis
|
|
|
109
101
|
* NOT on `origin/main`, so they may not be wrapped here however useful a tool
|
|
110
102
|
* for them would be. What moved was the platform, not the bar.
|
|
111
103
|
*
|
|
112
|
-
* ==
|
|
104
|
+
* == Removal and the key lifecycle
|
|
113
105
|
*
|
|
114
106
|
* - `remove_repository` wraps `DELETE /api/v1/repositories/:id`, and
|
|
115
107
|
* `create_repository_api_key` / `revoke_repository_api_key` wrap the two
|
|
@@ -129,7 +121,18 @@ import updateRepositoryMemberPermissions from "./update-repository-member-permis
|
|
|
129
121
|
* as a comment only). A tool advertised in `tools/list` remains a promise an
|
|
130
122
|
* agent will act on.
|
|
131
123
|
*
|
|
132
|
-
* ==
|
|
124
|
+
* == Near-duplicate clusters: the forbid that retired
|
|
125
|
+
*
|
|
126
|
+
* - `near_duplicate_clusters` wraps `GET /api/v1/repository?near_duplicates=`
|
|
127
|
+
* (shipped: SPGD-703, `specguard` `c43dc19`, 2026-08-28), serving
|
|
128
|
+
* `RepositoryOverview#serialized_near_duplicates` behind an opt-in ask.
|
|
129
|
+
*
|
|
130
|
+
* Duplicate clustering was once under the same forbid that still keeps
|
|
131
|
+
* `/check-intent` out — the standing rule: no tool may wrap what has not
|
|
132
|
+
* shipped. That half retired when the platform moved and this entry became
|
|
133
|
+
* wrappable. What moved was the platform, not the bar.
|
|
134
|
+
*
|
|
135
|
+
* == Member management
|
|
133
136
|
*
|
|
134
137
|
* - `list_repository_members`, `add_repository_member`,
|
|
135
138
|
* `update_repository_member_permissions` and `remove_repository_member`
|
|
@@ -148,7 +151,7 @@ import updateRepositoryMemberPermissions from "./update-repository-member-permis
|
|
|
148
151
|
* stays scoped to the repository (a foreign id is refused 404), and there is
|
|
149
152
|
* no name-based lookup.
|
|
150
153
|
*
|
|
151
|
-
* ==
|
|
154
|
+
* == Rename
|
|
152
155
|
*
|
|
153
156
|
* - `rename_repository` wraps `PATCH /api/v1/repositories/:id` (shipped:
|
|
154
157
|
* SPGD-878, `specguard@origin/main` e026793, PR #266).
|
|
@@ -164,7 +167,7 @@ import updateRepositoryMemberPermissions from "./update-repository-member-permis
|
|
|
164
167
|
* remove-and-re-register, which destroys every key, run and intent; this one
|
|
165
168
|
* keeps them.
|
|
166
169
|
*
|
|
167
|
-
* == The
|
|
170
|
+
* == The agent-key family
|
|
168
171
|
*
|
|
169
172
|
* - `list_repository_agent_keys` and `revoke_repository_agent_key` wrap the
|
|
170
173
|
* inventory and revoke of `user_repository_agent_keys` (shipped: SPGD-1004,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/tools/index.ts"],"names":[],"mappings":"AAAA,OAAO,aAAa,MAAM,qBAAqB,CAAC;AAChD,OAAO,mBAAmB,MAAM,4BAA4B,CAAC;AAC7D,OAAO,sBAAsB,MAAM,gCAAgC,CAAC;AACpE,OAAO,qBAAqB,MAAM,8BAA8B,CAAC;AACjE,OAAO,gBAAgB,MAAM,wBAAwB,CAAC;AACtD,OAAO,uBAAuB,MAAM,iCAAiC,CAAC;AACtE,OAAO,uCAAuC,MAAM,mDAAmD,CAAC;AACxG,OAAO,qBAAqB,MAAM,8BAA8B,CAAC;AACjE,OAAO,qBAAqB,MAAM,8BAA8B,CAAC;AACjE,OAAO,qBAAqB,MAAM,0BAA0B,CAAC;AAC7D,OAAO,uBAAuB,MAAM,+BAA+B,CAAC;AACpE,OAAO,gBAAgB,MAAM,wBAAwB,CAAC;AACtD,OAAO,sBAAsB,MAAM,+BAA+B,CAAC;AACnE,OAAO,gBAAgB,MAAM,wBAAwB,CAAC;AACtD,OAAO,wBAAwB,MAAM,kCAAkC,CAAC;AACxE,OAAO,sBAAsB,MAAM,gCAAgC,CAAC;AACpE,OAAO,iCAAiC,MAAM,2CAA2C,CAAC;AAG1F
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/tools/index.ts"],"names":[],"mappings":"AAAA,OAAO,aAAa,MAAM,qBAAqB,CAAC;AAChD,OAAO,mBAAmB,MAAM,4BAA4B,CAAC;AAC7D,OAAO,sBAAsB,MAAM,gCAAgC,CAAC;AACpE,OAAO,qBAAqB,MAAM,8BAA8B,CAAC;AACjE,OAAO,gBAAgB,MAAM,wBAAwB,CAAC;AACtD,OAAO,uBAAuB,MAAM,iCAAiC,CAAC;AACtE,OAAO,uCAAuC,MAAM,mDAAmD,CAAC;AACxG,OAAO,qBAAqB,MAAM,8BAA8B,CAAC;AACjE,OAAO,qBAAqB,MAAM,8BAA8B,CAAC;AACjE,OAAO,qBAAqB,MAAM,0BAA0B,CAAC;AAC7D,OAAO,uBAAuB,MAAM,+BAA+B,CAAC;AACpE,OAAO,gBAAgB,MAAM,wBAAwB,CAAC;AACtD,OAAO,sBAAsB,MAAM,+BAA+B,CAAC;AACnE,OAAO,gBAAgB,MAAM,wBAAwB,CAAC;AACtD,OAAO,wBAAwB,MAAM,kCAAkC,CAAC;AACxE,OAAO,sBAAsB,MAAM,gCAAgC,CAAC;AACpE,OAAO,iCAAiC,MAAM,2CAA2C,CAAC;AAG1F;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+KG;AACH,MAAM,CAAC,MAAM,KAAK,GAA8B;IAC9C,qBAAqB;IACrB,qBAAqB;IACrB,gBAAgB;IAChB,aAAa;IACb,uBAAuB;IACvB,gBAAgB;IAChB,sBAAsB;IACtB,sBAAsB;IACtB,qBAAqB;IACrB,qBAAqB;IACrB,mBAAmB;IACnB,iCAAiC;IACjC,sBAAsB;IACtB,gBAAgB;IAChB,uBAAuB;IACvB,uCAAuC;IACvC,wBAAwB;CACzB,CAAC"}
|
|
@@ -92,11 +92,51 @@ import type { ToolDefinition } from "./types.js";
|
|
|
92
92
|
*
|
|
93
93
|
* Same rule as every other tool here (`types.ts`: "A thin client that reshapes
|
|
94
94
|
* its upstream is not thin"), and it has real content on this body. The
|
|
95
|
-
* controller serves each entry as `id`, `full_name`, `name`, `registered_at
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
95
|
+
* controller serves each entry as `id`, `full_name`, `name`, `registered_at`,
|
|
96
|
+
* `role`, `delivery_health` and `latest_run`
|
|
97
|
+
* (`Api::V1::UserRepositoriesController#serialize`), and it says why about the
|
|
98
|
+
* identity fields: `id`, `full_name`, `name` and `registered_at` are
|
|
99
|
+
* DELIBERATELY the same fields, under the same names, that
|
|
100
|
+
* `GET /api/v1/repository` serves in its own `repository` block, so a client
|
|
101
|
+
* that has read one knows how to read the other. Renaming or flattening
|
|
102
|
+
* anything here would spend that parity on the last hop. The rest of each
|
|
103
|
+
* entry — and the block beside `repositories:` at the top level — is payload an
|
|
104
|
+
* agent must know ARRIVES: a description that omits it sends the agent to pay
|
|
105
|
+
* per-repository calls for data this one call already handed it (SPGD-1067).
|
|
106
|
+
*
|
|
107
|
+
* `delivery_health` rides EVERY entry (`#delivery_verdicts`): `{refusing,
|
|
108
|
+
* last_rejection_at}` — the staleness verdict `get_repository_overview` serves
|
|
109
|
+
* in its fuller per-repository block, spelled here as the one-call fleet
|
|
110
|
+
* triage. "Which of my repositories has a refusing ingest pipeline?" is
|
|
111
|
+
* answerable from THIS call alone, never one overview call per repository. A
|
|
112
|
+
* quiet verdict is a finding, not a gap, on the same rule the overview's
|
|
113
|
+
* description states: `refusing: false` is "nothing was refused", not
|
|
114
|
+
* "delivery is untracked".
|
|
115
|
+
*
|
|
116
|
+
* `latest_run` is the entry's newest run at the serializer's LIST depth
|
|
117
|
+
* (`LatestRunSerializer::LIST_DEPTH`): when CI last reported, on what branch
|
|
118
|
+
* and commit, how big the suite is, how much of it SpecGuard can read, and the
|
|
119
|
+
* run-level cost scalars — the drill-ins stay on the overview's full depth.
|
|
120
|
+
* `null` means CI has NEVER reported for that repository, and on this list the
|
|
121
|
+
* key is PRESENT-and-null (`#index` always passes the block;
|
|
122
|
+
* `LatestRunSerializer#body` returns nil for a nil run), the serializer's
|
|
123
|
+
* "nil, not a zeroed block" rule — a repository CI has never run must not read
|
|
124
|
+
* byte-identically to one that ran and found an empty suite. The ABSENT-key arm
|
|
125
|
+
* of that same marker belongs to `#update`'s rename receipt, not to this list.
|
|
126
|
+
*
|
|
127
|
+
* `credential` is the block that is NOT repository-scoped: under an `sga_` key
|
|
128
|
+
* the TOP level carries `credential: {capabilities}` — the calling key's own
|
|
129
|
+
* grant (SPGD-977), with every capability in `RepositoryPolicy::CAPABILITIES`
|
|
130
|
+
* ASKED of `AgentApiKeyPolicy` server-side, so the booleans cannot disagree
|
|
131
|
+
* with a 403 the same key would get (an empty permission set thereby reads
|
|
132
|
+
* affirmatively — `view` true, every further verb false, the machine
|
|
133
|
+
* counterpart of the account page's "read only" — rather than a copy of the
|
|
134
|
+
* stored permissions array, which under-states and over-states the grant at
|
|
135
|
+
* once). Its only other carrier is the minting person's browser account page,
|
|
136
|
+
* so before it a machine credential's only discovery path for its own
|
|
137
|
+
* permissions was 403 trial-and-error. Under an `sgu_` key the block is ABSENT
|
|
138
|
+
* rather than nulled — a person key has no mint-time permission set, and a null
|
|
139
|
+
* would assert one exists and is empty.
|
|
100
140
|
*
|
|
101
141
|
* `role` is the field this surface adds, and its value depends on WHICH
|
|
102
142
|
* credential answered — three values, one per credential kind, and all three
|
|
@@ -93,11 +93,51 @@ import { optionalString } from "./args.js";
|
|
|
93
93
|
*
|
|
94
94
|
* Same rule as every other tool here (`types.ts`: "A thin client that reshapes
|
|
95
95
|
* its upstream is not thin"), and it has real content on this body. The
|
|
96
|
-
* controller serves each entry as `id`, `full_name`, `name`, `registered_at
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
96
|
+
* controller serves each entry as `id`, `full_name`, `name`, `registered_at`,
|
|
97
|
+
* `role`, `delivery_health` and `latest_run`
|
|
98
|
+
* (`Api::V1::UserRepositoriesController#serialize`), and it says why about the
|
|
99
|
+
* identity fields: `id`, `full_name`, `name` and `registered_at` are
|
|
100
|
+
* DELIBERATELY the same fields, under the same names, that
|
|
101
|
+
* `GET /api/v1/repository` serves in its own `repository` block, so a client
|
|
102
|
+
* that has read one knows how to read the other. Renaming or flattening
|
|
103
|
+
* anything here would spend that parity on the last hop. The rest of each
|
|
104
|
+
* entry — and the block beside `repositories:` at the top level — is payload an
|
|
105
|
+
* agent must know ARRIVES: a description that omits it sends the agent to pay
|
|
106
|
+
* per-repository calls for data this one call already handed it (SPGD-1067).
|
|
107
|
+
*
|
|
108
|
+
* `delivery_health` rides EVERY entry (`#delivery_verdicts`): `{refusing,
|
|
109
|
+
* last_rejection_at}` — the staleness verdict `get_repository_overview` serves
|
|
110
|
+
* in its fuller per-repository block, spelled here as the one-call fleet
|
|
111
|
+
* triage. "Which of my repositories has a refusing ingest pipeline?" is
|
|
112
|
+
* answerable from THIS call alone, never one overview call per repository. A
|
|
113
|
+
* quiet verdict is a finding, not a gap, on the same rule the overview's
|
|
114
|
+
* description states: `refusing: false` is "nothing was refused", not
|
|
115
|
+
* "delivery is untracked".
|
|
116
|
+
*
|
|
117
|
+
* `latest_run` is the entry's newest run at the serializer's LIST depth
|
|
118
|
+
* (`LatestRunSerializer::LIST_DEPTH`): when CI last reported, on what branch
|
|
119
|
+
* and commit, how big the suite is, how much of it SpecGuard can read, and the
|
|
120
|
+
* run-level cost scalars — the drill-ins stay on the overview's full depth.
|
|
121
|
+
* `null` means CI has NEVER reported for that repository, and on this list the
|
|
122
|
+
* key is PRESENT-and-null (`#index` always passes the block;
|
|
123
|
+
* `LatestRunSerializer#body` returns nil for a nil run), the serializer's
|
|
124
|
+
* "nil, not a zeroed block" rule — a repository CI has never run must not read
|
|
125
|
+
* byte-identically to one that ran and found an empty suite. The ABSENT-key arm
|
|
126
|
+
* of that same marker belongs to `#update`'s rename receipt, not to this list.
|
|
127
|
+
*
|
|
128
|
+
* `credential` is the block that is NOT repository-scoped: under an `sga_` key
|
|
129
|
+
* the TOP level carries `credential: {capabilities}` — the calling key's own
|
|
130
|
+
* grant (SPGD-977), with every capability in `RepositoryPolicy::CAPABILITIES`
|
|
131
|
+
* ASKED of `AgentApiKeyPolicy` server-side, so the booleans cannot disagree
|
|
132
|
+
* with a 403 the same key would get (an empty permission set thereby reads
|
|
133
|
+
* affirmatively — `view` true, every further verb false, the machine
|
|
134
|
+
* counterpart of the account page's "read only" — rather than a copy of the
|
|
135
|
+
* stored permissions array, which under-states and over-states the grant at
|
|
136
|
+
* once). Its only other carrier is the minting person's browser account page,
|
|
137
|
+
* so before it a machine credential's only discovery path for its own
|
|
138
|
+
* permissions was 403 trial-and-error. Under an `sgu_` key the block is ABSENT
|
|
139
|
+
* rather than nulled — a person key has no mint-time permission set, and a null
|
|
140
|
+
* would assert one exists and is empty.
|
|
101
141
|
*
|
|
102
142
|
* `role` is the field this surface adds, and its value depends on WHICH
|
|
103
143
|
* credential answered — three values, one per credential kind, and all three
|
|
@@ -130,13 +170,27 @@ const listRepositories = {
|
|
|
130
170
|
"ask about\", which no other tool here can give, because every other tool is already scoped " +
|
|
131
171
|
"to one repository by its key. " +
|
|
132
172
|
"Each entry carries `id`, `full_name` (`org/repo`, and the handle every other surface names " +
|
|
133
|
-
"a repository by), `name`, `registered_at` and `
|
|
173
|
+
"a repository by), `name`, `registered_at`, `role`, `delivery_health` and `latest_run`. " +
|
|
174
|
+
"`delivery_health` is the entry's delivery verdict — `refusing` beside `last_rejection_at` — " +
|
|
175
|
+
"served on every entry, so this one call triages ingest-pipeline health across the whole " +
|
|
176
|
+
"reachable set (the coarse sibling of get_repository_overview's fuller per-repository block) " +
|
|
177
|
+
"instead of paying one overview call per repository. A quiet verdict is a finding, not a " +
|
|
178
|
+
"gap: `refusing: false` means nothing was refused, never that delivery is untracked. " +
|
|
179
|
+
"`latest_run` is the entry's newest run — when CI last reported, on what branch and commit, " +
|
|
180
|
+
"how big the suite is, how much of it SpecGuard can read, and the run-level cost scalars; " +
|
|
181
|
+
"`null` means CI has never reported for that repository, never a run that found an empty " +
|
|
182
|
+
"suite. " +
|
|
134
183
|
"`role` has one value per credential kind. Under a PERSON key (`sgu_…`) it is `owner` or " +
|
|
135
184
|
"`member`: the list mixes repositories this person owns with repositories somebody shared " +
|
|
136
185
|
"with them, and nothing else distinguishes the two. Under an AGENT key (`sga_…`) every " +
|
|
137
186
|
"entry is `agent` — the value that says the ownership question does not apply because the " +
|
|
138
187
|
"key speaks for nobody; branching on owner/member correctly reads false for both. Read it " +
|
|
139
188
|
"before assuming a repository is yours to administer. " +
|
|
189
|
+
"Under an AGENT key the top level also carries `credential.capabilities` — the calling " +
|
|
190
|
+
"key's own grant, read through the server's policy, so an agent key sees what it was " +
|
|
191
|
+
"granted here instead of discovering its permissions by hitting refusals; under a PERSON " +
|
|
192
|
+
"key the block is ABSENT, not `null`, because a person key has no mint-time permission " +
|
|
193
|
+
"set. " +
|
|
140
194
|
"Three optional asks narrow WITHIN that set — none of them can widen it — all optional, " +
|
|
141
195
|
"composable on one call: `q` (case-insensitive substring on `full_name`), " +
|
|
142
196
|
"`role: \"owned\"` or `\"shared\"` (one half of the owned/shared mix — note the ask is " +
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"list-repositories.js","sourceRoot":"","sources":["../../../src/tools/list-repositories.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,2BAA2B,EAAE,MAAM,6BAA6B,CAAC;AACzF,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAG3C
|
|
1
|
+
{"version":3,"file":"list-repositories.js","sourceRoot":"","sources":["../../../src/tools/list-repositories.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,2BAA2B,EAAE,MAAM,6BAA6B,CAAC;AACzF,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAG3C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkKG;AACH,MAAM,gBAAgB,GAAmB;IACvC,IAAI,EAAE,mBAAmB;IACzB,KAAK,EAAE,mBAAmB;IAC1B,WAAW,EACT,2FAA2F;QAC3F,6FAA6F;QAC7F,gCAAgC;QAChC,6FAA6F;QAC7F,yFAAyF;QACzF,8FAA8F;QAC9F,0FAA0F;QAC1F,8FAA8F;QAC9F,0FAA0F;QAC1F,sFAAsF;QACtF,6FAA6F;QAC7F,2FAA2F;QAC3F,0FAA0F;QAC1F,SAAS;QACT,0FAA0F;QAC1F,2FAA2F;QAC3F,wFAAwF;QACxF,2FAA2F;QAC3F,2FAA2F;QAC3F,uDAAuD;QACvD,wFAAwF;QACxF,sFAAsF;QACtF,0FAA0F;QAC1F,wFAAwF;QACxF,OAAO;QACP,yFAAyF;QACzF,2EAA2E;QAC3E,wFAAwF;QACxF,0FAA0F;QAC1F,wFAAwF;QACxF,kCAAkC;QAClC,mFAAmF;QACnF,wEAAwE;QACxE,yFAAyF;QACzF,cAAc;QACd,4FAA4F;QAC5F,0FAA0F;QAC1F,SAAS;QACT,0FAA0F;QAC1F,6FAA6F;QAC7F,6FAA6F;QAC7F,8FAA8F;QAC9F,2FAA2F;QAC3F,4BAA4B;QAC5B,wEAAwE;QACxE,6EAA6E;IAC/E,WAAW,EAAE;QACX,IAAI,EAAE,QAAQ;QACd,UAAU,EAAE;YACV,CAAC,EAAE;gBACD,IAAI,EAAE,QAAQ;gBACd,WAAW,EACT,iFAAiF;oBACjF,gFAAgF;oBAChF,sFAAsF;oBACtF,oFAAoF;oBACpF,8CAA8C;oBAC9C,uFAAuF;oBACvF,mFAAmF;oBACnF,oFAAoF;oBACpF,gBAAgB;oBAChB,gFAAgF;oBAChF,WAAW;aACd;YACD,IAAI,EAAE;gBACJ,IAAI,EAAE,QAAQ;gBACd,IAAI,EAAE,CAAC,OAAO,EAAE,QAAQ,CAAC;gBACzB,WAAW,EACT,mFAAmF;oBACnF,iFAAiF;oBACjF,uFAAuF;oBACvF,iFAAiF;oBACjF,uEAAuE;oBACvE,uFAAuF;oBACvF,yFAAyF;oBACzF,wEAAwE;oBACxE,oFAAoF;oBACpF,mFAAmF;oBACnF,sFAAsF;oBACtF,oFAAoF;oBACpF,oFAAoF;oBACpF,iCAAiC;oBACjC,qEAAqE;aACxE;YACD,IAAI,EAAE;gBACJ,IAAI,EAAE,QAAQ;gBACd,IAAI,EAAE,CAAC,OAAO,CAAC;gBACf,WAAW,EACT,qFAAqF;oBACrF,mFAAmF;oBACnF,gFAAgF;oBAChF,sFAAsF;oBACtF,sFAAsF;oBACtF,eAAe;oBACf,iFAAiF;oBACjF,sFAAsF;oBACtF,iFAAiF;oBACjF,YAAY;oBACZ,uFAAuF;oBACvF,uFAAuF;oBACvF,WAAW;aACd;SACF;QACD,6EAA6E;QAC7E,2EAA2E;QAC3E,sEAAsE;QACtE,yEAAyE;QACzE,wEAAwE;QACxE,2EAA2E;QAC3E,2EAA2E;QAC3E,gCAAgC;QAChC,oBAAoB,EAAE,KAAK;KAC5B;IAED,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO;QACrB,MAAM,CAAC,GAAG,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC,CAAC;QACzC,MAAM,IAAI,GAAG,cAAc,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC;QAClD,MAAM,IAAI,GAAG,cAAc,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC;QAClD,2EAA2E;QAC3E,mEAAmE;QACnE,uEAAuE;QACvE,MAAM,GAAG,GAAG,2BAA2B,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QAExD,mEAAmE;QACnE,4EAA4E;QAC5E,0EAA0E;QAC1E,4EAA4E;QAC5E,wEAAwE;QACxE,2BAA2B;QAC3B,MAAM,OAAO,GAAG,MAAM,aAAa,CACjC,GAAG,EACH,sBAAsB,EACtB,EAAE,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,EACjB,OAAO,CAAC,KAAK,CACd,CAAC;QAEF,OAAO;YACL,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;YACtC,UAAU,EAAE,OAAO;SACpB,CAAC;IACJ,CAAC;CACF,CAAC;AAEF,eAAe,gBAAgB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "specguard-mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.14",
|
|
4
4
|
"description": "MCP server exposing SpecGuard suite intelligence to AI coding agents",
|
|
5
5
|
"license": "ISC",
|
|
6
6
|
"type": "module",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"scripts": {
|
|
20
20
|
"build": "tsc --project tsconfig.build.json",
|
|
21
21
|
"typecheck": "tsc --noEmit",
|
|
22
|
-
"pretest": "tsc --project tsconfig.test.json",
|
|
22
|
+
"pretest": "rm -rf .test-build && tsc --project tsconfig.test.json",
|
|
23
23
|
"test": "files=$(find .test-build/test -name '*.test.js'); [ -n \"$files\" ] || { echo 'specguard-mcp: no compiled test files found — did pretest run?'; exit 1; }; node --test $files",
|
|
24
24
|
"prepare": "npm run build"
|
|
25
25
|
},
|