@descryy/mcp 0.1.0
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/LICENSE +21 -0
- package/dist/action-handshake.d.ts +86 -0
- package/dist/action-handshake.d.ts.map +1 -0
- package/dist/action-handshake.js +144 -0
- package/dist/action-handshake.js.map +1 -0
- package/dist/bin/descry-mcp.d.ts +18 -0
- package/dist/bin/descry-mcp.d.ts.map +1 -0
- package/dist/bin/descry-mcp.js +54 -0
- package/dist/bin/descry-mcp.js.map +1 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/language-guess.d.ts +17 -0
- package/dist/language-guess.d.ts.map +1 -0
- package/dist/language-guess.js +43 -0
- package/dist/language-guess.js.map +1 -0
- package/dist/protocol.d.ts +91 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +127 -0
- package/dist/protocol.js.map +1 -0
- package/dist/registry.d.ts +72 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +136 -0
- package/dist/registry.js.map +1 -0
- package/dist/render.d.ts +275 -0
- package/dist/render.d.ts.map +1 -0
- package/dist/render.js +333 -0
- package/dist/render.js.map +1 -0
- package/dist/server.d.ts +42 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +263 -0
- package/dist/server.js.map +1 -0
- package/dist/session.d.ts +164 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +302 -0
- package/dist/session.js.map +1 -0
- package/dist/tools/analyze.d.ts +124 -0
- package/dist/tools/analyze.d.ts.map +1 -0
- package/dist/tools/analyze.js +411 -0
- package/dist/tools/analyze.js.map +1 -0
- package/dist/tools/contracts.d.ts +105 -0
- package/dist/tools/contracts.d.ts.map +1 -0
- package/dist/tools/contracts.js +141 -0
- package/dist/tools/contracts.js.map +1 -0
- package/dist/tools/cross-pr.d.ts +137 -0
- package/dist/tools/cross-pr.d.ts.map +1 -0
- package/dist/tools/cross-pr.js +216 -0
- package/dist/tools/cross-pr.js.map +1 -0
- package/dist/tools/git-diff.d.ts +29 -0
- package/dist/tools/git-diff.d.ts.map +1 -0
- package/dist/tools/git-diff.js +117 -0
- package/dist/tools/git-diff.js.map +1 -0
- package/dist/tools/git-history.d.ts +27 -0
- package/dist/tools/git-history.d.ts.map +1 -0
- package/dist/tools/git-history.js +91 -0
- package/dist/tools/git-history.js.map +1 -0
- package/dist/tools/history.d.ts +63 -0
- package/dist/tools/history.d.ts.map +1 -0
- package/dist/tools/history.js +114 -0
- package/dist/tools/history.js.map +1 -0
- package/dist/tools/impact.d.ts +81 -0
- package/dist/tools/impact.d.ts.map +1 -0
- package/dist/tools/impact.js +229 -0
- package/dist/tools/impact.js.map +1 -0
- package/dist/tools/index.d.ts +33 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +47 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/kit.d.ts +183 -0
- package/dist/tools/kit.d.ts.map +1 -0
- package/dist/tools/kit.js +117 -0
- package/dist/tools/kit.js.map +1 -0
- package/dist/tools/link-workspace.d.ts +62 -0
- package/dist/tools/link-workspace.d.ts.map +1 -0
- package/dist/tools/link-workspace.js +156 -0
- package/dist/tools/link-workspace.js.map +1 -0
- package/dist/tools/lookup.d.ts +29 -0
- package/dist/tools/lookup.d.ts.map +1 -0
- package/dist/tools/lookup.js +73 -0
- package/dist/tools/lookup.js.map +1 -0
- package/dist/tools/pr-analysis.d.ts +89 -0
- package/dist/tools/pr-analysis.d.ts.map +1 -0
- package/dist/tools/pr-analysis.js +169 -0
- package/dist/tools/pr-analysis.js.map +1 -0
- package/dist/tools/propagation.d.ts +74 -0
- package/dist/tools/propagation.d.ts.map +1 -0
- package/dist/tools/propagation.js +194 -0
- package/dist/tools/propagation.js.map +1 -0
- package/dist/tools/questions.d.ts +80 -0
- package/dist/tools/questions.d.ts.map +1 -0
- package/dist/tools/questions.js +213 -0
- package/dist/tools/questions.js.map +1 -0
- package/dist/tools/refusal-fetch.d.ts +71 -0
- package/dist/tools/refusal-fetch.d.ts.map +1 -0
- package/dist/tools/refusal-fetch.js +137 -0
- package/dist/tools/refusal-fetch.js.map +1 -0
- package/dist/tools/scope.d.ts +60 -0
- package/dist/tools/scope.d.ts.map +1 -0
- package/dist/tools/scope.js +160 -0
- package/dist/tools/scope.js.map +1 -0
- package/dist/tools/similar-incidents.d.ts +105 -0
- package/dist/tools/similar-incidents.d.ts.map +1 -0
- package/dist/tools/similar-incidents.js +228 -0
- package/dist/tools/similar-incidents.js.map +1 -0
- package/dist/tools/validate.d.ts +103 -0
- package/dist/tools/validate.d.ts.map +1 -0
- package/dist/tools/validate.js +176 -0
- package/dist/tools/validate.js.map +1 -0
- package/dist/tools/verification-status.d.ts +68 -0
- package/dist/tools/verification-status.d.ts.map +1 -0
- package/dist/tools/verification-status.js +179 -0
- package/dist/tools/verification-status.js.map +1 -0
- package/dist/transport.d.ts +58 -0
- package/dist/transport.d.ts.map +1 -0
- package/dist/transport.js +115 -0
- package/dist/transport.js.map +1 -0
- package/package.json +33 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `refusal_fetch` — the fourteenth endpoint (`ai-layer-plan.md` §3), and the
|
|
3
|
+
* new work that endpoint's own re-derivation pointed at. §4 technique 4:
|
|
4
|
+
* *"Refusals summarised, never enumerated. A count, one verbatim exemplar, a
|
|
5
|
+
* handle to fetch the rest."*
|
|
6
|
+
*
|
|
7
|
+
* The logic underneath — `summariseRefusals`/`fetchRefusalPage`
|
|
8
|
+
* (`@descryy/core`'s `query/refusal-fetch.ts`) — is already built and
|
|
9
|
+
* unit-tested; this is the tool boundary its own header names as still open:
|
|
10
|
+
* *"An MCP surface that wants a prettier, shorter token... is free to wrap
|
|
11
|
+
* this — that is a presentation choice for P27."* No wrapping was needed —
|
|
12
|
+
* the handle is already opaque and self-describing — so this tool is thin:
|
|
13
|
+
* read the population, hand it to whichever of the two functions the call
|
|
14
|
+
* shape asks for.
|
|
15
|
+
*
|
|
16
|
+
* ## Two calls, one tool — same pattern `questions.ts` already uses
|
|
17
|
+
*
|
|
18
|
+
* No `handle` argument → a fresh summary (`summariseRefusals`): cheap, one
|
|
19
|
+
* count, one exemplar, a handle. A `handle` argument → redeem it for a real
|
|
20
|
+
* page (`fetchRefusalPage`). This mirrors `questions.ts`'s own "no `answer`
|
|
21
|
+
* argument is a pure read" split rather than inventing a second shape.
|
|
22
|
+
*
|
|
23
|
+
* ## The call-site trap this tool must not reproduce
|
|
24
|
+
*
|
|
25
|
+
* `refusal-fetch.ts`'s own header names it explicitly: the store reader
|
|
26
|
+
* defaults to `classified: true`, which silently drops every unclassified
|
|
27
|
+
* row (DEC-243's grouper only wants the classifiable subset). This tool
|
|
28
|
+
* always asks for `{ classified: false }` — the complete ledger, classified
|
|
29
|
+
* and unclassified alike — because a `refusal_fetch` that only showed the
|
|
30
|
+
* askable subset would be the empty-denominator defect wearing this
|
|
31
|
+
* technique's clothes, exactly what that module's header warns against.
|
|
32
|
+
*
|
|
33
|
+
* ## No cap on the population read
|
|
34
|
+
*
|
|
35
|
+
* `provider.unresolvedRefs({ classified: false })` is called with no
|
|
36
|
+
* `limit` — the handle's population fingerprint has to be stable between the
|
|
37
|
+
* summary call and every page redemption, and a capped read here would make
|
|
38
|
+
* "the ledger changed" indistinguishable from "the cap sampled differently
|
|
39
|
+
* this time." Correctness over a cheap shortcut, same choice `refusal-fetch.ts`
|
|
40
|
+
* itself already made.
|
|
41
|
+
*/
|
|
42
|
+
import { type ToolDefinition } from "./kit.ts";
|
|
43
|
+
export interface RefusalFetchSummaryItem {
|
|
44
|
+
readonly fromNodeId: string;
|
|
45
|
+
readonly edgeType: string;
|
|
46
|
+
readonly rawTarget: string;
|
|
47
|
+
readonly file: string | null;
|
|
48
|
+
readonly line: number | null;
|
|
49
|
+
readonly producedBy: string;
|
|
50
|
+
readonly reason: string;
|
|
51
|
+
}
|
|
52
|
+
export interface RefusalFetchSummaryData {
|
|
53
|
+
readonly kind: "summary";
|
|
54
|
+
/** The honest denominator — every unresolved row in the graph, classified and unclassified alike. */
|
|
55
|
+
readonly count: number;
|
|
56
|
+
/** One row's `reason`, verbatim — never re-worded (§4 technique 4). `null` only when `count === 0`. */
|
|
57
|
+
readonly exemplar: string | null;
|
|
58
|
+
/** Redeem with a follow-up call passing this as `handle`. `null` only when `count === 0`. */
|
|
59
|
+
readonly handle: string | null;
|
|
60
|
+
}
|
|
61
|
+
export interface RefusalFetchPageData {
|
|
62
|
+
readonly kind: "page";
|
|
63
|
+
readonly items: readonly RefusalFetchSummaryItem[];
|
|
64
|
+
/** Restated on every page — the truncation rule pointed the other way (§5.5). */
|
|
65
|
+
readonly total: number;
|
|
66
|
+
/** Redeem for the next page. `null` on the last page. */
|
|
67
|
+
readonly nextHandle: string | null;
|
|
68
|
+
}
|
|
69
|
+
export type RefusalFetchData = RefusalFetchSummaryData | RefusalFetchPageData;
|
|
70
|
+
export declare const refusalFetchTool: ToolDefinition;
|
|
71
|
+
//# sourceMappingURL=refusal-fetch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"refusal-fetch.d.ts","sourceRoot":"","sources":["../../src/tools/refusal-fetch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAIH,OAAO,EAKL,KAAK,cAAc,EACpB,MAAM,UAAU,CAAC;AAqBlB,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,qGAAqG;IACrG,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,uGAAuG;IACvG,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,6FAA6F;IAC7F,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,SAAS,uBAAuB,EAAE,CAAC;IACnD,iFAAiF;IACjF,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,yDAAyD;IACzD,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CACpC;AAED,MAAM,MAAM,gBAAgB,GAAG,uBAAuB,GAAG,oBAAoB,CAAC;AAiF9E,eAAO,MAAM,gBAAgB,EAAE,cAe9B,CAAC"}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `refusal_fetch` — the fourteenth endpoint (`ai-layer-plan.md` §3), and the
|
|
3
|
+
* new work that endpoint's own re-derivation pointed at. §4 technique 4:
|
|
4
|
+
* *"Refusals summarised, never enumerated. A count, one verbatim exemplar, a
|
|
5
|
+
* handle to fetch the rest."*
|
|
6
|
+
*
|
|
7
|
+
* The logic underneath — `summariseRefusals`/`fetchRefusalPage`
|
|
8
|
+
* (`@descryy/core`'s `query/refusal-fetch.ts`) — is already built and
|
|
9
|
+
* unit-tested; this is the tool boundary its own header names as still open:
|
|
10
|
+
* *"An MCP surface that wants a prettier, shorter token... is free to wrap
|
|
11
|
+
* this — that is a presentation choice for P27."* No wrapping was needed —
|
|
12
|
+
* the handle is already opaque and self-describing — so this tool is thin:
|
|
13
|
+
* read the population, hand it to whichever of the two functions the call
|
|
14
|
+
* shape asks for.
|
|
15
|
+
*
|
|
16
|
+
* ## Two calls, one tool — same pattern `questions.ts` already uses
|
|
17
|
+
*
|
|
18
|
+
* No `handle` argument → a fresh summary (`summariseRefusals`): cheap, one
|
|
19
|
+
* count, one exemplar, a handle. A `handle` argument → redeem it for a real
|
|
20
|
+
* page (`fetchRefusalPage`). This mirrors `questions.ts`'s own "no `answer`
|
|
21
|
+
* argument is a pure read" split rather than inventing a second shape.
|
|
22
|
+
*
|
|
23
|
+
* ## The call-site trap this tool must not reproduce
|
|
24
|
+
*
|
|
25
|
+
* `refusal-fetch.ts`'s own header names it explicitly: the store reader
|
|
26
|
+
* defaults to `classified: true`, which silently drops every unclassified
|
|
27
|
+
* row (DEC-243's grouper only wants the classifiable subset). This tool
|
|
28
|
+
* always asks for `{ classified: false }` — the complete ledger, classified
|
|
29
|
+
* and unclassified alike — because a `refusal_fetch` that only showed the
|
|
30
|
+
* askable subset would be the empty-denominator defect wearing this
|
|
31
|
+
* technique's clothes, exactly what that module's header warns against.
|
|
32
|
+
*
|
|
33
|
+
* ## No cap on the population read
|
|
34
|
+
*
|
|
35
|
+
* `provider.unresolvedRefs({ classified: false })` is called with no
|
|
36
|
+
* `limit` — the handle's population fingerprint has to be stable between the
|
|
37
|
+
* summary call and every page redemption, and a capped read here would make
|
|
38
|
+
* "the ledger changed" indistinguishable from "the cap sampled differently
|
|
39
|
+
* this time." Correctness over a cheap shortcut, same choice `refusal-fetch.ts`
|
|
40
|
+
* itself already made.
|
|
41
|
+
*/
|
|
42
|
+
import { fetchRefusalPage, summariseRefusals } from "@descryy/core";
|
|
43
|
+
import { answer, optionalInteger, optionalString, } from "./kit.js";
|
|
44
|
+
const SCHEMA = {
|
|
45
|
+
type: "object",
|
|
46
|
+
properties: {
|
|
47
|
+
handle: {
|
|
48
|
+
type: "string",
|
|
49
|
+
description: "Redeem a handle from a prior refusal_fetch call to fetch the next page of rows. Omit for " +
|
|
50
|
+
"a fresh summary — count, one exemplar reason, and a handle to start paging.",
|
|
51
|
+
},
|
|
52
|
+
pageSize: {
|
|
53
|
+
type: "integer",
|
|
54
|
+
minimum: 1,
|
|
55
|
+
description: "Rows per page when minting a fresh handle (a summary call). Default 50. Ignored on a paged call — the page size travels inside the handle itself.",
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
additionalProperties: false,
|
|
59
|
+
};
|
|
60
|
+
function brief(ref) {
|
|
61
|
+
return {
|
|
62
|
+
fromNodeId: ref.fromNodeId,
|
|
63
|
+
edgeType: ref.edgeType,
|
|
64
|
+
rawTarget: ref.rawTarget,
|
|
65
|
+
file: ref.file,
|
|
66
|
+
line: ref.line,
|
|
67
|
+
producedBy: ref.producedBy,
|
|
68
|
+
reason: ref.reason,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
async function run(args, ctx) {
|
|
72
|
+
const handle = optionalString(args, "handle");
|
|
73
|
+
const pageSize = optionalInteger(args, "pageSize", 1);
|
|
74
|
+
const base = ctx.session.provider().baseStamp();
|
|
75
|
+
ctx.progress("Reading the unresolved-reference ledger");
|
|
76
|
+
const { refs } = ctx.session.provider().unresolvedRefs({ classified: false });
|
|
77
|
+
const envelopeBase = {
|
|
78
|
+
resolutionFloor: 0, // a ledger read, not a graph traversal claim — same convention as similar_incidents' R0 floor
|
|
79
|
+
commitSha: base.commitSha,
|
|
80
|
+
graphBuiltAt: base.graphBuiltAt,
|
|
81
|
+
irSchemaVersion: base.irSchemaVersion,
|
|
82
|
+
nameLevel: true,
|
|
83
|
+
};
|
|
84
|
+
if (handle === undefined) {
|
|
85
|
+
ctx.progress(`Summarising ${refs.length} unresolved reference(s)`);
|
|
86
|
+
const summary = summariseRefusals(refs, pageSize);
|
|
87
|
+
return answer({
|
|
88
|
+
...envelopeBase,
|
|
89
|
+
state: summary.count === 0 ? "empty" : "ok",
|
|
90
|
+
headline: summary.count === 0
|
|
91
|
+
? "No unresolved references are recorded — nothing for refusal_fetch to page through."
|
|
92
|
+
: `${summary.count} unresolved reference(s) recorded. Exemplar: "${summary.exemplar}". Call again with { "handle": "${summary.handle}" } to page through the rest.`,
|
|
93
|
+
notes: summary.count === 0
|
|
94
|
+
? ["The graph has no unresolved-reference rows at all — every edge either resolved or was never attempted."]
|
|
95
|
+
: [],
|
|
96
|
+
data: { kind: "summary", count: summary.count, exemplar: summary.exemplar, handle: summary.handle },
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
ctx.progress("Redeeming the refusal_fetch handle");
|
|
100
|
+
const page = fetchRefusalPage(refs, handle);
|
|
101
|
+
if (page.state === "refused") {
|
|
102
|
+
return answer({
|
|
103
|
+
...envelopeBase,
|
|
104
|
+
state: "refused",
|
|
105
|
+
headline: "This refusal_fetch handle could not be redeemed.",
|
|
106
|
+
notes: [page.reason],
|
|
107
|
+
data: { kind: "page", items: [], total: 0, nextHandle: null },
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
return answer({
|
|
111
|
+
...envelopeBase,
|
|
112
|
+
headline: `Page of ${page.items.length} unresolved reference(s), of ${page.total} total.`,
|
|
113
|
+
notes: [],
|
|
114
|
+
data: {
|
|
115
|
+
kind: "page",
|
|
116
|
+
items: page.items.map(brief),
|
|
117
|
+
total: page.total,
|
|
118
|
+
nextHandle: page.nextHandle,
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
export const refusalFetchTool = {
|
|
123
|
+
name: "refusal_fetch",
|
|
124
|
+
class: "read",
|
|
125
|
+
tier: "evidence",
|
|
126
|
+
version: "1.0.0",
|
|
127
|
+
title: "Page through the graph's recorded refusals",
|
|
128
|
+
description: "The graph's full ledger of unresolved references — edges an adapter could not resolve, with " +
|
|
129
|
+
"the reason recorded rather than guessed. Call with no arguments for a one-line summary (count, " +
|
|
130
|
+
"one exemplar reason, a handle); call again with that handle to page through every row. Never " +
|
|
131
|
+
"enumerates in full on the first call — §4 technique 4: summarise, don't enumerate. A handle " +
|
|
132
|
+
"issued against a population that has since changed (a re-analyse ran) refuses rather than " +
|
|
133
|
+
"silently returning a wrong page.",
|
|
134
|
+
inputSchema: SCHEMA,
|
|
135
|
+
run,
|
|
136
|
+
};
|
|
137
|
+
//# sourceMappingURL=refusal-fetch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"refusal-fetch.js","sourceRoot":"","sources":["../../src/tools/refusal-fetch.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `scope` — "I am about to change this. What else does it touch?"
|
|
3
|
+
*
|
|
4
|
+
* §12.5's diff scoping, exposed. Expansion stops on **score**, not hop count:
|
|
5
|
+
* a delete reaching a payment gateway four hops out matters and a logging import
|
|
6
|
+
* one hop out does not, and hop-count scoping cannot tell those apart.
|
|
7
|
+
*
|
|
8
|
+
* ## Files in, node ids out — and the misses are part of the answer
|
|
9
|
+
*
|
|
10
|
+
* A developer names files; scoping seeds on nodes. A path in the request that
|
|
11
|
+
* the graph has never heard of is the interesting case, and it has two very
|
|
12
|
+
* different causes that look identical from here: the file is not analysable by
|
|
13
|
+
* any configured adapter, or the graph is stale and predates the file. Both mean
|
|
14
|
+
* the blast radius omits it, so both are disclosed. Reporting "0 affected" for a
|
|
15
|
+
* file the graph does not contain would be the exact confirmed-clean-versus-not-
|
|
16
|
+
* analysable collapse §20.2 exists to prevent.
|
|
17
|
+
*/
|
|
18
|
+
import { type ScopeTier } from "@descryy/core";
|
|
19
|
+
import type { EdgeType } from "@descryy/ir";
|
|
20
|
+
import { type BriefNode, type ToolDefinition } from "./kit.ts";
|
|
21
|
+
/** A node in the scope, with why it is in it. */
|
|
22
|
+
export interface ScopedNodeReport extends BriefNode {
|
|
23
|
+
readonly score: number;
|
|
24
|
+
readonly tier: ScopeTier;
|
|
25
|
+
readonly depth: number;
|
|
26
|
+
/** The edge type this node was reached through. Absent for a seed. */
|
|
27
|
+
readonly via?: EdgeType;
|
|
28
|
+
}
|
|
29
|
+
/** Tier 1 is the changed set itself; 2-4 are the scoring bands beneath it. */
|
|
30
|
+
export interface ScopeTierCount {
|
|
31
|
+
readonly tier: number;
|
|
32
|
+
readonly count: number;
|
|
33
|
+
}
|
|
34
|
+
export interface ScopeReport {
|
|
35
|
+
readonly changed: readonly ScopedNodeReport[];
|
|
36
|
+
/** Capped by `limit`; `affectedTotal` is the uncapped count. */
|
|
37
|
+
readonly affected: readonly ScopedNodeReport[];
|
|
38
|
+
readonly affectedTotal: number;
|
|
39
|
+
readonly droppedForBudget: number;
|
|
40
|
+
readonly budget: number;
|
|
41
|
+
readonly budgetUsed: number;
|
|
42
|
+
readonly byTier: readonly ScopeTierCount[];
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Nothing the caller named is in the graph, so the traversal never ran.
|
|
46
|
+
*
|
|
47
|
+
* A genuinely different shape rather than a `ScopeReport` full of zeroes, and
|
|
48
|
+
* the absent keys are the tell: there is no `budget` because nothing was
|
|
49
|
+
* spent, and no `affectedTotal` because nothing was counted. `changed` and
|
|
50
|
+
* `affected` are always empty here and `seeds` is always 0 — narrow on
|
|
51
|
+
* `"seeds" in data`.
|
|
52
|
+
*/
|
|
53
|
+
export interface ScopeNotSeeded {
|
|
54
|
+
readonly changed: readonly ScopedNodeReport[];
|
|
55
|
+
readonly affected: readonly ScopedNodeReport[];
|
|
56
|
+
readonly seeds: number;
|
|
57
|
+
}
|
|
58
|
+
export type ScopeData = ScopeReport | ScopeNotSeeded;
|
|
59
|
+
export declare const scopeTool: ToolDefinition;
|
|
60
|
+
//# sourceMappingURL=scope.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scope.d.ts","sourceRoot":"","sources":["../../src/tools/scope.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAA8C,KAAK,SAAS,EAAE,MAAM,eAAe,CAAC;AAC3F,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE5C,OAAO,EAOL,KAAK,SAAS,EAEd,KAAK,cAAc,EACpB,MAAM,UAAU,CAAC;AAGlB,iDAAiD;AACjD,MAAM,WAAW,gBAAiB,SAAQ,SAAS;IACjD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,sEAAsE;IACtE,QAAQ,CAAC,GAAG,CAAC,EAAE,QAAQ,CAAC;CACzB;AAED,8EAA8E;AAC9E,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,OAAO,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAC9C,gEAAgE;IAChE,QAAQ,CAAC,QAAQ,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAC/C,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,SAAS,cAAc,EAAE,CAAC;CAC5C;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,OAAO,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAC9C,QAAQ,CAAC,QAAQ,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAC/C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,MAAM,SAAS,GAAG,WAAW,GAAG,cAAc,CAAC;AA0JrD,eAAO,MAAM,SAAS,EAAE,cAavB,CAAC"}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `scope` — "I am about to change this. What else does it touch?"
|
|
3
|
+
*
|
|
4
|
+
* §12.5's diff scoping, exposed. Expansion stops on **score**, not hop count:
|
|
5
|
+
* a delete reaching a payment gateway four hops out matters and a logging import
|
|
6
|
+
* one hop out does not, and hop-count scoping cannot tell those apart.
|
|
7
|
+
*
|
|
8
|
+
* ## Files in, node ids out — and the misses are part of the answer
|
|
9
|
+
*
|
|
10
|
+
* A developer names files; scoping seeds on nodes. A path in the request that
|
|
11
|
+
* the graph has never heard of is the interesting case, and it has two very
|
|
12
|
+
* different causes that look identical from here: the file is not analysable by
|
|
13
|
+
* any configured adapter, or the graph is stale and predates the file. Both mean
|
|
14
|
+
* the blast radius omits it, so both are disclosed. Reporting "0 affected" for a
|
|
15
|
+
* file the graph does not contain would be the exact confirmed-clean-versus-not-
|
|
16
|
+
* analysable collapse §20.2 exists to prevent.
|
|
17
|
+
*/
|
|
18
|
+
import { nodesInFiles, scopeChange } from "@descryy/core";
|
|
19
|
+
import { answer, briefNode, optionalEnum, optionalInteger, optionalStringArray, ToolInputError, } from "./kit.js";
|
|
20
|
+
const TRIGGERS = ["pre-push", "pr", "nightly"];
|
|
21
|
+
const SCHEMA = {
|
|
22
|
+
type: "object",
|
|
23
|
+
properties: {
|
|
24
|
+
files: {
|
|
25
|
+
type: "array",
|
|
26
|
+
items: { type: "string" },
|
|
27
|
+
description: "Repository-relative paths that are changing. Every symbol declared in them seeds the " +
|
|
28
|
+
"blast radius.",
|
|
29
|
+
},
|
|
30
|
+
nodeIds: {
|
|
31
|
+
type: "array",
|
|
32
|
+
items: { type: "string" },
|
|
33
|
+
description: "Graph node ids to seed with directly, instead of or as well as files.",
|
|
34
|
+
},
|
|
35
|
+
trigger: {
|
|
36
|
+
type: "string",
|
|
37
|
+
enum: [...TRIGGERS],
|
|
38
|
+
description: "Which node budget to use: pre-push 200, pr 1000, nightly 5000. Default pr.",
|
|
39
|
+
},
|
|
40
|
+
budget: {
|
|
41
|
+
type: "integer",
|
|
42
|
+
minimum: 1,
|
|
43
|
+
description: "Override the trigger's node budget.",
|
|
44
|
+
},
|
|
45
|
+
limit: {
|
|
46
|
+
type: "integer",
|
|
47
|
+
minimum: 1,
|
|
48
|
+
description: "Cap how many affected nodes come back in the reply. Default 25 (MK-9 — the same walk-cost " +
|
|
49
|
+
"reduction impact.ts applies, for the same reason). A capped reply always discloses " +
|
|
50
|
+
"shown/total; raise this explicitly when the full set is needed.",
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
additionalProperties: false,
|
|
54
|
+
};
|
|
55
|
+
function shape(entry) {
|
|
56
|
+
return {
|
|
57
|
+
...briefNode(entry.node),
|
|
58
|
+
score: Number(entry.score.toFixed(4)),
|
|
59
|
+
tier: entry.tier,
|
|
60
|
+
depth: entry.depth,
|
|
61
|
+
...(entry.via === null ? {} : { via: entry.via.type }),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
async function run(args, ctx) {
|
|
65
|
+
const files = optionalStringArray(args, "files") ?? [];
|
|
66
|
+
const seedIds = [...(optionalStringArray(args, "nodeIds") ?? [])];
|
|
67
|
+
const trigger = optionalEnum(args, "trigger", TRIGGERS) ?? "pr";
|
|
68
|
+
const budget = optionalInteger(args, "budget", 1);
|
|
69
|
+
const limit = optionalInteger(args, "limit", 1) ?? 25;
|
|
70
|
+
if (files.length === 0 && seedIds.length === 0) {
|
|
71
|
+
throw new ToolInputError('give at least one of "files" or "nodeIds"');
|
|
72
|
+
}
|
|
73
|
+
const notes = [];
|
|
74
|
+
const session = ctx.session;
|
|
75
|
+
if (files.length > 0) {
|
|
76
|
+
ctx.progress(`Resolving ${files.length} path(s) to graph nodes`);
|
|
77
|
+
const absolute = files.filter((f) => f.startsWith("/"));
|
|
78
|
+
if (absolute.length > 0) {
|
|
79
|
+
notes.push(`${absolute.length} path(s) were absolute. The graph stores repository-relative paths, so ` +
|
|
80
|
+
"these matched nothing. Pass paths relative to the repository root.");
|
|
81
|
+
}
|
|
82
|
+
const { nodes, missing } = nodesInFiles(session.store().driver, files);
|
|
83
|
+
for (const node of nodes)
|
|
84
|
+
seedIds.push(node.id);
|
|
85
|
+
if (missing.length > 0) {
|
|
86
|
+
notes.push(`${missing.length} of ${files.length} path(s) have no node in the graph ` +
|
|
87
|
+
`(${missing.slice(0, 5).join(", ")}${missing.length > 5 ? ", …" : ""}). Either no ` +
|
|
88
|
+
"configured adapter reads them or the graph predates them — run analyze. Their impact " +
|
|
89
|
+
"is not in this answer and they are not reported as having none.");
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (seedIds.length === 0) {
|
|
93
|
+
const base = session.provider().baseStamp();
|
|
94
|
+
return answer({
|
|
95
|
+
headline: "Nothing in the graph matches what you named, so no blast radius could be computed.",
|
|
96
|
+
state: "refused",
|
|
97
|
+
nameLevel: true,
|
|
98
|
+
resolutionFloor: 0,
|
|
99
|
+
commitSha: base.commitSha,
|
|
100
|
+
graphBuiltAt: base.graphBuiltAt,
|
|
101
|
+
irSchemaVersion: base.irSchemaVersion,
|
|
102
|
+
notes: [
|
|
103
|
+
...notes,
|
|
104
|
+
"This is not a result of zero impact. No seed node was found, so the traversal never ran.",
|
|
105
|
+
],
|
|
106
|
+
data: { changed: [], affected: [], seeds: 0 },
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
ctx.progress(`Expanding from ${seedIds.length} changed symbol(s)`);
|
|
110
|
+
const scoped = scopeChange(session.provider(), [...new Set(seedIds)], {
|
|
111
|
+
trigger,
|
|
112
|
+
...(budget === undefined ? {} : { budget }),
|
|
113
|
+
});
|
|
114
|
+
const affected = scoped.data.affected;
|
|
115
|
+
const shown = affected.slice(0, limit);
|
|
116
|
+
if (shown.length < affected.length) {
|
|
117
|
+
notes.push(`${affected.length - shown.length} lower-scoring affected node(s) are in the scope but not ` +
|
|
118
|
+
`listed here — the reply is capped at ${limit}. Raise "limit" to see them; they were ` +
|
|
119
|
+
"analysed either way.");
|
|
120
|
+
}
|
|
121
|
+
const byTier = [1, 2, 3, 4].map((tier) => ({
|
|
122
|
+
tier,
|
|
123
|
+
count: tier === 1
|
|
124
|
+
? scoped.data.changed.length
|
|
125
|
+
: affected.filter((node) => node.tier === tier).length,
|
|
126
|
+
}));
|
|
127
|
+
return {
|
|
128
|
+
result: {
|
|
129
|
+
...scoped,
|
|
130
|
+
data: {
|
|
131
|
+
changed: scoped.data.changed.map(shape),
|
|
132
|
+
affected: shown.map(shape),
|
|
133
|
+
affectedTotal: affected.length,
|
|
134
|
+
droppedForBudget: scoped.data.dropped.length,
|
|
135
|
+
budget: scoped.data.budget,
|
|
136
|
+
budgetUsed: scoped.data.budgetUsed,
|
|
137
|
+
byTier,
|
|
138
|
+
},
|
|
139
|
+
notes: [...scoped.notes, ...notes],
|
|
140
|
+
},
|
|
141
|
+
...(affected.length === 0 ? { state: "empty" } : {}),
|
|
142
|
+
nameLevel: true,
|
|
143
|
+
headline: `${scoped.data.changed.length} changed symbol(s) reach ${affected.length} other node(s) ` +
|
|
144
|
+
`above the impact threshold, under the ${trigger} budget of ${scoped.data.budget}.`,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
export const scopeTool = {
|
|
148
|
+
name: "scope",
|
|
149
|
+
class: "read",
|
|
150
|
+
tier: "evidence",
|
|
151
|
+
version: "1.0.0",
|
|
152
|
+
title: "Blast radius of a change",
|
|
153
|
+
description: "Given the files or symbols you are changing, return what else is affected, ranked by impact " +
|
|
154
|
+
"score and grouped into tiers 1-4. Expansion stops on score rather than hop count, so a " +
|
|
155
|
+
"distant high-impact dependency is kept and a nearby trivial one is dropped. Names the paths " +
|
|
156
|
+
"it could not find in the graph rather than reporting them as unaffected.",
|
|
157
|
+
inputSchema: SCHEMA,
|
|
158
|
+
run,
|
|
159
|
+
};
|
|
160
|
+
//# sourceMappingURL=scope.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scope.js","sourceRoot":"","sources":["../../src/tools/scope.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `similar_incidents` — "have we seen this failure before?", asked about a
|
|
3
|
+
* failure that is **not yet in the graph**.
|
|
4
|
+
*
|
|
5
|
+
* ## Why this is a separate tool from `impact`'s `related` field
|
|
6
|
+
*
|
|
7
|
+
* `rootCauseSearch` — the `related` list every `impact` reply carries — walks
|
|
8
|
+
* `INCIDENT_CORRELATED`/`FIXED_BY` edges out of a node that already exists.
|
|
9
|
+
* It cannot help with a failure observed a moment ago: a stack trace and a set
|
|
10
|
+
* of touched files have no node, so there is nothing to walk from. This tool
|
|
11
|
+
* is the other direction. It takes the failure as a description and ranks the
|
|
12
|
+
* `INCIDENT` nodes the graph already holds against it, using the two signals
|
|
13
|
+
* `similarIncidents` (`@descryy/core`, DEC-274 step 2) computes from data the
|
|
14
|
+
* graph already has: file-set Jaccard over `INCIDENT_CORRELATED`, and
|
|
15
|
+
* token-set Jaccard over the incident's own text. Deterministic, no vector
|
|
16
|
+
* store, no model call.
|
|
17
|
+
*
|
|
18
|
+
* ## The disclosure this tool exists to carry, and it is not optional
|
|
19
|
+
*
|
|
20
|
+
* DEC-279 makes one sentence mandatory here, and DEC-274 is why. That
|
|
21
|
+
* measurement ran the correlation machinery over `descry-core`,
|
|
22
|
+
* `descry-runtime` and `descry-adapters` — 734, 459 and 203 commits — and
|
|
23
|
+
* found **zero `INCIDENT` nodes in all three**. Not few: zero. Only an
|
|
24
|
+
* explicit `git revert` mints an `INCIDENT` today; a fix-forward commit, which
|
|
25
|
+
* is how almost every real bug is actually fixed, mints nothing at all.
|
|
26
|
+
*
|
|
27
|
+
* So this matcher's **recall is not computable — 0/0, not 0%**. It has never
|
|
28
|
+
* been handed a real incident to find, and no number describes how many it
|
|
29
|
+
* would miss. An empty result therefore means *"no linked incident in this
|
|
30
|
+
* graph, and this graph has few or no incidents"* and must never be read as
|
|
31
|
+
* *"no similar incident exists."* That is the same *absence is not evidence*
|
|
32
|
+
* obligation `impact` already carries on every reply, and `similar-incidents.test.ts`
|
|
33
|
+
* fails if it goes missing from either the empty or the non-empty branch.
|
|
34
|
+
*
|
|
35
|
+
* ## Two different absences, and they are not the same result
|
|
36
|
+
*
|
|
37
|
+
* `ai-tool-contract.md` §1.3a splits them, and the split is the whole reason
|
|
38
|
+
* this endpoint is interesting:
|
|
39
|
+
*
|
|
40
|
+
* - **Incidents exist and none matched.** The denominator is real, the tool
|
|
41
|
+
* looked completely, and the absence is a fact about the code. `state:
|
|
42
|
+
* "empty"`, per §1.1's split.
|
|
43
|
+
* - **The graph holds zero `INCIDENT` nodes.** There is no denominator.
|
|
44
|
+
* Nothing was looked at, and the reason is Descry's producer coverage
|
|
45
|
+
* rather than anything about this repository: only `git revert` mints an
|
|
46
|
+
* `INCIDENT` and fix-forward mints nothing. Answering "empty" there would
|
|
47
|
+
* say *"this code has no similar incidents"* when the true statement is
|
|
48
|
+
* *"Descry cannot see most incidents in any codebase"* — the
|
|
49
|
+
* empty-denominator defect inside the endpoint written to demonstrate the
|
|
50
|
+
* rule against it. So it is a refusal, carried as `state: "refused"` (§1.1:
|
|
51
|
+
* `refused` is a result state, and `not analysable` is a report category —
|
|
52
|
+
* different axes, never this).
|
|
53
|
+
*
|
|
54
|
+
* §1.3a generalises it past this tool: **when a query's population is itself
|
|
55
|
+
* zero, the answer is a refusal, never an empty result.** That puts this in
|
|
56
|
+
* line with its siblings rather than apart from them — `contracts` refuses on
|
|
57
|
+
* a graph with no endpoint, `propagation` when no walked edge type touches
|
|
58
|
+
* the start node.
|
|
59
|
+
*
|
|
60
|
+
* The denominator travels in the payload either way. `incidentsInGraph` is
|
|
61
|
+
* the population and `analysed` is false only when it is zero, so a caller
|
|
62
|
+
* can tell the two absences apart **from the payload alone, with no string
|
|
63
|
+
* matching on prose** — §1's distinguishability requirement, satisfied
|
|
64
|
+
* without waiting for the wider envelope.
|
|
65
|
+
*/
|
|
66
|
+
import { type SimilarIncidentSignalKind } from "@descryy/core";
|
|
67
|
+
import { type BriefNode, type ToolDefinition } from "./kit.ts";
|
|
68
|
+
/** One signal that fired for a candidate — the "why", never a bare score. */
|
|
69
|
+
export interface SimilarIncidentSignalReport {
|
|
70
|
+
readonly kind: SimilarIncidentSignalKind;
|
|
71
|
+
/** Jaccard similarity for this signal alone, 0..1. */
|
|
72
|
+
readonly score: number;
|
|
73
|
+
}
|
|
74
|
+
export interface SimilarIncidentReport {
|
|
75
|
+
readonly incident: BriefNode;
|
|
76
|
+
/** The `FIX_PATTERN` this incident resolved to, when a `FIXED_BY` edge exists. */
|
|
77
|
+
readonly fix: BriefNode | null;
|
|
78
|
+
readonly signals: readonly SimilarIncidentSignalReport[];
|
|
79
|
+
/** Weighted combination of every signal the query attempted, 0..1. */
|
|
80
|
+
readonly score: number;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* One shape, both branches.
|
|
84
|
+
*
|
|
85
|
+
* `analysed: false` with `incidentsInGraph: 0` is the zero-denominator case
|
|
86
|
+
* and arrives with `state: "refused"`; every other reply is `ok`.
|
|
87
|
+
* The two counts are in the payload rather than only in the prose because a
|
|
88
|
+
* caller deciding what to say about an empty list needs the denominator, and
|
|
89
|
+
* a sentence it has to parse is not a denominator.
|
|
90
|
+
*/
|
|
91
|
+
export interface SimilarIncidentsData {
|
|
92
|
+
readonly query: {
|
|
93
|
+
readonly touchedFiles: readonly string[];
|
|
94
|
+
readonly errorMessage: string | null;
|
|
95
|
+
};
|
|
96
|
+
readonly matches: readonly SimilarIncidentReport[];
|
|
97
|
+
/** Count before `limit` — honest about what was capped. */
|
|
98
|
+
readonly matchesTotal: number;
|
|
99
|
+
/** How many `INCIDENT` nodes were available to compare against. Zero is the headline fact. */
|
|
100
|
+
readonly incidentsInGraph: number;
|
|
101
|
+
/** False when the graph holds no `INCIDENT` node at all: nothing was compared. */
|
|
102
|
+
readonly analysed: boolean;
|
|
103
|
+
}
|
|
104
|
+
export declare const similarIncidentsTool: ToolDefinition;
|
|
105
|
+
//# sourceMappingURL=similar-incidents.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"similar-incidents.d.ts","sourceRoot":"","sources":["../../src/tools/similar-incidents.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgEG;AAEH,OAAO,EAAoB,KAAK,yBAAyB,EAAE,MAAM,eAAe,CAAC;AAEjF,OAAO,EAOL,KAAK,SAAS,EAEd,KAAK,cAAc,EACpB,MAAM,UAAU,CAAC;AAiClB,6EAA6E;AAC7E,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,IAAI,EAAE,yBAAyB,CAAC;IACzC,sDAAsD;IACtD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,QAAQ,EAAE,SAAS,CAAC;IAC7B,kFAAkF;IAClF,QAAQ,CAAC,GAAG,EAAE,SAAS,GAAG,IAAI,CAAC;IAC/B,QAAQ,CAAC,OAAO,EAAE,SAAS,2BAA2B,EAAE,CAAC;IACzD,sEAAsE;IACtE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,KAAK,EAAE;QACd,QAAQ,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;QACzC,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;KACtC,CAAC;IACF,QAAQ,CAAC,OAAO,EAAE,SAAS,qBAAqB,EAAE,CAAC;IACnD,2DAA2D;IAC3D,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,8FAA8F;IAC9F,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,kFAAkF;IAClF,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;CAC5B;AAiJD,eAAO,MAAM,oBAAoB,EAAE,cAkBlC,CAAC"}
|