specguard-mcp 0.1.38 → 0.1.40

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 CHANGED
@@ -33,14 +33,14 @@ refuses to boot and takes the tools that needed no configuration down with it.
33
33
  | Variable | Needed by | Default | What it is |
34
34
  | --- | --- | --- | --- |
35
35
  | `SPECGUARD_ENDPOINT` | `get_repository_overview`, `get_intent_schema`, `get_server_version`, `list_repositories`, `add_repository`, `registrable_repositories` | — | your SpecGuard instance's root URL, **including the scheme** — e.g. `https://specguard.example.com`, or `http://localhost:3000`. A value with no scheme is refused by name (`SPECGUARD_ENDPOINT is not a usable URL: "sg.example.com"`) rather than surfacing later as an opaque failure. `SPECGUARD_URL` is accepted as an alias, and is the name every message uses when it is the one you set. A blank value counts as unset, so leaving `SPECGUARD_ENDPOINT` empty in a templated config falls through to `SPECGUARD_URL` instead of suppressing it. `get_server_version` and `get_intent_schema` need only this variable — they send no API key, because the routes they read are unauthenticated by design |
36
- | `SPECGUARD_API_KEY` | `get_repository_overview`, `near_duplicate_clusters` (default calls) | — | an agent/CI API key (`sgk_…`) issued by that deployment — a **per-repository** key, which is the single repository those tools answer about by default |
37
- | `SPECGUARD_USER_API_KEY` | `list_repositories` (fallback), `add_repository`, `registrable_repositories`, `remove_repository` (fallback), `create_repository_api_key` (fallback), `revoke_repository_api_key` (fallback), `list_repository_api_keys` (fallback), `list_repository_agent_keys` (fallback), `revoke_repository_agent_key` (fallback), `list_repository_agent_keys_presented_revoked` (fallback), `list_repository_members` (fallback), `add_repository_member` (fallback), `update_repository_member_permissions` (fallback), `remove_repository_member` (fallback), `get_repository_overview` / `near_duplicate_clusters` **with** `repository` (fallback), `rename_repository` | — | a **user** API key (`sgu_…`), minted from that deployment's account page. A different credential from the one above, not a second place to put the same value: SpecGuard decides which of them a request may use from the token's prefix, before it reads anything, and answers `401` for the other one. Set whichever your tools need — both, if you use both |
38
- | `SPECGUARD_AGENT_API_KEY` | `list_repositories` (preferred), `remove_repository` (preferred), `create_repository_api_key` (preferred), `revoke_repository_api_key` (preferred), `list_repository_api_keys` (preferred), `list_repository_agent_keys` (preferred), `revoke_repository_agent_key` (preferred), `list_repository_agent_keys_presented_revoked` (preferred), `list_repository_members` (preferred), `add_repository_member` (preferred), `update_repository_member_permissions` (preferred), `remove_repository_member` (preferred), `get_repository_overview` / `near_duplicate_clusters` **with** `repository` (preferred) | — | an **agent** API key (`sga_…`), minted from that deployment's account page (Agent keys panel) with an explicit set of repositories and permissions. It speaks for nobody: its reach is exactly the set granted onto it, fixed at mint time, and every read is bounded by that set server-side. This is the credential to give an automated agent — one key, many repositories, none of a person's rights. When it and `SPECGUARD_USER_API_KEY` are both set, every tool that answers either key — `list_repositories`, `remove_repository`, the members tools, the key-lifecycle tools, and `get_repository_overview` / `near_duplicate_clusters` **with** `repository` — uses **this** one, so discovery stays inside the set the other tools can reach |
36
+ | `SPECGUARD_API_KEY` | `get_repository_overview`, `near_duplicate_clusters`, `find_tests_near_behavior` (default calls) | — | an agent/CI API key (`sgk_…`) issued by that deployment — a **per-repository** key, which is the single repository those tools answer about by default |
37
+ | `SPECGUARD_USER_API_KEY` | `list_repositories` (fallback), `add_repository`, `registrable_repositories`, `remove_repository` (fallback), `create_repository_api_key` (fallback), `revoke_repository_api_key` (fallback), `list_repository_api_keys` (fallback), `list_repository_agent_keys` (fallback), `revoke_repository_agent_key` (fallback), `list_repository_agent_keys_presented_revoked` (fallback), `list_repository_members` (fallback), `add_repository_member` (fallback), `update_repository_member_permissions` (fallback), `remove_repository_member` (fallback), `get_repository_overview` / `near_duplicate_clusters` / `find_tests_near_behavior` **with** `repository` (fallback), `rename_repository` | — | a **user** API key (`sgu_…`), minted from that deployment's account page. A different credential from the one above, not a second place to put the same value: SpecGuard decides which of them a request may use from the token's prefix, before it reads anything, and answers `401` for the other one. Set whichever your tools need — both, if you use both |
38
+ | `SPECGUARD_AGENT_API_KEY` | `list_repositories` (preferred), `remove_repository` (preferred), `create_repository_api_key` (preferred), `revoke_repository_api_key` (preferred), `list_repository_api_keys` (preferred), `list_repository_agent_keys` (preferred), `revoke_repository_agent_key` (preferred), `list_repository_agent_keys_presented_revoked` (preferred), `list_repository_members` (preferred), `add_repository_member` (preferred), `update_repository_member_permissions` (preferred), `remove_repository_member` (preferred), `get_repository_overview` / `near_duplicate_clusters` / `find_tests_near_behavior` **with** `repository` (preferred) | — | an **agent** API key (`sga_…`), minted from that deployment's account page (Agent keys panel) with an explicit set of repositories and permissions. It speaks for nobody: its reach is exactly the set granted onto it, fixed at mint time, and every read is bounded by that set server-side. This is the credential to give an automated agent — one key, many repositories, none of a person's rights. When it and `SPECGUARD_USER_API_KEY` are both set, every tool that answers either key — `list_repositories`, `remove_repository`, the members tools, the key-lifecycle tools, and `get_repository_overview` / `near_duplicate_clusters` / `find_tests_near_behavior` **with** `repository` — uses **this** one, so discovery stays inside the set the other tools can reach |
39
39
  | `SPECGUARD_LINT_COMMAND` | `lint_intent_annotations` | `specguard-lint` | the command that runs the linter — the one switch between the Ruby and the JS/TS client. Most Ruby projects need `bundle exec specguard-lint`; a JS/TS project sets `npx -p @yatfa/specguard specguard lint` (and needs the validator backend: a `validate-intent` binary whose path `SPECGUARD_VALIDATE_INTENT` names — today the only way to supply it) |
40
40
  | `SPECGUARD_TIMEOUT_MS` | HTTP tools | `30000` | how long a call to SpecGuard may take |
41
41
 
42
42
  `SPECGUARD_ENDPOINT` and `SPECGUARD_API_KEY` are the same variables
