@kernlang/review 3.3.5 → 3.3.6

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 (50) hide show
  1. package/dist/concept-rules/auth-drift.d.ts +29 -0
  2. package/dist/concept-rules/auth-drift.js +127 -0
  3. package/dist/concept-rules/auth-drift.js.map +1 -0
  4. package/dist/concept-rules/contract-drift.js +2 -3
  5. package/dist/concept-rules/contract-drift.js.map +1 -1
  6. package/dist/concept-rules/contract-method-drift.d.ts +22 -0
  7. package/dist/concept-rules/contract-method-drift.js +105 -0
  8. package/dist/concept-rules/contract-method-drift.js.map +1 -0
  9. package/dist/concept-rules/cross-stack-utils.d.ts +46 -0
  10. package/dist/concept-rules/cross-stack-utils.js +161 -0
  11. package/dist/concept-rules/cross-stack-utils.js.map +1 -1
  12. package/dist/concept-rules/duplicate-route.d.ts +20 -0
  13. package/dist/concept-rules/duplicate-route.js +112 -0
  14. package/dist/concept-rules/duplicate-route.js.map +1 -0
  15. package/dist/concept-rules/index.js +14 -0
  16. package/dist/concept-rules/index.js.map +1 -1
  17. package/dist/concept-rules/missing-response-model.d.ts +10 -0
  18. package/dist/concept-rules/missing-response-model.js +38 -0
  19. package/dist/concept-rules/missing-response-model.js.map +1 -0
  20. package/dist/concept-rules/orphan-route.d.ts +20 -0
  21. package/dist/concept-rules/orphan-route.js +96 -0
  22. package/dist/concept-rules/orphan-route.js.map +1 -0
  23. package/dist/concept-rules/sync-handler-does-io.d.ts +9 -0
  24. package/dist/concept-rules/sync-handler-does-io.js +56 -0
  25. package/dist/concept-rules/sync-handler-does-io.js.map +1 -0
  26. package/dist/concept-rules/tainted-across-wire.js +2 -5
  27. package/dist/concept-rules/tainted-across-wire.js.map +1 -1
  28. package/dist/concept-rules/untyped-api-response.js +8 -6
  29. package/dist/concept-rules/untyped-api-response.js.map +1 -1
  30. package/dist/concept-rules/untyped-both-ends-response.d.ts +10 -0
  31. package/dist/concept-rules/untyped-both-ends-response.js +55 -0
  32. package/dist/concept-rules/untyped-both-ends-response.js.map +1 -0
  33. package/dist/index.js +40 -3
  34. package/dist/index.js.map +1 -1
  35. package/dist/llm-bridge.d.ts +12 -0
  36. package/dist/llm-bridge.js +131 -7
  37. package/dist/llm-bridge.js.map +1 -1
  38. package/dist/mappers/ts-concepts.js +406 -13
  39. package/dist/mappers/ts-concepts.js.map +1 -1
  40. package/dist/rules/index.js +16 -0
  41. package/dist/rules/index.js.map +1 -1
  42. package/dist/rules/kern-source.js +2 -0
  43. package/dist/rules/kern-source.js.map +1 -1
  44. package/dist/rules/set-setter-collision.d.ts +21 -0
  45. package/dist/rules/set-setter-collision.js +74 -0
  46. package/dist/rules/set-setter-collision.js.map +1 -0
  47. package/dist/rules/suggest-kern-primitive.d.ts +30 -0
  48. package/dist/rules/suggest-kern-primitive.js +543 -0
  49. package/dist/rules/suggest-kern-primitive.js.map +1 -0
  50. package/package.json +2 -2
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Rule: duplicate-route
3
+ *
4
+ * Server-side rule — fires when two or more route decorators declare the
5
+ * same `{path, method}` combination in the reviewed project. Real-bug
6
+ * classes:
7
+ * - Someone renamed a handler but forgot to delete the old one; both fire,
8
+ * order-dependent, one silently shadows the other.
9
+ * - A copy-paste left an `@router.get("/users")` pair intact.
10
+ * - A FastAPI router was mounted twice under the same prefix by accident.
11
+ *
12
+ * Fires on the SECOND and later occurrences (the first is the canonical
13
+ * declaration; duplicates are the bug). No-verb routes (`app.use`) key
14
+ * under `ANY` so two `use('/x')` calls still surface as duplicates.
15
+ *
16
+ * Path-only scope: does not cross-correlate client calls. Graph mode only.
17
+ */
18
+ import { createFingerprint } from '../types.js';
19
+ import { CROSS_STACK_EXACT_CONFIDENCE, collectRoutesAcrossGraph } from './cross-stack-utils.js';
20
+ export function duplicateRoute(ctx) {
21
+ if (!ctx.allConcepts || ctx.allConcepts.size === 0)
22
+ return [];
23
+ const routes = collectRoutesAcrossGraph(ctx.allConcepts);
24
+ if (routes.length < 2)
25
+ return [];
26
+ // Group by `${METHOD} ${path}` for exact duplicates, BUT wildcard routes
27
+ // (`ALL`/`ANY`/undefined) shadow specific verbs on the same path. Codex
28
+ // review caught that the naïve keying missed `app.all('/x')` +
29
+ // `app.get('/x')` collisions. We do two passes:
30
+ // 1. Group routes at each path by wildcard-vs-specific classification.
31
+ // 2. Within a path, if there's a wildcard route AND any specific verb
32
+ // route, they collide (pick the later one as the "duplicate").
33
+ // 3. Also flag same-path-same-method duplicates as before.
34
+ const byKey = new Map();
35
+ const byPath = new Map();
36
+ for (const r of routes) {
37
+ const method = (r.method ?? 'ANY').toUpperCase();
38
+ const key = `${method} ${r.path}`;
39
+ const keyList = byKey.get(key) ?? [];
40
+ keyList.push(r);
41
+ byKey.set(key, keyList);
42
+ const pathList = byPath.get(r.path) ?? [];
43
+ pathList.push(r);
44
+ byPath.set(r.path, pathList);
45
+ }
46
+ // Wildcard-vs-specific collisions: when a path has a wildcard-accepting
47
+ // route and any specific-verb route, the handlers shadow each other.
48
+ const WILDCARD = new Set(['ALL', 'ANY']);
49
+ for (const [, pathRoutes] of byPath) {
50
+ if (pathRoutes.length < 2)
51
+ continue;
52
+ const wildcards = pathRoutes.filter((r) => {
53
+ const m = (r.method ?? 'ANY').toUpperCase();
54
+ return WILDCARD.has(m);
55
+ });
56
+ if (wildcards.length === 0)
57
+ continue;
58
+ // Synthesize a collision key so the existing emission loop fires.
59
+ // Keep the wildcard as canonical (first), flag every specific-verb route
60
+ // on this path as the duplicate.
61
+ const specifics = pathRoutes.filter((r) => {
62
+ const m = (r.method ?? 'ANY').toUpperCase();
63
+ return !WILDCARD.has(m);
64
+ });
65
+ if (specifics.length === 0)
66
+ continue;
67
+ const collisionKey = `* ${wildcards[0].path}`;
68
+ if (!byKey.has(collisionKey))
69
+ byKey.set(collisionKey, [wildcards[0], ...specifics]);
70
+ }
71
+ const findings = [];
72
+ const seen = new Set();
73
+ for (const [key, list] of byKey) {
74
+ if (list.length < 2)
75
+ continue;
76
+ const isWildcardCollision = key.startsWith('* ');
77
+ const duplicates = list.slice(1);
78
+ for (const dup of duplicates) {
79
+ if (!dup.node || dup.node.primarySpan.file !== ctx.filePath)
80
+ continue;
81
+ const fingerprint = createFingerprint('duplicate-route', dup.node.primarySpan.startLine, dup.node.primarySpan.startCol);
82
+ if (seen.has(fingerprint))
83
+ continue;
84
+ seen.add(fingerprint);
85
+ const first = list[0];
86
+ const firstFile = first.node?.primarySpan.file ?? 'another file';
87
+ const firstLine = first.node?.primarySpan.startLine;
88
+ const firstRef = firstLine != null ? `${shortPath(firstFile)}:${firstLine}` : shortPath(firstFile);
89
+ const firstMethod = (first.method ?? 'ANY').toUpperCase();
90
+ const dupMethod = (dup.method ?? 'ANY').toUpperCase();
91
+ const message = isWildcardCollision
92
+ ? `Route \`${dupMethod} ${dup.path}\` is shadowed by wildcard route \`${firstMethod} ${dup.path}\` at ${firstRef}. The wildcard handler will match ${dupMethod} requests depending on registration order.`
93
+ : `Duplicate route declaration: \`${key}\` is already declared at ${firstRef}. One of the two handlers will silently shadow the other.`;
94
+ findings.push({
95
+ source: 'kern',
96
+ ruleId: 'duplicate-route',
97
+ severity: 'warning',
98
+ category: 'bug',
99
+ message,
100
+ primarySpan: dup.node.primarySpan,
101
+ fingerprint,
102
+ confidence: dup.node.confidence * CROSS_STACK_EXACT_CONFIDENCE,
103
+ });
104
+ }
105
+ }
106
+ return findings;
107
+ }
108
+ function shortPath(filePath) {
109
+ const parts = filePath.split('/');
110
+ return parts.slice(-2).join('/');
111
+ }
112
+ //# sourceMappingURL=duplicate-route.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"duplicate-route.js","sourceRoot":"","sources":["../../src/concept-rules/duplicate-route.ts"],"names":[],"mappings":"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"}
@@ -4,21 +4,35 @@
4
4
  * These rules work on any language that emits concepts.
