specguard-mcp 0.1.1 → 0.1.3
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 +129 -22
- package/dist/bin/specguard-mcp.js +5 -0
- package/dist/bin/specguard-mcp.js.map +1 -1
- package/dist/src/config.d.ts +104 -14
- package/dist/src/config.js +78 -7
- package/dist/src/config.js.map +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.js +6 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/support/run-command.d.ts +42 -0
- package/dist/src/support/run-command.js +124 -8
- package/dist/src/support/run-command.js.map +1 -1
- package/dist/src/support/specguard-api.d.ts +53 -2
- package/dist/src/support/specguard-api.js +153 -8
- package/dist/src/support/specguard-api.js.map +1 -1
- package/dist/src/support/teardown.d.ts +33 -0
- package/dist/src/support/teardown.js +56 -0
- package/dist/src/support/teardown.js.map +1 -0
- package/dist/src/tools/add-repository.d.ts +55 -0
- package/dist/src/tools/add-repository.js +114 -0
- package/dist/src/tools/add-repository.js.map +1 -0
- package/dist/src/tools/args.d.ts +20 -0
- package/dist/src/tools/args.js +30 -0
- package/dist/src/tools/args.js.map +1 -1
- package/dist/src/tools/index.d.ts +33 -0
- package/dist/src/tools/index.js +41 -1
- package/dist/src/tools/index.js.map +1 -1
- package/dist/src/tools/list-repositories.d.ts +75 -0
- package/dist/src/tools/list-repositories.js +112 -0
- package/dist/src/tools/list-repositories.js.map +1 -0
- package/dist/src/tools/repository-overview.js +35 -15
- package/dist/src/tools/repository-overview.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { ToolDefinition } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* `POST /api/v1/repositories` as a tool — shipped today in the platform
|
|
4
|
+
* (`specguard/config/routes.rb`, `Api::V1::UserRepositoriesController#create`).
|
|
5
|
+
*
|
|
6
|
+
* == The first tool here that WRITES, and what that changed
|
|
7
|
+
*
|
|
8
|
+
* Everything before this read. The registry's standing rule is that a tool in
|
|
9
|
+
* `tools/list` is a promise an agent will act on, and this endpoint has existed
|
|
10
|
+
* on the platform for some time — what was missing was on THIS side: `getJson`
|
|
11
|
+
* hardcoded `method: "GET"` and took no body. `tools/index.ts` and
|
|
12
|
+
* `list-repositories.ts` both said so, and said the transport should land with
|
|
13
|
+
* the first write tool so it could be designed against a real request body and a
|
|
14
|
+
* real 4xx surface rather than invented for a caller that did not exist. This is
|
|
15
|
+
* that tool, and `postJson`/`postJsonObject` are that transport.
|
|
16
|
+
*
|
|
17
|
+
* == The request body is top-level, because the caller is an agent
|
|
18
|
+
*
|
|
19
|
+
* `{"github_full_name": "org/repo"}`, not `{"repository": {…}}`. The controller
|
|
20
|
+
* permits it that way and says why: this is a JSON API being driven by an agent,
|
|
21
|
+
* not a Rails form being submitted by a browser, and the top-level shape is what
|
|
22
|
+
* a caller writing curl by hand will send.
|
|
23
|
+
*
|
|
24
|
+
* == The format is NOT re-validated here, deliberately
|
|
25
|
+
*
|
|
26
|
+
* `Repository` validates `org/repo` itself, and a refusal now arrives through
|
|
27
|
+
* the 400 branch in `describeFailure` in SpecGuard's own words. A second format
|
|
28
|
+
* rule on this side is exactly what "a thin client that reshapes its upstream is
|
|
29
|
+
* not thin" forbids — it would be a rule with no owner, free to drift from the
|
|
30
|
+
* one that actually decides, and its divergence would surface as this bridge
|
|
31
|
+
* refusing a name the platform would have accepted. Checking that `full_name` is
|
|
32
|
+
* a present, non-blank string is the whole of the bridge's business: that is a
|
|
33
|
+
* shape check, which is why it is `requireString` from `args.ts` and not a
|
|
34
|
+
* hand-rolled one here.
|
|
35
|
+
*
|
|
36
|
+
* == The MODAL first answer is a 400, and it is the useful one
|
|
37
|
+
*
|
|
38
|
+
* `RepositoryRegistration::GrantVerifier` fails closed on a grant that is
|
|
39
|
+
* missing or stale, which is every person who has not opened SpecGuard in a
|
|
40
|
+
* browser since this shipped — the controller records that this is "an ordinary
|
|
41
|
+
* state and not an error". The sentence that comes back names the operator's
|
|
42
|
+
* exact next move (sign in, reconnect GitHub, retry), and reaching the agent
|
|
43
|
+
* intact is what the 400 branch in `specguard-api.ts` is for.
|
|
44
|
+
*
|
|
45
|
+
* == Why the description carries a hazard paragraph
|
|
46
|
+
*
|
|
47
|
+
* `types.ts` calls the description "prompt material, not documentation … the
|
|
48
|
+
* entire basis on which a model decides whether to call the tool". This tool is
|
|
49
|
+
* NOT idempotent and its 201 carries a reveal-once token, so an agent that
|
|
50
|
+
* learns those facts by hitting them has already lost the token. They are stated
|
|
51
|
+
* where they are read BEFORE the call is committed to, rather than left to be
|
|
52
|
+
* discovered from a failure.
|
|
53
|
+
*/
|
|
54
|
+
declare const addRepository: ToolDefinition;
|
|
55
|
+
export default addRepository;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { postJsonObject, requireUserApiConfig } from "../support/specguard-api.js";
|
|
2
|
+
import { requireString } from "./args.js";
|
|
3
|
+
/**
|
|
4
|
+
* `POST /api/v1/repositories` as a tool — shipped today in the platform
|
|
5
|
+
* (`specguard/config/routes.rb`, `Api::V1::UserRepositoriesController#create`).
|
|
6
|
+
*
|
|
7
|
+
* == The first tool here that WRITES, and what that changed
|
|
8
|
+
*
|
|
9
|
+
* Everything before this read. The registry's standing rule is that a tool in
|
|
10
|
+
* `tools/list` is a promise an agent will act on, and this endpoint has existed
|
|
11
|
+
* on the platform for some time — what was missing was on THIS side: `getJson`
|
|
12
|
+
* hardcoded `method: "GET"` and took no body. `tools/index.ts` and
|
|
13
|
+
* `list-repositories.ts` both said so, and said the transport should land with
|
|
14
|
+
* the first write tool so it could be designed against a real request body and a
|
|
15
|
+
* real 4xx surface rather than invented for a caller that did not exist. This is
|
|
16
|
+
* that tool, and `postJson`/`postJsonObject` are that transport.
|
|
17
|
+
*
|
|
18
|
+
* == The request body is top-level, because the caller is an agent
|
|
19
|
+
*
|
|
20
|
+
* `{"github_full_name": "org/repo"}`, not `{"repository": {…}}`. The controller
|
|
21
|
+
* permits it that way and says why: this is a JSON API being driven by an agent,
|
|
22
|
+
* not a Rails form being submitted by a browser, and the top-level shape is what
|
|
23
|
+
* a caller writing curl by hand will send.
|
|
24
|
+
*
|
|
25
|
+
* == The format is NOT re-validated here, deliberately
|
|
26
|
+
*
|
|
27
|
+
* `Repository` validates `org/repo` itself, and a refusal now arrives through
|
|
28
|
+
* the 400 branch in `describeFailure` in SpecGuard's own words. A second format
|
|
29
|
+
* rule on this side is exactly what "a thin client that reshapes its upstream is
|
|
30
|
+
* not thin" forbids — it would be a rule with no owner, free to drift from the
|
|
31
|
+
* one that actually decides, and its divergence would surface as this bridge
|
|
32
|
+
* refusing a name the platform would have accepted. Checking that `full_name` is
|
|
33
|
+
* a present, non-blank string is the whole of the bridge's business: that is a
|
|
34
|
+
* shape check, which is why it is `requireString` from `args.ts` and not a
|
|
35
|
+
* hand-rolled one here.
|
|
36
|
+
*
|
|
37
|
+
* == The MODAL first answer is a 400, and it is the useful one
|
|
38
|
+
*
|
|
39
|
+
* `RepositoryRegistration::GrantVerifier` fails closed on a grant that is
|
|
40
|
+
* missing or stale, which is every person who has not opened SpecGuard in a
|
|
41
|
+
* browser since this shipped — the controller records that this is "an ordinary
|
|
42
|
+
* state and not an error". The sentence that comes back names the operator's
|
|
43
|
+
* exact next move (sign in, reconnect GitHub, retry), and reaching the agent
|
|
44
|
+
* intact is what the 400 branch in `specguard-api.ts` is for.
|
|
45
|
+
*
|
|
46
|
+
* == Why the description carries a hazard paragraph
|
|
47
|
+
*
|
|
48
|
+
* `types.ts` calls the description "prompt material, not documentation … the
|
|
49
|
+
* entire basis on which a model decides whether to call the tool". This tool is
|
|
50
|
+
* NOT idempotent and its 201 carries a reveal-once token, so an agent that
|
|
51
|
+
* learns those facts by hitting them has already lost the token. They are stated
|
|
52
|
+
* where they are read BEFORE the call is committed to, rather than left to be
|
|
53
|
+
* discovered from a failure.
|
|
54
|
+
*/
|
|
55
|
+
const addRepository = {
|
|
56
|
+
name: "add_repository",
|
|
57
|
+
title: "Add repository",
|
|
58
|
+
description: "Registers a GitHub repository with SpecGuard for the person behind this server's user API " +
|
|
59
|
+
"key, and returns the repository along with its first CI API key. " +
|
|
60
|
+
"Takes `full_name` as `org/repo` — the same handle `list_repositories` reports and every other " +
|
|
61
|
+
"SpecGuard surface names a repository by. " +
|
|
62
|
+
"On success the response carries a `repository` block (`id`, `full_name`, `name`, " +
|
|
63
|
+
"`registered_at`) and an `api_key` block (`name`, `token`, `hint`, `created_at`). " +
|
|
64
|
+
"⚠️ `api_key.token` is shown THIS ONCE AND NEVER AGAIN — nothing stores it and no endpoint can " +
|
|
65
|
+
"re-serve it, so hand it to the user in your reply rather than assuming it can be fetched " +
|
|
66
|
+
"later. " +
|
|
67
|
+
"⚠️ This tool is NOT idempotent and it WRITES. If the call times out (SPECGUARD_TIMEOUT_MS) " +
|
|
68
|
+
"the registration may still have succeeded on the server, taking its one-time token with it; " +
|
|
69
|
+
"retrying then fails with `has already been taken`, which is the honest answer, and the " +
|
|
70
|
+
"recovery is SpecGuard's API-keys page in a browser. Do not retry a timeout blindly. " +
|
|
71
|
+
"Requires a CURRENT record of the caller's GitHub permissions, which only a browser session " +
|
|
72
|
+
"creates: a person who has not signed in to SpecGuard and connected GitHub recently is refused " +
|
|
73
|
+
"with a message saying exactly that, and the fix is theirs to perform in a browser — no " +
|
|
74
|
+
"argument to this tool can substitute for it. The repository must also be one the SpecGuard " +
|
|
75
|
+
"GitHub App is installed on and that this person administers. " +
|
|
76
|
+
"Needs SPECGUARD_USER_API_KEY (an sgu_… key), the same credential `list_repositories` reads " +
|
|
77
|
+
"and a DIFFERENT one from the sgk_… repository key `get_repository_overview` uses.",
|
|
78
|
+
inputSchema: {
|
|
79
|
+
type: "object",
|
|
80
|
+
properties: {
|
|
81
|
+
full_name: {
|
|
82
|
+
type: "string",
|
|
83
|
+
description: "The repository to register, as `org/repo` (for example `acme/billing`) — the same " +
|
|
84
|
+
"handle `list_repositories` reports. Not a URL and not a bare repository name. " +
|
|
85
|
+
"SpecGuard validates the format and refuses an unusable one in its own words.",
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
required: ["full_name"],
|
|
89
|
+
// Closed for the reason `list_repositories` states: `server.ts` forwards
|
|
90
|
+
// `arguments` unvalidated, so an open schema would let a misspelled argument
|
|
91
|
+
// be dropped silently and the call answered as though it had been honoured.
|
|
92
|
+
// On a WRITE that is worse than on a read — the call still registers
|
|
93
|
+
// something, just not what the agent believed it was asking for.
|
|
94
|
+
additionalProperties: false,
|
|
95
|
+
},
|
|
96
|
+
async run(args, context) {
|
|
97
|
+
// Argument shape FIRST, before the config is resolved and before anything is
|
|
98
|
+
// sent: a malformed call is the one failure the agent can fix unaided, and
|
|
99
|
+
// it must not cost a write attempt to discover.
|
|
100
|
+
const fullName = requireString(args["full_name"], "full_name");
|
|
101
|
+
const api = requireUserApiConfig(context.config);
|
|
102
|
+
const registration = await postJsonObject(api, "/api/v1/repositories", { github_full_name: fullName }, context.fetch);
|
|
103
|
+
// Passed through exactly as `list_repositories` passes its listing through.
|
|
104
|
+
// It matters more here: `api_key.token` exists nowhere else, so any reshaping
|
|
105
|
+
// on this hop is a value that cannot be recovered rather than a field that
|
|
106
|
+
// can be re-fetched.
|
|
107
|
+
return {
|
|
108
|
+
text: JSON.stringify(registration, null, 2),
|
|
109
|
+
structured: registration,
|
|
110
|
+
};
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
export default addRepository;
|
|
114
|
+
//# sourceMappingURL=add-repository.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"add-repository.js","sourceRoot":"","sources":["../../../src/tools/add-repository.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AACnF,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAG1C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AACH,MAAM,aAAa,GAAmB;IACpC,IAAI,EAAE,gBAAgB;IACtB,KAAK,EAAE,gBAAgB;IACvB,WAAW,EACT,4FAA4F;QAC5F,mEAAmE;QACnE,gGAAgG;QAChG,2CAA2C;QAC3C,mFAAmF;QACnF,mFAAmF;QACnF,gGAAgG;QAChG,2FAA2F;QAC3F,SAAS;QACT,6FAA6F;QAC7F,8FAA8F;QAC9F,yFAAyF;QACzF,sFAAsF;QACtF,6FAA6F;QAC7F,gGAAgG;QAChG,yFAAyF;QACzF,6FAA6F;QAC7F,+DAA+D;QAC/D,6FAA6F;QAC7F,mFAAmF;IACrF,WAAW,EAAE;QACX,IAAI,EAAE,QAAQ;QACd,UAAU,EAAE;YACV,SAAS,EAAE;gBACT,IAAI,EAAE,QAAQ;gBACd,WAAW,EACT,oFAAoF;oBACpF,gFAAgF;oBAChF,8EAA8E;aACjF;SACF;QACD,QAAQ,EAAE,CAAC,WAAW,CAAC;QACvB,yEAAyE;QACzE,6EAA6E;QAC7E,4EAA4E;QAC5E,qEAAqE;QACrE,iEAAiE;QACjE,oBAAoB,EAAE,KAAK;KAC5B;IAED,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO;QACrB,6EAA6E;QAC7E,2EAA2E;QAC3E,gDAAgD;QAChD,MAAM,QAAQ,GAAG,aAAa,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,WAAW,CAAC,CAAC;QAE/D,MAAM,GAAG,GAAG,oBAAoB,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QAEjD,MAAM,YAAY,GAAG,MAAM,cAAc,CACvC,GAAG,EACH,sBAAsB,EACtB,EAAE,gBAAgB,EAAE,QAAQ,EAAE,EAC9B,OAAO,CAAC,KAAK,CACd,CAAC;QAEF,4EAA4E;QAC5E,8EAA8E;QAC9E,2EAA2E;QAC3E,qBAAqB;QACrB,OAAO;YACL,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC;YAC3C,UAAU,EAAE,YAAY;SACzB,CAAC;IACJ,CAAC;CACF,CAAC;AAEF,eAAe,aAAa,CAAC"}
|
package/dist/src/tools/args.d.ts
CHANGED
|
@@ -45,4 +45,24 @@
|
|
|
45
45
|
* A non-blank string, or nothing.
|
|
46
46
|
*/
|
|
47
47
|
export declare function optionalString(value: unknown, field: string): string | undefined;
|
|
48
|
+
/**
|
|
49
|
+
* A non-blank string, and nothing else will do.
|
|
50
|
+
*
|
|
51
|
+
* The mandatory counterpart of `optionalString`, and it belongs here for the
|
|
52
|
+
* reason stated above rather than in the first tool that needs one: "is it
|
|
53
|
+
* present and a non-blank string" is a check about the SHAPE of a value, whose
|
|
54
|
+
* failure is always `ArgumentError`. Hand-rolling it inside a tool is the exact
|
|
55
|
+
* copy-paste this file was created to end — and the copy would have to re-pick
|
|
56
|
+
* the error class, which is the one thing the two `optionalString` copies got
|
|
57
|
+
* wrong.
|
|
58
|
+
*
|
|
59
|
+
* Trimmed like its optional sibling, and for the same reason: a value an agent
|
|
60
|
+
* produced by concatenating strings arrives with whitespace that is not part of
|
|
61
|
+
* what it meant to send.
|
|
62
|
+
*
|
|
63
|
+
* The two refusals are separate sentences on purpose. "You sent a number" and
|
|
64
|
+
* "you sent nothing" are different mistakes with different fixes, and a single
|
|
65
|
+
* message covering both would leave the agent to work out which it made.
|
|
66
|
+
*/
|
|
67
|
+
export declare function requireString(value: unknown, field: string): string;
|
|
48
68
|
export declare function optionalBoolean(value: unknown, field: string): boolean | undefined;
|
package/dist/src/tools/args.js
CHANGED
|
@@ -56,6 +56,36 @@ export function optionalString(value, field) {
|
|
|
56
56
|
const trimmed = value.trim();
|
|
57
57
|
return trimmed === "" ? undefined : trimmed;
|
|
58
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* A non-blank string, and nothing else will do.
|
|
61
|
+
*
|
|
62
|
+
* The mandatory counterpart of `optionalString`, and it belongs here for the
|
|
63
|
+
* reason stated above rather than in the first tool that needs one: "is it
|
|
64
|
+
* present and a non-blank string" is a check about the SHAPE of a value, whose
|
|
65
|
+
* failure is always `ArgumentError`. Hand-rolling it inside a tool is the exact
|
|
66
|
+
* copy-paste this file was created to end — and the copy would have to re-pick
|
|
67
|
+
* the error class, which is the one thing the two `optionalString` copies got
|
|
68
|
+
* wrong.
|
|
69
|
+
*
|
|
70
|
+
* Trimmed like its optional sibling, and for the same reason: a value an agent
|
|
71
|
+
* produced by concatenating strings arrives with whitespace that is not part of
|
|
72
|
+
* what it meant to send.
|
|
73
|
+
*
|
|
74
|
+
* The two refusals are separate sentences on purpose. "You sent a number" and
|
|
75
|
+
* "you sent nothing" are different mistakes with different fixes, and a single
|
|
76
|
+
* message covering both would leave the agent to work out which it made.
|
|
77
|
+
*/
|
|
78
|
+
export function requireString(value, field) {
|
|
79
|
+
if (value === undefined || value === null) {
|
|
80
|
+
throw new ArgumentError(`\`${field}\` is required.`);
|
|
81
|
+
}
|
|
82
|
+
if (typeof value !== "string")
|
|
83
|
+
throw new ArgumentError(`\`${field}\` must be a string.`);
|
|
84
|
+
const trimmed = value.trim();
|
|
85
|
+
if (trimmed === "")
|
|
86
|
+
throw new ArgumentError(`\`${field}\` must not be blank.`);
|
|
87
|
+
return trimmed;
|
|
88
|
+
}
|
|
59
89
|
export function optionalBoolean(value, field) {
|
|
60
90
|
if (value === undefined || value === null)
|
|
61
91
|
return undefined;
|
|
@@ -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,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":"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"}
|
|
@@ -20,6 +20,18 @@ import type { ToolDefinition } from "./types.js";
|
|
|
20
20
|
* the two halves of the surface — one local subprocess, one authenticated HTTP
|
|
21
21
|
* call — so the shape is proven on both kinds of capability rather than on one.
|
|
22
22
|
*
|
|
23
|
+
* == The third: the first tool that reads the OTHER credential
|
|
24
|
+
*
|
|
25
|
+
* - `list_repositories` wraps `GET /api/v1/repositories` (shipped:
|
|
26
|
+
* `specguard/config/routes.rb`, `Api::V1::UserRepositoriesController`).
|
|
27
|
+
*
|
|
28
|
+
* It is the only tool here that answers to an `sgu_` USER key rather than an
|
|
29
|
+
* `sgk_` repository key, and SpecGuard refuses each credential in the other's
|
|
30
|
+
* place before it reads a table. So this entry is also what proves the second
|
|
31
|
+
* variable, the second `require*` helper and the credential-aware diagnostics
|
|
32
|
+
* work end to end — one tool, over a real endpoint, rather than a seam nothing
|
|
33
|
+
* exercises.
|
|
34
|
+
*
|
|
23
35
|
* == What is deliberately absent
|
|
24
36
|
*
|
|
25
37
|
* `/check-intent` and duplicate clustering are NOT here and must not be added
|
|
@@ -28,6 +40,27 @@ import type { ToolDefinition } from "./types.js";
|
|
|
28
40
|
* endpoint that does not exist would produce a server that discovers cleanly
|
|
29
41
|
* and fails on use, which is worse than not offering the tool, because the
|
|
30
42
|
* agent has already committed to a plan by the time it finds out.
|
|
43
|
+
*
|
|
44
|
+
* == The fourth: the first tool that WRITES
|
|
45
|
+
*
|
|
46
|
+
* - `add_repository` wraps `POST /api/v1/repositories` (shipped:
|
|
47
|
+
* `specguard/config/routes.rb`, `Api::V1::UserRepositoriesController#create`).
|
|
48
|
+
*
|
|
49
|
+
* The endpoint had shipped for a while; what this bridge lacked was a way to
|
|
50
|
+
* CALL it — `support/specguard-api.ts` offered only `getJson`, which hardcoded
|
|
51
|
+
* `method: "GET"` and took no body. The reservation recorded here was that the
|
|
52
|
+
* write transport should land WITH the first write tool, designed against a real
|
|
53
|
+
* request body and a real 4xx surface rather than invented for a caller that did
|
|
54
|
+
* not exist. That is what happened: `postJson`/`postJsonObject` arrived with
|
|
55
|
+
* this entry, sharing `fetchWithTimeout` with the read path rather than standing
|
|
56
|
+
* beside it, and `describeFailure` grew the `400` branch that surfaces
|
|
57
|
+
* SpecGuard's own refusal sentence — the modal answer this endpoint gives.
|
|
58
|
+
*
|
|
59
|
+
* The standing rule is unchanged and still binding, which is what keeps the rest
|
|
60
|
+
* of the user-scoped write surface out: `DELETE /api/v1/repositories/:id` and
|
|
61
|
+
* the API-key endpoints (SPGD-754) are NOT on `origin/main`, so they may not be
|
|
62
|
+
* wrapped here however useful a tool for them would be. What moved was the
|
|
63
|
+
* platform, not the bar.
|
|
31
64
|
*/
|
|
32
65
|
export declare const TOOLS: readonly ToolDefinition[];
|
|
33
66
|
export type { ToolContext, ToolDefinition, ToolResult } from "./types.js";
|
package/dist/src/tools/index.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import addRepository from "./add-repository.js";
|
|
1
2
|
import lintIntentAnnotations from "./lint-intent-annotations.js";
|
|
3
|
+
import listRepositories from "./list-repositories.js";
|
|
2
4
|
import getRepositoryOverview from "./repository-overview.js";
|
|
3
5
|
/**
|
|
4
6
|
* THE REGISTRY — the one file that changes when the toolset grows.
|
|
@@ -21,6 +23,18 @@ import getRepositoryOverview from "./repository-overview.js";
|
|
|
21
23
|
* the two halves of the surface — one local subprocess, one authenticated HTTP
|
|
22
24
|
* call — so the shape is proven on both kinds of capability rather than on one.
|
|
23
25
|
*
|
|
26
|
+
* == The third: the first tool that reads the OTHER credential
|
|
27
|
+
*
|
|
28
|
+
* - `list_repositories` wraps `GET /api/v1/repositories` (shipped:
|
|
29
|
+
* `specguard/config/routes.rb`, `Api::V1::UserRepositoriesController`).
|
|
30
|
+
*
|
|
31
|
+
* It is the only tool here that answers to an `sgu_` USER key rather than an
|
|
32
|
+
* `sgk_` repository key, and SpecGuard refuses each credential in the other's
|
|
33
|
+
* place before it reads a table. So this entry is also what proves the second
|
|
34
|
+
* variable, the second `require*` helper and the credential-aware diagnostics
|
|
35
|
+
* work end to end — one tool, over a real endpoint, rather than a seam nothing
|
|
36
|
+
* exercises.
|
|
37
|
+
*
|
|
24
38
|
* == What is deliberately absent
|
|
25
39
|
*
|
|
26
40
|
* `/check-intent` and duplicate clustering are NOT here and must not be added
|
|
@@ -29,6 +43,32 @@ import getRepositoryOverview from "./repository-overview.js";
|
|
|
29
43
|
* endpoint that does not exist would produce a server that discovers cleanly
|
|
30
44
|
* and fails on use, which is worse than not offering the tool, because the
|
|
31
45
|
* agent has already committed to a plan by the time it finds out.
|
|
46
|
+
*
|
|
47
|
+
* == The fourth: the first tool that WRITES
|
|
48
|
+
*
|
|
49
|
+
* - `add_repository` wraps `POST /api/v1/repositories` (shipped:
|
|
50
|
+
* `specguard/config/routes.rb`, `Api::V1::UserRepositoriesController#create`).
|
|
51
|
+
*
|
|
52
|
+
* The endpoint had shipped for a while; what this bridge lacked was a way to
|
|
53
|
+
* CALL it — `support/specguard-api.ts` offered only `getJson`, which hardcoded
|
|
54
|
+
* `method: "GET"` and took no body. The reservation recorded here was that the
|
|
55
|
+
* write transport should land WITH the first write tool, designed against a real
|
|
56
|
+
* request body and a real 4xx surface rather than invented for a caller that did
|
|
57
|
+
* not exist. That is what happened: `postJson`/`postJsonObject` arrived with
|
|
58
|
+
* this entry, sharing `fetchWithTimeout` with the read path rather than standing
|
|
59
|
+
* beside it, and `describeFailure` grew the `400` branch that surfaces
|
|
60
|
+
* SpecGuard's own refusal sentence — the modal answer this endpoint gives.
|
|
61
|
+
*
|
|
62
|
+
* The standing rule is unchanged and still binding, which is what keeps the rest
|
|
63
|
+
* of the user-scoped write surface out: `DELETE /api/v1/repositories/:id` and
|
|
64
|
+
* the API-key endpoints (SPGD-754) are NOT on `origin/main`, so they may not be
|
|
65
|
+
* wrapped here however useful a tool for them would be. What moved was the
|
|
66
|
+
* platform, not the bar.
|
|
32
67
|
*/
|
|
33
|
-
export const TOOLS = [
|
|
68
|
+
export const TOOLS = [
|
|
69
|
+
lintIntentAnnotations,
|
|
70
|
+
getRepositoryOverview,
|
|
71
|
+
listRepositories,
|
|
72
|
+
addRepository,
|
|
73
|
+
];
|
|
34
74
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/tools/index.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,MAAM,8BAA8B,CAAC;AACjE,OAAO,qBAAqB,MAAM,0BAA0B,CAAC;AAG7D
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/tools/index.ts"],"names":[],"mappings":"AAAA,OAAO,aAAa,MAAM,qBAAqB,CAAC;AAChD,OAAO,qBAAqB,MAAM,8BAA8B,CAAC;AACjE,OAAO,gBAAgB,MAAM,wBAAwB,CAAC;AACtD,OAAO,qBAAqB,MAAM,0BAA0B,CAAC;AAG7D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8DG;AACH,MAAM,CAAC,MAAM,KAAK,GAA8B;IAC9C,qBAAqB;IACrB,qBAAqB;IACrB,gBAAgB;IAChB,aAAa;CACd,CAAC"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { ToolDefinition } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* `GET /api/v1/repositories` as a tool — shipped today in the platform
|
|
4
|
+
* (`specguard/config/routes.rb`, `Api::V1::UserRepositoriesController#index`).
|
|
5
|
+
*
|
|
6
|
+
* == Why this is the first user-scoped tool, and for now the only one
|
|
7
|
+
*
|
|
8
|
+
* Every other tool here answers about ONE repository the caller has already
|
|
9
|
+
* named — and a bridge that can only answer about a repository you can already
|
|
10
|
+
* name cannot tell an agent which repositories there ARE. `get_repository_overview`
|
|
11
|
+
* does not take a repository: its `sgk_` key IS the repository, so the question
|
|
12
|
+
* "what may I ask about" has no answer anywhere in this server. This is that
|
|
13
|
+
* answer, and it is the whole of what this tool does.
|
|
14
|
+
*
|
|
15
|
+
* It is also the tool that proves the second credential slot works end to end,
|
|
16
|
+
* which is why it shipped alone. It no longer IS alone: `add_repository`
|
|
17
|
+
* (SPGD-764) reads the same `sgu_` key and wraps `POST /api/v1/repositories`,
|
|
18
|
+
* which arrived once this bridge had a write transport to call it with —
|
|
19
|
+
* `postJson`/`postJsonObject` in `support/specguard-api.ts`, landed with that
|
|
20
|
+
* tool and designed against a real request body and a real 4xx surface, exactly
|
|
21
|
+
* as the reservation recorded here asked. The registry's standing rule
|
|
22
|
+
* (`tools/index.ts`) is unchanged and still binding: a tool in `tools/list` is a
|
|
23
|
+
* promise an agent acts on, so the REST of the user-scoped surface — removing a
|
|
24
|
+
* repository, minting or revoking keys — stays out until those endpoints ship.
|
|
25
|
+
*
|
|
26
|
+
* What this tool still uniquely answers is the question above: which
|
|
27
|
+
* repositories there ARE. `add_repository` extends that surface rather than
|
|
28
|
+
* replacing it, and the two share a handle — the `full_name` reported here is
|
|
29
|
+
* the `full_name` that one takes.
|
|
30
|
+
*
|
|
31
|
+
* == It reads the OTHER key, and that is the point
|
|
32
|
+
*
|
|
33
|
+
* `Api::BaseController` decides which credential table to consult from the
|
|
34
|
+
* token's PREFIX, before any table is read, and answers 401 on a mismatch
|
|
35
|
+
* without a lookup — so this endpoint refuses the `sgk_` key
|
|
36
|
+
* `get_repository_overview` uses, and that one refuses this key. Hence
|
|
37
|
+
* `requireUserApiConfig` rather than `requireApiConfig`: the two are the same
|
|
38
|
+
* function over different variables, and the `Credential` each carries is what
|
|
39
|
+
* makes a 401 or an unset variable name the one the OPERATOR of this tool has
|
|
40
|
+
* to go and fix. See `config.ts`.
|
|
41
|
+
*
|
|
42
|
+
* == No arguments, because the credential is the whole question
|
|
43
|
+
*
|
|
44
|
+
* The endpoint takes no parameters: the person the `sgu_` key speaks for is the
|
|
45
|
+
* entire scope of the answer, and `Repository.accessible_by` — owned UNION
|
|
46
|
+
* shared-through-a-membership — is the platform's read-side boundary rather
|
|
47
|
+
* than a filter this bridge could widen or narrow. A repository the person
|
|
48
|
+
* neither owns nor is a member of never enters the response, so there is
|
|
49
|
+
* nothing here for a parameter to select and nothing an argument could reach.
|
|
50
|
+
*
|
|
51
|
+
* == The response is passed through, not re-modelled
|
|
52
|
+
*
|
|
53
|
+
* Same rule as every other tool here (`types.ts`: "A thin client that reshapes
|
|
54
|
+
* its upstream is not thin"), and it has real content on this body. The
|
|
55
|
+
* controller serves each entry as `id`, `full_name`, `name`, `registered_at`
|
|
56
|
+
* and `role`, and says why: the first four are DELIBERATELY the same four
|
|
57
|
+
* fields, under the same names, that `GET /api/v1/repository` serves in its own
|
|
58
|
+
* `repository` block, so a client that has read one knows how to read the other.
|
|
59
|
+
* Renaming or flattening anything here would spend that parity on the last hop.
|
|
60
|
+
*
|
|
61
|
+
* `role` is the field this surface adds — `"owner"` or `"member"` — because the
|
|
62
|
+
* list MIXES repositories the person owns with repositories somebody shared
|
|
63
|
+
* with them and no other field separates the two. An agent that will later
|
|
64
|
+
* register keys or change settings needs to know which of these it may expect
|
|
65
|
+
* to administer, so the value is named in the description rather than left to be
|
|
66
|
+
* discovered from the data.
|
|
67
|
+
*
|
|
68
|
+
* The order is `full_name` ascending, which the controller picks as the only
|
|
69
|
+
* column a client can page or diff against without SpecGuard promising an id
|
|
70
|
+
* ordering it has not designed. It is stated here for the same reason the other
|
|
71
|
+
* tool states its orders: a list whose order is a coincidence and a list whose
|
|
72
|
+
* order is a contract look identical in a response body.
|
|
73
|
+
*/
|
|
74
|
+
declare const listRepositories: ToolDefinition;
|
|
75
|
+
export default listRepositories;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { getJsonObject, requireUserApiConfig } from "../support/specguard-api.js";
|
|
2
|
+
/**
|
|
3
|
+
* `GET /api/v1/repositories` as a tool — shipped today in the platform
|
|
4
|
+
* (`specguard/config/routes.rb`, `Api::V1::UserRepositoriesController#index`).
|
|
5
|
+
*
|
|
6
|
+
* == Why this is the first user-scoped tool, and for now the only one
|
|
7
|
+
*
|
|
8
|
+
* Every other tool here answers about ONE repository the caller has already
|
|
9
|
+
* named — and a bridge that can only answer about a repository you can already
|
|
10
|
+
* name cannot tell an agent which repositories there ARE. `get_repository_overview`
|
|
11
|
+
* does not take a repository: its `sgk_` key IS the repository, so the question
|
|
12
|
+
* "what may I ask about" has no answer anywhere in this server. This is that
|
|
13
|
+
* answer, and it is the whole of what this tool does.
|
|
14
|
+
*
|
|
15
|
+
* It is also the tool that proves the second credential slot works end to end,
|
|
16
|
+
* which is why it shipped alone. It no longer IS alone: `add_repository`
|
|
17
|
+
* (SPGD-764) reads the same `sgu_` key and wraps `POST /api/v1/repositories`,
|
|
18
|
+
* which arrived once this bridge had a write transport to call it with —
|
|
19
|
+
* `postJson`/`postJsonObject` in `support/specguard-api.ts`, landed with that
|
|
20
|
+
* tool and designed against a real request body and a real 4xx surface, exactly
|
|
21
|
+
* as the reservation recorded here asked. The registry's standing rule
|
|
22
|
+
* (`tools/index.ts`) is unchanged and still binding: a tool in `tools/list` is a
|
|
23
|
+
* promise an agent acts on, so the REST of the user-scoped surface — removing a
|
|
24
|
+
* repository, minting or revoking keys — stays out until those endpoints ship.
|
|
25
|
+
*
|
|
26
|
+
* What this tool still uniquely answers is the question above: which
|
|
27
|
+
* repositories there ARE. `add_repository` extends that surface rather than
|
|
28
|
+
* replacing it, and the two share a handle — the `full_name` reported here is
|
|
29
|
+
* the `full_name` that one takes.
|
|
30
|
+
*
|
|
31
|
+
* == It reads the OTHER key, and that is the point
|
|
32
|
+
*
|
|
33
|
+
* `Api::BaseController` decides which credential table to consult from the
|
|
34
|
+
* token's PREFIX, before any table is read, and answers 401 on a mismatch
|
|
35
|
+
* without a lookup — so this endpoint refuses the `sgk_` key
|
|
36
|
+
* `get_repository_overview` uses, and that one refuses this key. Hence
|
|
37
|
+
* `requireUserApiConfig` rather than `requireApiConfig`: the two are the same
|
|
38
|
+
* function over different variables, and the `Credential` each carries is what
|
|
39
|
+
* makes a 401 or an unset variable name the one the OPERATOR of this tool has
|
|
40
|
+
* to go and fix. See `config.ts`.
|
|
41
|
+
*
|
|
42
|
+
* == No arguments, because the credential is the whole question
|
|
43
|
+
*
|
|
44
|
+
* The endpoint takes no parameters: the person the `sgu_` key speaks for is the
|
|
45
|
+
* entire scope of the answer, and `Repository.accessible_by` — owned UNION
|
|
46
|
+
* shared-through-a-membership — is the platform's read-side boundary rather
|
|
47
|
+
* than a filter this bridge could widen or narrow. A repository the person
|
|
48
|
+
* neither owns nor is a member of never enters the response, so there is
|
|
49
|
+
* nothing here for a parameter to select and nothing an argument could reach.
|
|
50
|
+
*
|
|
51
|
+
* == The response is passed through, not re-modelled
|
|
52
|
+
*
|
|
53
|
+
* Same rule as every other tool here (`types.ts`: "A thin client that reshapes
|
|
54
|
+
* its upstream is not thin"), and it has real content on this body. The
|
|
55
|
+
* controller serves each entry as `id`, `full_name`, `name`, `registered_at`
|
|
56
|
+
* and `role`, and says why: the first four are DELIBERATELY the same four
|
|
57
|
+
* fields, under the same names, that `GET /api/v1/repository` serves in its own
|
|
58
|
+
* `repository` block, so a client that has read one knows how to read the other.
|
|
59
|
+
* Renaming or flattening anything here would spend that parity on the last hop.
|
|
60
|
+
*
|
|
61
|
+
* `role` is the field this surface adds — `"owner"` or `"member"` — because the
|
|
62
|
+
* list MIXES repositories the person owns with repositories somebody shared
|
|
63
|
+
* with them and no other field separates the two. An agent that will later
|
|
64
|
+
* register keys or change settings needs to know which of these it may expect
|
|
65
|
+
* to administer, so the value is named in the description rather than left to be
|
|
66
|
+
* discovered from the data.
|
|
67
|
+
*
|
|
68
|
+
* The order is `full_name` ascending, which the controller picks as the only
|
|
69
|
+
* column a client can page or diff against without SpecGuard promising an id
|
|
70
|
+
* ordering it has not designed. It is stated here for the same reason the other
|
|
71
|
+
* tool states its orders: a list whose order is a coincidence and a list whose
|
|
72
|
+
* order is a contract look identical in a response body.
|
|
73
|
+
*/
|
|
74
|
+
const listRepositories = {
|
|
75
|
+
name: "list_repositories",
|
|
76
|
+
title: "List repositories",
|
|
77
|
+
description: "Lists the SpecGuard repositories the person behind this server's user API key may open — " +
|
|
78
|
+
"the answer to \"what can I ask about\", which no other tool here can give, because every " +
|
|
79
|
+
"other tool is already scoped to one repository by its key. " +
|
|
80
|
+
"Each entry carries `id`, `full_name` (`org/repo`, and the handle every other surface names " +
|
|
81
|
+
"a repository by), `name`, `registered_at` and `role`. " +
|
|
82
|
+
"`role` is `owner` or `member`: the list mixes repositories this person owns with " +
|
|
83
|
+
"repositories somebody shared with them, and nothing else distinguishes the two — read it " +
|
|
84
|
+
"before assuming a repository is yours to administer. " +
|
|
85
|
+
"Ordered by `full_name` ascending, which is stable across calls. " +
|
|
86
|
+
"The set is exactly what this person may see — a repository they neither own nor were given " +
|
|
87
|
+
"access to is absent rather than filtered, so an empty list means no access, never an error. " +
|
|
88
|
+
"Needs SPECGUARD_USER_API_KEY (an sgu_… key), which is a DIFFERENT credential from the " +
|
|
89
|
+
"sgk_… repository key get_repository_overview reads; SpecGuard refuses each in the other's " +
|
|
90
|
+
"place.",
|
|
91
|
+
inputSchema: {
|
|
92
|
+
type: "object",
|
|
93
|
+
// No properties, deliberately — see this file's header. Still CLOSED rather
|
|
94
|
+
// than merely empty: `additionalProperties: false` is advertised in
|
|
95
|
+
// `tools/list`, so a client that honours the schema REJECTS an invented
|
|
96
|
+
// argument before the call is made. Nothing on this side refuses it —
|
|
97
|
+
// `server.ts` forwards `arguments` unvalidated and `run` ignores them — so
|
|
98
|
+
// an open schema would have the argument silently dropped and the call
|
|
99
|
+
// answered as if it had been honoured.
|
|
100
|
+
additionalProperties: false,
|
|
101
|
+
},
|
|
102
|
+
async run(_args, context) {
|
|
103
|
+
const api = requireUserApiConfig(context.config);
|
|
104
|
+
const listing = await getJsonObject(api, "/api/v1/repositories", {}, context.fetch);
|
|
105
|
+
return {
|
|
106
|
+
text: JSON.stringify(listing, null, 2),
|
|
107
|
+
structured: listing,
|
|
108
|
+
};
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
export default listRepositories;
|
|
112
|
+
//# sourceMappingURL=list-repositories.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list-repositories.js","sourceRoot":"","sources":["../../../src/tools/list-repositories.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AAGlF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuEG;AACH,MAAM,gBAAgB,GAAmB;IACvC,IAAI,EAAE,mBAAmB;IACzB,KAAK,EAAE,mBAAmB;IAC1B,WAAW,EACT,2FAA2F;QAC3F,2FAA2F;QAC3F,6DAA6D;QAC7D,6FAA6F;QAC7F,wDAAwD;QACxD,mFAAmF;QACnF,2FAA2F;QAC3F,uDAAuD;QACvD,kEAAkE;QAClE,6FAA6F;QAC7F,8FAA8F;QAC9F,wFAAwF;QACxF,4FAA4F;QAC5F,QAAQ;IACV,WAAW,EAAE;QACX,IAAI,EAAE,QAAQ;QACd,4EAA4E;QAC5E,oEAAoE;QACpE,wEAAwE;QACxE,sEAAsE;QACtE,2EAA2E;QAC3E,uEAAuE;QACvE,uCAAuC;QACvC,oBAAoB,EAAE,KAAK;KAC5B;IAED,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,OAAO;QACtB,MAAM,GAAG,GAAG,oBAAoB,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QAEjD,MAAM,OAAO,GAAG,MAAM,aAAa,CAAC,GAAG,EAAE,sBAAsB,EAAE,EAAE,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;QAEpF,OAAO;YACL,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;YACtC,UAAU,EAAE,OAAO;SACpB,CAAC;IACJ,CAAC;CACF,CAAC;AAEF,eAAe,gBAAgB,CAAC"}
|