43
- [`specguard-rspec`](https://github.com/yatfa-ai/specguard-rspec) uses to ship a run, so a repository
43
+ [`specguard-ruby`](https://github.com/yatfa-ai/specguard-ruby) uses to ship a run, so a repository
44
44
  that already posts telemetry to SpecGuard already has them. `SPECGUARD_USER_API_KEY` is **not** one
45
45
  of them — the gem has no notion of a user key — so that one is minted and set here for the first
46
46
  time.
@@ -500,7 +500,7 @@ blank value is no ask: passing an empty string and omitting the argument make th
500
500
  | --- | --- |
501
501
  | `q` | keep only repositories whose `full_name` (`org/repo`) contains this substring, case-insensitively. A plain substring, not a pattern — the LIKE wildcards `%`/`_` are escaped server-side, so `org/my_repo` matches itself. A match-less ask is an empty list, not an error |
502
502
  | `role` | `"owned"` or `"shared"` — one half of the list's mix: the repositories this person owns, or the ones shared with them. Mind the spelling: the *ask* values are `owned`/`shared`, while each entry's `role` field reads `owner`/`member` — `role: "owner"` is not a valid ask and settles to no ask (full list, no error). Under the `sga_…` agent key the ask settles to no ask for every value: ownership is a person fact and the key speaks for nobody, so the full granted list is served |
503
- | `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 |
503
+ | `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. `"annotated"` — re-order least-annotated share first: a suite measured at 0-of-N leads, repositories with no run or an unmeasured suite come last (never ranked as 0%), and `full_name` breaks ties. The same entries, a different order; the default order stays `full_name` ascending |
504
504
 
505
505
  The body comes back as SpecGuard serves it — `{"repositories": […]}`, each entry carrying `id`,
506
506
  `full_name`, `name`, `registered_at`, `role`, `delivery_health` and `latest_run`, ordered by
@@ -714,6 +714,57 @@ path's one deliberate omission: `api_key` is **absent** from the plural body rat
714
714
  because the block describes the credential that made the request and no member credential is a
715
715
  repository key (an absent key there is the surface's shape, never a dropped block).
716
716
 
717
+ ### `find_tests_near_behavior`
718
+
719
+ Asks a repository's stored test suite map which tests are **nearest a behavior phrase** you give
720
+ it. The server embeds the phrase and ranks the repository's stored test identities by similarity,
721
+ returning the top hits in the `near` block (`GET /api/v1/repository?near=<phrase>`, or the plural
722
+ `GET /api/v1/repositories/:id?near=<phrase>`) — each hit with its similarity, its `signal_source`,
723
+ its last-known path and the weight the latest run measured.
724
+
725
+ | argument | |
726
+ | --- | --- |
727
+ | `behavior` | **required** — the behavior phrase, in plain words (e.g. "rejects an expired password reset token"). Trimmed and sent as `near`. Blank or whitespace-only is refused before any request, and so is a phrase containing a NUL (`\u0000`) character: the server would read either as no ask and answer the whole overview with `near: null`, which would look like an answered ask |
728
+ | `repository` | ask THIS repository (its numeric id from `list_repositories`) under the **agent key** (`SPECGUARD_AGENT_API_KEY`, `sga_…`), or — when no agent key is set — the **user key** (`SPECGUARD_USER_API_KEY`, `sgu_…`), instead of the one the `sgk_…` key resolves to — omit it for the default, `sgk_`-keyed ask |
729
+ | `limit` | optional integer page size, **1–50** — omit it for the default page of 10. Sent as `&limit=<n>` only when supplied, so an ask without it is the same request as before. The server clamps the ask to 50 and reports the value it applied as `limit` in the `near` block; a non-integer, zero or negative value is refused before any request |
730
+
731
+ **The answer is a page, not the set.** Read `truncated` and `limit` in the returned `near` block:
732
+ `limit` is the page size the server applied, and `truncated: true` means **more matches exist than
733
+ were returned** — a full page of 10 may be "10 of 37". When `truncated` is true, re-ask the **same
734
+ phrase** with a larger `limit` (max 50) before paraphrasing it: a repeated phrase is cache-served and
735
+ free, while each paraphrase is a novel phrase and one billed embed.
736
+
737
+ **What the answer is not.** It ranks *stored* tests nearest the phrase and **never answers "is this
738
+ already tested?"** — it never gates a write and never issues a verdict. A hit near the phrase is not
739
+ coverage, and an empty answer is not proof of absence. `similarity_floor` is the near-duplicate
740
+ census's redundancy bar, **not** the 0.95 matching threshold that decides whether two tests are the
741
+ same test. Read `similarity_basis` and `similarity_floor` before any figure.
742
+
743
+ **The three silences differ** — never collapse them, and `null` is never `[]`:
744
+
745
+ - `status` of `provider_unconfigured` or `embedding_failed` with `ranked: null` — no ranking was
746
+ attempted, or the provider refused (`error` carries its reason). This says nothing about the suite.
747
+ - `identity_count: 0` with `ranked: []` — the repository holds no identities; nothing has been
748
+ ingested.
749
+ - `ranked: []` with `identity_count` above zero and `best_below_floor_similarity` — identities exist,
750
+ the search ran, and none is near the phrase; the nearest one's similarity is served so "nothing
751
+ near" is a finding you can check.
752
+
753
+ `signal_sources` (the composition of the served page) and each hit's `signal_source` (matched on
754
+ declared intent, or on name) are different evidence — read the source before leaning on a hit.
755
+
756
+ **Cost.** Each *novel* phrase costs **one billed embedding call** at the provider; a repeated phrase
757
+ is served from the cache and costs nothing (`cache_served` says which happened). The ask is live —
758
+ computed on the request — unlike the stored census `near_duplicate_clusters` returns, and it is a
759
+ tool of its own so that a `get_repository_overview` call never pays an embed by accident.
760
+
761
+ Same endpoints and credentials as `get_repository_overview`: without `repository`, an `sgk_…`
762
+ repository key on `GET /api/v1/repository`; with `repository`, either member credential on the
763
+ plural endpoint — the agent key preferred, the user key when no agent key is set. A repository
764
+ outside the presented credential's grant answers 404 at the server. The response is that body with
765
+ the `near` block opened, passed through unmodified; on the plural path `api_key` is **absent** from
766
+ the body rather than nulled (the surface's shape, never a dropped block).
767
+
717
768
  ### `remove_repository`
718
769
 
719
770
  Removes a repository from SpecGuard — and with it **every key, run and intent on it**. This is the
@@ -780,7 +831,11 @@ serves live rows only). One row per key: `id`, `name`, `token_hint`, `created_at
780
831
  omitted — `rotated_at`, and `rotated_and_unused` (live-scoped: the stranded shape
781
832
  `get_repository_overview`'s `credential_health` reports, at row grain). `status` is
782
833
  `live`/`revoked`, with `revoked_at` present only on revoked rows — a revoked `sgk_` row is not a
783
- credential either, but the rotation's story lives in the population.
834
+ credential either, but the rotation's story lives in the population. A **revoked** row also
835
+ serves `last_refused_at`: `null` means offboarding took (nothing has presented the dead token
836
+ since the cut), a timestamp means the dead token is still arriving and that row's `token_hint`
837
+ is what to hunt in the secret stores that may still hold it. Live rows carry no
838
+ `last_refused_at` key.
784
839
 
785
840
  | argument | |
786
841
  | --- | --- |
@@ -1146,7 +1201,7 @@ npm run typecheck # types only
1146
1201
  ## Related repositories
1147
1202
 
1148
1203
  - [`specguard`](https://github.com/yatfa-ai/specguard) — the platform: ingest API + Hotwire dashboard
1149
- - [`specguard-rspec`](https://github.com/yatfa-ai/specguard-rspec) — Ruby client (RSpec formatter + `@intent` linter)
1204
+ - [`specguard-ruby`](https://github.com/yatfa-ai/specguard-ruby) — Ruby client (RSpec formatter, Minitest reporter + `@intent` linter)
1150
1205
  - [`open-test-intent`](https://github.com/yatfa-ai/open-test-intent) — the annotation protocol SpecGuard consumes
1151
1206
 
1152
1207
  ## License
@@ -19,7 +19,7 @@
19
19
  *
20
20
  * == SPECGUARD_ENDPOINT, with SPECGUARD_URL as an accepted alias
21
21
  *
22
- * `SPECGUARD_ENDPOINT` is the name the shipped `specguard-rspec` gem already
22
+ * `SPECGUARD_ENDPOINT` is the name the shipped `specguard-ruby` gem already
23
23
  * reads, so a repository whose CI posts runs to SpecGuard has it set — that is
24
24
  * the whole reason this server borrows the name rather than coining one. The
25
25
  * SPGD-310 brief writes it as `SPECGUARD_URL`, so that spelling is accepted
@@ -222,7 +222,7 @@ export declare const REPOSITORY_CREDENTIAL: Credential;
222
222
  /**
223
223
  * The `sgu_` key: a person, and a variable nothing else in the toolchain reads.
224
224
  *
225
- * `specguard-rspec` ships runs with an `sgk_` key and has no notion of this one,
225
+ * `specguard-ruby` ships runs with an `sgk_` key and has no notion of this one,
226
226
  * so an operator who already has CI reporting to SpecGuard does NOT already
227
227
  * have this variable — which is why the message says where to mint one rather
228
228
  * than assuming it is lying around.
@@ -47,7 +47,7 @@ export const REPOSITORY_CREDENTIAL = {
47
47
  /**
48
48
  * The `sgu_` key: a person, and a variable nothing else in the toolchain reads.
49
49
  *
50
- * `specguard-rspec` ships runs with an `sgk_` key and has no notion of this one,
50
+ * `specguard-ruby` ships runs with an `sgk_` key and has no notion of this one,
51
51
  * so an operator who already has CI reporting to SpecGuard does NOT already
52
52
  * have this variable — which is why the message says where to mint one rather
53
53
  * than assuming it is lying around.
@@ -272,7 +272,7 @@ async function requestUserAgent() {
272
272
  * ONE TOTAL BUDGET, not one per phase. `requestTimeoutMs` bounds the whole call:
273
273
  * headers and body share it, so a response whose headers took 29s of a 30s
274
274
  * budget has 1s left in which to deliver its body. The sibling transport in
275
- * `specguard-rspec` (`lib/specguard/rspec/transport.rb`) gives each phase its own
275
+ * `specguard-ruby` (`lib/specguard/client/transport.rb`) gives each phase its own
276
276
  * full `@timeout` because `Net::HTTP` exposes exactly that knob and no other;
277
277
  * here the deadline is ours to place, and a single total is both stricter and
278
278
  * the thing an operator who set one number actually meant.
@@ -66,6 +66,17 @@ export declare function optionalString(value: unknown, field: string): string |
66
66
  */
67
67
  export declare function requireString(value: unknown, field: string): string;
68
68
  export declare function optionalBoolean(value: unknown, field: string): boolean | undefined;
69
+ /**
70
+ * A positive integer, or nothing.
71
+ *
72
+ * Accepts only a finite JSON number with no fractional part that is >= 1. A
73
+ * numeric string ("10"), a float (2.5), zero, a negative, NaN and Infinity all
74
+ * throw `ArgumentError` — the same class every other shape check here throws.
75
+ * No upper bound is applied: a ceiling is the callee's to own (the server
76
+ * clamps and reports the value it applied), and the one a tool's schema
77
+ * advertises is the schema's to declare, not this helper's to duplicate.
78
+ */
79
+ export declare function optionalPositiveInteger(value: unknown, field: string): number | undefined;
69
80
  /**
70
81
  * An optional array of non-blank strings, or nothing.
71
82
  *
@@ -93,6 +93,26 @@ export function optionalBoolean(value, field) {
93
93
  throw new ArgumentError(`\`${field}\` must be a boolean.`);
94
94
  return value;
95
95
  }
