specguard-mcp 0.1.4 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/README.md +150 -1
  2. package/dist/src/support/specguard-api.d.ts +19 -0
  3. package/dist/src/support/specguard-api.js +26 -0
  4. package/dist/src/support/specguard-api.js.map +1 -1
  5. package/dist/src/tools/add-repository-member.d.ts +31 -0
  6. package/dist/src/tools/add-repository-member.js +105 -0
  7. package/dist/src/tools/add-repository-member.js.map +1 -0
  8. package/dist/src/tools/args.d.ts +26 -0
  9. package/dist/src/tools/args.js +52 -0
  10. package/dist/src/tools/args.js.map +1 -1
  11. package/dist/src/tools/index.d.ts +53 -9
  12. package/dist/src/tools/index.js +65 -9
  13. package/dist/src/tools/index.js.map +1 -1
  14. package/dist/src/tools/list-repository-members.d.ts +32 -0
  15. package/dist/src/tools/list-repository-members.js +76 -0
  16. package/dist/src/tools/list-repository-members.js.map +1 -0
  17. package/dist/src/tools/near-duplicate-clusters.d.ts +58 -0
  18. package/dist/src/tools/near-duplicate-clusters.js +116 -0
  19. package/dist/src/tools/near-duplicate-clusters.js.map +1 -0
  20. package/dist/src/tools/remove-repository-member.d.ts +30 -0
  21. package/dist/src/tools/remove-repository-member.js +93 -0
  22. package/dist/src/tools/remove-repository-member.js.map +1 -0
  23. package/dist/src/tools/rename-repository.d.ts +44 -0
  24. package/dist/src/tools/rename-repository.js +99 -0
  25. package/dist/src/tools/rename-repository.js.map +1 -0
  26. package/dist/src/tools/update-repository-member-permissions.d.ts +30 -0
  27. package/dist/src/tools/update-repository-member-permissions.js +97 -0
  28. package/dist/src/tools/update-repository-member-permissions.js.map +1 -0
  29. package/package.json +2 -1
package/README.md CHANGED
@@ -34,7 +34,7 @@ refuses to boot and takes the tools that needed no configuration down with it.
34
34
  | --- | --- | --- | --- |
35
35
  | `SPECGUARD_ENDPOINT` | `get_repository_overview`, `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 |
36
36
  | `SPECGUARD_API_KEY` | `get_repository_overview` | — | an agent/CI API key (`sgk_…`) issued by that deployment |
37
- | `SPECGUARD_USER_API_KEY` | `list_repositories`, `add_repository`, `registrable_repositories`, `remove_repository`, `create_repository_api_key`, `revoke_repository_api_key` | — | 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 |
37
+ | `SPECGUARD_USER_API_KEY` | `list_repositories`, `add_repository`, `registrable_repositories`, `remove_repository`, `create_repository_api_key`, `revoke_repository_api_key`, `list_repository_members`, `add_repository_member`, `update_repository_member_permissions`, `remove_repository_member`, `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
38
  | `SPECGUARD_LINT_COMMAND` | `lint_intent_annotations` | `specguard-lint` | the command that runs the linter. Most Ruby projects need `bundle exec specguard-lint` |
39
39
  | `SPECGUARD_TIMEOUT_MS` | HTTP tools | `30000` | how long a call to SpecGuard may take |
40
40
 
@@ -434,6 +434,47 @@ speaks for.
434
434
  It reads `SPECGUARD_USER_API_KEY` (`sgu_…`), the same credential as `list_repositories` and
435
435
  `add_repository` and a different one from the `sgk_…` key `get_repository_overview` uses.
436
436
 
437
+ ### `near_duplicate_clusters`
438
+
439
+ Runs SpecGuard's near-duplicate census over a repository's tests — which tests *read alike* (same
440
+ body text, whatever file they sit in), clustered by similarity. Answers the refactoring question
441
+ the overview's per-run rankings cannot: where the same test is written twice, before you delete or
442
+ merge anything.
443
+
444
+ **This is the expensive read in this toolset.** The census is linear but measured in seconds —
445
+ seven queries at every size, tens of seconds extrapolated at the 20,000-test design point — which
446
+ is why the platform serves it only to a client that asks (`?near_duplicates=`, shipped by SPGD-703)
447
+ and answers `near_duplicates: null` on the plain overview. Calling this tool **is** the ask;
448
+ `get_repository_overview` never sends it, so an agent reading the overview cannot pay the census by
449
+ accident.
450
+
451
+ **This tool takes no arguments** — nothing about the census is choosable. The clusters are the
452
+ repository's, computed over every run; one call returns them all. (The server reads only that the
453
+ `near_duplicates` key is *present* — `=false` would open it too — so there is no value for an
454
+ argument to carry.)
455
+
456
+ Read the response with its own rules in mind:
457
+
458
+ - `similarity_floor` and `similarity_basis` sit **first** in the block and qualify every figure
459
+ below them — a cluster count without what "similar" meant is a count you cannot act on.
460
+ - `member_count` (texts in the repository, across every run) and `example_count` (examples in the
461
+ one run `weighed_run_id` names) are **different grains**: a three-example table-driven loop is
462
+ one member and three examples. Never fold them.
463
+ - `truncated: true` means the `clusters` list was cut at the cap while the counts above it
464
+ (`cluster_count`, `identity_count`, `clustered_*`) describe the whole census.
465
+ - `unobserved_members: true` on a cluster says a member identity the weighed run did not observe
466
+ (deleted, renamed, deselected) is still listed — do not reconcile the member list against that
467
+ run's examples.
468
+ - `similarity_range` is `[strongest, weakest]`: membership is transitive, similarity is not.
469
+ - `total_seconds` is `null` where nothing was timed — never a zero that would read as free.
470
+ - `clusters: []` with a real `identity_count` is the **success** state (nothing reads alike); the
471
+ three silences — nothing ingested, nothing embedded, nothing alike — are kept distinguishable by
472
+ `recorded_count` / `identity_count` / the list itself.
473
+
474
+ Same credential and endpoint as `get_repository_overview` (`sgk_…` repository key on
475
+ `GET /api/v1/repository`); the response is that endpoint's full body with the `near_duplicates`
476
+ block opened, passed through unmodified.
477
+
437
478
  ### `remove_repository`
