@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.
Files changed (118) hide show
  1. package/LICENSE +21 -0
  2. package/dist/action-handshake.d.ts +86 -0
  3. package/dist/action-handshake.d.ts.map +1 -0
  4. package/dist/action-handshake.js +144 -0
  5. package/dist/action-handshake.js.map +1 -0
  6. package/dist/bin/descry-mcp.d.ts +18 -0
  7. package/dist/bin/descry-mcp.d.ts.map +1 -0
  8. package/dist/bin/descry-mcp.js +54 -0
  9. package/dist/bin/descry-mcp.js.map +1 -0
  10. package/dist/index.d.ts +32 -0
  11. package/dist/index.d.ts.map +1 -0
  12. package/dist/index.js +15 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/language-guess.d.ts +17 -0
  15. package/dist/language-guess.d.ts.map +1 -0
  16. package/dist/language-guess.js +43 -0
  17. package/dist/language-guess.js.map +1 -0
  18. package/dist/protocol.d.ts +91 -0
  19. package/dist/protocol.d.ts.map +1 -0
  20. package/dist/protocol.js +127 -0
  21. package/dist/protocol.js.map +1 -0
  22. package/dist/registry.d.ts +72 -0
  23. package/dist/registry.d.ts.map +1 -0
  24. package/dist/registry.js +136 -0
  25. package/dist/registry.js.map +1 -0
  26. package/dist/render.d.ts +275 -0
  27. package/dist/render.d.ts.map +1 -0
  28. package/dist/render.js +333 -0
  29. package/dist/render.js.map +1 -0
  30. package/dist/server.d.ts +42 -0
  31. package/dist/server.d.ts.map +1 -0
  32. package/dist/server.js +263 -0
  33. package/dist/server.js.map +1 -0
  34. package/dist/session.d.ts +164 -0
  35. package/dist/session.d.ts.map +1 -0
  36. package/dist/session.js +302 -0
  37. package/dist/session.js.map +1 -0
  38. package/dist/tools/analyze.d.ts +124 -0
  39. package/dist/tools/analyze.d.ts.map +1 -0
  40. package/dist/tools/analyze.js +411 -0
  41. package/dist/tools/analyze.js.map +1 -0
  42. package/dist/tools/contracts.d.ts +105 -0
  43. package/dist/tools/contracts.d.ts.map +1 -0
  44. package/dist/tools/contracts.js +141 -0
  45. package/dist/tools/contracts.js.map +1 -0
  46. package/dist/tools/cross-pr.d.ts +137 -0
  47. package/dist/tools/cross-pr.d.ts.map +1 -0
  48. package/dist/tools/cross-pr.js +216 -0
  49. package/dist/tools/cross-pr.js.map +1 -0
  50. package/dist/tools/git-diff.d.ts +29 -0
  51. package/dist/tools/git-diff.d.ts.map +1 -0
  52. package/dist/tools/git-diff.js +117 -0
  53. package/dist/tools/git-diff.js.map +1 -0
  54. package/dist/tools/git-history.d.ts +27 -0
  55. package/dist/tools/git-history.d.ts.map +1 -0
  56. package/dist/tools/git-history.js +91 -0
  57. package/dist/tools/git-history.js.map +1 -0
  58. package/dist/tools/history.d.ts +63 -0
  59. package/dist/tools/history.d.ts.map +1 -0
  60. package/dist/tools/history.js +114 -0
  61. package/dist/tools/history.js.map +1 -0
  62. package/dist/tools/impact.d.ts +81 -0
  63. package/dist/tools/impact.d.ts.map +1 -0
  64. package/dist/tools/impact.js +229 -0
  65. package/dist/tools/impact.js.map +1 -0
  66. package/dist/tools/index.d.ts +33 -0
  67. package/dist/tools/index.d.ts.map +1 -0
  68. package/dist/tools/index.js +47 -0
  69. package/dist/tools/index.js.map +1 -0
  70. package/dist/tools/kit.d.ts +183 -0
  71. package/dist/tools/kit.d.ts.map +1 -0
  72. package/dist/tools/kit.js +117 -0
  73. package/dist/tools/kit.js.map +1 -0
  74. package/dist/tools/link-workspace.d.ts +62 -0
  75. package/dist/tools/link-workspace.d.ts.map +1 -0
  76. package/dist/tools/link-workspace.js +156 -0
  77. package/dist/tools/link-workspace.js.map +1 -0
  78. package/dist/tools/lookup.d.ts +29 -0
  79. package/dist/tools/lookup.d.ts.map +1 -0
  80. package/dist/tools/lookup.js +73 -0
  81. package/dist/tools/lookup.js.map +1 -0
  82. package/dist/tools/pr-analysis.d.ts +89 -0
  83. package/dist/tools/pr-analysis.d.ts.map +1 -0
  84. package/dist/tools/pr-analysis.js +169 -0
  85. package/dist/tools/pr-analysis.js.map +1 -0
  86. package/dist/tools/propagation.d.ts +74 -0
  87. package/dist/tools/propagation.d.ts.map +1 -0
  88. package/dist/tools/propagation.js +194 -0
  89. package/dist/tools/propagation.js.map +1 -0
  90. package/dist/tools/questions.d.ts +80 -0
  91. package/dist/tools/questions.d.ts.map +1 -0
  92. package/dist/tools/questions.js +213 -0
  93. package/dist/tools/questions.js.map +1 -0
  94. package/dist/tools/refusal-fetch.d.ts +71 -0
  95. package/dist/tools/refusal-fetch.d.ts.map +1 -0
  96. package/dist/tools/refusal-fetch.js +137 -0
  97. package/dist/tools/refusal-fetch.js.map +1 -0
  98. package/dist/tools/scope.d.ts +60 -0
  99. package/dist/tools/scope.d.ts.map +1 -0
  100. package/dist/tools/scope.js +160 -0
  101. package/dist/tools/scope.js.map +1 -0
  102. package/dist/tools/similar-incidents.d.ts +105 -0
  103. package/dist/tools/similar-incidents.d.ts.map +1 -0
  104. package/dist/tools/similar-incidents.js +228 -0
  105. package/dist/tools/similar-incidents.js.map +1 -0
  106. package/dist/tools/validate.d.ts +103 -0
  107. package/dist/tools/validate.d.ts.map +1 -0
  108. package/dist/tools/validate.js +176 -0
  109. package/dist/tools/validate.js.map +1 -0
  110. package/dist/tools/verification-status.d.ts +68 -0
  111. package/dist/tools/verification-status.d.ts.map +1 -0
  112. package/dist/tools/verification-status.js +179 -0
  113. package/dist/tools/verification-status.js.map +1 -0
  114. package/dist/transport.d.ts +58 -0
  115. package/dist/transport.d.ts.map +1 -0
  116. package/dist/transport.js +115 -0
  117. package/dist/transport.js.map +1 -0
  118. package/package.json +33 -0