96
+ /**
97
+ * A positive integer, or nothing.
98
+ *
99
+ * Accepts only a finite JSON number with no fractional part that is >= 1. A
100
+ * numeric string ("10"), a float (2.5), zero, a negative, NaN and Infinity all
101
+ * throw `ArgumentError` — the same class every other shape check here throws.
102
+ * No upper bound is applied: a ceiling is the callee's to own (the server
103
+ * clamps and reports the value it applied), and the one a tool's schema
104
+ * advertises is the schema's to declare, not this helper's to duplicate.
105
+ */
106
+ export function optionalPositiveInteger(value, field) {
107
+ if (value === undefined || value === null)
108
+ return undefined;
109
+ if (typeof value !== "number" || !Number.isInteger(value)) {
110
+ throw new ArgumentError(`\`${field}\` must be an integer.`);
111
+ }
112
+ if (value < 1)
113
+ throw new ArgumentError(`\`${field}\` must be at least 1.`);
114
+ return value;
115
+ }
96
116
  /**
97
117
  * An optional array of non-blank strings, or nothing.
98
118
  *
@@ -1 +1 @@
1
- {"version":3,"file":"args.js","sourceRoot":"","sources":["../../../src/tools/args.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"args.js","sourceRoot":"","sources":["../../../src/tools/args.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ import type { ToolDefinition } from "./types.js";
2
+ /**
3
+ * `GET /api/v1/repository?near=<behavior phrase>` as a tool — the bridge half
4
+ * of roadmap SPGD-1102 ("Ask the suite map"), slice 3. The platform half
5
+ * shipped first: `RequestedNearParam` guards the ask and `NearProbe` answers
6
+ * it, behind the `near` block of `RepositoryOverview`, on both overview doors
7
+ * (the singular `GET /api/v1/repository` under an `sgk_` key, the plural
8
+ * `GET /api/v1/repositories/:id` under an agent or user key).
9
+ *
10
+ * == Why a SEPARATE tool, and not a parameter on `get_repository_overview`
11
+ *
12
+ * Every other ask on the overview is free or stored. This one is LIVE and
13
+ * BILLED: a novel phrase costs one embedding call at the provider (a repeated
14
+ * phrase is served from the embedding cache and buys nothing). An agent
15
+ * calling the overview must never pay an embed by accident, and an agent
16
+ * calling this tool has asked for nothing else — the same line
17
+ * `near_duplicate_clusters` draws for the census, drawn here for a cost that
18
+ * is per-call rather than per-ingest.
19
+ *
20
+ * == The phrase is sent trimmed, and a NUL is refused HERE
21
+ *
22
+ * `requireString` trims and refuses blank. The NUL refusal is the bridge's to
23
+ * make, because the server's guard treats a NUL-containing, blank or
24
+ * non-String `near` as NO ASK and answers the plain overview with `near:
25
+ * null` — a 200 that would read, to an agent that sent a phrase, as an
26
+ * answered ask. Refusing client-side keeps "asked, nothing came back" and
27
+ * "never asked" from sharing one wire shape.
28
+ *
29
+ * == The response is passed through, not re-modelled
30
+ *
31
+ * Same rule as `near-duplicate-clusters.ts`. The `near` block's shape carries
32
+ * distinctions the serializer spent care on — `ranked: null` (no ranking was
33
+ * attempted: `provider_unconfigured` / `embedding_failed`) versus `ranked: []`
34
+ * (the search ran and found nothing near), and, inside the latter,
35
+ * `identity_count: 0` versus `identity_count > 0` with
36
+ * `best_below_floor_similarity`. Coalescing any of them would turn three
37
+ * different silences into one. The body goes back as it arrived.
38
+ */
39
+ declare const findTestsNearBehavior: ToolDefinition;
40
+ export default findTestsNearBehavior;
@@ -0,0 +1,160 @@
1
+ import { ArgumentError } from "../errors.js";
2
+ import { getJsonObject, repositoryTarget } from "../support/specguard-api.js";
3
+ import { optionalPositiveInteger, optionalString, requireString } from "./args.js";
4
+ /**
5
+ * `GET /api/v1/repository?near=<behavior phrase>` as a tool — the bridge half
6
+ * of roadmap SPGD-1102 ("Ask the suite map"), slice 3. The platform half
7
+ * shipped first: `RequestedNearParam` guards the ask and `NearProbe` answers
8
+ * it, behind the `near` block of `RepositoryOverview`, on both overview doors
9
+ * (the singular `GET /api/v1/repository` under an `sgk_` key, the plural
10
+ * `GET /api/v1/repositories/:id` under an agent or user key).
11
+ *
12
+ * == Why a SEPARATE tool, and not a parameter on `get_repository_overview`
13
+ *
14
+ * Every other ask on the overview is free or stored. This one is LIVE and
15
+ * BILLED: a novel phrase costs one embedding call at the provider (a repeated
16
+ * phrase is served from the embedding cache and buys nothing). An agent
17
+ * calling the overview must never pay an embed by accident, and an agent
18
+ * calling this tool has asked for nothing else — the same line
19
+ * `near_duplicate_clusters` draws for the census, drawn here for a cost that
20
+ * is per-call rather than per-ingest.
21
+ *
22
+ * == The phrase is sent trimmed, and a NUL is refused HERE
23
+ *
24
+ * `requireString` trims and refuses blank. The NUL refusal is the bridge's to
25
+ * make, because the server's guard treats a NUL-containing, blank or
26
+ * non-String `near` as NO ASK and answers the plain overview with `near:
27
+ * null` — a 200 that would read, to an agent that sent a phrase, as an
28
+ * answered ask. Refusing client-side keeps "asked, nothing came back" and
29
+ * "never asked" from sharing one wire shape.
30
+ *
31
+ * == The response is passed through, not re-modelled
32
+ *
33
+ * Same rule as `near-duplicate-clusters.ts`. The `near` block's shape carries
34
+ * distinctions the serializer spent care on — `ranked: null` (no ranking was
35
+ * attempted: `provider_unconfigured` / `embedding_failed`) versus `ranked: []`
36
+ * (the search ran and found nothing near), and, inside the latter,
37
+ * `identity_count: 0` versus `identity_count > 0` with
38
+ * `best_below_floor_similarity`. Coalescing any of them would turn three
39
+ * different silences into one. The body goes back as it arrived.
40
+ */
41
+ const findTestsNearBehavior = {
42
+ name: "find_tests_near_behavior",
43
+ title: "Find tests near a behavior",
44
+ description: "Ask a repository's stored test suite map: which tests are nearest a behavior phrase you give it? " +
45
+ "The server embeds your phrase and ranks the repository's stored test identities by similarity, " +
46
+ "returning the top hits in the `near` block — each with its similarity, `signal_source`, last-known " +
47
+ "path and the weight the latest run measured. " +
48
+ "THE ANSWER IS A PAGE, NOT THE SET: the default page is 10 hits. READ `truncated` AND `limit` in the " +
49
+ "returned `near` block — `limit` is the page size the server APPLIED (it clamps your ask to 50) and " +
50
+ "`truncated: true` means MORE matches exist than were returned, so a full page of 10 may be \"10 of " +
51
+ "37\". When `truncated` is true, re-ask the SAME phrase with a larger optional `limit` (max 50) " +
52
+ "BEFORE paraphrasing it: a repeated phrase is cache-served and free, while each paraphrase is a " +
53
+ "novel phrase and one billed embed. " +
54
+ "WHAT THE ANSWER IS NOT: it ranks STORED tests nearest the phrase and NEVER answers \"is this " +
55
+ "already tested?\". It never gates a write and never issues a verdict — a hit near the phrase is not " +
56
+ "coverage, and an empty answer is not proof of absence. `similarity_floor` is the near-duplicate " +
57
+ "census's redundancy bar (how alike two tests must read for the census to pair them), NOT the 0.95 " +
58
+ "matching threshold that decides whether two tests are the same test; do not read it as a pass mark. " +
59
+ "READ `similarity_basis` AND `similarity_floor` BEFORE ANY FIGURE: a similarity without the statement " +
60
+ "of what it measures is a confident number over nothing. " +
61
+ "THE THREE SILENCES ARE DIFFERENT AND MUST NOT BE COLLAPSED: (1) `status` of `provider_unconfigured` " +
62
+ "or `embedding_failed` with `ranked: null` — no ranking was attempted or the provider refused (an " +
63
+ "`error` carries the provider's own reason); this says NOTHING about the suite. (2) `identity_count: " +
64
+ "0` with `ranked: []` — the repository holds no identities, nothing has been ingested. (3) `ranked: " +
65
+ "[]` with `identity_count` above zero and `best_below_floor_similarity` — identities exist and the " +
66
+ "search ran, and none is near the phrase; the nearest one's similarity is served so that \"nothing " +
67
+ "near\" is a finding you can check. `null` and `[]` are never interchangeable here. " +
68
+ "`signal_sources` (the composition of the served page) and each hit's `signal_source` (whether the " +
69
+ "hit matched on declared intent or on its name) are DIFFERENT EVIDENCE: a name match and an intent " +
70
+ "match are not the same claim, so read the source before leaning on a hit. " +
71
+ "COST: each NOVEL phrase costs ONE BILLED EMBEDDING CALL at the provider; repeating a phrase is " +
72
+ "served from the cache and costs nothing (`cache_served` says which happened). The ask is LIVE — " +
73
+ "computed on the request — unlike the stored census `near_duplicate_clusters` returns; it is its " +
74
+ "own tool so that an overview call never pays an embed by accident. Send a considered phrase, not " +
75
+ "a sweep of guesses. " +
76
+ "`behavior` is REQUIRED: a behavior phrase in plain words (trimmed; blank is refused here before " +
77
+ "any request, and so is a NUL character — the server would read it as no ask and answer the whole " +
78
+ "overview with `near: null`, which would look like an answered ask). " +
79
+ "WHICH repository is asked is optional: pass `repository` (a numeric id from `list_repositories`) " +
80
+ "to ask that named repository under either member credential — the agent key preferred, the person " +
81
+ "key when no agent key is set — or omit it to ask the repository the configured sgk_… key resolves " +
82
+ "to. Same endpoints and credentials as `get_repository_overview`: without `repository`, an `sgk_` " +
83
+ "repository key (SPECGUARD_API_KEY) on `GET /api/v1/repository`; with `repository`, EITHER member " +
84
+ "credential on `GET /api/v1/repositories/:id` — SPECGUARD_AGENT_API_KEY (an sga_… agent key) " +
85
+ "preferred, and, when that is not set, SPECGUARD_USER_API_KEY (an sgu_… person key); with both set " +
86
+ "the agent key wins. The server owns the refusals on that path: a repository outside the presented " +
87
+ "credential's grant answers 404, person and agent alike. " +
88
+ "The response is the endpoint's full body with the `near` block OPENED, passed through unmodified — " +
89
+ "with the one surface difference `get_repository_overview` documents for its own `repository` ask: " +
90
+ "on the plural path the `api_key` block is ABSENT from the body rather than nulled.",
91
+ inputSchema: {
92
+ type: "object",
93
+ properties: {
94
+ behavior: {
95
+ type: "string",
96
+ description: "REQUIRED. The behavior phrase to look for — plain words describing what a test would " +
97
+ "check, e.g. \"rejects an expired password reset token\". Trimmed and sent as `near`. " +
98
+ "Blank or whitespace-only is refused before any request, and so is a phrase containing a " +
99
+ "NUL character (U+0000): the server reads those shapes as no ask at all and would answer " +
100
+ "the whole overview with `near: null`. NOTE: every NOVEL phrase costs one billed " +
101
+ "embedding call; a repeated phrase is cache-served.",
102
+ },
103
+ repository: {
104
+ type: "string",
105
+ description: "Ask THIS repository instead of the one the configured sgk_… key resolves to. The " +
106
+ "value is the repository's NUMERIC ID, exactly as served in `list_repositories` " +
107
+ "entries' `id` — not the `org/repo` handle. " +
108
+ "The credential changes with it: the call authenticates with EITHER member credential, " +
109
+ "whichever is set — SPECGUARD_AGENT_API_KEY (an sga_… agent key; the set of " +
110
+ "repositories granted onto it at mint time is the boundary the id is resolved " +
111
+ "inside) and, when that is not set, SPECGUARD_USER_API_KEY (an sgu_… key — a PERSON " +
112
+ "key, whose accessible set is the boundary instead); with both set the agent key " +
113
+ "wins. A repository outside the presented credential's grant answers 404, " +
114
+ "indistinguishable from one that does not exist. " +
115
+ "Omit it — or pass a blank — and the call is the singular one under SPECGUARD_API_KEY.",
116
+ },
117
+ limit: {
118
+ type: "integer",
119
+ minimum: 1,
120
+ maximum: 50,
121
+ description: "OPTIONAL page size: how many hits to return in the `near` block. Omit it for the " +
122
+ "default page of 10. The server clamps the ask to 50 and reports the value it applied " +
123
+ "as `limit` in the `near` block, beside `truncated` — read both: `truncated: true` " +
124
+ "means more matches exist than were returned, and asking again with a larger `limit` " +
125
+ "(same phrase, so cache-served and free) is cheaper than paraphrasing it (a novel " +
126
+ "phrase, one billed embed). Must be an integer >= 1; anything else is refused before " +
127
+ "any request.",
128
+ },
129
+ },
130
+ required: ["behavior"],
131
+ additionalProperties: false,
132
+ },
133
+ async run(args, context) {
134
+ // Argument checks come FIRST, before config resolution or any request:
135
+ // a malformed phrase must not cost a credential lookup, let alone a
136
+ // billed embed.
137
+ const behavior = requireString(args["behavior"], "behavior");
138
+ if (behavior.includes("\u0000")) {
139
+ throw new ArgumentError("`behavior` must not contain a NUL (\\u0000) character: the server reads a NUL-containing " +
140
+ "`near` as no ask and would answer the whole overview with `near: null`, which would look " +
141
+ "like an answered ask. Remove the character and send the phrase again.");
142
+ }
143
+ const repository = optionalString(args["repository"], "repository");
144
+ const limit = optionalPositiveInteger(args["limit"], "limit");
145
+ const { api, path } = repositoryTarget(context.config, repository);
146
+ // `limit` rides the wire only when supplied, so an ask without it is
147
+ // byte-identical to the request this tool made before `limit` existed. The
148
+ // server owns the clamp (50) and reports the applied value in `near.limit`.
149
+ const query = { near: behavior };
150
+ if (limit !== undefined)
151
+ query["limit"] = String(limit);
152
+ const overview = await getJsonObject(api, path, query, context.fetch);
153
+ return {
154
+ text: JSON.stringify(overview, null, 2),
155
+ structured: overview,
156
+ };
157
+ },
158
+ };
159
+ export default findTestsNearBehavior;
160
+ //# sourceMappingURL=find-tests-near-behavior.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"find-tests-near-behavior.js","sourceRoot":"","sources":["../../../src/tools/find-tests-near-behavior.ts"],"names":[],"mappings":"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"}
@@ -12,7 +12,7 @@ import type { ToolDefinition } from "./types.js";
12
12
  * == What is in the bootstrap, and why only these two