438
479
 
439
480
  Removes a repository from SpecGuard — and with it **every key, run and intent on it**. This is the
@@ -498,6 +539,114 @@ means the key is revoked.
498
539
  It reads `SPECGUARD_USER_API_KEY` (`sgu_…`), the same credential as `list_repositories` and
499
540
  `add_repository` and a different one from the `sgk_…` key `get_repository_overview` uses.
500
541
 
542
+ ### `list_repository_members`
543
+
544
+ Lists who has access to a SpecGuard repository: one row per member with their `handle`,
545
+ `permissions`, `granted_by` (who last set them) and `created_at`, ordered by handle.
546
+
547
+ | argument | |
548
+ | --- | --- |
549
+ | `repository_id` | the repository — its numeric `id`, as `add_repository` returns and `list_repositories` reports, not the `org/repo` handle |
550
+
551
+ The list answers **memberships only** and never reports how many CI keys a member has minted
552
+ (`keys_minted`) — that is a separate `keys.manage` disclosure this endpoint deliberately withholds;
553
+ the API-keys tools are the surface for it. The response carries **no membership id**, by design.
554
+
555
+ Authorization is the `members.manage` capability: a caller who is not a member is refused `404`
556
+ (the repository's existence stays hidden), and a member without `members.manage` is refused `403`
557
+ with SpecGuard's own sentence, verbatim.
558
+
559
+ It reads `SPECGUARD_USER_API_KEY` (`sgu_…`), the same credential as `list_repositories` and
560
+ `add_repository` and a different one from the `sgk_…` key `get_repository_overview` uses.
561
+
562
+ ### `add_repository_member`
563
+
564
+ Grants a person access to a SpecGuard repository by their GitHub handle, with an optional list of
565
+ permissions.
566
+
567
+ | argument | |
568
+ | --- | --- |
569
+ | `repository_id` | the repository to grant access to — its numeric `id`, as `add_repository` returns and `list_repositories` reports, not the `org/repo` handle |
570
+ | `handle` | the person's GitHub **login** — `octocat`, not a profile URL and not a display name; SpecGuard refuses both with its own sentence |
571
+ | `permissions` | an optional list of permission strings (`view`, `keys.manage`, `members.manage`, `repo.delete`); omit it to grant access with no additional permissions |
572
+
573
+ Each resolution failure — nobody has signed in as that handle yet, the account is archived, the
574
+ handle is ambiguous, the handle is not a login — arrives as a **distinguishable** 400 message
575
+ naming the exact next move. The grantor recorded on the membership is always the person behind
576
+ `SPECGUARD_USER_API_KEY`; no argument can name a different one.
577
+
578
+ On success (`201`) the response carries a `member` block (`handle`, `permissions`, `granted_by`,
579
+ `created_at`) — and **no membership id**, by design. Authorization is the `members.manage`
580
+ capability; a member without it is refused `403` with SpecGuard's own sentence, verbatim.
581
+
582
+ It reads `SPECGUARD_USER_API_KEY` (`sgu_…`), the same credential as `list_repositories` and
583
+ `add_repository` and a different one from the `sgk_…` key `get_repository_overview` uses.
584
+
585
+ ### `update_repository_member_permissions`
586
+
587
+ Replaces one member's permission set on a SpecGuard repository.
588
+
589
+ | argument | |
590
+ | --- | --- |
591
+ | `repository_id` | the repository the membership belongs to — its numeric `id`, as `add_repository` returns and `list_repositories` reports, not the `org/repo` handle |
592
+ | `member_id` | the id of the **membership** row to edit — not a user id, not the handle. Scoped to `repository_id`: a foreign membership id is refused `404` |
593
+ | `permissions` | the member's **complete new** permission set (`view`, `keys.manage`, `members.manage`, `repo.delete`) — it replaces what they hold today, it is not merged |
594
+
595
+ **`permissions` replaces the whole set** — name every permission the member should end with.
596
+ Values are validated by SpecGuard; an unknown value is refused with its own sentence, verbatim.
597
+
598
+ **Known limitation:** no API endpoint serves the membership id — the member list and the
599
+ add-member response both omit it by design — so the id must be obtained from the platform (today
600
+ via the repository's web members page). There is no name-based lookup. Authorization is the
601
+ `members.manage` capability; a member without it is refused `403` with SpecGuard's own sentence,
602
+ verbatim.
603
+
604
+ It reads `SPECGUARD_USER_API_KEY` (`sgu_…`), the same credential as `list_repositories` and
605
+ `add_repository` and a different one from the `sgk_…` key `get_repository_overview` uses.
606
+
607
+ ### `remove_repository_member`
608
+
609
+ Revokes one person's access to a SpecGuard repository by removing their membership. A `204` means
610
+ it is revoked.
611
+
612
+ | argument | |
613
+ | --- | --- |
614
+ | `repository_id` | the repository the membership belongs to — its numeric `id`, as `add_repository` returns and `list_repositories` reports, not the `org/repo` handle |
615
+ | `member_id` | the id of the **membership** row to revoke — not a user id, not the handle. Scoped to `repository_id`: a foreign membership id is refused `404` |
616
+
617
+ **Revoking does NOT revoke that member's minted CI keys.** Any `sgk_…` keys they created on the
618
+ repository keep authenticating by design; the lever for those is the API-keys surface
619
+ (`revoke_repository_api_key`), not this one. Self-revocation is permitted — after it, the caller's
620
+ next request to the member routes answers `404`, not `403`. The repository owner's membership
621
+ cannot be removed at all. The same known limitation as the edit tool applies to `member_id`: no
622
+ endpoint serves it, so it comes from the platform's web members page today. Authorization is the
623
+ `members.manage` capability; a member without it is refused `403` with SpecGuard's own sentence,
624
+ verbatim.
625
+
626
+ It reads `SPECGUARD_USER_API_KEY` (`sgu_…`), the same credential as `list_repositories` and
627
+ `add_repository` and a different one from the `sgk_…` key `get_repository_overview` uses.
628
+
629
+ ### `rename_repository`
630
+
631
+ Renames a SpecGuard repository — changes the `org/repo` GitHub full name it is registered under —
632
+ keeping every API key, run and intent on it.
633
+
634
+ | argument | |
635
+ | --- | --- |
636
+ | `repository_id` | the id of the repository to rename — its numeric `id`, as `add_repository` returns and `list_repositories` reports, not the `org/repo` handle |
637
+ | `github_full_name` | the new GitHub full name, `org/repo` — sent top-level in the body, not nested under `repository` |
638
+
639
+ This is the alternative to the remove-and-re-register path: that one **deletes every API key, run
640
+ and intent** on the repository (`remove_repository`), while this keeps all of them. **Authorization
641
+ is owner-only** — deliberately narrower than removal: a member granted `repo.delete` may destroy
642
+ the repository but may not rename it. The owner check also redeems a browser-issued grant valid for
643
+ 7 days; a nil or stale one is refused `403` with SpecGuard's own sentence naming the fix
644
+ (re-grant via the browser). A name another repository already holds is refused `400` — `taken`, not
645
+ `409` — verbatim. The `200` body is `{repository: …}` in the same shape `list_repositories` serves.
646
+
647
+ It reads `SPECGUARD_USER_API_KEY` (`sgu_…`), the same credential as `list_repositories` and
648
+ `add_repository` and a different one from the `sgk_…` key `get_repository_overview` uses.
649
+
501
650
  ## How it works
502
651
 
503
652
  ```
@@ -25,6 +25,18 @@ export declare function getJson(api: ApiConfig, path: string, query: Record<stri
25
25
  * send a body whose `Content-Type` says JSON and whose bytes are not.
26
26
  */
27
27
  export declare function postJson(api: ApiConfig, path: string, body: Record<string, unknown>, fetchImpl: typeof globalThis.fetch): Promise<unknown>;
28
+ /**
29
+ * `PATCH` with a JSON body — the mutating-without-replacing half of the
30
+ * transport, and deliberately the SAME function underneath `postJson`, for the
31
+ * reason that header states: everything expensive about this module is about
32
+ * the deadline, not the verb.
33
+ *
34
+ * Routed through `requestJson` rather than `deleteJson`'s raw-body handling,
35
+ * because every PATCH this surface serves answers `200` WITH a JSON body —
36
+ * `requestJson`'s parse is correct here, and re-deriving success handling would
37
+ * be a third copy of the one status check.
38
+ */
39
+ export declare function patchJson(api: ApiConfig, path: string, body: Record<string, unknown>, fetchImpl: typeof globalThis.fetch): Promise<unknown>;
28
40
  /**
29
41
  * `DELETE` — the destructive half of the transport, and deliberately the SAME
30
42
  * function underneath `postJson` rather than beside it, for the reason
@@ -58,6 +70,13 @@ export declare function deleteJson(api: ApiConfig, path: string, fetchImpl: type
58
70
  * answers with an array.
59
71
  */
60
72
  export declare function postJsonObject(api: ApiConfig, path: string, body: Record<string, unknown>, fetchImpl: typeof globalThis.fetch): Promise<Record<string, unknown>>;
73
+ /**
74
+ * `patchJson`, narrowed exactly as `postJsonObject` narrows `postJson` — same
75
+ * guard, same sentence, same reason: `ToolResult.structured` is a
76
+ * `Record<string, unknown>`, so no tool should re-type the three-clause check
77
+ * on this verb either.
78
+ */
79
+ export declare function patchJsonObject(api: ApiConfig, path: string, body: Record<string, unknown>, fetchImpl: typeof globalThis.fetch): Promise<Record<string, unknown>>;
61
80
  /**
62
81
  * `getJson`, narrowed to the object every tool here actually asks it for.
63
82
  *
@@ -38,6 +38,23 @@ export async function postJson(api, path, body, fetchImpl) {
38
38
  body: JSON.stringify(body),
39
39
  });
40
40
  }
41
+ /**
42
+ * `PATCH` with a JSON body — the mutating-without-replacing half of the
43
+ * transport, and deliberately the SAME function underneath `postJson`, for the
44
+ * reason that header states: everything expensive about this module is about
45
+ * the deadline, not the verb.
46
+ *
47
+ * Routed through `requestJson` rather than `deleteJson`'s raw-body handling,
48
+ * because every PATCH this surface serves answers `200` WITH a JSON body —
49
+ * `requestJson`'s parse is correct here, and re-deriving success handling would
50
+ * be a third copy of the one status check.
51
+ */
52
+ export async function patchJson(api, path, body, fetchImpl) {
53
+ return requestJson(new URL(`${api.endpoint}${path}`), api, fetchImpl, {
54
+ method: "PATCH",
55
+ body: JSON.stringify(body),
56
+ });
57
+ }
41
58
  /**
42
59
  * `DELETE` — the destructive half of the transport, and deliberately the SAME
43
60
  * function underneath `postJson` rather than beside it, for the reason
@@ -78,6 +95,15 @@ export async function deleteJson(api, path, fetchImpl) {
78
95
  export async function postJsonObject(api, path, body, fetchImpl) {
79
96
  return asJsonObject(await postJson(api, path, body, fetchImpl));
80
97
  }
98
+ /**
99
+ * `patchJson`, narrowed exactly as `postJsonObject` narrows `postJson` — same
100
+ * guard, same sentence, same reason: `ToolResult.structured` is a
101
+ * `Record<string, unknown>`, so no tool should re-type the three-clause check
102
+ * on this verb either.
103
+ */
104
+ export async function patchJsonObject(api, path, body, fetchImpl) {
105
+ return asJsonObject(await patchJson(api, path, body, fetchImpl));
106
+ }
81
107
  /**
82
108
  * Everything both verbs do with a response, in one place.
83
109
  *
@@ -1 +1 @@
1
- {"version":3,"file":"specguard-api.js","sourceRoot":"","sources":["../../../src/support/specguard-api.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"specguard-api.js","sourceRoot":"","sources":["../../../src/support/specguard-api.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ import type { ToolDefinition } from "./types.js";
2
+ /**
3
+ * `POST /api/v1/repositories/:repository_id/members` as a tool — shipped in the
4
+ * platform (`specguard/config/routes.rb`,
5
+ * `Api::V1::UserRepositoryMembersController#create`, SPGD-875).
6
+ *
7
+ * == The handle is the LOGIN itself
8
+ *
9
+ * `User.resolve_by_handle` refuses a profile URL and a display name with its
10
+ * own sentence ("Send the login itself — octocat, not a profile URL or a
11
+ * display name"), and each non-`:found` resolution — never-signed-in,
12
+ * archived, ambiguous, malformed — arrives as a distinguishable 400 sentence
13
+ * through the landed `refusalMessage` branch. This bridge does not re-derive
14
+ * that vocabulary: a client-side copy is a rule with no owner, free to drift
15
+ * from the one that actually decides.
16
+ *
17
+ * == The grantor is always the authenticated principal
18
+ *
19
+ * `granted_by_user` is stamped server-side from the `sgu_` credential and is
20
+ * deliberately absent from the permitted params. The tool therefore takes no
21
+ * grantor argument, and no caller can name one.
22
+ *
23
+ * == The 201 body omits the membership id, by server design
24
+ *
25
+ * `#serialize` serves `handle`, `permissions`, `granted_by`, `created_at` —
26
+ * and no id, because ids are not portable between repositories and serving
27
+ * one invites treating it as portable. See
28
+ * `update_repository_member_permissions` for where the id comes from today.
29
+ */
30
+ declare const addRepositoryMember: ToolDefinition;
31
+ export default addRepositoryMember;
@@ -0,0 +1,105 @@
1
+ import { postJsonObject, requireUserApiConfig } from "../support/specguard-api.js";
2
+ import { optionalStringArray, requireString } from "./args.js";
3
+ /**
4
+ * `POST /api/v1/repositories/:repository_id/members` as a tool — shipped in the
5
+ * platform (`specguard/config/routes.rb`,
6
+ * `Api::V1::UserRepositoryMembersController#create`, SPGD-875).
7
+ *
8
+ * == The handle is the LOGIN itself
9
+ *
10
+ * `User.resolve_by_handle` refuses a profile URL and a display name with its
11
+ * own sentence ("Send the login itself — octocat, not a profile URL or a
12
+ * display name"), and each non-`:found` resolution — never-signed-in,
13
+ * archived, ambiguous, malformed — arrives as a distinguishable 400 sentence
14
+ * through the landed `refusalMessage` branch. This bridge does not re-derive
15
+ * that vocabulary: a client-side copy is a rule with no owner, free to drift
16
+ * from the one that actually decides.
17
+ *
18
+ * == The grantor is always the authenticated principal
19
+ *
20
+ * `granted_by_user` is stamped server-side from the `sgu_` credential and is
21
+ * deliberately absent from the permitted params. The tool therefore takes no
22
+ * grantor argument, and no caller can name one.
23
+ *
24
+ * == The 201 body omits the membership id, by server design
25
+ *
26
+ * `#serialize` serves `handle`, `permissions`, `granted_by`, `created_at` —
27
+ * and no id, because ids are not portable between repositories and serving
28
+ * one invites treating it as portable. See
29
+ * `update_repository_member_permissions` for where the id comes from today.
30
+ */
31
+ const addRepositoryMember = {
32
+ name: "add_repository_member",
33
+ title: "Add repository member",
34
+ description: "Grants a person access to a SpecGuard repository by their GitHub handle, with an optional " +
35
+ "list of permissions. " +
36
+ "The `handle` is the LOGIN itself — `octocat`, not a profile URL and not a display name: " +
37
+ "SpecGuard refuses each of those with its own sentence, and every other resolution failure " +
38
+ "(nobody has signed in as that handle yet, the account is archived, the handle is ambiguous) " +
39
+ "arrives as a distinguishable 400 message naming the exact next move. " +
40
+ "The grantor recorded on the membership is always the person behind this server's user API " +
41
+ "key — the server stamps it, and no argument can name a different one. " +
42
+ "On success (201) the response carries a `member` block (`handle`, `permissions`, " +
43
+ "`granted_by`, `created_at`). It carries NO membership id, by design: ids are not portable " +
44
+ "between repositories, and editing or revoking a membership names a membership id obtained " +
45
+ "from the platform (today only via the web members page) — see the edit/revoke tools. " +
46
+ "Permissions are strings from SpecGuard's own set (`view`, `keys.manage`, `members.manage`, " +
47
+ "`repo.delete`); an unknown value is refused in SpecGuard's own words, and omitting the " +
48
+ "list grants access with no additional permissions. The repository owner cannot be added " +
49
+ "(they hold everything by construction). " +
50
+ "Authorization is the `members.manage` capability — a member without it is refused 403 in " +
51
+ "SpecGuard's own words. " +
52
+ "Takes `repository_id` — the numeric id `list_repositories` reports, not the `org/repo` " +
53
+ "handle. " +
54
+ "Needs SPECGUARD_USER_API_KEY (an sgu_… key), the same credential `add_repository` " +
55
+ "writes with and a DIFFERENT one from the sgk_… repository key `get_repository_overview` uses.",
56
+ inputSchema: {
57
+ type: "object",
58
+ properties: {
59
+ repository_id: {
60
+ type: "string",
61
+ description: "The repository to grant access to — its numeric id, as `add_repository` " +
62
+ "returns and `list_repositories` reports, not the `org/repo` handle.",
63
+ },
64
+ handle: {
65
+ type: "string",
66
+ description: "The GitHub login of the person to add, e.g. `octocat` — not a profile URL and not a " +
67
+ "display name; SpecGuard refuses both with its own sentence. The person must have " +
68
+ "signed in to SpecGuard at least once.",
69
+ },
70
+ permissions: {
71
+ type: "array",
72
+ items: { type: "string" },
73
+ description: "An optional list of permission strings from SpecGuard's own set (`view`, " +
74
+ "`keys.manage`, `members.manage`, `repo.delete`). Omit it to grant access with no " +
75
+ "additional permissions. SpecGuard validates the values and refuses an unknown one " +
76
+ "in its own words.",
77
+ },
78
+ },
79
+ required: ["repository_id", "handle"],
80
+ // Closed for the reason every tool here states — and on a WRITE, a silently
81
+ // dropped misspelled argument still grants something, just not what the
82
+ // agent believed it was asking for.
83
+ additionalProperties: false,
84
+ },
85
+ async run(args, context) {
86
+ const repositoryId = requireString(args["repository_id"], "repository_id");
87
+ const handle = requireString(args["handle"], "handle");
88
+ const permissions = optionalStringArray(args["permissions"], "permissions");
89
+ const api = requireUserApiConfig(context.config);
90
+ // Top-level `{handle, permissions}` — the shape the controller permits,
91
+ // matching `add_repository`'s stated rule: this is JSON an agent writes,
92
+ // not a Rails form. `permissions` omitted when absent so the server's own
93
+ // default applies; passed through as an ARRAY when present (the column is
94
+ // `text[]`, and a scalar on the wire is silently dropped server-side).
95
+ const created = await postJsonObject(api, `/api/v1/repositories/${encodeURIComponent(repositoryId)}/members`, permissions === undefined ? { handle } : { handle, permissions }, context.fetch);
96
+ // Unreshaped, the standing rule — and the 201 body is the only answer this
97
+ // verb gives; nothing here exists to re-fetch elsewhere.
98
+ return {
99
+ text: JSON.stringify(created, null, 2),
100
+ structured: created,
101
+ };
102
+ },
103
+ };
104
+ export default addRepositoryMember;
105
+ //# sourceMappingURL=add-repository-member.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"add-repository-member.js","sourceRoot":"","sources":["../../../src/tools/add-repository-member.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AACnF,OAAO,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAG/D;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,mBAAmB,GAAmB;IAC1C,IAAI,EAAE,uBAAuB;IAC7B,KAAK,EAAE,uBAAuB;IAC9B,WAAW,EACT,4FAA4F;QAC5F,uBAAuB;QACvB,0FAA0F;QAC1F,4FAA4F;QAC5F,8FAA8F;QAC9F,uEAAuE;QACvE,4FAA4F;QAC5F,wEAAwE;QACxE,mFAAmF;QACnF,4FAA4F;QAC5F,4FAA4F;QAC5F,uFAAuF;QACvF,6FAA6F;QAC7F,yFAAyF;QACzF,0FAA0F;QAC1F,0CAA0C;QAC1C,2FAA2F;QAC3F,yBAAyB;QACzB,yFAAyF;QACzF,UAAU;QACV,oFAAoF;QACpF,+FAA+F;IACjG,WAAW,EAAE;QACX,IAAI,EAAE,QAAQ;QACd,UAAU,EAAE;YACV,aAAa,EAAE;gBACb,IAAI,EAAE,QAAQ;gBACd,WAAW,EACT,0EAA0E;oBAC1E,qEAAqE;aACxE;YACD,MAAM,EAAE;gBACN,IAAI,EAAE,QAAQ;gBACd,WAAW,EACT,sFAAsF;oBACtF,mFAAmF;oBACnF,uCAAuC;aAC1C;YACD,WAAW,EAAE;gBACX,IAAI,EAAE,OAAO;gBACb,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;gBACzB,WAAW,EACT,2EAA2E;oBAC3E,mFAAmF;oBACnF,oFAAoF;oBACpF,mBAAmB;aACtB;SACF;QACD,QAAQ,EAAE,CAAC,eAAe,EAAE,QAAQ,CAAC;QACrC,4EAA4E;QAC5E,wEAAwE;QACxE,oCAAoC;QACpC,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;QAC3E,MAAM,MAAM,GAAG,aAAa,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC,CAAC;QACvD,MAAM,WAAW,GAAG,mBAAmB,CAAC,IAAI,CAAC,aAAa,CAAC,EAAE,aAAa,CAAC,CAAC;QAE5E,MAAM,GAAG,GAAG,oBAAoB,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QAEjD,wEAAwE;QACxE,yEAAyE;QACzE,0EAA0E;QAC1E,0EAA0E;QAC1E,uEAAuE;QACvE,MAAM,OAAO,GAAG,MAAM,cAAc,CAClC,GAAG,EACH,wBAAwB,kBAAkB,CAAC,YAAY,CAAC,UAAU,EAClE,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,EAChE,OAAO,CAAC,KAAK,CACd,CAAC;QAEF,2EAA2E;QAC3E,yDAAyD;QACzD,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,mBAAmB,CAAC"}
@@ -66,3 +66,29 @@ 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
+ * An optional array of non-blank strings, or nothing.
71
+ *
72
+ * The SHAPE-ONLY sibling of the linter's `optionalStringArray` (which stays in
73
+ * `lint-intent-annotations.ts` because its refusals — empty list, leading dash
74
+ * — are about the linter's own argument grammar and throw `CommandError`). This
75
+ * one answers only "is it an array of strings" and always throws
76
+ * `ArgumentError`, which is what a pass-through array of permission names
77
+ * needs: the platform is the authority on which strings are legal, and a
78
+ * client-side copy of that rule is a rule with no owner, free to drift from the
79
+ * one that actually decides.
80
+ */
81
+ export declare function optionalStringArray(value: unknown, field: string): string[] | undefined;
82
+ /**
83
+ * A mandatory array of non-blank strings, and nothing else will do.
84
+ *
85
+ * The mandatory counterpart of `optionalStringArray`, added with
86
+ * `update_repository_member_permissions` (SPGD-885): the server column behind
87
+ * that call is `text[]`, and a scalar on the wire is silently dropped
88
+ * server-side — so the array shape is checked HERE, before a write, rather
89
+ * than persisted as a member holding nothing. Blank entries are dropped rather
90
+ * than refused: the server's own normalisation does the same, so refusing here
91
+ * would be a second vocabulary the caller must satisfy before the one that
92
+ * decides even sees the request.
93
+ */
94
+ export declare function requireStringArray(value: unknown, field: string): string[];
@@ -93,4 +93,56 @@ export function optionalBoolean(value, field) {
93
93
  throw new ArgumentError(`\`${field}\` must be a boolean.`);
94
94
  return value;
95
95
  }
96
+ /**
97
+ * An optional array of non-blank strings, or nothing.
98
+ *
99
+ * The SHAPE-ONLY sibling of the linter's `optionalStringArray` (which stays in
100
+ * `lint-intent-annotations.ts` because its refusals — empty list, leading dash
101
+ * — are about the linter's own argument grammar and throw `CommandError`). This
102
+ * one answers only "is it an array of strings" and always throws
103
+ * `ArgumentError`, which is what a pass-through array of permission names
104
+ * needs: the platform is the authority on which strings are legal, and a
105
+ * client-side copy of that rule is a rule with no owner, free to drift from the
106
+ * one that actually decides.
107
+ */
108
+ export function optionalStringArray(value, field) {
109
+ if (value === undefined || value === null)
110
+ return undefined;
111
+ if (!Array.isArray(value))
112
+ throw new ArgumentError(`\`${field}\` must be an array of strings.`);
113
+ const entries = value.map((entry) => {
114
+ if (typeof entry !== "string")
115
+ throw new ArgumentError(`\`${field}\` must contain only strings.`);
116
+ // Trimmed like its scalar siblings: a value an agent produced by
117
+ // concatenating strings arrives with whitespace that is not part of what it
118
+ // meant to send.
119
+ return entry.trim();
120
+ });
121
+ return entries;
122
+ }
123
+ /**
124
+ * A mandatory array of non-blank strings, and nothing else will do.
125
+ *
126
+ * The mandatory counterpart of `optionalStringArray`, added with
127
+ * `update_repository_member_permissions` (SPGD-885): the server column behind
128
+ * that call is `text[]`, and a scalar on the wire is silently dropped
129
+ * server-side — so the array shape is checked HERE, before a write, rather
130
+ * than persisted as a member holding nothing. Blank entries are dropped rather
131
+ * than refused: the server's own normalisation does the same, so refusing here
132
+ * would be a second vocabulary the caller must satisfy before the one that
133
+ * decides even sees the request.
134
+ */
135
+ export function requireStringArray(value, field) {
136
+ if (value === undefined || value === null) {
137
+ throw new ArgumentError(`\`${field}\` is required.`);
138
+ }
139
+ if (!Array.isArray(value))
140
+ throw new ArgumentError(`\`${field}\` must be an array of strings.`);
141
+ const entries = value.map((entry) => {
142
+ if (typeof entry !== "string")
143
+ throw new ArgumentError(`\`${field}\` must contain only strings.`);
144
+ return entry.trim();
145
+ });
146
+ return entries.filter((entry) => entry !== "");
147
+ }
96
148
  //# sourceMappingURL=args.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"args.js","sourceRoot":"","sources":["../../../src/tools/args.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAE7C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AAEH;;GAEG;AACH,MAAM,UAAU,cAAc,CAAC,KAAc,EAAE,KAAa;IAC1D,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,SAAS,CAAC;IAC5D,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,MAAM,IAAI,aAAa,CAAC,KAAK,KAAK,sBAAsB,CAAC,CAAC;IACzF,8EAA8E;IAC9E,gFAAgF;IAChF,2EAA2E;IAC3E,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAC7B,OAAO,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC;AAC9C,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,aAAa,CAAC,KAAc,EAAE,KAAa;IACzD,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QAC1C,MAAM,IAAI,aAAa,CAAC,KAAK,KAAK,iBAAiB,CAAC,CAAC;IACvD,CAAC;IACD,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,MAAM,IAAI,aAAa,CAAC,KAAK,KAAK,sBAAsB,CAAC,CAAC;IAEzF,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAC7B,IAAI,OAAO,KAAK,EAAE;QAAE,MAAM,IAAI,aAAa,CAAC,KAAK,KAAK,uBAAuB,CAAC,CAAC;IAE/E,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,KAAc,EAAE,KAAa;IAC3D,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,SAAS,CAAC;IAC5D,IAAI,OAAO,KAAK,KAAK,SAAS;QAAE,MAAM,IAAI,aAAa,CAAC,KAAK,KAAK,uBAAuB,CAAC,CAAC;IAC3F,OAAO,KAAK,CAAC;AACf,CAAC"}
1
+ {"version":3,"file":"args.js","sourceRoot":"","sources":["../../../src/tools/args.ts"],"names":[],"mappings":"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"}
@@ -34,12 +34,21 @@ import type { ToolDefinition } from "./types.js";
34
34
  *
35
35
  * == What is deliberately absent
36
36
  *
37
- * `/check-intent` and duplicate clustering are NOT here and must not be added
38
- * until their backing engine and data exist (SPGD-114 / SPGD-115). A tool
39
- * advertised in `tools/list` is a promise an agent will act on: wrapping an
40
- * endpoint that does not exist would produce a server that discovers cleanly
41
- * and fails on use, which is worse than not offering the tool, because the
42
- * agent has already committed to a plan by the time it finds out.
37
+ * `/check-intent` is NOT here and must not be added until its backing endpoint
38
+ * exists `specguard/config/routes.rb:113` carries it as a comment only, which
39
+ * is the evidence this forbid rests on. A tool advertised in `tools/list` is a
40
+ * promise an agent will act on: wrapping an endpoint that does not exist would
41
+ * produce a server that discovers cleanly and fails on use, which is worse than
42
+ * not offering the tool, because the agent has already committed to a plan by
43
+ * the time it finds out.
44
+ *
45
+ * Duplicate clustering was once under this same forbid, on the same standing
46
+ * rule — no tool may wrap what has not shipped. That half retired when the
47
+ * platform moved: SPGD-703 (`specguard` `c43dc19`, 2026-08-28) shipped
48
+ * `GET /api/v1/repository?near_duplicates=`, serving
49
+ * `RepositoryOverview#serialized_near_duplicates` behind an opt-in ask, and
50
+ * `near_duplicate_clusters` below wraps it. What moved was the platform, not
51
+ * the bar.
43
52
  *
44
53
  * == The fourth: the first tool that WRITES
45
54
  *
@@ -99,9 +108,44 @@ import type { ToolDefinition } from "./types.js";
99
108
  * `requestJson`'s JSON parse.
100
109
  *
101
110
  * The standing rule itself is unchanged and still binding — which still keeps
102
- * out `/check-intent`, duplicate clustering, member management and rename: none
103
- * of their backing endpoints has shipped, and a tool advertised in `tools/list`
104
- * remains a promise an agent will act on.
111
+ * out `/check-intent`: it has no backing endpoint (`routes.rb:113` mounts it
112
+ * as a comment only). A tool advertised in `tools/list` remains a promise an
113
+ * agent will act on.
114
+ *
115
+ * == The ninth through twelfth: member management
116
+ *
117
+ * - `list_repository_members`, `add_repository_member`,
118
+ * `update_repository_member_permissions` and `remove_repository_member`
119
+ * wrap the four `user_repository_members` endpoints (all shipped: SPGD-875,
120
+ * specguard@origin/main).
121
+ *
122
+ * They also forced the transport's fourth verb: `update` is a PATCH, which
123
+ * arrived with `patchJson` in this same slice — 200 with a JSON body, so it
124
+ * routes through `requestJson` like `postJson` rather than `deleteJson`'s
125
+ * raw-body handling.
126
+ *
127
+ * A known limitation rides with them and is stated in the edit/revoke tool
128
+ * descriptions rather than papered over: PATCH and DELETE name a membership by
129
+ * id, but no endpoint serves that id (the platform's `#serialize` deliberately
130
+ * omits it), so the id comes from the platform's web members page today. That
131
+ * is a platform-side follow-up, not a client-side workaround — there is no
132
+ * name-based lookup.
133
+ *
134
+ * == The thirteenth: rename
135
+ *
136
+ * - `rename_repository` wraps `PATCH /api/v1/repositories/:id` (shipped:
137
+ * SPGD-878, `specguard@origin/main` e026793, PR #266).
138
+ *
139
+ * This is the entry the forbid above once named as "the natural next slice" —
140
+ * the platform moved (SPGD-878 shipped the endpoint), so the forbid retired
141
+ * exactly as the duplicate-clustering one did. It reuses `patchJson` landed
142
+ * with the member-management slice rather than minting a transport of its own.
143
+ * The verb's one asymmetry is carried in the tool description rather than
144
+ * papered over: rename authorizes OWNER-ONLY, while removal authorizes the
145
+ * BROADER `repo.delete` capability — a member who may destroy the repository
146
+ * may not rename it. Before this tool the bridge's only rename path was
147
+ * remove-and-re-register, which destroys every key, run and intent; this one
148
+ * keeps them.
105
149
  */
106
150
  export declare const TOOLS: readonly ToolDefinition[];
107
151
  export type { ToolContext, ToolDefinition, ToolResult } from "./types.js";