@@ -0,0 +1,228 @@
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 { similarIncidents } from "@descryy/core";
67
+ import { answer, briefNode, optionalInteger, optionalString, optionalStringArray, ToolInputError, } from "./kit.js";
68
+ const SCHEMA = {
69
+ type: "object",
70
+ properties: {
71
+ touchedFiles: {
72
+ type: "array",
73
+ items: { type: "string" },
74
+ description: "Repository-relative paths the failure touched — from a stack trace, a failing test's " +
75
+ "imports, or the diff being investigated. Matched against each incident's own correlated " +
76
+ "file set.",
77
+ },
78
+ errorMessage: {
79
+ type: "string",
80
+ description: "The failure's error or exception text. Matched against each incident's text, which today " +
81
+ "is always a commit subject line — real signal, but thin.",
82
+ },
83
+ minScore: {
84
+ type: "number",
85
+ minimum: 0,
86
+ maximum: 1,
87
+ description: "Drop candidates scoring below this. Default 0, meaning any signal that fired at all is " +
88
+ "returned — ranked, so a weak match is visible as a weak match rather than hidden.",
89
+ },
90
+ limit: { type: "integer", minimum: 1, description: "Cap on matches in the reply. Default 20." },
91
+ },
92
+ additionalProperties: false,
93
+ };
94
+ /**
95
+ * The mandatory sentence, in the form that fits each branch.
96
+ *
97
+ * Written once here rather than inline at three return sites, because the one
98
+ * failure mode this tool has is a branch that forgets it.
99
+ */
100
+ function recallDisclosure(incidentsInGraph) {
101
+ return ((incidentsInGraph === 0
102
+ ? "This graph holds no INCIDENT node at all, so nothing was compared. "
103
+ : `${incidentsInGraph} INCIDENT node(s) were available to compare against. `) +
104
+ "Absence is not evidence here, and the reason is measured rather than assumed: only an " +
105
+ "explicit git revert mints an INCIDENT, and a fix-forward commit — how almost every real bug " +
106
+ "is actually fixed — mints nothing. DEC-274 found zero INCIDENT nodes across three real " +
107
+ "repositories of 734, 459 and 203 commits. This matcher's recall is therefore not computable " +
108
+ "(0/0), not 0%: it has never been given a real incident to find. A failure this repository " +
109
+ "has genuinely hit before can be absent from these results entirely.");
110
+ }
111
+ async function run(args, ctx) {
112
+ const touchedFiles = optionalStringArray(args, "touchedFiles") ?? [];
113
+ const errorMessage = optionalString(args, "errorMessage");
114
+ const limit = optionalInteger(args, "limit", 1) ?? 20;
115
+ const rawMinScore = args["minScore"];
116
+ if (rawMinScore !== undefined && rawMinScore !== null) {
117
+ if (typeof rawMinScore !== "number" || rawMinScore < 0 || rawMinScore > 1) {
118
+ throw new ToolInputError('"minScore" must be a number between 0 and 1');
119
+ }
120
+ }
121
+ const minScore = typeof rawMinScore === "number" ? rawMinScore : undefined;
122
+ // Describing nothing and being told "0 matches" is the empty-denominator
123
+ // defect at the input side: the reply would be indistinguishable from a real
124
+ // search that found nothing.
125
+ if (touchedFiles.length === 0 && (errorMessage === undefined || errorMessage === "")) {
126
+ throw new ToolInputError('give at least one of "touchedFiles" or "errorMessage" — with neither, there is nothing to ' +
127
+ "compare an incident against, and an empty answer would look like a search that ran");
128
+ }
129
+ const provider = ctx.session.provider();
130
+ // The population, read for its own sake. `similarIncidents` scans the same
131
+ // set internally but returns only what matched, and the count of what did
132
+ // *not* match is the number this tool's honesty rests on.
133
+ ctx.progress("Reading the incident population");
134
+ const population = provider.nodesOfType("INCIDENT");
135
+ const incidentsInGraph = population.nodes.length;
136
+ const base = provider.baseStamp();
137
+ if (incidentsInGraph === 0) {
138
+ return answer({
139
+ headline: "No incident is recorded in this graph, so nothing was compared against the failure you " +
140
+ "described. This is not a finding that the failure is new.",
141
+ // A refusal, not an empty result — §1.3a. `not_analysable` is the value
142
+ // today's envelope has for it; §1.1 renames it `refused` in P2.
143
+ state: "refused",
144
+ // Whether an incident resembles a failure is a claim about names and
145
+ // paths, not about shapes — the same class as `history`'s co-change
146
+ // rows. R0 is the honest floor: an INCIDENT is minted from a commit
147
+ // log, which is R0 by nature rather than by degradation.
148
+ nameLevel: true,
149
+ resolutionFloor: 0,
150
+ commitSha: base.commitSha,
151
+ graphBuiltAt: base.graphBuiltAt,
152
+ irSchemaVersion: base.irSchemaVersion,
153
+ notes: [recallDisclosure(0)],
154
+ data: {
155
+ query: { touchedFiles, errorMessage: errorMessage ?? null },
156
+ matches: [],
157
+ matchesTotal: 0,
158
+ incidentsInGraph: 0,
159
+ analysed: false,
160
+ },
161
+ });
162
+ }
163
+ ctx.progress(`Ranking ${incidentsInGraph} incident(s) against the failure`);
164
+ const ranked = similarIncidents(provider, {
165
+ ...(touchedFiles.length === 0 ? {} : { touchedFiles }),
166
+ ...(errorMessage === undefined ? {} : { errorMessage }),
167
+ }, { ...(minScore === undefined ? {} : { minScore }) });
168
+ const shown = ranked.data.slice(0, limit);
169
+ const notes = [...ranked.notes, recallDisclosure(incidentsInGraph)];
170
+ if (shown.length < ranked.data.length) {
171
+ notes.push(`${ranked.data.length - shown.length} further match(es) scored above the threshold but are ` +
172
+ `not listed — the reply is capped at ${limit}.`);
173
+ }
174
+ if (population.truncated) {
175
+ notes.push("The incident read was capped, so some INCIDENT nodes were never scored. The counts here " +
176
+ "are a lower bound.");
177
+ }
178
+ return {
179
+ result: {
180
+ data: {
181
+ query: { touchedFiles, errorMessage: errorMessage ?? null },
182
+ matches: shown.map((match) => ({
183
+ incident: briefNode(match.incident),
184
+ fix: match.fix === null ? null : briefNode(match.fix),
185
+ signals: match.signals.map((s) => ({ kind: s.kind, score: Number(s.score.toFixed(4)) })),
186
+ score: Number(match.combinedScore.toFixed(4)),
187
+ })),
188
+ matchesTotal: ranked.data.length,
189
+ incidentsInGraph,
190
+ analysed: true,
191
+ },
192
+ stamp: ranked.stamp,
193
+ truncated: ranked.truncated || shown.length < ranked.data.length || population.truncated,
194
+ notes,
195
+ },
196
+ ...(ranked.data.length === 0 ? { state: "empty" } : {}),
197
+ ...(shown.length < ranked.data.length
198
+ ? { truncatedDetail: { shown: shown.length, total: ranked.data.length, more: null } }
199
+ : {}),
200
+ nameLevel: true,
201
+ headline: ranked.data.length === 0
202
+ ? `None of the ${incidentsInGraph} incident(s) in this graph share a file or a phrase with ` +
203
+ "the failure you described. That is a statement about this graph's incident record, not " +
204
+ "about the failure."
205
+ : `${ranked.data.length} of ${incidentsInGraph} recorded incident(s) resemble this failure; ` +
206
+ `closest: ${shown[0].incident.name}` +
207
+ `${shown[0].fix === null ? "" : ` (fixed by: ${shown[0].fix.name})`}.`,
208
+ };
209
+ }
210
+ export const similarIncidentsTool = {
211
+ name: "similar_incidents",
212
+ class: "read",
213
+ tier: "evidence",
214
+ version: "1.0.0",
215
+ title: "Past incidents that resemble a failure happening now",
216
+ description: "Given the files a failure touched and/or its error text, rank the incidents already recorded " +
217
+ "in the graph by how much they resemble it, and name which signal fired for each — file " +
218
+ "overlap, message similarity, or both. Use this for a failure that has no graph node yet (a " +
219
+ "stack trace, a red test); use \"impact\" instead for incidents linked to a symbol that " +
220
+ "already exists. Deterministic graph and text matching only; no embedding search runs. Two " +
221
+ "absences and they are different answers: an empty result means this graph's recorded " +
222
+ "incidents were compared and none matched, while a refusal means the graph holds no incident " +
223
+ "at all — common, because only an explicit git revert mints one and a fix-forward commit " +
224
+ "mints nothing. Neither is evidence that the failure is new.",
225
+ inputSchema: SCHEMA,
226
+ run,
227
+ };
228
+ //# sourceMappingURL=similar-incidents.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"similar-incidents.js","sourceRoot":"","sources":["../../src/tools/similar-incidents.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,103 @@
1
+ /**
2
+ * `validate` — §15's static validation engine, currently its env/config check.
3
+ *
4
+ * The architecture calls this one *"cheapest and most reliable — ship first"*,
5
+ * and it is the first tool here whose output is a statement about the user's
6
+ * code rather than a measurement of the graph.
7
+ *
8
+ * ## Two things this tool refuses to do, and both are the point
9
+ *
10
+ * 1. **It does not report a variable that is read and never declared, by
11
+ * default.** Measured on two real repositories, that rule produces 18 wrong
12
+ * findings on one and 107 on the other. A finding needs a read that asserts
13
+ * *requirement* — no fallback at the read site — and a declaration surface
14
+ * complete enough for an absence to mean something. See `validation/env.ts`.
15
+ *
16
+ * 2. **It does not return `ok` with an empty list when no adapter supplied
17
+ * reads.** That is `refused` with the reason and the exact `attrs` key
18
+ * it needs, because "no missing variables" is a false statement when the code
19
+ * was never read.
20
+ *
21
+ * The declaration half runs regardless: configuration files are read straight
22
+ * from the repository, which needs no adapter and no resolution.
23
+ */
24
+ import type { ConfigGraph, ConfigReadSite, EnvClaim, EnvEvidence, EnvFindingKind, EnvReport, RollbackFinding, RollbackReport } from "@descryy/core";
25
+ import type { ResolutionLevel } from "@descryy/ir";
26
+ import { type ToolDefinition } from "./kit.ts";
27
+ /**
28
+ * One environment/configuration finding.
29
+ *
30
+ * `claim` is the axis that matters: a `defect` is a statement about the code, a
31
+ * `lead` is something to look at. A variable read with no fallback and
32
+ * declared nowhere is the first; a variable set in one file and documented in
33
+ * no example file is the second, and reporting it as the first is how this
34
+ * check produced 18 and 107 wrong findings on two real repositories before the
35
+ * distinction existed.
36
+ */
37
+ export interface EnvFindingReport {
38
+ readonly kind: EnvFindingKind;
39
+ readonly name: string;
40
+ readonly claim: EnvClaim;
41
+ readonly detail: string;
42
+ /** Every location the finding rests on. Never empty. */
43
+ readonly evidence: readonly EnvEvidence[];
44
+ readonly resolution: ResolutionLevel;
45
+ }
46
+ /**
47
+ * A variable and how much of the repository stands behind it.
48
+ *
49
+ * Only variables whose dependent set could actually be computed appear —
50
+ * §15.3's third check feeding the first. An unknown count is not a zero, so it
51
+ * is left out rather than reported as none.
52
+ */
53
+ export interface ConfigDependencyReport {
54
+ readonly name: string;
55
+ /** A lower bound, never a total. */
56
+ readonly dependents: number | null;
57
+ /** True when the traversal's budget stopped it short. */
58
+ readonly truncated: boolean;
59
+ /** Up to three read sites. */
60
+ readonly readAt: readonly ConfigReadSite[];
61
+ /** A few dependent files, for a reader who wants to look. */
62
+ readonly sample: readonly string[];
63
+ }
64
+ export interface ValidateData {
65
+ readonly findings: readonly EnvFindingReport[];
66
+ /** The count before `maxFindings` capped it — P27's disclosed remainder (§4 technique 5). */
67
+ readonly findingsTotal: number;
68
+ readonly surfaces: EnvReport["surfaces"];
69
+ readonly reads: EnvReport["reads"];
70
+ /** Null when no read existed at all, so the coverage guard could not be evaluated. */
71
+ readonly coverage: EnvReport["coverage"];
72
+ /** Declared and read nowhere. A dead-configuration lead, not a finding. */
73
+ readonly declaredNeverRead: number;
74
+ /**
75
+ * False when no adapter supplied environment reads.
76
+ *
77
+ * The whole point of the field: with it false, "no missing variables" would
78
+ * be a statement about code that was never read, so the call is degraded
79
+ * rather than clean.
80
+ */
81
+ readonly readsAvailable: boolean;
82
+ readonly configGraph: {
83
+ readonly counts: ConfigGraph["counts"];
84
+ /** True when the graph carried at least one resolved import edge. */
85
+ readonly importsAvailable: boolean;
86
+ /** The ten most-depended-on variables that had a computable dependent set. */
87
+ readonly mostDependedOn: readonly ConfigDependencyReport[];
88
+ };
89
+ readonly rollback: {
90
+ readonly findings: readonly RollbackFinding[];
91
+ /** The count before `maxFindings` capped it — P27's disclosed remainder (§4 technique 5). */
92
+ readonly findingsTotal: number;
93
+ readonly examined: RollbackReport["examined"];
94
+ /** Operations this check declined to report, by reason. The size of what it did not say. */
95
+ readonly withheld: RollbackReport["withheld"];
96
+ /** False when no source supplied migration operations. */
97
+ readonly ran: boolean;
98
+ /** Named in plain words when `ran` is false. */
99
+ readonly blockers: readonly string[];
100
+ };
101
+ }
102
+ export declare const validateTool: ToolDefinition;
103
+ //# sourceMappingURL=validate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/tools/validate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAUH,OAAO,KAAK,EACV,WAAW,EACX,cAAc,EACd,QAAQ,EACR,WAAW,EACX,cAAc,EACd,SAAS,EACT,eAAe,EACf,cAAc,EACf,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnD,OAAO,EAA0D,KAAK,cAAc,EAAE,MAAM,UAAU,CAAC;AAGvG;;;;;;;;;GASG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,wDAAwD;IACxD,QAAQ,CAAC,QAAQ,EAAE,SAAS,WAAW,EAAE,CAAC;IAC1C,QAAQ,CAAC,UAAU,EAAE,eAAe,CAAC;CACtC;AAED;;;;;;GAMG;AACH,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,oCAAoC;IACpC,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,yDAAyD;IACzD,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,8BAA8B;IAC9B,QAAQ,CAAC,MAAM,EAAE,SAAS,cAAc,EAAE,CAAC;IAC3C,6DAA6D;IAC7D,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;CACpC;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,QAAQ,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAC/C,6FAA6F;IAC7F,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,QAAQ,EAAE,SAAS,CAAC,UAAU,CAAC,CAAC;IACzC,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC,OAAO,CAAC,CAAC;IACnC,sFAAsF;IACtF,QAAQ,CAAC,QAAQ,EAAE,SAAS,CAAC,UAAU,CAAC,CAAC;IACzC,2EAA2E;IAC3E,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC;;;;;;OAMG;IACH,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC,QAAQ,CAAC,WAAW,EAAE;QACpB,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC,QAAQ,CAAC,CAAC;QACvC,qEAAqE;QACrE,QAAQ,CAAC,gBAAgB,EAAE,OAAO,CAAC;QACnC,8EAA8E;QAC9E,QAAQ,CAAC,cAAc,EAAE,SAAS,sBAAsB,EAAE,CAAC;KAC5D,CAAC;IACF,QAAQ,CAAC,QAAQ,EAAE;QACjB,QAAQ,CAAC,QAAQ,EAAE,SAAS,eAAe,EAAE,CAAC;QAC9C,6FAA6F;QAC7F,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;QAC/B,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAC,UAAU,CAAC,CAAC;QAC9C,4FAA4F;QAC5F,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAC,UAAU,CAAC,CAAC;QAC9C,0DAA0D;QAC1D,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;QACtB,gDAAgD;QAChD,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;KACtC,CAAC;CACH;AA4JD,eAAO,MAAM,YAAY,EAAE,cAgB1B,CAAC"}
@@ -0,0 +1,176 @@
1
+ /**
2
+ * `validate` — §15's static validation engine, currently its env/config check.
3
+ *
4
+ * The architecture calls this one *"cheapest and most reliable — ship first"*,
5
+ * and it is the first tool here whose output is a statement about the user's
6
+ * code rather than a measurement of the graph.
7
+ *
8
+ * ## Two things this tool refuses to do, and both are the point
9
+ *
10
+ * 1. **It does not report a variable that is read and never declared, by
11
+ * default.** Measured on two real repositories, that rule produces 18 wrong
12
+ * findings on one and 107 on the other. A finding needs a read that asserts
13
+ * *requirement* — no fallback at the read site — and a declaration surface
14
+ * complete enough for an absence to mean something. See `validation/env.ts`.
15
+ *
16
+ * 2. **It does not return `ok` with an empty list when no adapter supplied
17
+ * reads.** That is `refused` with the reason and the exact `attrs` key
18
+ * it needs, because "no missing variables" is a false statement when the code
19
+ * was never read.
20
+ *
21
+ * The declaration half runs regardless: configuration files are read straight
22
+ * from the repository, which needs no adapter and no resolution.
23
+ */
24
+ import { buildConfigGraph, checkEnvironment, checkRollbackReadiness, scanConfigSurfaces, scanMigrations, } from "@descryy/core";
25
+ import { optionalInteger, optionalStringArray } from "./kit.js";
26
+ const SCHEMA = {
27
+ type: "object",
28
+ properties: {
29
+ files: {
30
+ type: "array",
31
+ items: { type: "string" },
32
+ description: "Restrict the code side to these repo-relative files — the scoped-subgraph run. " +
33
+ "Configuration files are always read in full, because a declaration is repository-wide.",
34
+ },
35
+ coverageFloor: {
36
+ type: "number",
37
+ minimum: 0,
38
+ maximum: 1,
39
+ description: "Fraction of required variables the repository must declare somewhere before any " +
40
+ "missing-variable finding is emitted. Default 0.6. Lowering it trades precision for " +
41
+ "recall on a repository whose configuration lives outside the code.",
42
+ },
43
+ maxFindings: {
44
+ type: "integer",
45
+ minimum: 1,
46
+ description: "Cap on findings returned. Default 25; overflow is disclosed, not dropped.",
47
+ },
48
+ },
49
+ additionalProperties: false,
50
+ };
51
+ async function run(args, ctx) {
52
+ const files = optionalStringArray(args, "files");
53
+ const maxFindings = optionalInteger(args, "maxFindings", 1);
54
+ const rawFloor = args["coverageFloor"];
55
+ const coverageFloor = typeof rawFloor === "number" && rawFloor >= 0 && rawFloor <= 1 ? rawFloor : undefined;
56
+ ctx.progress("Reading configuration files");
57
+ const scan = scanConfigSurfaces(ctx.session.repoPath);
58
+ ctx.progress("Cross-checking against environment reads in the graph");
59
+ const provider = ctx.session.provider();
60
+ const checked = checkEnvironment(provider, scan, {
61
+ ...(files === undefined ? {} : { files }),
62
+ ...(maxFindings === undefined ? {} : { maxFindings }),
63
+ ...(coverageFloor === undefined ? {} : { coverageFloor }),
64
+ });
65
+ ctx.progress("Tracing what depends on each configuration variable");
66
+ const configGraph = buildConfigGraph(provider, scan);
67
+ ctx.progress("Reading migrations for destructive operations");
68
+ const rollback = checkRollbackReadiness(provider, scanMigrations(ctx.session.repoPath), {
69
+ ...(files === undefined ? {} : { files }),
70
+ ...(maxFindings === undefined ? {} : { maxFindings }),
71
+ });
72
+ const report = checked.data;
73
+ const defects = report.findings.filter((f) => f.claim === "defect").length;
74
+ const leads = report.findings.length - defects;
75
+ // Nothing was compared at all: no configuration files, no reads, and no
76
+ // migration. Any one of them alone is a real answer; none of them is not.
77
+ const nothingToCheck = report.surfaces.length === 0 && !report.readsAvailable && !rollback.data.ran;
78
+ // MK-7: the leading clause a caller reads first, graded on the axis that
79
+ // actually matters — `claim`. A lead or a rollback finding is worth a look;
80
+ // a defect is the thing this tool exists to catch. Never blur the two into
81
+ // one count, the exact failure `EnvFindingReport.claim`'s own doc names.
82
+ const otherFindings = leads + rollback.data.findings.length;
83
+ const verdict = defects > 0
84
+ ? `${defects} defect(s) found`
85
+ : otherFindings > 0
86
+ ? `No defects; ${otherFindings} lead(s)/rollback finding(s) to review`
87
+ : "Clean";
88
+ return {
89
+ result: {
90
+ ...checked,
91
+ data: {
92
+ findings: report.findings.map((f) => ({
93
+ kind: f.kind,
94
+ name: f.name,
95
+ claim: f.claim,
96
+ detail: f.detail,
97
+ evidence: f.evidence,
98
+ resolution: f.resolution,
99
+ })),
100
+ findingsTotal: report.findingsTotal,
101
+ surfaces: report.surfaces,
102
+ reads: report.reads,
103
+ coverage: report.coverage,
104
+ declaredNeverRead: report.declaredNeverRead,
105
+ readsAvailable: report.readsAvailable,
106
+ // §15.3's third check feeds the first: a variable read by a module forty
107
+ // others import is a different sentence from one read by a single
108
+ // script. Only variables whose dependent set could be computed are
109
+ // carried — an unknown count is not a zero and is not worth a row.
110
+ configGraph: {
111
+ counts: configGraph.data.counts,
112
+ importsAvailable: configGraph.data.importsAvailable,
113
+ mostDependedOn: configGraph.data.variables
114
+ .filter((v) => v.dependents !== null && v.dependents > 0)
115
+ .slice(0, 10)
116
+ .map((v) => ({
117
+ name: v.name,
118
+ dependents: v.dependents,
119
+ truncated: v.dependentsTruncated,
120
+ readAt: v.readAt.slice(0, 3),
121
+ sample: v.dependentSample,
122
+ })),
123
+ },
124
+ rollback: {
125
+ findings: rollback.data.findings,
126
+ findingsTotal: rollback.data.findingsTotal,
127
+ examined: rollback.data.examined,
128
+ withheld: rollback.data.withheld,
129
+ ran: rollback.data.ran,
130
+ blockers: rollback.data.blockers,
131
+ },
132
+ },
133
+ // Both checks' disclosures reach the reader. Dropping one half's notes
134
+ // because the other half had something to say is the silent-degradation
135
+ // failure rule 7 exists to prevent.
136
+ notes: [...checked.notes, ...rollback.notes, ...configGraph.notes],
137
+ truncated: checked.truncated || rollback.truncated || configGraph.truncated,
138
+ stamp: {
139
+ ...checked.stamp,
140
+ resolutionFloor: Math.min(checked.stamp.resolutionFloor, rollback.stamp.resolutionFloor, configGraph.stamp.resolutionFloor),
141
+ },
142
+ },
143
+ // A missing environment variable is §11A.5's canonical name-level fact —
144
+ // the variable exists or it does not — which is the one claim R2 supports at
145
+ // class A. The shape of its value is never asserted here.
146
+ nameLevel: true,
147
+ ...(nothingToCheck ? { state: "refused" } : {}),
148
+ headline: nothingToCheck
149
+ ? "No configuration files, environment reads or migrations were found, so there was " +
150
+ "nothing to check."
151
+ : `${verdict}. ${report.surfaces.length} configuration file(s) declare ` +
152
+ `${countDeclared(report.surfaces)} variable(s); ` +
153
+ `${report.reads.required} variable(s) are read through an accessor that fails when unset; ` +
154
+ `${rollback.data.examined.operations} destructive migration operation(s) examined.`,
155
+ };
156
+ }
157
+ function countDeclared(surfaces) {
158
+ return surfaces.reduce((total, s) => total + s.declared, 0);
159
+ }
160
+ export const validateTool = {
161
+ name: "validate",
162
+ class: "read",
163
+ tier: "conclusion",
164
+ version: "1.0.0",
165
+ title: "Environment and configuration checks",
166
+ description: "Cross-check the environment variables the code requires against the ones this repository " +
167
+ "declares. Reports a variable only when the code reads it with no fallback and no " +
168
+ "configuration file declares it, and only when the repository's declaration surface is " +
169
+ "complete enough for that absence to mean something — a variable supplied by a secrets " +
170
+ "manager or a platform is disclosed with a count, never reported as missing. Also reports " +
171
+ "variables set in one configuration file and documented in no example file, stated as a lead " +
172
+ "rather than a defect.",
173
+ inputSchema: SCHEMA,
174
+ run,
175
+ };
176
+ //# sourceMappingURL=validate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate.js","sourceRoot":"","sources":["../../src/tools/validate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,68 @@
1
+ /**
2
+ * `verification_status` — §4 technique 6: *"Coverage is a separate axis and
3
+ * a separate call. Appending it everywhere pays on every call; a tool pays
4
+ * once, when the client is actually reporting."*
5
+ *
6
+ * Wraps `packages/core/src/query/verification-status.ts`'s
7
+ * `computeVerificationStatus` — the module's own header calls itself "the
8
+ * static half only" and says building the `ScopeItem[]` from a caller's refs
9
+ * is "the caller's job, done above this boundary." This tool is that caller.
10
+ *
11
+ * ## Real capability lookup, without ever naming a language up here
12
+ *
13
+ * `computeVerificationStatus`'s `ScopeItem.capabilities`, when supplied,
14
+ * lets a `notExercised` reason describe a real declared gap rather than a
15
+ * generic absence. The first design attempted here guessed a language from
16
+ * a ref's file extension and matched it against a `language` tag on a
17
+ * configured source — and `npm run lint` caught it immediately:
18
+ * `descry-boundary/no-language-vocabulary` and `no-language-branch` both
19
+ * fired, because `packages/*\/src/**` (this file included, not just
20
+ * `packages/core`) is bound by architecture principle 7 — nothing above the
21
+ * Canonical IR may name or dispatch on a language, full stop. A hardcoded
22
+ * extension table and a `.get(guessedLanguage)` lookup are exactly the
23
+ * "lookup dispatch" shape that rule exists to catch, not a false positive.
24
+ *
25
+ * The fix moves the decision below the boundary, where it belongs:
26
+ * `LanguageAdapter` (`@descryy/ir`) gained an optional
27
+ * `ownsPath(path): boolean` — the adapter decides, using whatever
28
+ * language-specific logic it wants, entirely inside `descry-adapters`,
29
+ * which is not bound by this repo's lint. This tool never sees or compares
30
+ * a language string; it just asks every loaded, configured adapter "do you
31
+ * own this path," and attaches whichever one says yes. An unresolved ref's
32
+ * `language` therefore stays `null` here always — this tool does not
33
+ * determine one, and does not need to: `computeVerificationStatus`'s reason
34
+ * text already reads correctly either way (see its own module header),
35
+ * naming an adapter as registered without ever naming which language it is
36
+ * for.
37
+ *
38
+ * A configured source that fails to import cannot be asked at all — this
39
+ * tool's own `notes` disclose that plainly, the same register `analyze.ts`
40
+ * already uses for a source that will not import.
41
+ *
42
+ * ## Resolving a ref: node id first, then file
43
+ *
44
+ * A ref that is a real node id resolves directly (`getNode`). Otherwise it
45
+ * is tried as a file path (`findNodes({ file: ref })`, the same reader
46
+ * `lookup.ts`'s `resolveOneNode` already uses for symbol/file matching): the
47
+ * file's own `FILE`-typed node stands in when one exists, else the node
48
+ * with the *weakest* resolution in that file — the same "report the
49
+ * weakest link" convention `resolutionFloor` uses elsewhere in this
50
+ * package, since a file is only as verified as its least-resolved member.
51
+ * A ref that resolves neither way is `notExercised`.
52
+ */
53
+ import type { ResolutionLevel } from "@descryy/ir";
54
+ import { type ToolDefinition } from "./kit.ts";
55
+ export interface VerificationStatusExercisedItem {
56
+ readonly ref: string;
57
+ readonly resolution: ResolutionLevel;
58
+ }
59
+ export interface VerificationStatusData {
60
+ readonly checked: readonly string[];
61
+ readonly exercised: readonly VerificationStatusExercisedItem[];
62
+ readonly notExercised: readonly {
63
+ readonly scope: string;
64
+ readonly reason: string;
65
+ }[];
66
+ }
67
+ export declare const verificationStatusTool: ToolDefinition;
68
+ //# sourceMappingURL=verification-status.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verification-status.d.ts","sourceRoot":"","sources":["../../src/tools/verification-status.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AAIH,OAAO,KAAK,EAA4B,eAAe,EAAE,MAAM,aAAa,CAAC;AAE7E,OAAO,EAKL,KAAK,cAAc,EACpB,MAAM,UAAU,CAAC;AAqBlB,MAAM,WAAW,+BAA+B;IAC9C,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,UAAU,EAAE,eAAe,CAAC;CACtC;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,SAAS,EAAE,SAAS,+BAA+B,EAAE,CAAC;IAC/D,QAAQ,CAAC,YAAY,EAAE,SAAS;QAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CACvF;AAoHD,eAAO,MAAM,sBAAsB,EAAE,cAgBpC,CAAC"}