13
13
  *
14
14
  * - `lint_intent_annotations` wraps `specguard-lint` (shipped:
15
- * `specguard-rspec/bin/specguard-lint`, SPGD-12 §1).
15
+ * `specguard-ruby/bin/specguard-lint`, SPGD-12 §1).
16
16
  * - `get_repository_overview` wraps `GET /api/v1/repository` (shipped:
17
17
  * `specguard/config/routes.rb`).
18
18
  *
@@ -231,6 +231,24 @@ import type { ToolDefinition } from "./types.js";
231
231
  * is unchanged and still binding — the route was verified-shipped, spec-covered
232
232
  * and root-level before this entry wrapped it, and `/check-intent` (a comment
233
233
  * in `routes.rb`) stays out.
234
+ *
235
+ * == Ask the suite map
236
+ *
237
+ * - `find_tests_near_behavior` wraps `GET /api/v1/repository?near=<phrase>`
238
+ * (and the plural `GET /api/v1/repositories/:id?near=`), serving the
239
+ * `near` block `RepositoryOverview#serialized_near` builds through
240
+ * `NearProbe` (shipped: roadmap SPGD-1102 slices 1–2, specguard
241
+ * `2e18377` and `59b0d19`).
242
+ *
243
+ * The first tool here whose ask is LIVE and BILLED — each novel phrase costs
244
+ * one embedding call — which is why it is a tool of its own rather than a
245
+ * parameter on `get_repository_overview`: an overview call must never pay an
246
+ * embed by accident. It ranks stored tests nearest a phrase and never answers
247
+ * "is this already tested?"; the tool description carries that and the three
248
+ * silences the block keeps apart. The standing rule is unchanged and still
249
+ * binding — `/check-intent` stays out: it has no backing endpoint, and this
250
+ * tool is deliberately not a stand-in for one (the platform's floor discloses
251
+ * and filters a ranked read; it never gates a write and issues no verdict).
234
252
  */