5
5
  * Language-agnostic by design.
6
6
  */
7
+ import { authDrift } from './auth-drift.js';
7
8
  import { boundaryMutation } from './boundary-mutation.js';
8
9
  import { contractDrift } from './contract-drift.js';
10
+ import { contractMethodDrift } from './contract-method-drift.js';
11
+ import { duplicateRoute } from './duplicate-route.js';
9
12
  import { ignoredError } from './ignored-error.js';
13
+ import { missingResponseModel } from './missing-response-model.js';
14
+ import { orphanRoute } from './orphan-route.js';
15
+ import { syncHandlerDoesIo } from './sync-handler-does-io.js';
10
16
  import { taintedAcrossWire } from './tainted-across-wire.js';
11
17
  import { unguardedEffect } from './unguarded-effect.js';
12
18
  import { unrecoveredEffect } from './unrecovered-effect.js';
13
19
  import { untypedApiResponse } from './untyped-api-response.js';
20
+ import { untypedBothEndsResponse } from './untyped-both-ends-response.js';
14
21
  export const conceptRules = [
22
+ authDrift,
15
23
  boundaryMutation,
16
24
  contractDrift,
25
+ contractMethodDrift,
26
+ duplicateRoute,
17
27
  ignoredError,
28
+ missingResponseModel,
29
+ syncHandlerDoesIo,
30
+ orphanRoute,
18
31
  taintedAcrossWire,
19
32
  unguardedEffect,
20
33
  unrecoveredEffect,
21
34
  untypedApiResponse,
35
+ untypedBothEndsResponse,
22
36
  ];
