carrick 0.3.103 → 0.3.105

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 (35) hide show
  1. package/package.json +6 -6
  2. package/plugin/.claude-plugin/plugin.json +1 -1
  3. package/sidecar/dist/src/capture/anchors.d.ts +6 -0
  4. package/sidecar/dist/src/capture/anchors.js +78 -13
  5. package/sidecar/dist/src/capture/api.d.ts +27 -2
  6. package/sidecar/dist/src/capture/check-classify.d.ts +1 -0
  7. package/sidecar/dist/src/capture/check-classify.js +23 -0
  8. package/sidecar/dist/src/capture/check-fields.js +4 -6
  9. package/sidecar/dist/src/capture/check-probe.d.ts +1 -1
  10. package/sidecar/dist/src/capture/check-probe.js +28 -4
  11. package/sidecar/dist/src/capture/check-scrub.d.ts +3 -0
  12. package/sidecar/dist/src/capture/check-scrub.js +8 -4
  13. package/sidecar/dist/src/capture/check.js +64 -50
  14. package/sidecar/dist/src/capture/deep-walk.d.ts +20 -0
  15. package/sidecar/dist/src/capture/deep-walk.js +51 -11
  16. package/sidecar/dist/src/capture/guarded-fs.d.ts +5 -1
  17. package/sidecar/dist/src/capture/guarded-fs.js +23 -1
  18. package/sidecar/dist/src/capture/index.d.ts +1 -0
  19. package/sidecar/dist/src/capture/index.js +11 -3
  20. package/sidecar/dist/src/capture/member-name.d.ts +20 -0
  21. package/sidecar/dist/src/capture/member-name.js +24 -0
  22. package/sidecar/dist/src/capture/self-check.js +41 -9
  23. package/sidecar/dist/src/capture/service-config.d.ts +19 -0
  24. package/sidecar/dist/src/capture/service-config.js +57 -0
  25. package/sidecar/dist/src/failure-path.d.ts +36 -0
  26. package/sidecar/dist/src/failure-path.js +212 -0
  27. package/sidecar/dist/src/index.js +2 -0
  28. package/sidecar/dist/src/project-loader.d.ts +10 -2
  29. package/sidecar/dist/src/project-loader.js +7 -16
  30. package/sidecar/dist/src/retype.js +15 -2
  31. package/sidecar/dist/src/type-inferrer.d.ts +2 -0
  32. package/sidecar/dist/src/type-inferrer.js +17 -0
  33. package/sidecar/dist/src/types.d.ts +8 -0
  34. package/sidecar/dist/src/validators.d.ts +12 -0
  35. package/sidecar/dist/src/validators.js +2 -0