235
253
  export declare const TOOLS: readonly ToolDefinition[];
236
254
  export type { ToolContext, ToolDefinition, ToolResult } from "./types.js";
@@ -1,6 +1,7 @@
1
1
  import addRepository from "./add-repository.js";
2
2
  import addRepositoryMember from "./add-repository-member.js";
3
3
  import createRepositoryApiKey from "./create-repository-api-key.js";
4
+ import findTestsNearBehavior from "./find-tests-near-behavior.js";
4
5
  import getIntentSchema from "./get-intent-schema.js";
5
6
  import getServerVersion from "./get-server-version.js";
6
7
  import lintIntentAnnotations from "./lint-intent-annotations.js";
@@ -31,7 +32,7 @@ import updateRepositoryMemberPermissions from "./update-repository-member-permis
31
32
  * == What is in the bootstrap, and why only these two
32
33
  *
33
34
  * - `lint_intent_annotations` wraps `specguard-lint` (shipped:
34
- * `specguard-rspec/bin/specguard-lint`, SPGD-12 §1).
35
+ * `specguard-ruby/bin/specguard-lint`, SPGD-12 §1).
35
36
  * - `get_repository_overview` wraps `GET /api/v1/repository` (shipped:
36
37
  * `specguard/config/routes.rb`).
