sverklo 0.3.0 → 0.4.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.
@@ -0,0 +1,293 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { dirname, resolve } from "node:path";
3
+ /**
4
+ * Scan a TypeScript/JavaScript file for GraphQL operations (gql tagged templates,
5
+ * graphql() calls, .graphql imports) and resolve field references against known contracts.
6
+ */
7
+ export function detectGraphQLConsumers(filePath, content, knownContracts) {
8
+ const matches = [];
9
+ // Build lookup maps from known contracts
10
+ const endpointContracts = new Map();
11
+ const fieldContracts = new Set();
12
+ const typeReturnMap = new Map(); // "Query.user" -> "User"
13
+ for (const c of knownContracts) {
14
+ if (c.symbolKind === "endpoint") {
15
+ endpointContracts.set(c.symbolName, c);
16
+ // Extract return type from signature, e.g. "user(id: ID!): User" -> "User"
17
+ const returnType = extractReturnType(c.signature ?? "");
18
+ if (returnType) {
19
+ typeReturnMap.set(c.symbolName, returnType);
20
+ }
21
+ }
22
+ if (c.symbolKind === "field" || c.symbolKind === "endpoint") {
23
+ fieldContracts.add(c.symbolName);
24
+ }
25
+ }
26
+ // Collect all GraphQL operation strings with their source locations
27
+ const operations = extractOperations(filePath, content);
28
+ for (const op of operations) {
29
+ const parsed = parseGraphQLOperation(op.graphql);
30
+ if (!parsed)
31
+ continue;
32
+ const referencedFields = [];
33
+ // Resolve root fields against endpoints
34
+ for (const rootField of parsed.rootFields) {
35
+ const endpointName = `${capitalize(parsed.operationType)}.${rootField.name}`;
36
+ const returnType = typeReturnMap.get(endpointName);
37
+ if (endpointContracts.has(endpointName)) {
38
+ referencedFields.push(endpointName);
39
+ }
40
+ // Resolve nested selections
41
+ if (rootField.selections.length > 0) {
42
+ const parentType = returnType ?? capitalize(rootField.name);
43
+ resolveSelections(rootField.selections, parentType, fieldContracts, typeReturnMap, referencedFields, returnType != null);
44
+ }
45
+ }
46
+ if (referencedFields.length > 0) {
47
+ matches.push({
48
+ file: filePath,
49
+ symbol: op.enclosingSymbol,
50
+ line: op.line,
51
+ referencedFields: [...new Set(referencedFields)],
52
+ edgeType: parsed.operationType,
53
+ });
54
+ }
55
+ }
56
+ return matches;
57
+ }
58
+ function extractOperations(filePath, content) {
59
+ const ops = [];
60
+ // Pattern 1: gql`...` or graphql`...` tagged template literals
61
+ const taggedTemplateRe = /\b(?:gql|graphql)\s*`([\s\S]*?)`/g;
62
+ let m;
63
+ while ((m = taggedTemplateRe.exec(content)) !== null) {
64
+ const line = lineAt(content, m.index);
65
+ ops.push({
66
+ graphql: m[1],
67
+ line,
68
+ enclosingSymbol: findEnclosingSymbol(content, m.index),
69
+ });
70
+ }
71
+ // Pattern 2: graphql("...") or graphql('...') function call with a string literal
72
+ const graphqlCallRe = /\bgraphql\s*\(\s*(['"`])([\s\S]*?)\1\s*\)/g;
73
+ while ((m = graphqlCallRe.exec(content)) !== null) {
74
+ const line = lineAt(content, m.index);
75
+ ops.push({
76
+ graphql: m[2],
77
+ line,
78
+ enclosingSymbol: findEnclosingSymbol(content, m.index),
79
+ });
80
+ }
81
+ // Pattern 3: import from .graphql file
82
+ const graphqlImportRe = /import\s+.*?\s+from\s+['"]([^'"]+\.graphql)['"]/g;
83
+ while ((m = graphqlImportRe.exec(content)) !== null) {
84
+ const importPath = m[1];
85
+ const resolved = resolve(dirname(filePath), importPath);
86
+ try {
87
+ const graphqlContent = readFileSync(resolved, "utf-8");
88
+ const line = lineAt(content, m.index);
89
+ ops.push({
90
+ graphql: graphqlContent,
91
+ line,
92
+ enclosingSymbol: findEnclosingSymbol(content, m.index),
93
+ });
94
+ }
95
+ catch {
96
+ // File not found — skip silently
97
+ }
98
+ }
99
+ return ops;
100
+ }
101
+ function parseGraphQLOperation(graphql) {
102
+ // Strip comments
103
+ const cleaned = graphql.replace(/#[^\n]*/g, "");
104
+ // Find operation keyword
105
+ const opMatch = cleaned.match(/\b(query|mutation|subscription)\b\s*([A-Za-z_]\w*)?\s*(?:\([^)]*\))?\s*\{/);
106
+ // If no explicit operation keyword, assume it's a query if we see { at top level
107
+ let operationType = "query";
108
+ let operationName = null;
109
+ let bodyStart;
110
+ if (opMatch) {
111
+ operationType = opMatch[1];
112
+ operationName = opMatch[2] ?? null;
113
+ bodyStart = opMatch.index + opMatch[0].length - 1; // position of {
114
+ }
115
+ else {
116
+ const braceIdx = cleaned.indexOf("{");
117
+ if (braceIdx === -1)
118
+ return null;
119
+ bodyStart = braceIdx;
120
+ }
121
+ const rootFields = parseSelectionSet(cleaned, bodyStart);
122
+ if (rootFields.length === 0)
123
+ return null;
124
+ return { operationType, operationName, rootFields };
125
+ }
126
+ /**
127
+ * Parse a selection set starting at the opening brace.
128
+ * Returns the field selections inside.
129
+ */
130
+ function parseSelectionSet(src, bracePos) {
131
+ const fields = [];
132
+ // Find the matching closing brace
133
+ let depth = 0;
134
+ let i = bracePos;
135
+ if (src[i] !== "{")
136
+ return fields;
137
+ depth = 1;
138
+ i++;
139
+ while (i < src.length && depth > 0) {
140
+ const ch = src[i];
141
+ if (ch === "{") {
142
+ depth++;
143
+ i++;
144
+ }
145
+ else if (ch === "}") {
146
+ depth--;
147
+ i++;
148
+ }
149
+ else if (depth === 1) {
150
+ // At the top-level of this selection set — look for a field name
151
+ const fieldMatch = src.slice(i).match(/^(\s*(?:\.\.\.\s+on\s+\w+\s*\{[\s\S]*?\}|\.\.\.\w+)\s*)/);
152
+ if (fieldMatch) {
153
+ // Fragment spread or inline fragment — skip for now
154
+ i += fieldMatch[1].length;
155
+ continue;
156
+ }
157
+ const nameMatch = src.slice(i).match(/^\s*([A-Za-z_]\w*)\s*(?:\([^)]*\))?\s*/);
158
+ if (nameMatch) {
159
+ const fieldName = nameMatch[1];
160
+ // Skip GraphQL keywords that aren't field names
161
+ if (fieldName === "on" || fieldName === "fragment") {
162
+ i += nameMatch[0].length;
163
+ continue;
164
+ }
165
+ // Check for alias: `alias: fieldName`
166
+ const afterName = i + nameMatch[0].length;
167
+ let actualName = fieldName;
168
+ // Handle alias pattern: aliasName: actualFieldName
169
+ const aliasCheck = src.slice(afterName).match(/^:\s*([A-Za-z_]\w*)\s*(?:\([^)]*\))?\s*/);
170
+ if (aliasCheck) {
171
+ actualName = aliasCheck[1];
172
+ const subStart = afterName + aliasCheck[0].length;
173
+ if (src[subStart] === "{") {
174
+ const subFields = parseSelectionSet(src, subStart);
175
+ fields.push({ name: actualName, selections: subFields });
176
+ // Advance past the sub-selection
177
+ i = skipSelectionSet(src, subStart);
178
+ }
179
+ else {
180
+ fields.push({ name: actualName, selections: [] });
181
+ i = subStart;
182
+ }
183
+ }
184
+ else if (src[afterName] === "{") {
185
+ const subFields = parseSelectionSet(src, afterName);
186
+ fields.push({ name: actualName, selections: subFields });
187
+ i = skipSelectionSet(src, afterName);
188
+ }
189
+ else {
190
+ fields.push({ name: actualName, selections: [] });
191
+ i = afterName;
192
+ }
193
+ }
194
+ else {
195
+ i++;
196
+ }
197
+ }
198
+ else {
199
+ i++;
200
+ }
201
+ }
202
+ return fields;
203
+ }
204
+ /** Skip past a balanced { ... } block, returning the position after the closing brace. */
205
+ function skipSelectionSet(src, bracePos) {
206
+ let depth = 0;
207
+ let i = bracePos;
208
+ while (i < src.length) {
209
+ if (src[i] === "{")
210
+ depth++;
211
+ else if (src[i] === "}") {
212
+ depth--;
213
+ if (depth === 0)
214
+ return i + 1;
215
+ }
216
+ i++;
217
+ }
218
+ return i;
219
+ }
220
+ // ---------------------------------------------------------------------------
221
+ // Field resolution against known contracts
222
+ // ---------------------------------------------------------------------------
223
+ function resolveSelections(selections, parentType, fieldContracts, typeReturnMap, referencedFields, exact) {
224
+ for (const sel of selections) {
225
+ const qualifiedName = `${parentType}.${sel.name}`;
226
+ if (fieldContracts.has(qualifiedName)) {
227
+ referencedFields.push(qualifiedName);
228
+ }
229
+ else if (!exact) {
230
+ // Inferred match — still include it but it will get 0.8 confidence
231
+ // at the edge level (handled by the caller/cross-indexer)
232
+ referencedFields.push(qualifiedName);
233
+ }
234
+ // Recurse into nested selections
235
+ if (sel.selections.length > 0) {
236
+ // Try to find the return type for this field from endpoint map
237
+ const returnType = typeReturnMap.get(qualifiedName);
238
+ const childType = returnType ?? capitalize(sel.name);
239
+ const childExact = returnType != null;
240
+ resolveSelections(sel.selections, childType, fieldContracts, typeReturnMap, referencedFields, childExact);
241
+ }
242
+ }
243
+ }
244
+ // ---------------------------------------------------------------------------
245
+ // Helpers
246
+ // ---------------------------------------------------------------------------
247
+ /** Extract the return type from a GraphQL field signature like "user(id: ID!): User!" */
248
+ function extractReturnType(signature) {
249
+ const m = signature.match(/:\s*\[?\s*([A-Za-z_]\w*)/);
250
+ return m ? m[1] : null;
251
+ }
252
+ /** Capitalize the first letter of a string. */
253
+ function capitalize(s) {
254
+ return s.charAt(0).toUpperCase() + s.slice(1);
255
+ }
256
+ /** Compute 1-based line number at a character offset within content. */
257
+ function lineAt(content, offset) {
258
+ let line = 1;
259
+ for (let i = 0; i < offset && i < content.length; i++) {
260
+ if (content[i] === "\n")
261
+ line++;
262
+ }
263
+ return line;
264
+ }
265
+ /**
266
+ * Find the name of the enclosing function/component at a given offset.
267
+ * Looks backwards for: function X, const X =, export function X, etc.
268
+ */
269
+ function findEnclosingSymbol(content, offset) {
270
+ // Get the content before the offset
271
+ const before = content.slice(0, offset);
272
+ // Try to find the nearest function/const/class declaration
273
+ // Search backwards through the lines
274
+ const lines = before.split("\n");
275
+ for (let i = lines.length - 1; i >= 0 && i >= lines.length - 30; i--) {
276
+ const line = lines[i];
277
+ // function declaration
278
+ const funcMatch = line.match(/(?:export\s+)?(?:async\s+)?function\s+([A-Za-z_]\w*)/);
279
+ if (funcMatch)
280
+ return funcMatch[1];
281
+ // arrow function / const assignment
282
+ const constMatch = line.match(/(?:export\s+)?(?:const|let|var)\s+([A-Za-z_]\w*)\s*=/);
283
+ if (constMatch)
284
+ return constMatch[1];
285
+ // class method
286
+ const methodMatch = line.match(/^\s*(?:async\s+)?([A-Za-z_]\w*)\s*\(/);
287
+ if (methodMatch && methodMatch[1] !== "if" && methodMatch[1] !== "for" && methodMatch[1] !== "while") {
288
+ return methodMatch[1];
289
+ }
290
+ }
291
+ return "<module>";
292
+ }
293
+ //# sourceMappingURL=graphql-consumer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graphql-consumer.js","sourceRoot":"","sources":["../../../src/workspace/graphql-consumer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAW7C;;;GAGG;AACH,MAAM,UAAU,sBAAsB,CACpC,QAAgB,EAChB,OAAe,EACf,cAAmC;IAEnC,MAAM,OAAO,GAAoB,EAAE,CAAC;IAEpC,yCAAyC;IACzC,MAAM,iBAAiB,GAAG,IAAI,GAAG,EAA6B,CAAC;IAC/D,MAAM,cAAc,GAAG,IAAI,GAAG,EAAU,CAAC;IACzC,MAAM,aAAa,GAAG,IAAI,GAAG,EAAkB,CAAC,CAAC,yBAAyB;IAE1E,KAAK,MAAM,CAAC,IAAI,cAAc,EAAE,CAAC;QAC/B,IAAI,CAAC,CAAC,UAAU,KAAK,UAAU,EAAE,CAAC;YAChC,iBAAiB,CAAC,GAAG,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC;YACvC,2EAA2E;YAC3E,MAAM,UAAU,GAAG,iBAAiB,CAAC,CAAC,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC;YACxD,IAAI,UAAU,EAAE,CAAC;gBACf,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;YAC9C,CAAC;QACH,CAAC;QACD,IAAI,CAAC,CAAC,UAAU,KAAK,OAAO,IAAI,CAAC,CAAC,UAAU,KAAK,UAAU,EAAE,CAAC;YAC5D,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;QACnC,CAAC;IACH,CAAC;IAED,oEAAoE;IACpE,MAAM,UAAU,GAAG,iBAAiB,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAExD,KAAK,MAAM,EAAE,IAAI,UAAU,EAAE,CAAC;QAC5B,MAAM,MAAM,GAAG,qBAAqB,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC;QACjD,IAAI,CAAC,MAAM;YAAE,SAAS;QAEtB,MAAM,gBAAgB,GAAa,EAAE,CAAC;QAEtC,wCAAwC;QACxC,KAAK,MAAM,SAAS,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC;YAC1C,MAAM,YAAY,GAAG,GAAG,UAAU,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,SAAS,CAAC,IAAI,EAAE,CAAC;YAC7E,MAAM,UAAU,GAAG,aAAa,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;YAEnD,IAAI,iBAAiB,CAAC,GAAG,CAAC,YAAY,CAAC,EAAE,CAAC;gBACxC,gBAAgB,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;YACtC,CAAC;YAED,4BAA4B;YAC5B,IAAI,SAAS,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACpC,MAAM,UAAU,GAAG,UAAU,IAAI,UAAU,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;gBAC5D,iBAAiB,CACf,SAAS,CAAC,UAAU,EACpB,UAAU,EACV,cAAc,EACd,aAAa,EACb,gBAAgB,EAChB,UAAU,IAAI,IAAI,CACnB,CAAC;YACJ,CAAC;QACH,CAAC;QAED,IAAI,gBAAgB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAChC,OAAO,CAAC,IAAI,CAAC;gBACX,IAAI,EAAE,QAAQ;gBACd,MAAM,EAAE,EAAE,CAAC,eAAe;gBAC1B,IAAI,EAAE,EAAE,CAAC,IAAI;gBACb,gBAAgB,EAAE,CAAC,GAAG,IAAI,GAAG,CAAC,gBAAgB,CAAC,CAAC;gBAChD,QAAQ,EAAE,MAAM,CAAC,aAAsD;aACxE,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC;AAYD,SAAS,iBAAiB,CACxB,QAAgB,EAChB,OAAe;IAEf,MAAM,GAAG,GAAyB,EAAE,CAAC;IAErC,+DAA+D;IAC/D,MAAM,gBAAgB,GAAG,mCAAmC,CAAC;IAC7D,IAAI,CAAyB,CAAC;IAC9B,OAAO,CAAC,CAAC,GAAG,gBAAgB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QACrD,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC;QACtC,GAAG,CAAC,IAAI,CAAC;YACP,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC;YACb,IAAI;YACJ,eAAe,EAAE,mBAAmB,CAAC,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC;SACvD,CAAC,CAAC;IACL,CAAC;IAED,kFAAkF;IAClF,MAAM,aAAa,GAAG,4CAA4C,CAAC;IACnE,OAAO,CAAC,CAAC,GAAG,aAAa,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QAClD,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC;QACtC,GAAG,CAAC,IAAI,CAAC;YACP,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC;YACb,IAAI;YACJ,eAAe,EAAE,mBAAmB,CAAC,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC;SACvD,CAAC,CAAC;IACL,CAAC;IAED,uCAAuC;IACvC,MAAM,eAAe,GACnB,kDAAkD,CAAC;IACrD,OAAO,CAAC,CAAC,GAAG,eAAe,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QACpD,MAAM,UAAU,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QACxB,MAAM,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,UAAU,CAAC,CAAC;QACxD,IAAI,CAAC;YACH,MAAM,cAAc,GAAG,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;YACvD,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC;YACtC,GAAG,CAAC,IAAI,CAAC;gBACP,OAAO,EAAE,cAAc;gBACvB,IAAI;gBACJ,eAAe,EAAE,mBAAmB,CAAC,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC;aACvD,CAAC,CAAC;QACL,CAAC;QAAC,MAAM,CAAC;YACP,iCAAiC;QACnC,CAAC;IACH,CAAC;IAED,OAAO,GAAG,CAAC;AACb,CAAC;AAiBD,SAAS,qBAAqB,CAAC,OAAe;IAC5C,iBAAiB;IACjB,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;IAEhD,yBAAyB;IACzB,MAAM,OAAO,GAAG,OAAO,CAAC,KAAK,CAC3B,2EAA2E,CAC5E,CAAC;IAEF,iFAAiF;IACjF,IAAI,aAAa,GAAG,OAAO,CAAC;IAC5B,IAAI,aAAa,GAAkB,IAAI,CAAC;IACxC,IAAI,SAAiB,CAAC;IAEtB,IAAI,OAAO,EAAE,CAAC;QACZ,aAAa,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;QAC3B,aAAa,GAAG,OAAO,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC;QACnC,SAAS,GAAG,OAAO,CAAC,KAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,gBAAgB;IACtE,CAAC;SAAM,CAAC;QACN,MAAM,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACtC,IAAI,QAAQ,KAAK,CAAC,CAAC;YAAE,OAAO,IAAI,CAAC;QACjC,SAAS,GAAG,QAAQ,CAAC;IACvB,CAAC;IAED,MAAM,UAAU,GAAG,iBAAiB,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;IACzD,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAEzC,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,UAAU,EAAE,CAAC;AACtD,CAAC;AAED;;;GAGG;AACH,SAAS,iBAAiB,CAAC,GAAW,EAAE,QAAgB;IACtD,MAAM,MAAM,GAAqB,EAAE,CAAC;IAEpC,kCAAkC;IAClC,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,CAAC,GAAG,QAAQ,CAAC;IACjB,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,GAAG;QAAE,OAAO,MAAM,CAAC;IAClC,KAAK,GAAG,CAAC,CAAC;IACV,CAAC,EAAE,CAAC;IAEJ,OAAO,CAAC,GAAG,GAAG,CAAC,MAAM,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;QACnC,MAAM,EAAE,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;QAClB,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;YACf,KAAK,EAAE,CAAC;YACR,CAAC,EAAE,CAAC;QACN,CAAC;aAAM,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;YACtB,KAAK,EAAE,CAAC;YACR,CAAC,EAAE,CAAC;QACN,CAAC;aAAM,IAAI,KAAK,KAAK,CAAC,EAAE,CAAC;YACvB,iEAAiE;YACjE,MAAM,UAAU,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,yDAAyD,CAAC,CAAC;YACjG,IAAI,UAAU,EAAE,CAAC;gBACf,oDAAoD;gBACpD,CAAC,IAAI,UAAU,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;gBAC1B,SAAS;YACX,CAAC;YAED,MAAM,SAAS,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,wCAAwC,CAAC,CAAC;YAC/E,IAAI,SAAS,EAAE,CAAC;gBACd,MAAM,SAAS,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC;gBAC/B,gDAAgD;gBAChD,IAAI,SAAS,KAAK,IAAI,IAAI,SAAS,KAAK,UAAU,EAAE,CAAC;oBACnD,CAAC,IAAI,SAAS,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;oBACzB,SAAS;gBACX,CAAC;gBAED,sCAAsC;gBACtC,MAAM,SAAS,GAAG,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;gBAC1C,IAAI,UAAU,GAAG,SAAS,CAAC;gBAE3B,mDAAmD;gBACnD,MAAM,UAAU,GAAG,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,yCAAyC,CAAC,CAAC;gBACzF,IAAI,UAAU,EAAE,CAAC;oBACf,UAAU,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC;oBAC3B,MAAM,QAAQ,GAAG,SAAS,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;oBAClD,IAAI,GAAG,CAAC,QAAQ,CAAC,KAAK,GAAG,EAAE,CAAC;wBAC1B,MAAM,SAAS,GAAG,iBAAiB,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;wBACnD,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,UAAU,EAAE,SAAS,EAAE,CAAC,CAAC;wBACzD,iCAAiC;wBACjC,CAAC,GAAG,gBAAgB,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;oBACtC,CAAC;yBAAM,CAAC;wBACN,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC,CAAC;wBAClD,CAAC,GAAG,QAAQ,CAAC;oBACf,CAAC;gBACH,CAAC;qBAAM,IAAI,GAAG,CAAC,SAAS,CAAC,KAAK,GAAG,EAAE,CAAC;oBAClC,MAAM,SAAS,GAAG,iBAAiB,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;oBACpD,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,UAAU,EAAE,SAAS,EAAE,CAAC,CAAC;oBACzD,CAAC,GAAG,gBAAgB,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;gBACvC,CAAC;qBAAM,CAAC;oBACN,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC,CAAC;oBAClD,CAAC,GAAG,SAAS,CAAC;gBAChB,CAAC;YACH,CAAC;iBAAM,CAAC;gBACN,CAAC,EAAE,CAAC;YACN,CAAC;QACH,CAAC;aAAM,CAAC;YACN,CAAC,EAAE,CAAC;QACN,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,0FAA0F;AAC1F,SAAS,gBAAgB,CAAC,GAAW,EAAE,QAAgB;IACrD,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,CAAC,GAAG,QAAQ,CAAC;IACjB,OAAO,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC;QACtB,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,GAAG;YAAE,KAAK,EAAE,CAAC;aACvB,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;YACxB,KAAK,EAAE,CAAC;YACR,IAAI,KAAK,KAAK,CAAC;gBAAE,OAAO,CAAC,GAAG,CAAC,CAAC;QAChC,CAAC;QACD,CAAC,EAAE,CAAC;IACN,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC;AAED,8EAA8E;AAC9E,2CAA2C;AAC3C,8EAA8E;AAE9E,SAAS,iBAAiB,CACxB,UAA4B,EAC5B,UAAkB,EAClB,cAA2B,EAC3B,aAAkC,EAClC,gBAA0B,EAC1B,KAAc;IAEd,KAAK,MAAM,GAAG,IAAI,UAAU,EAAE,CAAC;QAC7B,MAAM,aAAa,GAAG,GAAG,UAAU,IAAI,GAAG,CAAC,IAAI,EAAE,CAAC;QAElD,IAAI,cAAc,CAAC,GAAG,CAAC,aAAa,CAAC,EAAE,CAAC;YACtC,gBAAgB,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QACvC,CAAC;aAAM,IAAI,CAAC,KAAK,EAAE,CAAC;YAClB,mEAAmE;YACnE,0DAA0D;YAC1D,gBAAgB,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QACvC,CAAC;QAED,iCAAiC;QACjC,IAAI,GAAG,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC9B,+DAA+D;YAC/D,MAAM,UAAU,GAAG,aAAa,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;YACpD,MAAM,SAAS,GAAG,UAAU,IAAI,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YACrD,MAAM,UAAU,GAAG,UAAU,IAAI,IAAI,CAAC;YACtC,iBAAiB,CACf,GAAG,CAAC,UAAU,EACd,SAAS,EACT,cAAc,EACd,aAAa,EACb,gBAAgB,EAChB,UAAU,CACX,CAAC;QACJ,CAAC;IACH,CAAC;AACH,CAAC;AAED,8EAA8E;AAC9E,UAAU;AACV,8EAA8E;AAE9E,yFAAyF;AACzF,SAAS,iBAAiB,CAAC,SAAiB;IAC1C,MAAM,CAAC,GAAG,SAAS,CAAC,KAAK,CAAC,0BAA0B,CAAC,CAAC;IACtD,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACzB,CAAC;AAED,+CAA+C;AAC/C,SAAS,UAAU,CAAC,CAAS;IAC3B,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AAChD,CAAC;AAED,wEAAwE;AACxE,SAAS,MAAM,CAAC,OAAe,EAAE,MAAc;IAC7C,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,IAAI,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACtD,IAAI,OAAO,CAAC,CAAC,CAAC,KAAK,IAAI;YAAE,IAAI,EAAE,CAAC;IAClC,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;GAGG;AACH,SAAS,mBAAmB,CAAC,OAAe,EAAE,MAAc;IAC1D,oCAAoC;IACpC,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;IAExC,2DAA2D;IAC3D,qCAAqC;IACrC,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACjC,KAAK,IAAI,CAAC,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,MAAM,GAAG,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC;QACrE,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QAEtB,uBAAuB;QACvB,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAC1B,sDAAsD,CACvD,CAAC;QACF,IAAI,SAAS;YAAE,OAAO,SAAS,CAAC,CAAC,CAAC,CAAC;QAEnC,oCAAoC;QACpC,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAC3B,sDAAsD,CACvD,CAAC;QACF,IAAI,UAAU;YAAE,OAAO,UAAU,CAAC,CAAC,CAAC,CAAC;QAErC,eAAe;QACf,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAC5B,sCAAsC,CACvC,CAAC;QACF,IAAI,WAAW,IAAI,WAAW,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,WAAW,CAAC,CAAC,CAAC,KAAK,KAAK,IAAI,WAAW,CAAC,CAAC,CAAC,KAAK,OAAO,EAAE,CAAC;YACrG,OAAO,WAAW,CAAC,CAAC,CAAC,CAAC;QACxB,CAAC;IACH,CAAC;IAED,OAAO,UAAU,CAAC;AACpB,CAAC"}
@@ -0,0 +1,13 @@
1
+ import type { InterfaceContract } from "./cross-db.js";
2
+ export interface GraphQLExtractionResult {
3
+ contracts: Omit<InterfaceContract, "id" | "projectId">[];
4
+ }
5
+ /**
6
+ * Extract type/field contracts from GraphQL schema SDL files.
7
+ *
8
+ * Produces a flat list of contracts:
9
+ * - One per type/input/interface/enum (symbolKind = "type")
10
+ * - One per field within a type (symbolKind = "field" or "endpoint" for root types)
11
+ * - One per scalar declaration (symbolKind = "type")
12
+ */
13
+ export declare function extractGraphQLContracts(filePath: string, content: string): GraphQLExtractionResult;
@@ -0,0 +1,172 @@
1
+ import { createHash } from "node:crypto";
2
+ // Root types whose fields are exposed as "endpoint" kind
3
+ const ROOT_TYPES = new Set(["Query", "Mutation", "Subscription"]);
4
+ /**
5
+ * Extract type/field contracts from GraphQL schema SDL files.
6
+ *
7
+ * Produces a flat list of contracts:
8
+ * - One per type/input/interface/enum (symbolKind = "type")
9
+ * - One per field within a type (symbolKind = "field" or "endpoint" for root types)
10
+ * - One per scalar declaration (symbolKind = "type")
11
+ */
12
+ export function extractGraphQLContracts(filePath, content) {
13
+ const contracts = [];
14
+ const lines = content.split("\n");
15
+ // Pre-process: strip comments and block descriptions to simplify parsing.
16
+ // We keep the original lines for line-number tracking and hashing,
17
+ // but work on a cleaned version for regex matching.
18
+ const cleaned = stripCommentsAndDescriptions(lines);
19
+ // Extract scalar declarations: scalar DateTime
20
+ for (let i = 0; i < cleaned.length; i++) {
21
+ const scalarMatch = cleaned[i].match(/^\s*scalar\s+([A-Za-z_]\w*)/);
22
+ if (scalarMatch) {
23
+ contracts.push({
24
+ symbolName: scalarMatch[1],
25
+ symbolKind: "type",
26
+ sourceFile: filePath,
27
+ fileLine: i + 1,
28
+ interfaceType: "graphql",
29
+ signature: `scalar ${scalarMatch[1]}`,
30
+ contentHash: sha256(lines[i]),
31
+ });
32
+ }
33
+ }
34
+ // Extract type / input / interface / enum blocks
35
+ // Handles: type Foo { | type Foo implements Bar { | extend type Foo {
36
+ // input Foo { | interface Foo { | enum Foo {
37
+ const blockStartRe = /^\s*(extend\s+)?(type|input|interface|enum)\s+([A-Za-z_]\w*)(?:\s+implements\s+[A-Za-z_][\w\s&,]*)?\s*\{/;
38
+ let i = 0;
39
+ while (i < cleaned.length) {
40
+ const m = cleaned[i].match(blockStartRe);
41
+ if (!m) {
42
+ i++;
43
+ continue;
44
+ }
45
+ const isExtend = !!m[1];
46
+ const blockKind = m[2]; // type | input | interface | enum
47
+ const typeName = m[3];
48
+ const blockStartLine = i;
49
+ // Emit a contract for the type itself (skip for extend blocks — the base
50
+ // type was already declared elsewhere)
51
+ if (!isExtend) {
52
+ contracts.push({
53
+ symbolName: typeName,
54
+ symbolKind: "type",
55
+ sourceFile: filePath,
56
+ fileLine: blockStartLine + 1,
57
+ interfaceType: "graphql",
58
+ signature: cleaned[i].trim(),
59
+ contentHash: sha256(lines[i]),
60
+ });
61
+ }
62
+ // Find the matching closing brace, accounting for nesting (rare in SDL
63
+ // but possible with nested input types etc.)
64
+ let depth = 1;
65
+ i++;
66
+ while (i < cleaned.length && depth > 0) {
67
+ for (const ch of cleaned[i]) {
68
+ if (ch === "{")
69
+ depth++;
70
+ else if (ch === "}")
71
+ depth--;
72
+ }
73
+ if (depth > 0) {
74
+ // This line is inside the block — try to extract a field
75
+ const field = parseField(cleaned[i], typeName, blockKind);
76
+ if (field) {
77
+ const symbolKind = ROOT_TYPES.has(typeName) ? "endpoint" : "field";
78
+ contracts.push({
79
+ symbolName: `${typeName}.${field.name}`,
80
+ symbolKind,
81
+ sourceFile: filePath,
82
+ fileLine: i + 1,
83
+ interfaceType: "graphql",
84
+ signature: field.signature,
85
+ contentHash: sha256(lines[i]),
86
+ });
87
+ }
88
+ }
89
+ i++;
90
+ }
91
+ }
92
+ return { contracts };
93
+ }
94
+ /**
95
+ * Parse a single field line inside a type block.
96
+ * Handles:
97
+ * fieldName: Type
98
+ * fieldName(arg: Type, arg2: Type): Type
99
+ * ENUM_VALUE (inside enum blocks)
100
+ */
101
+ function parseField(line, _typeName, blockKind) {
102
+ const trimmed = line.trim();
103
+ if (!trimmed || trimmed === "{" || trimmed === "}")
104
+ return null;
105
+ if (blockKind === "enum") {
106
+ // Enum values are simple identifiers, possibly followed by @directives
107
+ const enumMatch = trimmed.match(/^([A-Za-z_]\w*)/);
108
+ if (enumMatch) {
109
+ return { name: enumMatch[1], signature: trimmed };
110
+ }
111
+ return null;
112
+ }
113
+ // Field: name(args): ReturnType or name: ReturnType
114
+ const fieldMatch = trimmed.match(/^([A-Za-z_]\w*)\s*(\([^)]*\))?\s*:\s*(.+)/);
115
+ if (fieldMatch) {
116
+ return { name: fieldMatch[1], signature: trimmed };
117
+ }
118
+ return null;
119
+ }
120
+ /**
121
+ * Strip # line comments and """ block descriptions from SDL lines.
122
+ * Returns a new array of the same length, with comment/description lines blanked out.
123
+ */
124
+ function stripCommentsAndDescriptions(lines) {
125
+ const result = new Array(lines.length);
126
+ let inBlockDesc = false;
127
+ for (let i = 0; i < lines.length; i++) {
128
+ if (inBlockDesc) {
129
+ // Look for the closing """
130
+ const closeIdx = lines[i].indexOf('"""');
131
+ if (closeIdx !== -1) {
132
+ inBlockDesc = false;
133
+ // Blank out up to and including the closing """
134
+ result[i] = lines[i].substring(closeIdx + 3);
135
+ }
136
+ else {
137
+ result[i] = "";
138
+ }
139
+ continue;
140
+ }
141
+ let line = lines[i];
142
+ // Handle opening """ — could be single-line """ ... """ or start a block
143
+ const openIdx = line.indexOf('"""');
144
+ if (openIdx !== -1) {
145
+ const afterOpen = line.substring(openIdx + 3);
146
+ const closeIdx = afterOpen.indexOf('"""');
147
+ if (closeIdx !== -1) {
148
+ // Single-line block description: """some text"""
149
+ line = line.substring(0, openIdx) + afterOpen.substring(closeIdx + 3);
150
+ }
151
+ else {
152
+ // Multi-line block description starts
153
+ inBlockDesc = true;
154
+ line = line.substring(0, openIdx);
155
+ }
156
+ }
157
+ // Strip # comments (but not inside strings — SDL doesn't have inline strings
158
+ // outside of descriptions, so this is safe)
159
+ const hashIdx = line.indexOf("#");
160
+ if (hashIdx !== -1) {
161
+ line = line.substring(0, hashIdx);
162
+ }
163
+ // Strip single-line "description" strings that precede fields
164
+ line = line.replace(/^\s*"[^"]*"\s*$/, "");
165
+ result[i] = line;
166
+ }
167
+ return result;
168
+ }
169
+ function sha256(content) {
170
+ return createHash("sha256").update(content).digest("hex");
171
+ }
172
+ //# sourceMappingURL=graphql-extractor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graphql-extractor.js","sourceRoot":"","sources":["../../../src/workspace/graphql-extractor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ export interface WorkspaceProject {
2
+ path: string;
3
+ role: "provider" | "consumer" | "both";
4
+ interfaces?: Array<{
5
+ type: "graphql" | "openapi" | "protobuf" | "npm" | "trpc";
6
+ schema?: string;
7
+ spec?: string;
8
+ }>;
9
+ }
10
+ export interface WorkspaceConfig {
11
+ workspace: string;
12
+ projects: WorkspaceProject[];
13
+ }
14
+ /**
15
+ * Load a workspace config from ~/.sverklo/workspaces/<name>.yaml.
16
+ * Returns null if the file doesn't exist or can't be parsed.
17
+ */
18
+ export declare function loadWorkspaceConfig(name: string): WorkspaceConfig | null;
19
+ /**
20
+ * Scan ~/.sverklo/workspaces/ for .yaml files and return their names.
21
+ */
22
+ export declare function listWorkspaces(): string[];
23
+ /**
24
+ * Find which workspace contains this project path (exact match on
25
+ * any project's path). Returns the first match or null.
26
+ */
27
+ export declare function findWorkspaceForProject(projectPath: string): WorkspaceConfig | null;
28
+ /**
29
+ * Get the path for the cross-repo SQLite database for a workspace.
30
+ * Creates the parent directory if it doesn't exist.
31
+ */
32
+ export declare function getWorkspaceDbPath(name: string): string;
@@ -0,0 +1,91 @@
1
+ import { existsSync, readdirSync, readFileSync, mkdirSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { homedir } from "node:os";
4
+ import { parse as parseYaml } from "yaml";
5
+ const WORKSPACES_DIR = join(homedir(), ".sverklo", "workspaces");
6
+ /**
7
+ * Load a workspace config from ~/.sverklo/workspaces/<name>.yaml.
8
+ * Returns null if the file doesn't exist or can't be parsed.
9
+ */
10
+ export function loadWorkspaceConfig(name) {
11
+ const filePath = join(WORKSPACES_DIR, `${name}.yaml`);
12
+ if (!existsSync(filePath))
13
+ return null;
14
+ try {
15
+ const raw = readFileSync(filePath, "utf-8");
16
+ const parsed = parseYaml(raw);
17
+ if (!parsed || typeof parsed !== "object")
18
+ return null;
19
+ const workspace = String(parsed.workspace ?? name);
20
+ const rawProjects = Array.isArray(parsed.projects) ? parsed.projects : [];
21
+ const projects = rawProjects
22
+ .filter((p) => typeof p === "object" && p !== null && typeof p.path === "string")
23
+ .map((p) => {
24
+ const role = ["provider", "consumer", "both"].includes(p.role)
25
+ ? p.role
26
+ : "both";
27
+ const interfaces = Array.isArray(p.interfaces)
28
+ ? p.interfaces
29
+ .filter((i) => typeof i === "object" &&
30
+ i !== null &&
31
+ typeof i.type === "string")
32
+ .map((i) => ({
33
+ type: i.type,
34
+ ...(typeof i.schema === "string" ? { schema: i.schema } : {}),
35
+ ...(typeof i.spec === "string" ? { spec: i.spec } : {}),
36
+ }))
37
+ : undefined;
38
+ return {
39
+ path: p.path,
40
+ role,
41
+ ...(interfaces && interfaces.length > 0 ? { interfaces } : {}),
42
+ };
43
+ });
44
+ return { workspace, projects };
45
+ }
46
+ catch (err) {
47
+ if (process.env.SVERKLO_DEBUG) {
48
+ process.stderr.write(`[sverklo] Failed to parse workspace '${name}': ${err}\n`);
49
+ }
50
+ return null;
51
+ }
52
+ }
53
+ /**
54
+ * Scan ~/.sverklo/workspaces/ for .yaml files and return their names.
55
+ */
56
+ export function listWorkspaces() {
57
+ if (!existsSync(WORKSPACES_DIR))
58
+ return [];
59
+ try {
60
+ return readdirSync(WORKSPACES_DIR)
61
+ .filter((f) => f.endsWith(".yaml"))
62
+ .map((f) => f.replace(/\.yaml$/, ""));
63
+ }
64
+ catch {
65
+ return [];
66
+ }
67
+ }
68
+ /**
69
+ * Find which workspace contains this project path (exact match on
70
+ * any project's path). Returns the first match or null.
71
+ */
72
+ export function findWorkspaceForProject(projectPath) {
73
+ const names = listWorkspaces();
74
+ for (const name of names) {
75
+ const config = loadWorkspaceConfig(name);
76
+ if (config && config.projects.some((p) => p.path === projectPath)) {
77
+ return config;
78
+ }
79
+ }
80
+ return null;
81
+ }
82
+ /**
83
+ * Get the path for the cross-repo SQLite database for a workspace.
84
+ * Creates the parent directory if it doesn't exist.
85
+ */
86
+ export function getWorkspaceDbPath(name) {
87
+ const dir = join(WORKSPACES_DIR, name);
88
+ mkdirSync(dir, { recursive: true });
89
+ return join(dir, "cross.db");
90
+ }
91
+ //# sourceMappingURL=workspace-config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace-config.js","sourceRoot":"","sources":["../../../src/workspace/workspace-config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAC3E,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,KAAK,IAAI,SAAS,EAAE,MAAM,MAAM,CAAC;AAE1C,MAAM,cAAc,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,UAAU,EAAE,YAAY,CAAC,CAAC;AAiBjE;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAY;IAC9C,MAAM,QAAQ,GAAG,IAAI,CAAC,cAAc,EAAE,GAAG,IAAI,OAAO,CAAC,CAAC;IACtD,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC;QAAE,OAAO,IAAI,CAAC;IACvC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QAC5C,MAAM,MAAM,GAAG,SAAS,CAAC,GAAG,CAA4B,CAAC;QACzD,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAC;QAEvD,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC,SAAS,IAAI,IAAI,CAAC,CAAC;QACnD,MAAM,WAAW,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;QAE1E,MAAM,QAAQ,GAAuB,WAAW;aAC7C,MAAM,CACL,CAAC,CAAU,EAAgC,EAAE,CAC3C,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,IAAI,IAAI,OAAQ,CAA6B,CAAC,IAAI,KAAK,QAAQ,CACjG;aACA,GAAG,CAAC,CAAC,CAA0B,EAAE,EAAE;YAClC,MAAM,IAAI,GAAI,CAAC,UAAU,EAAE,UAAU,EAAE,MAAM,CAAW,CAAC,QAAQ,CAC/D,CAAC,CAAC,IAAwC,CAC3C;gBACC,CAAC,CAAE,CAAC,CAAC,IAAyC;gBAC9C,CAAC,CAAC,MAAM,CAAC;YAEX,MAAM,UAAU,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC;gBAC5C,CAAC,CAAE,CAAC,CAAC,UAAwC;qBACxC,MAAM,CACL,CAAC,CAAC,EAAE,EAAE,CACJ,OAAO,CAAC,KAAK,QAAQ;oBACrB,CAAC,KAAK,IAAI;oBACV,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ,CAC7B;qBACA,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;oBACX,IAAI,EAAE,CAAC,CAAC,IAA2D;oBACnE,GAAG,CAAC,OAAO,CAAC,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;oBAC7D,GAAG,CAAC,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;iBACxD,CAAC,CAAC;gBACP,CAAC,CAAC,SAAS,CAAC;YAEd,OAAO;gBACL,IAAI,EAAE,CAAC,CAAC,IAAc;gBACtB,IAAI;gBACJ,GAAG,CAAC,UAAU,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aAC/D,CAAC;QACJ,CAAC,CAAC,CAAC;QAEL,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC;IACjC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,OAAO,CAAC,GAAG,CAAC,aAAa,EAAE,CAAC;YAC9B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,wCAAwC,IAAI,MAAM,GAAG,IAAI,CAAC,CAAC;QAClF,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,cAAc;IAC5B,IAAI,CAAC,UAAU,CAAC,cAAc,CAAC;QAAE,OAAO,EAAE,CAAC;IAC3C,IAAI,CAAC;QACH,OAAO,WAAW,CAAC,cAAc,CAAC;aAC/B,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;aAClC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,CAAC;IAC1C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,uBAAuB,CAAC,WAAmB;IACzD,MAAM,KAAK,GAAG,cAAc,EAAE,CAAC;IAC/B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,MAAM,GAAG,mBAAmB,CAAC,IAAI,CAAC,CAAC;QACzC,IAAI,MAAM,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,WAAW,CAAC,EAAE,CAAC;YAClE,OAAO,MAAM,CAAC;QAChB,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAAY;IAC7C,MAAM,GAAG,GAAG,IAAI,CAAC,cAAc,EAAE,IAAI,CAAC,CAAC;IACvC,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACpC,OAAO,IAAI,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC;AAC/B,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sverklo",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Sverklo — local-first code intelligence MCP server. Diff-aware MR review, risk scoring, hybrid semantic search, PageRank ranking, persistent memory. Zero config.",
5
5
  "type": "module",
6
6
  "bin": {