@@ -0,0 +1,212 @@
1
+ /**
2
+ * Whether the source reaches a node only when an HTTP response FAILED
3
+ * (carrick#1796).
4
+ *
5
+ * A consumer reads the success body on one path and the error text or the
6
+ * error body on another. Only the first is the call's response contract, so
7
+ * the def-use walk behind a `call_result` row must not take a read from the
8
+ * second. Which path a read is on is told by the source's own tests of the
9
+ * response, read two ways:
10
+ *
11
+ * - the read sits in a branch of a test: `if (!res.ok) { ... }`, the `else`
12
+ * of `if (res.ok)`, either arm of a conditional expression;
13
+ * - the read follows an `if` whose other branch cannot complete, so the rest
14
+ * of the block runs only on the side that did not leave:
15
+ * `if (res.ok) { return ... } const text = await res.text()`.
16
+ *
17
+ * A test is read as the set of statuses it lets through. `ok` is true for
18
+ * 200-299 and false for every other status; a comparison of the status with a
19
+ * number lets through what it admits; `!`, `&&` and `||` combine the sets, and
20
+ * any other condition lets everything through. A node is on the failure path
21
+ * only when NO status in 200-299 can reach it.
22
+ *
23
+ * That is deliberately stricter than the retype check's reading of a status
24
+ * test (`okWhenTrue` in retype.ts), which reads the false side of
25
+ * `res.status === 200` as the failure path so it can find a success read to
26
+ * judge. Here a decided failure REMOVES a read, so the reading has to be sound
27
+ * the other way round: after `if (res.status === 204) return null`, 200 still
28
+ * gets through, and the json read that follows is the payload.
29
+ */
30
+ import { Node, SyntaxKind } from 'ts-morph';
31
+ const FIRST_STATUS = 100;
32
+ const LAST_STATUS = 599;
33
+ const EVERY_STATUS = () => true;
34
+ /** The statuses `ok` is true for. */
35
+ const SUCCEEDED = (status) => status >= 200 && status <= 299;
36
+ const UNDECIDED = { whenTrue: EVERY_STATUS, whenFalse: EVERY_STATUS };
37
+ /**
38
+ * True when the source reaches `node` only after the response named by
39
+ * `isResponse` failed. The walk climbs from `node` to `boundary` (the function
40
+ * the call sits in) and never looks at a test outside it.
41
+ */
42
+ export function reachedOnlyOnFailure(node, boundary, isResponse) {
43
+ let admitted = EVERY_STATUS;
44
+ let narrowed = false;
45
+ const narrow = (by) => {
46
+ if (by === EVERY_STATUS)
47
+ return;
48
+ const before = admitted;
49
+ admitted = (status) => before(status) && by(status);
50
+ narrowed = true;
51
+ };
52
+ for (let child = node, parent = node.getParent(); parent && parent !== boundary; child = parent, parent = parent.getParent()) {
53
+ if (Node.isIfStatement(parent)) {
54
+ if (child === parent.getThenStatement()) {
55
+ narrow(readTest(parent.getExpression(), isResponse).whenTrue);
56
+ }
57
+ else if (child === parent.getElseStatement()) {
58
+ narrow(readTest(parent.getExpression(), isResponse).whenFalse);
59
+ }
60
+ }
61
+ else if (Node.isConditionalExpression(parent)) {
62
+ if (child === parent.getWhenTrue()) {
63
+ narrow(readTest(parent.getCondition(), isResponse).whenTrue);
64
+ }
65
+ else if (child === parent.getWhenFalse()) {
66
+ narrow(readTest(parent.getCondition(), isResponse).whenFalse);
67
+ }
68
+ }
69
+ if (Node.isBlock(parent) ||
70
+ Node.isSourceFile(parent) ||
71
+ Node.isCaseClause(parent) ||
72
+ Node.isDefaultClause(parent)) {
73
+ for (const statement of parent.getStatements()) {
74
+ if (statement === child)
75
+ break;
76
+ if (!Node.isIfStatement(statement))
77
+ continue;
78
+ const otherwise = statement.getElseStatement();
79
+ const thenLeaves = cannotComplete(statement.getThenStatement());
80
+ const elseLeaves = otherwise !== undefined && cannotComplete(otherwise);
81
+ if (thenLeaves && !elseLeaves) {
82
+ narrow(readTest(statement.getExpression(), isResponse).whenFalse);
83
+ }
84
+ else if (elseLeaves && !thenLeaves) {
85
+ narrow(readTest(statement.getExpression(), isResponse).whenTrue);
86
+ }
87
+ }
88
+ }
89
+ }
90
+ if (!narrowed)
91
+ return false;
92
+ let reachable = false;
93
+ for (let status = FIRST_STATUS; status <= LAST_STATUS; status++) {
94
+ if (!admitted(status))
95
+ continue;
96
+ if (SUCCEEDED(status))
97
+ return false;
98
+ reachable = true;
99
+ }
100
+ return reachable;
101
+ }
102
+ /** The statuses `condition` lets through when it is true and when it is false. */
103
+ function readTest(condition, isResponse) {
104
+ let test = condition;
105
+ while (Node.isParenthesizedExpression(test))
106
+ test = test.getExpression();
107
+ if (Node.isPrefixUnaryExpression(test) &&
108
+ test.getOperatorToken() === SyntaxKind.ExclamationToken) {
109
+ const inner = readTest(test.getOperand(), isResponse);
110
+ return { whenTrue: inner.whenFalse, whenFalse: inner.whenTrue };
111
+ }
112
+ if (isMemberOfResponse(test, 'ok', isResponse)) {
113
+ return { whenTrue: SUCCEEDED, whenFalse: (status) => !SUCCEEDED(status) };
114
+ }
115
+ if (Node.isBinaryExpression(test)) {
116
+ const operator = test.getOperatorToken().getKind();
117
+ if (operator === SyntaxKind.AmpersandAmpersandToken ||
118
+ operator === SyntaxKind.BarBarToken) {
119
+ const left = readTest(test.getLeft(), isResponse);
120
+ const right = readTest(test.getRight(), isResponse);
121
+ if (left === UNDECIDED && right === UNDECIDED)
122
+ return UNDECIDED;
123
+ return operator === SyntaxKind.AmpersandAmpersandToken
124
+ ? {
125
+ whenTrue: (status) => left.whenTrue(status) && right.whenTrue(status),
126
+ whenFalse: (status) => left.whenFalse(status) || right.whenFalse(status),
127
+ }
128
+ : {
129
+ whenTrue: (status) => left.whenTrue(status) || right.whenTrue(status),
130
+ whenFalse: (status) => left.whenFalse(status) && right.whenFalse(status),
131
+ };
132
+ }
133
+ const compared = statusComparison(test.getLeft(), operator, test.getRight(), isResponse);
134
+ if (compared) {
135
+ return { whenTrue: compared, whenFalse: (status) => !compared(status) };
136
+ }
137
+ }
138
+ return UNDECIDED;
139
+ }
140
+ /**
141
+ * `res.status <op> N` or `N <op> res.status`, as the statuses it is true for;
142
+ * `undefined` when the comparison is not one of the response's status with a
143
+ * number.
144
+ */
145
+ function statusComparison(left, operator, right, isResponse) {
146
+ let op = operator;
147
+ let value;
148
+ if (isMemberOfResponse(left, 'status', isResponse) && Node.isNumericLiteral(right)) {
149
+ value = right.getLiteralValue();
150
+ }
151
+ else if (isMemberOfResponse(right, 'status', isResponse) && Node.isNumericLiteral(left)) {
152
+ value = left.getLiteralValue();
153
+ op = MIRRORED[op] ?? op;
154
+ }
155
+ else {
156
+ return undefined;
157
+ }
158
+ switch (op) {
159
+ case SyntaxKind.EqualsEqualsEqualsToken:
160
+ case SyntaxKind.EqualsEqualsToken:
161
+ return (status) => status === value;
162
+ case SyntaxKind.ExclamationEqualsEqualsToken:
163
+ case SyntaxKind.ExclamationEqualsToken:
164
+ return (status) => status !== value;
165
+ case SyntaxKind.LessThanToken:
166
+ return (status) => status < value;
167
+ case SyntaxKind.LessThanEqualsToken:
168
+ return (status) => status <= value;
169
+ case SyntaxKind.GreaterThanToken:
170
+ return (status) => status > value;
171
+ case SyntaxKind.GreaterThanEqualsToken:
172
+ return (status) => status >= value;
173
+ default:
174
+ return undefined;
175
+ }
176
+ }
177
+ /** `400 <= res.status` reads as `res.status >= 400`. */
178
+ const MIRRORED = {
179
+ [SyntaxKind.LessThanToken]: SyntaxKind.GreaterThanToken,
180
+ [SyntaxKind.LessThanEqualsToken]: SyntaxKind.GreaterThanEqualsToken,
181
+ [SyntaxKind.GreaterThanToken]: SyntaxKind.LessThanToken,
182
+ [SyntaxKind.GreaterThanEqualsToken]: SyntaxKind.LessThanEqualsToken,
183
+ };
184
+ function isMemberOfResponse(node, name, isResponse) {
185
+ return (Node.isPropertyAccessExpression(node) &&
186
+ node.getName() === name &&
187
+ isResponse(node.getExpression()));
188
+ }
189
+ /**
190
+ * A statement that never runs on into the statement after it: it returns,
191
+ * throws, breaks or continues, or every path through it does. A loop, a
192
+ * `switch` or a `try` is read as one that can complete, which only ever
193
+ * leaves a read on the path it was on.
194
+ */
195
+ function cannotComplete(statement) {
196
+ if (Node.isReturnStatement(statement) ||
197
+ Node.isThrowStatement(statement) ||
198
+ Node.isBreakStatement(statement) ||
199
+ Node.isContinueStatement(statement)) {
200
+ return true;
201
+ }
202
+ if (Node.isBlock(statement)) {
203
+ return statement.getStatements().some((inner) => cannotComplete(inner));
204
+ }
205
+ if (Node.isIfStatement(statement)) {
206
+ const otherwise = statement.getElseStatement();
207
+ return (otherwise !== undefined &&
208
+ cannotComplete(statement.getThenStatement()) &&
209
+ cannotComplete(otherwise));
210
+ }
211
+ return false;
212
+ }
@@ -125,6 +125,7 @@ function handleInit(request) {
125
125
  projectLoader = new ProjectLoader({
126
126
  repoRoot: request.repo_root,
127
127
  tsconfigPath: request.tsconfig_path,
128
+ scanRoot: request.scan_root,
128
129
  tsconfigSnapshot: request.tsconfig_snapshot,
129
130
  pinnedDependencies: request.pinned_dependencies,
130
131
  });
@@ -213,6 +214,7 @@ function handleCaptureV2(request) {
213
214
  anchors: request.anchors,
214
215
  outDir: request.out_dir,
215
216
  tsconfigPath: request.tsconfig_path,
217
+ scanRoot: request.scan_root,
216
218
  });