37
38
  *
@@ -250,6 +251,24 @@ import updateRepositoryMemberPermissions from "./update-repository-member-permis
250
251
  * is unchanged and still binding — the route was verified-shipped, spec-covered
251
252
  * and root-level before this entry wrapped it, and `/check-intent` (a comment
252
253
  * in `routes.rb`) stays out.
254
+ *
255
+ * == Ask the suite map
256
+ *
257
+ * - `find_tests_near_behavior` wraps `GET /api/v1/repository?near=<phrase>`
258
+ * (and the plural `GET /api/v1/repositories/:id?near=`), serving the
259
+ * `near` block `RepositoryOverview#serialized_near` builds through
260
+ * `NearProbe` (shipped: roadmap SPGD-1102 slices 1–2, specguard
261
+ * `2e18377` and `59b0d19`).
262
+ *
263
+ * The first tool here whose ask is LIVE and BILLED — each novel phrase costs
264
+ * one embedding call — which is why it is a tool of its own rather than a
265
+ * parameter on `get_repository_overview`: an overview call must never pay an
266
+ * embed by accident. It ranks stored tests nearest a phrase and never answers
267
+ * "is this already tested?"; the tool description carries that and the three
268
+ * silences the block keeps apart. The standing rule is unchanged and still
269
+ * binding — `/check-intent` stays out: it has no backing endpoint, and this
270
+ * tool is deliberately not a stand-in for one (the platform's floor discloses
271
+ * and filters a ranked read; it never gates a write and issues no verdict).
253
272
  */
254
273
  export const TOOLS = [
255
274
  lintIntentAnnotations,
@@ -272,5 +291,6 @@ export const TOOLS = [
272
291
  listRepositoryAgentKeys,
273
292
  listRepositoryAgentKeysPresentedRevoked,
274
293
  revokeRepositoryAgentKey,
294
+ findTestsNearBehavior,
275
295
  ];
276
296
  //# sourceMappingURL=index.js.map
@@ -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,eAAe,MAAM,wBAAwB,CAAC;AACrD,OAAO,gBAAgB,MAAM,yBAAyB,CAAC;AACvD,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,+BAA+B,CAAC;AAClE,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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwOG;AACH,MAAM,CAAC,MAAM,KAAK,GAA8B;IAC9C,qBAAqB;IACrB,eAAe;IACf,qBAAqB;IACrB,gBAAgB;IAChB,gBAAgB;IAChB,aAAa;IACb,uBAAuB;IACvB,gBAAgB;IAChB,sBAAsB;IACtB,sBAAsB;IACtB,qBAAqB;IACrB,qBAAqB;IACrB,qBAAqB;IACrB,mBAAmB;IACnB,iCAAiC;IACjC,sBAAsB;IACtB,gBAAgB;IAChB,uBAAuB;IACvB,uCAAuC;IACvC,wBAAwB;CACzB,CAAC"}
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,+BAA+B,CAAC;AAClE,OAAO,eAAe,MAAM,wBAAwB,CAAC;AACrD,OAAO,gBAAgB,MAAM,yBAAyB,CAAC;AACvD,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,+BAA+B,CAAC;AAClE,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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0PG;AACH,MAAM,CAAC,MAAM,KAAK,GAA8B;IAC9C,qBAAqB;IACrB,eAAe;IACf,qBAAqB;IACrB,gBAAgB;IAChB,gBAAgB;IAChB,aAAa;IACb,uBAAuB;IACvB,gBAAgB;IAChB,sBAAsB;IACtB,sBAAsB;IACtB,qBAAqB;IACrB,qBAAqB;IACrB,qBAAqB;IACrB,mBAAmB;IACnB,iCAAiC;IACjC,sBAAsB;IACtB,gBAAgB;IAChB,uBAAuB;IACvB,uCAAuC;IACvC,wBAAwB;IACxB,qBAAqB;CACtB,CAAC"}
@@ -61,7 +61,9 @@ import type { ToolDefinition } from "./types.js";
61
61
  * and `?role=shared` (the exact partition of the accessible set into owned and
62
62
  * shared halves), and `?sort=stale` (never-ingested repositories first, then
63
63
  * least-recently-ingested, `github_full_name` breaking ties so the order is
64
- * deterministic). The concern's own comment says the endpoint reads them "for
64
+ * deterministic) or `?sort=annotated` (least-annotated share first; a
65
+ * repository with no run or an unmeasured suite last, never ranked as 0%; a
66
+ * suite measured at 0-of-N first; `github_full_name` breaking ties). The concern's own comment says the endpoint reads them "for
65
67
  * a machine" — and this bridge IS that machine, so they are forwarded here
