specguard-mcp 0.1.3 → 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.
- package/README.md +255 -6
- package/dist/src/support/specguard-api.d.ts +36 -0
- package/dist/src/support/specguard-api.js +59 -4
- package/dist/src/support/specguard-api.js.map +1 -1
- package/dist/src/tools/add-repository-member.d.ts +31 -0
- package/dist/src/tools/add-repository-member.js +105 -0
- package/dist/src/tools/add-repository-member.js.map +1 -0
- package/dist/src/tools/args.d.ts +26 -0
- package/dist/src/tools/args.js +52 -0
- package/dist/src/tools/args.js.map +1 -1
- package/dist/src/tools/create-repository-api-key.d.ts +26 -0
- package/dist/src/tools/create-repository-api-key.js +83 -0
- package/dist/src/tools/create-repository-api-key.js.map +1 -0
- package/dist/src/tools/index.d.ts +96 -11
- package/dist/src/tools/index.js +116 -11
- package/dist/src/tools/index.js.map +1 -1
- package/dist/src/tools/list-repository-members.d.ts +32 -0
- package/dist/src/tools/list-repository-members.js +76 -0
- package/dist/src/tools/list-repository-members.js.map +1 -0
- package/dist/src/tools/near-duplicate-clusters.d.ts +58 -0
- package/dist/src/tools/near-duplicate-clusters.js +116 -0
- package/dist/src/tools/near-duplicate-clusters.js.map +1 -0
- package/dist/src/tools/registrable-repositories.d.ts +51 -0
- package/dist/src/tools/registrable-repositories.js +92 -0
- package/dist/src/tools/registrable-repositories.js.map +1 -0
- package/dist/src/tools/remove-repository-member.d.ts +30 -0
- package/dist/src/tools/remove-repository-member.js +93 -0
- package/dist/src/tools/remove-repository-member.js.map +1 -0
- package/dist/src/tools/remove-repository.d.ts +33 -0
- package/dist/src/tools/remove-repository.js +81 -0
- package/dist/src/tools/remove-repository.js.map +1 -0
- package/dist/src/tools/rename-repository.d.ts +44 -0
- package/dist/src/tools/rename-repository.js +99 -0
- package/dist/src/tools/rename-repository.js.map +1 -0
- package/dist/src/tools/revoke-repository-api-key.d.ts +29 -0
- package/dist/src/tools/revoke-repository-api-key.js +85 -0
- package/dist/src/tools/revoke-repository-api-key.js.map +1 -0
- package/dist/src/tools/update-repository-member-permissions.d.ts +30 -0
- package/dist/src/tools/update-repository-member-permissions.js +97 -0
- package/dist/src/tools/update-repository-member-permissions.js.map +1 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -32,9 +32,9 @@ refuses to boot and takes the tools that needed no configuration down with it.
|
|
|
32
32
|
|
|
33
33
|
| Variable | Needed by | Default | What it is |
|
|
34
34
|
| --- | --- | --- | --- |
|
|
35
|
-
| `SPECGUARD_ENDPOINT` | `get_repository_overview`, `list_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 |
|
|
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` | — | 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
|
|
|
@@ -351,10 +351,11 @@ decides which table is consulted before any of them is read — so the two are n
|
|
|
351
351
|
setting one does not stand in for the other. Every message this tool produces names the variable
|
|
352
352
|
*it* reads, so a `401` here never sends you to check the key `get_repository_overview` uses.
|
|
353
353
|
|
|
354
|
-
Registering a repository is
|
|
355
|
-
`full_name` this tool reports.
|
|
356
|
-
|
|
357
|
-
for the capability rather than the other way
|
|
354
|
+
Registering a repository is `add_repository`, below — it reads the same `sgu_…` key and takes the
|
|
355
|
+
`full_name` this tool reports. Removal and the key lifecycle (`remove_repository`,
|
|
356
|
+
`create_repository_api_key`, `revoke_repository_api_key`) are below too, on the same key; a tool
|
|
357
|
+
here is a promise the agent will act on, so each waits for the capability rather than the other way
|
|
358
|
+
round.
|
|
358
359
|
|
|
359
360
|
### `add_repository`
|
|
360
361
|
|
|
@@ -398,6 +399,254 @@ as this bridge rejecting a name the platform would have accepted.
|
|
|
398
399
|
It reads `SPECGUARD_USER_API_KEY` (`sgu_…`), the same credential as `list_repositories` and a
|
|
399
400
|
different one from the `sgk_…` key `get_repository_overview` uses.
|
|
400
401
|
|
|
402
|
+
### `registrable_repositories`
|
|
403
|
+
|
|
404
|
+
Lists the GitHub repositories the person behind `SPECGUARD_USER_API_KEY` **could** register with
|
|
405
|
+
SpecGuard — the set the registration gate would consult, read out loud in advance, so an agent can
|
|
406
|
+
pick a `full_name` for `add_repository` from a real answer rather than by guessing. `list_repositories`
|
|
407
|
+
reports what *is* registered; this reports what could be, and the two answer different questions.
|
|
408
|
+
|
|
409
|
+
The body comes back as SpecGuard serves it: `{"repositories": […]}` with each entry carrying
|
|
410
|
+
`full_name` and `registered`, ordered by `full_name` ascending, plus a `grant` block (`captured_at`,
|
|
411
|
+
`expires_at`, `stale`) describing the stored record of this person's GitHub permissions.
|
|
412
|
+
|
|
413
|
+
**`registered` is asked globally, not just of your own repositories.** An entry marked
|
|
414
|
+
`registered: true` was registered by *somebody* — possibly someone else — and a POST naming it will
|
|
415
|
+
be refused with `has already been taken`. That is exactly why entries are *marked* rather than
|
|
416
|
+
excluded: a reading scoped to what you can open would send you at a name nobody can register.
|
|
417
|
+
|
|
418
|
+
**A name appearing here is not a promise the write will succeed.** This is the set the gate would
|
|
419
|
+
consult at the moment of the read; the repository may be registered by someone else between this
|
|
420
|
+
call and your POST.
|
|
421
|
+
|
|
422
|
+
**A missing or stale grant is not an error — it is the modal first answer.** SpecGuard fails closed
|
|
423
|
+
when it has no current record of your GitHub permissions, which is every person who has not opened
|
|
424
|
+
SpecGuard in a browser recently. The call then answers `403` with SpecGuard's own sentence naming
|
|
425
|
+
the fix: *sign in to SpecGuard in a browser and reconnect GitHub, then try again*. On that refusal
|
|
426
|
+
the body still carries `grant`, and it distinguishes the two cases: `grant: null` means there never
|
|
427
|
+
was one (first-time setup), a populated grant with `stale: true` means an existing connection lapsed
|
|
428
|
+
— same remedy, very different urgency. Read it before telling the person what to do.
|
|
429
|
+
|
|
430
|
+
**This tool takes no arguments** — the credential is the whole of the scope. The endpoint takes no
|
|
431
|
+
parameters; which repositories are in the answer is decided by SpecGuard from the person the key
|
|
432
|
+
speaks for.
|
|
433
|
+
|
|
434
|
+
It reads `SPECGUARD_USER_API_KEY` (`sgu_…`), the same credential as `list_repositories` and
|
|
435
|
+
`add_repository` and a different one from the `sgk_…` key `get_repository_overview` uses.
|
|
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
|
+
|
|
478
|
+
### `remove_repository`
|
|
479
|
+
|
|
480
|
+
Removes a repository from SpecGuard — and with it **every key, run and intent on it**. This is the
|
|
481
|
+
destructive gesture in this toolset: irreversible, no undo, and a `204` means the repository and its
|
|
482
|
+
history are gone short of re-registering from scratch. Confirm with the user before calling.
|
|
483
|
+
|
|
484
|
+
| argument | |
|
|
485
|
+
| --- | --- |
|
|
486
|
+
| `repository_id` | the repository to remove — its numeric `id`, as `add_repository` returns and `list_repositories` reports, not the `org/repo` handle |
|
|
487
|
+
|
|
488
|
+
Authorization is the `repo.delete` capability at **either surface** — an owner, or a member granted
|
|
489
|
+
`repo.delete`, may remove the repository. A member without it is refused `403` with SpecGuard's own
|
|
490
|
+
sentence, verbatim. The repository's CI keys stop authenticating the moment it succeeds.
|
|
491
|
+
|
|
492
|
+
It reads `SPECGUARD_USER_API_KEY` (`sgu_…`), the same credential as `list_repositories` and
|
|
493
|
+
`add_repository` and a different one from the `sgk_…` key `get_repository_overview` uses.
|
|
494
|
+
|
|
495
|
+
### `create_repository_api_key`
|
|
496
|
+
|
|
497
|
+
Mints a new CI API key (`sgk_…`) for a SpecGuard repository and returns it alongside the
|
|
498
|
+
repository's existing keys. Minting does not disturb existing keys — each key on a repository
|
|
499
|
+
authenticates independently until revoked.
|
|
500
|
+
|
|
501
|
+
| argument | |
|
|
502
|
+
| --- | --- |
|
|
503
|
+
| `repository_id` | the repository to mint the key for — its numeric `id`, as `add_repository` returns and `list_repositories` reports, not the `org/repo` handle |
|
|
504
|
+
| `name` | an optional label for the key; omit it to let SpecGuard use its default name |
|
|
505
|
+
|
|
506
|
+
The body comes back as SpecGuard serves it: an `api_key` block (`name`, `token`, `hint`,
|
|
507
|
+
`created_at`) — the same shape `add_repository` serves.
|
|
508
|
+
|
|
509
|
+
> ⚠️ **`api_key.token` is shown once and never again.** Nothing stores it and no endpoint can
|
|
510
|
+
> re-serve it. Hand it to the person you are working for in your reply. If it is dropped, the
|
|
511
|
+
> recovery is minting another key with this same tool — the platform has no regenerate — then
|
|
512
|
+
> revoking the orphaned one with `revoke_repository_api_key`.
|
|
513
|
+
|
|
514
|
+
Authorization is the `keys_manage` capability; a member without it is refused `403` with SpecGuard's
|
|
515
|
+
own sentence, verbatim.
|
|
516
|
+
|
|
517
|
+
It reads `SPECGUARD_USER_API_KEY` (`sgu_…`), the same credential as `list_repositories` and
|
|
518
|
+
`add_repository` and a different one from the `sgk_…` key `get_repository_overview` uses.
|
|
519
|
+
|
|
520
|
+
### `revoke_repository_api_key`
|
|
521
|
+
|
|
522
|
+
Revokes one CI API key on a SpecGuard repository. The key stops authenticating immediately; every
|
|
523
|
+
**other** key on the repository keeps working, so CI keeps ingesting if it holds a surviving key.
|
|
524
|
+
|
|
525
|
+
| argument | |
|
|
526
|
+
| --- | --- |
|
|
527
|
+
| `repository_id` | the repository the key belongs to — its numeric `id`, as `add_repository` returns and `list_repositories` reports, not the `org/repo` handle |
|
|
528
|
+
| `key_id` | the id of the key to revoke, as served in the `api_key` block of `add_repository` or `create_repository_api_key` |
|
|
529
|
+
|
|
530
|
+
The `key_id` is scoped to `repository_id`: a key id belonging to a different repository is refused
|
|
531
|
+
`404`, never a cross-repository delete. Authorization is the `keys_manage` capability; a member
|
|
532
|
+
without it is refused `403` with SpecGuard's own sentence, verbatim.
|
|
533
|
+
|
|
534
|
+
**Key rotation is mint-then-revoke, in that order.** The platform has no regenerate, so mint a
|
|
535
|
+
replacement with `create_repository_api_key` and deploy it BEFORE revoking the old one — revoke
|
|
536
|
+
first and the repository's CI is locked out until a human mints a new key in a browser. A `204`
|
|
537
|
+
means the key is revoked.
|
|
538
|
+
|
|
539
|
+
It reads `SPECGUARD_USER_API_KEY` (`sgu_…`), the same credential as `list_repositories` and
|
|
540
|
+
`add_repository` and a different one from the `sgk_…` key `get_repository_overview` uses.
|
|
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
|
+
|
|
401
650
|
## How it works
|
|
402
651
|
|
|
403
652
|
```
|
|
@@ -25,6 +25,35 @@ 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>;
|
|
40
|
+
/**
|
|
41
|
+
* `DELETE` — the destructive half of the transport, and deliberately the SAME
|
|
42
|
+
* function underneath `postJson` rather than beside it, for the reason
|
|
43
|
+
* `postJson`'s header states: everything expensive about this module is about
|
|
44
|
+
* the deadline, not the verb.
|
|
45
|
+
*
|
|
46
|
+
* Returns the RAW BODY TEXT rather than a parsed value, because the endpoints
|
|
47
|
+
* this serves answer `204` with NO body at all — the one response in the `sgu_`
|
|
48
|
+
* surface that is deliberately not JSON. `requestJson` JSON-parses every 2xx it
|
|
49
|
+
* sees, so routing a `204` through it would turn a successful delete into
|
|
50
|
+
* "answered 204 but the body was not JSON" — the trap this verb specifically
|
|
51
|
+
* introduces, and the reason the DELETE path has its own success handling
|
|
52
|
+
* instead of sharing `requestJson`'s. The status check and the
|
|
53
|
+
* "reached and refused" hand-off to `describeFailure` are still shared
|
|
54
|
+
* verbatim: only what happens to a SUCCESS body differs.
|
|
55
|
+
*/
|
|
56
|
+
export declare function deleteJson(api: ApiConfig, path: string, fetchImpl: typeof globalThis.fetch): Promise<string>;
|
|
28
57
|
/**
|
|
29
58
|
* `postJson`, narrowed exactly as `getJsonObject` narrows `getJson`.
|
|
30
59
|
*
|
|
@@ -41,6 +70,13 @@ export declare function postJson(api: ApiConfig, path: string, body: Record<stri
|
|
|
41
70
|
* answers with an array.
|
|
42
71
|
*/
|
|
43
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>>;
|
|
44
80
|
/**
|
|
45
81
|
* `getJson`, narrowed to the object every tool here actually asks it for.
|
|
46
82
|
*
|
|
@@ -38,6 +38,45 @@ 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
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* `DELETE` — the destructive half of the transport, and deliberately the SAME
|
|
60
|
+
* function underneath `postJson` rather than beside it, for the reason
|
|
61
|
+
* `postJson`'s header states: everything expensive about this module is about
|
|
62
|
+
* the deadline, not the verb.
|
|
63
|
+
*
|
|
64
|
+
* Returns the RAW BODY TEXT rather than a parsed value, because the endpoints
|
|
65
|
+
* this serves answer `204` with NO body at all — the one response in the `sgu_`
|
|
66
|
+
* surface that is deliberately not JSON. `requestJson` JSON-parses every 2xx it
|
|
67
|
+
* sees, so routing a `204` through it would turn a successful delete into
|
|
68
|
+
* "answered 204 but the body was not JSON" — the trap this verb specifically
|
|
69
|
+
* introduces, and the reason the DELETE path has its own success handling
|
|
70
|
+
* instead of sharing `requestJson`'s. The status check and the
|
|
71
|
+
* "reached and refused" hand-off to `describeFailure` are still shared
|
|
72
|
+
* verbatim: only what happens to a SUCCESS body differs.
|
|
73
|
+
*/
|
|
74
|
+
export async function deleteJson(api, path, fetchImpl) {
|
|
75
|
+
const { response, body } = await fetchWithTimeout(new URL(`${api.endpoint}${path}`), api, fetchImpl, { method: "DELETE" });
|
|
76
|
+
if (!response.ok)
|
|
77
|
+
throw describeFailure(response.status, body, api);
|
|
78
|
+
return body;
|
|
79
|
+
}
|
|
41
80
|
/**
|
|
42
81
|
* `postJson`, narrowed exactly as `getJsonObject` narrows `getJson`.
|
|
43
82
|
*
|
|
@@ -56,6 +95,15 @@ export async function postJson(api, path, body, fetchImpl) {
|
|
|
56
95
|
export async function postJsonObject(api, path, body, fetchImpl) {
|
|
57
96
|
return asJsonObject(await postJson(api, path, body, fetchImpl));
|
|
58
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
|
+
}
|
|
59
107
|
/**
|
|
60
108
|
* Everything both verbs do with a response, in one place.
|
|
61
109
|
*
|
|
@@ -245,8 +293,8 @@ function describeFailure(status, body, api) {
|
|
|
245
293
|
return new ApiError(`${api.endpoint} has no such endpoint (404). Check that ${api.endpointVariable} is the ` +
|
|
246
294
|
"deployment's root URL, without a path.", status);
|
|
247
295
|
}
|
|
248
|
-
if (status === 400) {
|
|
249
|
-
const message =
|
|
296
|
+
if (status === 400 || status === 403) {
|
|
297
|
+
const message = refusalMessage(body, status);
|
|
250
298
|
if (message !== undefined)
|
|
251
299
|
return new ApiError(message, status);
|
|
252
300
|
}
|
|
@@ -262,6 +310,13 @@ function describeFailure(status, body, api) {
|
|
|
262
310
|
* `origin/main` route here, so this branch serves the API surface rather than
|
|
263
311
|
* one tool.
|
|
264
312
|
*
|
|
313
|
+
* The 403 is the same shape under another status. `UserRepositoriesController#
|
|
314
|
+
* render_not_granted` renders `{error: "not_granted", message:, grant:}` — the
|
|
315
|
+
* `grant` block is simply ignored by the extractor, exactly as `details` is.
|
|
316
|
+
* Same defect (the generic branch truncating the one sentence that names the
|
|
317
|
+
* fix), same remedy — which is why the helper is ONE function parameterised on
|
|
318
|
+
* the status rather than two copies beside each other.
|
|
319
|
+
*
|
|
265
320
|
* SURFACING IT IS THE OPPOSITE OF RESHAPING IT. The generic branch below turns
|
|
266
321
|
* the most useful sentence in this direction —
|
|
267
322
|
*
|
|
@@ -283,7 +338,7 @@ function describeFailure(status, body, api) {
|
|
|
283
338
|
* whose `message` is absent or is not a string — still gets the generic
|
|
284
339
|
* sentence, which at least shows the operator what actually came back.
|
|
285
340
|
*/
|
|
286
|
-
function
|
|
341
|
+
function refusalMessage(body, status) {
|
|
287
342
|
let parsed;
|
|
288
343
|
try {
|
|
289
344
|
parsed = JSON.parse(body);
|
|
@@ -296,7 +351,7 @@ function badRequestMessage(body) {
|
|
|
296
351
|
const message = parsed["message"];
|
|
297
352
|
if (typeof message !== "string" || message.trim() === "")
|
|
298
353
|
return undefined;
|
|
299
|
-
return `SpecGuard refused the request (
|
|
354
|
+
return `SpecGuard refused the request (${status}): ${message.trim()}`;
|
|
300
355
|
}
|
|
301
356
|
export { requireApiConfig, requireUserApiConfig };
|
|
302
357
|
//# sourceMappingURL=specguard-api.js.map
|
|
@@ -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"}
|
package/dist/src/tools/args.d.ts
CHANGED
|
@@ -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[];
|