217
219
  return {
218
220
  request_id: request.request_id,
@@ -19,6 +19,12 @@ export interface ProjectLoaderOptions {
19
19
  repoRoot: string;
20
20
  /** Optional path to tsconfig.json (relative to repo root or absolute) */
21
21
  tsconfigPath?: string;
22
+ /**
23
+ * The scanned repo's root, the upper bound of the search for a tsconfig
24
+ * above `repoRoot` when none is named (carrick#1776). Without it only
25
+ * `repoRoot` is searched.
26
+ */
27
+ scanRoot?: string;
22
28
  /** Optional tsconfig snapshot (closed/merged) - preferred over tsconfigPath */
23
29
  tsconfigSnapshot?: TsconfigSnapshot;
24
30
  /** Optional pinned dependencies for this repo */
@@ -69,6 +75,7 @@ export declare class ProjectLoader {
69
75
  private readonly ownerProjects;
70
76
  private readonly repoRoot;
71
77
  private readonly tsconfigPath;
78
+ private readonly scanRoot;
72
79
  private readonly tsconfigSnapshot;
73
80
  private readonly pinnedDependencies;
74
81
  private initialized;
@@ -140,9 +147,10 @@ export declare class ProjectLoader {
140
147
  */
141
148
  getPinnedDependencies(): PinnedDependencySnapshot | undefined;
142
149
  /**
143
- * Find the tsconfig.json file to use
150
+ * The tsconfig to build from: the named one when it exists, else the one
151
+ * `findServiceTsconfig` finds for the service, which capture also reads.
144
152
  *
145
- * @returns Absolute path to tsconfig.json, or undefined if not found
153
+ * @returns Absolute path to the tsconfig, or undefined if not found
146
154
  */
147
155
  private findTsConfig;
148
156
  /**
@@ -12,7 +12,7 @@
12
12
  import { Project } from 'ts-morph';
13
13
  import * as path from 'node:path';
14
14
  import * as fs from 'node:fs';
15
- import { DenoProject, findDenoConfig, serviceConfigPath } from './capture/index.js';
15
+ import { DenoProject, findDenoConfig, findServiceTsconfig, serviceConfigPath } from './capture/index.js';
16
16
  import { moduleFormatResolutionHost } from './module-format.js';
17
17
  import { ExternalImports, registerExternalImports } from './origin.js';
18
18
  /**
@@ -125,6 +125,7 @@ export class ProjectLoader {
125
125
  ownerProjects = new Map();
126
126
  repoRoot;
127
127
  tsconfigPath;
128
+ scanRoot;
128
129
  tsconfigSnapshot;
129
130
  pinnedDependencies;
130
131
  initialized = false;
@@ -141,6 +142,7 @@ export class ProjectLoader {
141
142
  ? options.tsconfigPath
142
143
  : path.resolve(this.repoRoot, options.tsconfigPath);
143
144
  }
145
+ this.scanRoot = options.scanRoot;
144
146
  // Store snapshot if provided
145
147
  this.tsconfigSnapshot = options.tsconfigSnapshot;
146
148
  this.pinnedDependencies = options.pinnedDependencies;
@@ -422,30 +424,19 @@ export class ProjectLoader {
422
424
  return this.pinnedDependencies;
423
425
  }
424
426
  /**
425
- * Find the tsconfig.json file to use
427
+ * The tsconfig to build from: the named one when it exists, else the one
428
+ * `findServiceTsconfig` finds for the service, which capture also reads.
426
429
  *
427
- * @returns Absolute path to tsconfig.json, or undefined if not found
430
+ * @returns Absolute path to the tsconfig, or undefined if not found
428
431
  */
429
432
  findTsConfig() {
430
- // If a specific path was provided, try to use it
431
433
  if (this.tsconfigPath) {
432
434
  if (fs.existsSync(this.tsconfigPath)) {
433
435
  return this.tsconfigPath;
434
436
  }
435
437
  this.log(`Specified tsconfig not found: ${this.tsconfigPath}`);
436
438
  }
437
- // Try common tsconfig locations
438
- const candidates = [
439
- path.join(this.repoRoot, 'tsconfig.json'),
440
- path.join(this.repoRoot, 'tsconfig.build.json'),
441
- path.join(this.repoRoot, 'tsconfig.app.json'),
442
- ];
443
- for (const candidate of candidates) {
444
- if (fs.existsSync(candidate)) {
445
- return candidate;
446
- }
447
- }
448
- return undefined;
439
+ return findServiceTsconfig(this.repoRoot, this.scanRoot);
449
440
  }
450
441
  /**
451
442
  * Add source files from common project locations when no tsconfig is found.
@@ -735,11 +735,23 @@ function branchSide(condition, whenTrue, isResponse) {
735
735
  return ok;
736
736
  return ok === whenTrue ? 'success' : 'failure';
737
737
  }
738
+ /**
739
+ * The success statuses whose response carries no content (RFC 9110: 204 No
740
+ * Content, 205 Reset Content).
741
+ */
742
+ const NO_CONTENT_STATUSES = new Set([204, 205]);
738
743
  /**
739
744
  * Whether `condition` being true means the response succeeded: `res.ok`,
740
745
  * `res.status === 200`, `res.status !== 200`, `res.status >= 400` and their
741
746
  * negations. Any other test of the response is `'unclear'`; a condition that does not
742
747
  * test the response is `undefined`.
748
+ *
749
+ * An equality or inequality with a no-content status is `undefined` too
750
+ * (carrick#1813). The retype only runs against a producer that publishes a
751
+ * response body, and that body never arrives with a 204 or 205, so
752
+ * `if (res.status === 204) return null` only takes away a status the body
753
+ * cannot come with. The read after it sits where a read no test decides
754
+ * sits, not on the error path.
743
755
  */
744
756
  function okWhenTrue(condition, isResponse) {
745
757
  let e = condition;
@@ -759,13 +771,14 @@ function okWhenTrue(condition, isResponse) {
759
771
  : undefined;
760
772
  if (value !== undefined) {
761
773
  const success = value >= 200 && value < 300;
774
+ const noContent = NO_CONTENT_STATUSES.has(value);
762
775
  switch (op) {
763
776
  case SyntaxKind.EqualsEqualsEqualsToken:
764
777
  case SyntaxKind.EqualsEqualsToken:
765
- return success;
778
+ return noContent ? undefined : success;
766
779
  case SyntaxKind.ExclamationEqualsEqualsToken:
767
780
  case SyntaxKind.ExclamationEqualsToken:
768
- return !success;
781
+ return noContent ? undefined : !success;
769
782
  case SyntaxKind.GreaterThanEqualsToken:
770
783
  if (value >= 300)
771
784
  return false;
@@ -434,6 +434,8 @@ export declare class TypeInferrer {
434
434
  private bodyReadOnReceiver;
435
435
  private collectDefUseNodes;
436
436
  private expressionUsesNames;
437
+ /** `expr` is a use of a tracked name, or contains one. */
438
+ private usesTrackedNames;
437
439
  private isIdentifierUsage;
438
440
  private isInFunctionScope;
439
441
  /**
@@ -21,6 +21,7 @@ import * as path from 'node:path';
21
21
  import { Node, SyntaxKind, ts, } from 'ts-morph';
22
22
  import { validateInferRequestItem } from './validators.js';
23
23
  import { externalImportsOf, isExternalOrigin } from './origin.js';
24
+ import { reachedOnlyOnFailure } from './failure-path.js';
24
25
  import { addedDiagnostics, applyInsertions, fileDiagnostics, literalInsertions, mapBack, mapForward, normalise, pathOf, } from './unwidened.js';
25
26
  import { expandTypeStructural, } from './type-structural-expander.js';
26
27
  /**
@@ -1813,6 +1814,16 @@ export class TypeInferrer {
1813
1814
  for (const expr of candidates) {
1814
1815
  if (expr.getStart() <= startPos)
1815
1816
  continue;
1817
+ // carrick#1796: what the source does with the response once it has
1818
+ // FAILED describes the failure, not the payload. `if (res.ok) {
1819
+ // return ... } const errorText = await res.text()` published the error
1820
+ // text as the response contract because the walk keeps the LAST whole
1821
+ // read, and the failure path's read comes after the success path's
1822
+ // return. A read there is neither the payload nor a part of it.
1823
+ if (this.usesTrackedNames(expr, currentNames) &&
1824
+ reachedOnlyOnFailure(expr, func, (node) => this.isIdentifierUsage(node, currentNames))) {
1825
+ continue;
1826
+ }
1816
1827
  // carrick#1017: a comparison is `boolean` by construction, so it is
1817
1828
  // never the payload a call yields. `const res = await fetch(url); if
1818
1829
  // (res.status === 404) return null; return await res.json() as Entry`
@@ -2361,6 +2372,12 @@ export class TypeInferrer {
2361
2372
  const identifiers = expr.getDescendantsOfKind(SyntaxKind.Identifier);
2362
2373
  return identifiers.some((id) => this.isIdentifierUsage(id, names));
2363
2374
  }
2375
+ /** `expr` is a use of a tracked name, or contains one. */
2376
+ usesTrackedNames(expr, names) {
2377
+ return Node.isIdentifier(expr)
2378
+ ? this.isIdentifierUsage(expr, names)
2379
+ : this.expressionUsesNames(expr, names);
2380
+ }
2364
2381
  isIdentifierUsage(id, names) {
2365
2382
  if (!Node.isIdentifier(id)) {
2366
2383
  return false;
@@ -110,6 +110,12 @@ export interface InitRequest extends BaseRequest {
110
110
  action: 'init';
111
111
  repo_root: string;
112
112
  tsconfig_path?: string;
113
+ /**
114
+ * The scanned repo's root: with no tsconfig named or in `repo_root`, the
115
+ * nearest `tsconfig.json` above `repo_root` up to this root types the
116
+ * service (carrick#1776). Without it only `repo_root` is searched.
117
+ */
118
+ scan_root?: string;
113
119
  /** Optional tsconfig snapshot (closed/merged) - preferred over tsconfig_path */
114
120
  tsconfig_snapshot?: TsconfigSnapshot;
115
121
  /** Optional pinned dependencies for this repo */
@@ -141,6 +147,8 @@ export interface CaptureV2Request extends BaseRequest {
141
147
  out_dir: string;
142
148
  /** Optional explicit tsconfig path (defaults to <repo_root>/tsconfig.json) */
143
149
  tsconfig_path?: string;
150
+ /** The scanned repo's root, as on `init` (carrick#1776). */
151
+ scan_root?: string;
144
152
  }
145
153
  /**
146
154
  * Response for the capture_v2 action
@@ -69,6 +69,7 @@ export declare const InitRequestSchema: z.ZodObject<{
69
69
  action: z.ZodLiteral<"init">;
70
70
  repo_root: z.ZodString;
71
71
  tsconfig_path: z.ZodOptional<z.ZodString>;
72
+ scan_root: z.ZodOptional<z.ZodString>;
72
73
  tsconfig_snapshot: z.ZodOptional<z.ZodObject<{
73
74
  compilerOptions: z.ZodObject<{
74
75
  module: z.ZodOptional<z.ZodString>;
@@ -161,6 +162,7 @@ export declare const InitRequestSchema: z.ZodObject<{
161
162
  action: "init";
162
163
  repo_root: string;
163
164
  tsconfig_path?: string | undefined;
165
+ scan_root?: string | undefined;
164
166
  tsconfig_snapshot?: {
165
167
  compilerOptions: {
166
168
  module?: string | undefined;
@@ -187,6 +189,7 @@ export declare const InitRequestSchema: z.ZodObject<{
187
189
  action: "init";
188
190
  repo_root: string;
189
191
  tsconfig_path?: string | undefined;
192
+ scan_root?: string | undefined;
190
193
  tsconfig_snapshot?: {
191
194
  compilerOptions: {
192
195
  module?: string | undefined;
@@ -344,6 +347,7 @@ export declare const CaptureV2RequestSchema: z.ZodObject<{
344
347
  }>]>, "many">;
345
348
  out_dir: z.ZodString;
346
349
  tsconfig_path: z.ZodOptional<z.ZodString>;
350
+ scan_root: z.ZodOptional<z.ZodString>;
347
351
  }, "strip", z.ZodTypeAny, {
348
352
  request_id: string;
349
353
  action: "capture_v2";
@@ -381,6 +385,7 @@ export declare const CaptureV2RequestSchema: z.ZodObject<{
381
385
  })[];
382
386
  out_dir: string;
383
387
  tsconfig_path?: string | undefined;
388
+ scan_root?: string | undefined;
384
389
  }, {
385
390
  request_id: string;
386
391
  action: "capture_v2";
@@ -418,6 +423,7 @@ export declare const CaptureV2RequestSchema: z.ZodObject<{
418
423
  })[];
419
424
  out_dir: string;
420
425
  tsconfig_path?: string | undefined;
426
+ scan_root?: string | undefined;
421
427
  }>;
422
428
  export declare const CheckV2RequestSchema: z.ZodObject<{
423
429
  request_id: z.ZodString;
@@ -1810,6 +1816,7 @@ export declare const SidecarRequestSchema: z.ZodDiscriminatedUnion<"action", [z.
1810
1816
  action: z.ZodLiteral<"init">;
1811
1817
  repo_root: z.ZodString;
1812
1818
  tsconfig_path: z.ZodOptional<z.ZodString>;
1819
+ scan_root: z.ZodOptional<z.ZodString>;
1813
1820
  tsconfig_snapshot: z.ZodOptional<z.ZodObject<{
1814
1821
  compilerOptions: z.ZodObject<{
1815
1822
  module: z.ZodOptional<z.ZodString>;
@@ -1902,6 +1909,7 @@ export declare const SidecarRequestSchema: z.ZodDiscriminatedUnion<"action", [z.
1902
1909
  action: "init";
1903
1910
  repo_root: string;
1904
1911
  tsconfig_path?: string | undefined;
1912
+ scan_root?: string | undefined;
1905
1913
  tsconfig_snapshot?: {
1906
1914
  compilerOptions: {
1907
1915
  module?: string | undefined;
@@ -1928,6 +1936,7 @@ export declare const SidecarRequestSchema: z.ZodDiscriminatedUnion<"action", [z.
1928
1936
  action: "init";
1929
1937
  repo_root: string;
1930
1938
  tsconfig_path?: string | undefined;
1939
+ scan_root?: string | undefined;
1931
1940
  tsconfig_snapshot?: {
1932
1941
  compilerOptions: {
1933
1942
  module?: string | undefined;
@@ -2083,6 +2092,7 @@ export declare const SidecarRequestSchema: z.ZodDiscriminatedUnion<"action", [z.
2083
2092
  }>]>, "many">;
2084
2093
  out_dir: z.ZodString;
2085
2094
  tsconfig_path: z.ZodOptional<z.ZodString>;
2095
+ scan_root: z.ZodOptional<z.ZodString>;
2086
2096
  }, "strip", z.ZodTypeAny, {
2087
2097
  request_id: string;
2088
2098
  action: "capture_v2";
@@ -2120,6 +2130,7 @@ export declare const SidecarRequestSchema: z.ZodDiscriminatedUnion<"action", [z.
2120
2130
  })[];
2121
2131
  out_dir: string;
2122
2132
  tsconfig_path?: string | undefined;
2133
+ scan_root?: string | undefined;
2123
2134
  }, {
2124
2135
  request_id: string;
2125
2136
  action: "capture_v2";
@@ -2157,6 +2168,7 @@ export declare const SidecarRequestSchema: z.ZodDiscriminatedUnion<"action", [z.
2157
2168
  })[];
2158
2169
  out_dir: string;
2159
2170
  tsconfig_path?: string | undefined;
2171
+ scan_root?: string | undefined;
2160
2172
  }>, z.ZodObject<{
2161
2173
  request_id: z.ZodString;
2162
2174
  } & {
@@ -136,6 +136,7 @@ export const InitRequestSchema = BaseRequestSchema.extend({
136
136
  action: z.literal('init'),
137
137
  repo_root: z.string().min(1, 'Repo root cannot be empty'),
138
138
  tsconfig_path: z.string().optional(),
139
+ scan_root: z.string().min(1).optional(),
139
140
  tsconfig_snapshot: TsconfigSnapshotSchema.optional(),
140
141
  pinned_dependencies: PinnedDependencySnapshotSchema.optional(),
141
142
  });
@@ -192,6 +193,7 @@ export const CaptureV2RequestSchema = BaseRequestSchema.extend({
192
193
  anchors: z.array(CaptureAnchorRequestSchema).min(1, 'At least one anchor is required'),
193
194
  out_dir: z.string().min(1, 'Output dir cannot be empty'),
194
195
  tsconfig_path: z.string().optional(),
196
+ scan_root: z.string().min(1).optional(),
195
197
  });
196
198
  const CheckStubInputSchema = z.object({
197
199
  service_name: z.string().min(1),