66
68
  * rather than re-invented.
67
69
  *
@@ -159,8 +161,8 @@ import type { ToolDefinition } from "./types.js";
159
161
  * The order is `full_name` ascending, which the controller picks as the only
160
162
  * column a client can page or diff against without SpecGuard promising an id
161
163
  * ordering it has not designed — and `?sort=stale` re-sequences exactly that
162
- * loaded set rather than issuing a different query, so the entries never
163
- * change, only their order does. It is stated here for the same reason the
164
+ * loaded set rather than issuing a different query (as does `?sort=annotated`),
165
+ * so the entries never change, only their order does. It is stated here for the same reason the
164
166
  * other tool states its orders: a list whose order is a coincidence and a list
165
167
  * whose order is a contract look identical in a response body.
166
168
  */
@@ -62,7 +62,9 @@ import { optionalString } from "./args.js";
62
62
  * and `?role=shared` (the exact partition of the accessible set into owned and
63
63
  * shared halves), and `?sort=stale` (never-ingested repositories first, then
64
64
  * least-recently-ingested, `github_full_name` breaking ties so the order is
65
- * deterministic). The concern's own comment says the endpoint reads them "for
65
+ * deterministic) or `?sort=annotated` (least-annotated share first; a
66
+ * repository with no run or an unmeasured suite last, never ranked as 0%; a
67
+ * suite measured at 0-of-N first; `github_full_name` breaking ties). The concern's own comment says the endpoint reads them "for
66
68
  * a machine" — and this bridge IS that machine, so they are forwarded here
67
69
  * rather than re-invented.
68
70
  *
@@ -160,8 +162,8 @@ import { optionalString } from "./args.js";
160
162
  * The order is `full_name` ascending, which the controller picks as the only
161
163
  * column a client can page or diff against without SpecGuard promising an id
162
164
  * ordering it has not designed — and `?sort=stale` re-sequences exactly that
163
- * loaded set rather than issuing a different query, so the entries never
164
- * change, only their order does. It is stated here for the same reason the
165
+ * loaded set rather than issuing a different query (as does `?sort=annotated`),
166
+ * so the entries never change, only their order does. It is stated here for the same reason the
165
167
  * other tool states its orders: a list whose order is a coincidence and a list
166
168
  * whose order is a contract look identical in a response body.
167
169
  */