23
37
  export function runConceptRules(concepts, filePath, allConcepts, graphImports) {
24
38
  const ctx = { concepts, filePath, allConcepts, graphImports };
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/concept-rules/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAa/D,MAAM,CAAC,MAAM,YAAY,GAAkB;IACzC,gBAAgB;IAChB,aAAa;IACb,YAAY;IACZ,iBAAiB;IACjB,eAAe;IACf,iBAAiB;IACjB,kBAAkB;CACnB,CAAC;AAEF,MAAM,UAAU,eAAe,CAC7B,QAAoB,EACpB,QAAgB,EAChB,WAAqC,EACrC,YAAoC;IAEpC,MAAM,GAAG,GAAuB,EAAE,QAAQ,EAAE,QAAQ,EAAE,WAAW,EAAE,YAAY,EAAE,CAAC;IAClF,MAAM,QAAQ,GAAoB,EAAE,CAAC;IACrC,KAAK,MAAM,IAAI,IAAI,YAAY,EAAE,CAAC;QAChC,QAAQ,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IAC9B,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/concept-rules/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AACnE,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EAAE,uBAAuB,EAAE,MAAM,iCAAiC,CAAC;AAa1E,MAAM,CAAC,MAAM,YAAY,GAAkB;IACzC,SAAS;IACT,gBAAgB;IAChB,aAAa;IACb,mBAAmB;IACnB,cAAc;IACd,YAAY;IACZ,oBAAoB;IACpB,iBAAiB;IACjB,WAAW;IACX,iBAAiB;IACjB,eAAe;IACf,iBAAiB;IACjB,kBAAkB;IAClB,uBAAuB;CACxB,CAAC;AAEF,MAAM,UAAU,eAAe,CAC7B,QAAoB,EACpB,QAAgB,EAChB,WAAqC,EACrC,YAAoC;IAEpC,MAAM,GAAG,GAAuB,EAAE,QAAQ,EAAE,QAAQ,EAAE,WAAW,EAAE,YAAY,EAAE,CAAC;IAClF,MAAM,QAAQ,GAAoB,EAAE,CAAC;IACrC,KAAK,MAAM,IAAI,IAAI,YAAY,EAAE,CAAC;QAChC,QAAQ,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IAC9B,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Rule: missing-response-model
3
+ *
4
+ * Fires on Python route decorators that do not declare a FastAPI
5
+ * `response_model=...`. Kept Python-scoped because other route mappers do
6
+ * not currently surface an equivalent response-schema signal.
7
+ */
8
+ import type { ReviewFinding } from '../types.js';
9
+ import type { ConceptRuleContext } from './index.js';
10
+ export declare function missingResponseModel(ctx: ConceptRuleContext): ReviewFinding[];
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Rule: missing-response-model
3
+ *
4
+ * Fires on Python route decorators that do not declare a FastAPI
5
+ * `response_model=...`. Kept Python-scoped because other route mappers do
6
+ * not currently surface an equivalent response-schema signal.
7
+ */
8
+ import { createFingerprint } from '../types.js';
9
+ import { CROSS_STACK_HEURISTIC_CONFIDENCE, hasFastApiEvidence } from './cross-stack-utils.js';
10
+ export function missingResponseModel(ctx) {
11
+ const findings = [];
12
+ if (!hasFastApiEvidence(ctx.concepts))
13
+ return findings;
14
+ for (const node of ctx.concepts.nodes) {
15
+ if (node.kind !== 'entrypoint')
16
+ continue;
17
+ if (node.payload.kind !== 'entrypoint')
18
+ continue;
19
+ if (node.payload.subtype !== 'route')
20
+ continue;
21
+ if (node.language !== 'py')
22
+ continue;
23
+ if (node.payload.responseModel)
24
+ continue;
25
+ findings.push({
26
+ source: 'kern',
27
+ ruleId: 'missing-response-model',
28
+ severity: 'warning',
29
+ category: 'bug',
30
+ message: `Route \`${node.payload.name}\` has no FastAPI response_model. Add response_model=... so backend response-shape drift is caught at the contract boundary.`,
31
+ primarySpan: node.primarySpan,
32
+ fingerprint: createFingerprint('missing-response-model', node.primarySpan.startLine, node.primarySpan.startCol),
33
+ confidence: node.confidence * CROSS_STACK_HEURISTIC_CONFIDENCE,
34
+ });
35
+ }
36
+ return findings;
37
+ }
38
+ //# sourceMappingURL=missing-response-model.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"missing-response-model.js","sourceRoot":"","sources":["../../src/concept-rules/missing-response-model.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,gCAAgC,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAG9F,MAAM,UAAU,oBAAoB,CAAC,GAAuB;IAC1D,MAAM,QAAQ,GAAoB,EAAE,CAAC;IACrC,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,QAAQ,CAAC;QAAE,OAAO,QAAQ,CAAC;IAEvD,KAAK,MAAM,IAAI,IAAI,GAAG,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;QACtC,IAAI,IAAI,CAAC,IAAI,KAAK,YAAY;YAAE,SAAS;QACzC,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,KAAK,YAAY;YAAE,SAAS;QACjD,IAAI,IAAI,CAAC,OAAO,CAAC,OAAO,KAAK,OAAO;YAAE,SAAS;QAC/C,IAAI,IAAI,CAAC,QAAQ,KAAK,IAAI;YAAE,SAAS;QACrC,IAAI,IAAI,CAAC,OAAO,CAAC,aAAa;YAAE,SAAS;QAEzC,QAAQ,CAAC,IAAI,CAAC;YACZ,MAAM,EAAE,MAAM;YACd,MAAM,EAAE,wBAAwB;YAChC,QAAQ,EAAE,SAAS;YACnB,QAAQ,EAAE,KAAK;YACf,OAAO,EAAE,WAAW,IAAI,CAAC,OAAO,CAAC,IAAI,8HAA8H;YACnK,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,WAAW,EAAE,iBAAiB,CAAC,wBAAwB,EAAE,IAAI,CAAC,WAAW,CAAC,SAAS,EAAE,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC;YAC/G,UAAU,EAAE,IAAI,CAAC,UAAU,GAAG,gCAAgC;SAC/D,CAAC,CAAC;IACL,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Rule: orphan-route
3
+ *
4
+ * Cross-stack rule — mirror of contract-drift. Fires when a server-side route
5
+ * exists that no client call in the reviewed graph targets. Real-bug classes:
6
+ * - Handler was kept live after the frontend renamed the URL it hits.
7
+ * - Endpoint was written before the UI that would call it (forgotten TODO).
8
+ * - A dev-only test endpoint made it into production code.
9
+ *
10
+ * v1 scope: path-only match (any client call whose normalized path matches
11
+ * the route template suppresses the finding, regardless of HTTP method). The
12
+ * method axis is left to `contract-method-drift` — compounding both here
13
+ * would double-fire on the same line.
14
+ *
15
+ * Requires graph mode: silent in single-file review. Single-file can't know
16
+ * "no one calls this" because callers live in other files by definition.
17
+ */
18
+ import type { ReviewFinding } from '../types.js';
19
+ import type { ConceptRuleContext } from './index.js';
20
+ export declare function orphanRoute(ctx: ConceptRuleContext): ReviewFinding[];
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Rule: orphan-route
3
+ *
4
+ * Cross-stack rule — mirror of contract-drift. Fires when a server-side route
5
+ * exists that no client call in the reviewed graph targets. Real-bug classes:
6
+ * - Handler was kept live after the frontend renamed the URL it hits.
7
+ * - Endpoint was written before the UI that would call it (forgotten TODO).
8
+ * - A dev-only test endpoint made it into production code.
9
+ *
10
+ * v1 scope: path-only match (any client call whose normalized path matches
11
+ * the route template suppresses the finding, regardless of HTTP method). The
12
+ * method axis is left to `contract-method-drift` — compounding both here
13
+ * would double-fire on the same line.
14
+ *
15
+ * Requires graph mode: silent in single-file review. Single-file can't know
16
+ * "no one calls this" because callers live in other files by definition.
17
+ */
18
+ import { createFingerprint } from '../types.js';
19
+ import { API_PATH_RE, CROSS_STACK_HEURISTIC_CONFIDENCE, collectRoutesAcrossGraph, findRoutesAtPath, normalizeClientUrl, } from './cross-stack-utils.js';
20
+ export function orphanRoute(ctx) {
21
+ if (!ctx.allConcepts || ctx.allConcepts.size === 0)
22
+ return [];
23
+ const serverRoutes = collectRoutesAcrossGraph(ctx.allConcepts);
24
+ if (serverRoutes.length === 0)
25
+ return [];
26
+ // Collect every client-call path in the graph once so each route checks
27
+ // against a shared set rather than re-walking allConcepts.
28
+ //
29
+ // Codex review: if ANY network effect has an unresolved target (imported
30
+ // constant, URL builder, variable expression), the rule MUST abstain —
31
+ // the unresolved call could be hitting any of the "orphan" routes and
32
+ // we'd fire a false positive. Only run the rule when every client call
33
+ // is statically resolvable.
34
+ const clientPaths = new Set();
35
+ let hasUnresolvedTarget = false;
36
+ for (const [, conceptMap] of ctx.allConcepts) {
37
+ for (const node of conceptMap.nodes) {
38
+ if (node.kind !== 'effect' || node.payload.kind !== 'effect' || node.payload.subtype !== 'network')
39
+ continue;
40
+ const target = node.payload.target;
41
+ if (typeof target !== 'string') {
42
+ hasUnresolvedTarget = true;
43
+ continue;
44
+ }
45
+ const normalized = normalizeClientUrl(target);
46
+ if (!normalized) {
47
+ hasUnresolvedTarget = true;
48
+ continue;
49
+ }
50
+ if (!API_PATH_RE.test(normalized))
51
+ continue;
52
+ clientPaths.add(normalized);
53
+ }
54
+ }
55
+ // Gate: backend-only project (no client calls) — silent.
56
+ // Gate: any unresolved client targets — silent (Codex P2).
57
+ if (clientPaths.size === 0)
58
+ return [];
59
+ if (hasUnresolvedTarget)
60
+ return [];
61
+ const findings = [];
62
+ const seenFingerprints = new Set();
63
+ for (const route of serverRoutes) {
64
+ if (!route.node || route.node.primarySpan.file !== ctx.filePath)
65
+ continue;
66
+ if (clientCallMatches(route.path, clientPaths))
67
+ continue;
68
+ const fingerprint = createFingerprint('orphan-route', route.node.primarySpan.startLine, route.node.primarySpan.startCol);
69
+ // Router-mount expansion can cause the same per-file route to surface
70
+ // twice under different prefixes when two mounts share a router (rare
71
+ // but legal). Dedupe by fingerprint so we emit one finding per span.
72
+ if (seenFingerprints.has(fingerprint))
73
+ continue;
74
+ seenFingerprints.add(fingerprint);
75
+ const methodLabel = route.method ? `${route.method} ` : '';
76
+ findings.push({
77
+ source: 'kern',
78
+ ruleId: 'orphan-route',
79
+ severity: 'warning',
80
+ category: 'bug',
81
+ message: `Server defines \`${methodLabel}${route.path}\` but no client in the reviewed project calls this path. Either remove the handler or add the frontend caller.`,
82
+ primarySpan: route.node.primarySpan,
83
+ fingerprint,
84
+ confidence: route.node.confidence * CROSS_STACK_HEURISTIC_CONFIDENCE,
85
+ });
86
+ }
87
+ return findings;
88
+ }
89
+ function clientCallMatches(routePath, clientPaths) {
90
+ for (const cp of clientPaths) {
91
+ if (findRoutesAtPath(cp, [{ path: routePath, method: undefined }]).length > 0)
92
+ return true;
93
+ }
94
+ return false;
95
+ }
96
+ //# sourceMappingURL=orphan-route.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"orphan-route.js","sourceRoot":"","sources":["../../src/concept-rules/orphan-route.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Rule: sync-handler-does-io
3
+ *
4
+ * Fires when a route handler is explicitly synchronous and performs
5
+ * network/db/fs I/O in the same function container.
6
+ */
7
+ import type { ReviewFinding } from '../types.js';
8
+ import type { ConceptRuleContext } from './index.js';
9
+ export declare function syncHandlerDoesIo(ctx: ConceptRuleContext): ReviewFinding[];
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Rule: sync-handler-does-io
3
+ *
4
+ * Fires when a route handler is explicitly synchronous and performs
5
+ * network/db/fs I/O in the same function container.
6
+ */
7
+ import { createFingerprint } from '../types.js';
8
+ const BLOCKING_IO_SUBTYPES = new Set(['network', 'db', 'fs']);
9
+ export function syncHandlerDoesIo(ctx) {
10
+ const effectsByContainer = new Map();
11
+ for (const node of ctx.concepts.nodes) {
12
+ if (node.kind !== 'effect')
13
+ continue;
14
+ if (node.payload.kind !== 'effect')
15
+ continue;
16
+ if (!BLOCKING_IO_SUBTYPES.has(node.payload.subtype))
17
+ continue;
18
+ if (!node.containerId)
19
+ continue;
20
+ const existing = effectsByContainer.get(node.containerId) ?? [];
21
+ existing.push(node);
22
+ effectsByContainer.set(node.containerId, existing);
23
+ }
24
+ const findings = [];
25
+ for (const node of ctx.concepts.nodes) {
26
+ if (node.kind !== 'entrypoint')
27
+ continue;
28
+ if (node.payload.kind !== 'entrypoint')
29
+ continue;
30
+ if (node.payload.subtype !== 'route')
31
+ continue;
32
+ if (node.payload.isAsync !== false)
33
+ continue;
34
+ if (!node.containerId)
35
+ continue;
36
+ const effects = effectsByContainer.get(node.containerId);
37
+ if (!effects || effects.length === 0)
38
+ continue;
39
+ const firstEffect = effects[0];
40
+ if (firstEffect.payload.kind !== 'effect')
41
+ continue;
42
+ findings.push({
43
+ source: 'kern',
44
+ ruleId: 'sync-handler-does-io',
45
+ severity: 'warning',
46
+ category: 'bug',
47
+ message: `Sync route handler \`${node.payload.name}\` performs ${firstEffect.payload.subtype} I/O. Make the handler async and use non-blocking I/O, or move the blocking work out of the request path.`,
48
+ primarySpan: node.primarySpan,
49
+ relatedSpans: effects.map((effect) => effect.primarySpan),
50
+ fingerprint: createFingerprint('sync-handler-does-io', node.primarySpan.startLine, node.primarySpan.startCol),
51
+ confidence: 0.9,
52
+ });
53
+ }
54
+ return findings;
55
+ }
56
+ //# sourceMappingURL=sync-handler-does-io.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync-handler-does-io.js","sourceRoot":"","sources":["../../src/concept-rules/sync-handler-does-io.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAGhD,MAAM,oBAAoB,GAAG,IAAI,GAAG,CAAC,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;AAE9D,MAAM,UAAU,iBAAiB,CAAC,GAAuB;IACvD,MAAM,kBAAkB,GAAG,IAAI,GAAG,EAAyB,CAAC;IAE5D,KAAK,MAAM,IAAI,IAAI,GAAG,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;QACtC,IAAI,IAAI,CAAC,IAAI,KAAK,QAAQ;YAAE,SAAS;QACrC,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,KAAK,QAAQ;YAAE,SAAS;QAC7C,IAAI,CAAC,oBAAoB,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC;YAAE,SAAS;QAC9D,IAAI,CAAC,IAAI,CAAC,WAAW;YAAE,SAAS;QAEhC,MAAM,QAAQ,GAAG,kBAAkB,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC;QAChE,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACpB,kBAAkB,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;IACrD,CAAC;IAED,MAAM,QAAQ,GAAoB,EAAE,CAAC;IAErC,KAAK,MAAM,IAAI,IAAI,GAAG,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;QACtC,IAAI,IAAI,CAAC,IAAI,KAAK,YAAY;YAAE,SAAS;QACzC,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,KAAK,YAAY;YAAE,SAAS;QACjD,IAAI,IAAI,CAAC,OAAO,CAAC,OAAO,KAAK,OAAO;YAAE,SAAS;QAC/C,IAAI,IAAI,CAAC,OAAO,CAAC,OAAO,KAAK,KAAK;YAAE,SAAS;QAC7C,IAAI,CAAC,IAAI,CAAC,WAAW;YAAE,SAAS;QAEhC,MAAM,OAAO,GAAG,kBAAkB,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QACzD,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;YAAE,SAAS;QAC/C,MAAM,WAAW,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;QAC/B,IAAI,WAAW,CAAC,OAAO,CAAC,IAAI,KAAK,QAAQ;YAAE,SAAS;QAEpD,QAAQ,CAAC,IAAI,CAAC;YACZ,MAAM,EAAE,MAAM;YACd,MAAM,EAAE,sBAAsB;YAC9B,QAAQ,EAAE,SAAS;YACnB,QAAQ,EAAE,KAAK;YACf,OAAO,EAAE,wBAAwB,IAAI,CAAC,OAAO,CAAC,IAAI,eAAe,WAAW,CAAC,OAAO,CAAC,OAAO,2GAA2G;YACvM,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,YAAY,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,WAAW,CAAC;YACzD,WAAW,EAAE,iBAAiB,CAAC,sBAAsB,EAAE,IAAI,CAAC,WAAW,CAAC,SAAS,EAAE,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC;YAC7G,UAAU,EAAE,GAAG;SAChB,CAAC,CAAC;IACL,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC"}
@@ -29,14 +29,11 @@
29
29
  * missing server matches (contract-drift owns that class).
30
30
  */
31
31
  import { createFingerprint } from '../types.js';
32
- import { API_PATH_RE, CROSS_STACK_HEURISTIC_CONFIDENCE, collectRoutes, findMatchingRoute, normalizeClientUrl, } from './cross-stack-utils.js';
32
+ import { API_PATH_RE, CROSS_STACK_HEURISTIC_CONFIDENCE, collectRoutesAcrossGraph, findMatchingRoute, normalizeClientUrl, } from './cross-stack-utils.js';
33
33
  export function taintedAcrossWire(ctx) {
34
34
  if (!ctx.allConcepts || ctx.allConcepts.size === 0)
35
35
  return [];
36
- const serverRoutes = [];
37
- for (const [, conceptMap] of ctx.allConcepts) {
38
- collectRoutes(conceptMap, serverRoutes);
39
- }
36
+ const serverRoutes = collectRoutesAcrossGraph(ctx.allConcepts);
40
37
  if (serverRoutes.length === 0)
41
38
  return [];
42
39
  // Build the set of files that contain at least one validation guard.
@@ -1 +1 @@
1
- {"version":3,"file":"tainted-across-wire.js","sourceRoot":"","sources":["../../src/concept-rules/tainted-across-wire.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"tainted-across-wire.js","sourceRoot":"","sources":["../../src/concept-rules/tainted-across-wire.ts"],"names":[],"mappings":"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"}
@@ -26,14 +26,11 @@
26
26
  * False positives here would poison the pitch.
27
27
  */
28
28
  import { createFingerprint } from '../types.js';
29
- import { API_PATH_RE, CROSS_STACK_HEURISTIC_CONFIDENCE, collectRoutes, hasMatchingRoute, normalizeClientUrl, } from './cross-stack-utils.js';
29
+ import { API_PATH_RE, CROSS_STACK_HEURISTIC_CONFIDENCE, collectRoutesAcrossGraph, findMatchingRoute, isFastApiRouteMissingResponseModel, normalizeClientUrl, } from './cross-stack-utils.js';
30
30
  export function untypedApiResponse(ctx) {
31
31
  if (!ctx.allConcepts || ctx.allConcepts.size === 0)
32
32
  return [];
33
- const serverRoutes = [];
34
- for (const [, conceptMap] of ctx.allConcepts) {
35
- collectRoutes(conceptMap, serverRoutes);
36
- }
33
+ const serverRoutes = collectRoutesAcrossGraph(ctx.allConcepts);
37
34
  if (serverRoutes.length === 0)
38
35
  return [];
39
36
  const findings = [];
@@ -50,8 +47,13 @@ export function untypedApiResponse(ctx) {
50
47
  const normalized = normalizeClientUrl(target);
51
48
  if (!normalized || !API_PATH_RE.test(normalized))
52
49
  continue;
53
- if (!hasMatchingRoute(normalized, serverRoutes))
50
+ const matchedRoute = findMatchingRoute(normalized, serverRoutes);
51
+ if (!matchedRoute)
52
+ continue;
53
+ if (matchedRoute.node &&
54
+ isFastApiRouteMissingResponseModel(matchedRoute.node, ctx.allConcepts.get(matchedRoute.node.primarySpan.file))) {
54
55
  continue;
56
+ }
55
57
  findings.push({
56
58
  source: 'kern',
57
59
  ruleId: 'untyped-api-response',
@@ -1 +1 @@
1
- {"version":3,"file":"untyped-api-response.js","sourceRoot":"","sources":["../../src/concept-rules/untyped-api-response.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAGH,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EACL,WAAW,EACX,gCAAgC,EAChC,aAAa,EACb,gBAAgB,EAChB,kBAAkB,GAEnB,MAAM,wBAAwB,CAAC;AAGhC,MAAM,UAAU,kBAAkB,CAAC,GAAuB;IACxD,IAAI,CAAC,GAAG,CAAC,WAAW,IAAI,GAAG,CAAC,WAAW,CAAC,IAAI,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAE9D,MAAM,YAAY,GAAkB,EAAE,CAAC;IACvC,KAAK,MAAM,CAAC,EAAE,UAAU,CAAC,IAAI,GAAG,CAAC,WAAW,EAAE,CAAC;QAC7C,aAAa,CAAC,UAAU,EAAE,YAAY,CAAC,CAAC;IAC1C,CAAC;IACD,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAEzC,MAAM,QAAQ,GAAoB,EAAE,CAAC;IACrC,0EAA0E;IAC1E,MAAM,aAAa,GAAG,GAAG,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC;IACxE,KAAK,MAAM,IAAI,IAAI,aAAa,CAAC,KAAK,EAAE,CAAC;QACvC,IAAI,IAAI,CAAC,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,OAAO,CAAC,OAAO,KAAK,SAAS;YAAE,SAAS;QAC7G,IAAI,IAAI,CAAC,OAAO,CAAC,gBAAgB,KAAK,KAAK;YAAE,SAAS,CAAC,yBAAyB;QAChF,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC;QACnC,IAAI,OAAO,MAAM,KAAK,QAAQ;YAAE,SAAS;QACzC,MAAM,UAAU,GAAG,kBAAkB,CAAC,MAAM,CAAC,CAAC;QAC9C,IAAI,CAAC,UAAU,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,UAAU,CAAC;YAAE,SAAS;QAC3D,IAAI,CAAC,gBAAgB,CAAC,UAAU,EAAE,YAAY,CAAC;YAAE,SAAS;QAE1D,QAAQ,CAAC,IAAI,CAAC;YACZ,MAAM,EAAE,MAAM;YACd,MAAM,EAAE,sBAAsB;YAC9B,QAAQ,EAAE,SAAS;YACnB,QAAQ,EAAE,KAAK;YACf,OAAO,EAAE,mBAAmB,MAAM,wPAAwP;YAC1R,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,WAAW,EAAE,iBAAiB,CAAC,sBAAsB,EAAE,IAAI,CAAC,WAAW,CAAC,SAAS,EAAE,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC;YAC7G,uEAAuE;YACvE,sEAAsE;YACtE,wCAAwC;YACxC,UAAU,EAAE,IAAI,CAAC,UAAU,GAAG,gCAAgC;SAC/D,CAAC,CAAC;IACL,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC"}
1
+ {"version":3,"file":"untyped-api-response.js","sourceRoot":"","sources":["../../src/concept-rules/untyped-api-response.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAGH,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EACL,WAAW,EACX,gCAAgC,EAChC,wBAAwB,EACxB,iBAAiB,EACjB,kCAAkC,EAClC,kBAAkB,GACnB,MAAM,wBAAwB,CAAC;AAGhC,MAAM,UAAU,kBAAkB,CAAC,GAAuB;IACxD,IAAI,CAAC,GAAG,CAAC,WAAW,IAAI,GAAG,CAAC,WAAW,CAAC,IAAI,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAE9D,MAAM,YAAY,GAAG,wBAAwB,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;IAC/D,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAEzC,MAAM,QAAQ,GAAoB,EAAE,CAAC;IACrC,0EAA0E;IAC1E,MAAM,aAAa,GAAG,GAAG,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC;IACxE,KAAK,MAAM,IAAI,IAAI,aAAa,CAAC,KAAK,EAAE,CAAC;QACvC,IAAI,IAAI,CAAC,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,OAAO,CAAC,OAAO,KAAK,SAAS;YAAE,SAAS;QAC7G,IAAI,IAAI,CAAC,OAAO,CAAC,gBAAgB,KAAK,KAAK;YAAE,SAAS,CAAC,yBAAyB;QAChF,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC;QACnC,IAAI,OAAO,MAAM,KAAK,QAAQ;YAAE,SAAS;QACzC,MAAM,UAAU,GAAG,kBAAkB,CAAC,MAAM,CAAC,CAAC;QAC9C,IAAI,CAAC,UAAU,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,UAAU,CAAC;YAAE,SAAS;QAC3D,MAAM,YAAY,GAAG,iBAAiB,CAAC,UAAU,EAAE,YAAY,CAAC,CAAC;QACjE,IAAI,CAAC,YAAY;YAAE,SAAS;QAC5B,IACE,YAAY,CAAC,IAAI;YACjB,kCAAkC,CAAC,YAAY,CAAC,IAAI,EAAE,GAAG,CAAC,WAAW,CAAC,GAAG,CAAC,YAAY,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,EAC9G,CAAC;YACD,SAAS;QACX,CAAC;QAED,QAAQ,CAAC,IAAI,CAAC;YACZ,MAAM,EAAE,MAAM;YACd,MAAM,EAAE,sBAAsB;YAC9B,QAAQ,EAAE,SAAS;YACnB,QAAQ,EAAE,KAAK;YACf,OAAO,EAAE,mBAAmB,MAAM,wPAAwP;YAC1R,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,WAAW,EAAE,iBAAiB,CAAC,sBAAsB,EAAE,IAAI,CAAC,WAAW,CAAC,SAAS,EAAE,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC;YAC7G,uEAAuE;YACvE,sEAAsE;YACtE,wCAAwC;YACxC,UAAU,EAAE,IAAI,CAAC,UAAU,GAAG,gCAAgC;SAC/D,CAAC,CAAC;IACL,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Rule: untyped-both-ends-response
3
+ *
4
+ * Cross-stack linker for the response typing wedge. Fires when the client
5
+ * consumes a matching API response without a type assertion and the Python
6
+ * server route also has no `response_model=...`.
7
+ */
8
+ import type { ReviewFinding } from '../types.js';
9
+ import type { ConceptRuleContext } from './index.js';
10
+ export declare function untypedBothEndsResponse(ctx: ConceptRuleContext): ReviewFinding[];
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Rule: untyped-both-ends-response
3
+ *
4
+ * Cross-stack linker for the response typing wedge. Fires when the client
5
+ * consumes a matching API response without a type assertion and the Python
6
+ * server route also has no `response_model=...`.
7
+ */
8
+ import { createFingerprint } from '../types.js';
9
+ import { API_PATH_RE, CROSS_STACK_HEURISTIC_CONFIDENCE, collectRoutesAcrossGraph, findMatchingRoute, isFastApiRouteMissingResponseModel, normalizeClientUrl, } from './cross-stack-utils.js';
10
+ export function untypedBothEndsResponse(ctx) {
11
+ if (!ctx.allConcepts || ctx.allConcepts.size === 0)
12
+ return [];
13
+ const allConcepts = ctx.allConcepts;
14
+ const routesMissingModel = collectRoutesAcrossGraph(allConcepts).filter((route) => routeMissingResponseModel(route, allConcepts));
15
+ if (routesMissingModel.length === 0)
16
+ return [];
17
+ const findings = [];
18
+ const localConcepts = allConcepts.get(ctx.filePath) ?? ctx.concepts;
19
+ for (const node of localConcepts.nodes) {
20
+ if (node.language !== 'ts')
21
+ continue;
22
+ if (node.kind !== 'effect' || node.payload.kind !== 'effect' || node.payload.subtype !== 'network')
23
+ continue;
24
+ if (node.payload.responseAsserted !== false)
25
+ continue;
26
+ const target = node.payload.target;
27
+ if (typeof target !== 'string')
28
+ continue;
29
+ const normalized = normalizeClientUrl(target);
30
+ if (!normalized || !API_PATH_RE.test(normalized))
31
+ continue;
32
+ const matchedRoute = findMatchingRoute(normalized, routesMissingModel);
33
+ if (!matchedRoute?.node)
34
+ continue;
35
+ findings.push({
36
+ source: 'kern',
37
+ ruleId: 'untyped-both-ends-response',
38
+ severity: 'warning',
39
+ category: 'bug',
40
+ message: `Response for \`${target}\` is untyped on both ends: the client consumes it without a type assertion and the matching backend route has no response_model.`,
41
+ primarySpan: node.primarySpan,
42
+ relatedSpans: [matchedRoute.node.primarySpan],
43
+ fingerprint: createFingerprint('untyped-both-ends-response', node.primarySpan.startLine, node.primarySpan.startCol),
44
+ confidence: Math.min(node.confidence, matchedRoute.node.confidence) * CROSS_STACK_HEURISTIC_CONFIDENCE,
45
+ });
46
+ }
47
+ return findings;
48
+ }
49
+ function routeMissingResponseModel(route, allConcepts) {
50
+ const node = route.node;
51
+ if (!node)
52
+ return false;
53
+ return isFastApiRouteMissingResponseModel(node, allConcepts.get(node.primarySpan.file));
54
+ }
55
+ //# sourceMappingURL=untyped-both-ends-response.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"untyped-both-ends-response.js","sourceRoot":"","sources":["../../src/concept-rules/untyped-both-ends-response.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EACL,WAAW,EACX,gCAAgC,EAChC,wBAAwB,EACxB,iBAAiB,EACjB,kCAAkC,EAClC,kBAAkB,GAEnB,MAAM,wBAAwB,CAAC;AAGhC,MAAM,UAAU,uBAAuB,CAAC,GAAuB;IAC7D,IAAI,CAAC,GAAG,CAAC,WAAW,IAAI,GAAG,CAAC,WAAW,CAAC,IAAI,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAC9D,MAAM,WAAW,GAAG,GAAG,CAAC,WAAW,CAAC;IAEpC,MAAM,kBAAkB,GAAG,wBAAwB,CAAC,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAChF,yBAAyB,CAAC,KAAK,EAAE,WAAW,CAAC,CAC9C,CAAC;IACF,IAAI,kBAAkB,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAE/C,MAAM,QAAQ,GAAoB,EAAE,CAAC;IACrC,MAAM,aAAa,GAAG,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC;IAEpE,KAAK,MAAM,IAAI,IAAI,aAAa,CAAC,KAAK,EAAE,CAAC;QACvC,IAAI,IAAI,CAAC,QAAQ,KAAK,IAAI;YAAE,SAAS;QACrC,IAAI,IAAI,CAAC,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,OAAO,CAAC,OAAO,KAAK,SAAS;YAAE,SAAS;QAC7G,IAAI,IAAI,CAAC,OAAO,CAAC,gBAAgB,KAAK,KAAK;YAAE,SAAS;QACtD,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC;QACnC,IAAI,OAAO,MAAM,KAAK,QAAQ;YAAE,SAAS;QACzC,MAAM,UAAU,GAAG,kBAAkB,CAAC,MAAM,CAAC,CAAC;QAC9C,IAAI,CAAC,UAAU,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,UAAU,CAAC;YAAE,SAAS;QAC3D,MAAM,YAAY,GAAG,iBAAiB,CAAC,UAAU,EAAE,kBAAkB,CAAC,CAAC;QACvE,IAAI,CAAC,YAAY,EAAE,IAAI;YAAE,SAAS;QAElC,QAAQ,CAAC,IAAI,CAAC;YACZ,MAAM,EAAE,MAAM;YACd,MAAM,EAAE,4BAA4B;YACpC,QAAQ,EAAE,SAAS;YACnB,QAAQ,EAAE,KAAK;YACf,OAAO,EAAE,kBAAkB,MAAM,mIAAmI;YACpK,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,YAAY,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,WAAW,CAAC;YAC7C,WAAW,EAAE,iBAAiB,CAC5B,4BAA4B,EAC5B,IAAI,CAAC,WAAW,CAAC,SAAS,EAC1B,IAAI,CAAC,WAAW,CAAC,QAAQ,CAC1B;YACD,UAAU,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,EAAE,YAAY,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,gCAAgC;SACvG,CAAC,CAAC;IACL,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,SAAS,yBAAyB,CAAC,KAAkB,EAAE,WAA4C;IACjG,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC;IACxB,IAAI,CAAC,IAAI;QAAE,OAAO,KAAK,CAAC;IACxB,OAAO,kCAAkC,CAAC,IAAI,EAAE,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC;AAC1F,CAAC"}