@@ -198,9 +200,11 @@ const listRepositories = {
198
200
  "`role: \"owned\"` or `\"shared\"` (one half of the owned/shared mix — note the ask is " +
199
201
  "spelled `owned`, not the response field's `owner`; under the AGENT key this ask settles " +
200
202
  "to the no-ask, because ownership is a person fact and the key speaks for nobody), and " +
201
- "`sort: \"stale\"` (repositories " +
203
+ "`sort` (`\"stale\"`: repositories " +
202
204
  "CI has never ingested a run for first, then least-recently-ingested, `full_name` " +
203
- "breaking ties). Omit them all and the request is the plain full list. " +
205
+ "breaking ties; `\"annotated\"`: least-annotated share first, repositories with no run " +
206
+ "or an unmeasured suite last — never ranked as 0% — a suite measured at 0-of-N first, " +
207
+ "`full_name` breaking ties). Omit them all and the request is the plain full list. " +
204
208
  "Ordered by `full_name` ascending unless `sort` asks otherwise, and stable across calls " +
205
209
  "either way. " +
206
210
  "The set is exactly what the key behind it may see — a repository outside the credential's " +
@@ -252,16 +256,20 @@ const listRepositories = {
252
256
  },
253
257
  sort: {
254
258
  type: "string",
255
- enum: ["stale"],
256
- description: "Re-order the list stalest-first. The default order is `full_name` ascending, which " +
259
+ enum: ["stale", "annotated"],
260
+ description: "Re-order the list. The default order is `full_name` ascending, which " +
257
261
  "is stable across calls but says nothing about what needs attention; `stale` puts " +
258
262
  "the repositories CI has NEVER ingested a run for FIRST (never-ingested is the " +
259
263
  "stalest state on this list, not a zero), then least-recently-ingested, newest last, " +
260
264
  "with `github_full_name` breaking ties so two calls with the same data agree element " +
261
265
  "for element. " +
266
+ "`annotated` puts the LEAST-annotated suites first (annotated share of the latest " +
267
+ "run, ascending): a suite measured at 0-of-N leads, and repositories CI has never " +
268
+ "reported a run for, or whose suite was not measured, come LAST — an unmeasured suite " +
269
+ "is never ranked as 0% — with `github_full_name` breaking ties. " +
262
270
  "It re-sequences the loaded set rather than issuing a different query: the same " +
263
- "entries, a different order. `stale` is the only ordering the endpoint names — there " +
264
- "is deliberately no word for the default, because omitting the argument already " +
271
+ "entries, a different order. `stale` and `annotated` are the sole sort words the endpoint " +
272
+ "names — there is deliberately no word for the default, because omitting the argument already " +
265
273
  "means it. " +
266
274
  "Any other value a schema-bypassing client sends settles to the server's no-ask clamp " +
267
275
  "(default order, no error); blank (or null) is no ask, byte-identical to omitting the " +
@@ -1 +1 @@
1
- {"version":3,"file":"list-repositories.js","sourceRoot":"","sources":["../../../src/tools/list-repositories.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"list-repositories.js","sourceRoot":"","sources":["../../../src/tools/list-repositories.ts"],"names":[],"mappings":"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"}
@@ -75,6 +75,10 @@ const listRepositoryApiKeys = {
75
75
  "`get_repository_overview`'s `credential_health` reports, at row grain. " +
76
76
  "`token_hint` is a hint, never the token — the plaintext existed for exactly one response " +
77
77
  "at mint time and nothing persisted it. " +
78
+ "The post-cut verify: a REVOKED row also serves `last_refused_at`. `null` means offboarding " +
79
+ "took — nothing has presented the dead token since the cut; a timestamp means the dead " +
80
+ "token is still arriving, and that row's `token_hint` is what to hunt in the secret stores " +
81
+ "that may still hold it. Live rows carry no `last_refused_at` key. " +
78
82
  "Takes `repository_id` (the numeric id `list_repositories` reports, not the `org/repo` handle). " +
79
83
  "It authenticates with EITHER of this server's two key-administration credentials, whichever " +
80
84
  "is set: SPECGUARD_AGENT_API_KEY (an sga_… key — the call then reaches only the repositories " +
@@ -1 +1 @@
1
- {"version":3,"file":"list-repository-api-keys.js","sourceRoot":"","sources":["../../../src/tools/list-repository-api-keys.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,2BAA2B,EAAE,MAAM,6BAA6B,CAAC;AACzF,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAG1C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AACH,MAAM,qBAAqB,GAAmB;IAC5C,IAAI,EAAE,0BAA0B;IAChC,KAAK,EAAE,0BAA0B;IACjC,WAAW,EACT,qFAAqF;QACrF,0FAA0F;QAC1F,6FAA6F;QAC7F,4FAA4F;QAC5F,uEAAuE;QACvE,uFAAuF;QACvF,uFAAuF;QACvF,yFAAyF;QACzF,+BAA+B;QAC/B,oFAAoF;QACpF,4FAA4F;QAC5F,2FAA2F;QAC3F,uFAAuF;QACvF,2FAA2F;QAC3F,wFAAwF;QACxF,yEAAyE;QACzE,2FAA2F;QAC3F,yCAAyC;QACzC,iGAAiG;QACjG,8FAA8F;QAC9F,8FAA8F;QAC9F,+FAA+F;QAC/F,4FAA4F;QAC5F,wEAAwE;QACxE,4FAA4F;QAC5F,wFAAwF;QACxF,mDAAmD;QACnD,kGAAkG;QAClG,oDAAoD;IACtD,WAAW,EAAE;QACX,IAAI,EAAE,QAAQ;QACd,UAAU,EAAE;YACV,aAAa,EAAE;gBACb,IAAI,EAAE,QAAQ;gBACd,WAAW,EACT,8EAA8E;oBAC9E,qEAAqE;aACxE;SACF;QACD,QAAQ,EAAE,CAAC,eAAe,CAAC;QAC3B,qEAAqE;QACrE,6EAA6E;QAC7E,uBAAuB;QACvB,oBAAoB,EAAE,KAAK;KAC5B;IAED,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO;QACrB,MAAM,YAAY,GAAG,aAAa,CAAC,IAAI,CAAC,eAAe,CAAC,EAAE,eAAe,CAAC,CAAC;QAE3E,MAAM,GAAG,GAAG,2BAA2B,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QAExD,MAAM,IAAI,GAAG,MAAM,aAAa,CAC9B,GAAG,EACH,wBAAwB,kBAAkB,CAAC,YAAY,CAAC,WAAW,EACnE,EAAE,EACF,OAAO,CAAC,KAAK,CACd,CAAC;QAEF,2EAA2E;QAC3E,2EAA2E;QAC3E,yEAAyE;QACzE,0DAA0D;QAC1D,OAAO;YACL,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;YACnC,UAAU,EAAE,IAAI;SACjB,CAAC;IACJ,CAAC;CACF,CAAC;AAEF,eAAe,qBAAqB,CAAC"}
1
+ {"version":3,"file":"list-repository-api-keys.js","sourceRoot":"","sources":["../../../src/tools/list-repository-api-keys.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,2BAA2B,EAAE,MAAM,6BAA6B,CAAC;AACzF,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAG1C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AACH,MAAM,qBAAqB,GAAmB;IAC5C,IAAI,EAAE,0BAA0B;IAChC,KAAK,EAAE,0BAA0B;IACjC,WAAW,EACT,qFAAqF;QACrF,0FAA0F;QAC1F,6FAA6F;QAC7F,4FAA4F;QAC5F,uEAAuE;QACvE,uFAAuF;QACvF,uFAAuF;QACvF,yFAAyF;QACzF,+BAA+B;QAC/B,oFAAoF;QACpF,4FAA4F;QAC5F,2FAA2F;QAC3F,uFAAuF;QACvF,2FAA2F;QAC3F,wFAAwF;QACxF,yEAAyE;QACzE,2FAA2F;QAC3F,yCAAyC;QACzC,6FAA6F;QAC7F,wFAAwF;QACxF,4FAA4F;QAC5F,oEAAoE;QACpE,iGAAiG;QACjG,8FAA8F;QAC9F,8FAA8F;QAC9F,+FAA+F;QAC/F,4FAA4F;QAC5F,wEAAwE;QACxE,4FAA4F;QAC5F,wFAAwF;QACxF,mDAAmD;QACnD,kGAAkG;QAClG,oDAAoD;IACtD,WAAW,EAAE;QACX,IAAI,EAAE,QAAQ;QACd,UAAU,EAAE;YACV,aAAa,EAAE;gBACb,IAAI,EAAE,QAAQ;gBACd,WAAW,EACT,8EAA8E;oBAC9E,qEAAqE;aACxE;SACF;QACD,QAAQ,EAAE,CAAC,eAAe,CAAC;QAC3B,qEAAqE;QACrE,6EAA6E;QAC7E,uBAAuB;QACvB,oBAAoB,EAAE,KAAK;KAC5B;IAED,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO;QACrB,MAAM,YAAY,GAAG,aAAa,CAAC,IAAI,CAAC,eAAe,CAAC,EAAE,eAAe,CAAC,CAAC;QAE3E,MAAM,GAAG,GAAG,2BAA2B,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QAExD,MAAM,IAAI,GAAG,MAAM,aAAa,CAC9B,GAAG,EACH,wBAAwB,kBAAkB,CAAC,YAAY,CAAC,WAAW,EACnE,EAAE,EACF,OAAO,CAAC,KAAK,CACd,CAAC;QAEF,2EAA2E;QAC3E,2EAA2E;QAC3E,yEAAyE;QACzE,0DAA0D;QAC1D,OAAO;YACL,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;YACnC,UAAU,EAAE,IAAI;SACjB,CAAC;IACJ,CAAC;CACF,CAAC;AAEF,eAAe,qBAAqB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "specguard-mcp",
3
- "version": "0.1.38",
3
+ "version": "0.1.40",
4
4
  "description": "MCP server exposing SpecGuard suite intelligence to AI coding agents",
5
5
  "license": "ISC",
6
6
  "type": "module",