@saptools/service-flow 0.1.68 → 0.1.69
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/README.md +11 -5
- package/TECHNICAL-NOTE.md +9 -0
- package/dist/{chunk-AEM4JY22.js → chunk-3N3B5KHV.js} +6983 -5500
- package/dist/chunk-3N3B5KHV.js.map +1 -0
- package/dist/cli.js +317 -105
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/src/cli/001-index-summary.ts +22 -0
- package/src/cli.ts +151 -87
- package/src/db/000-call-fact-repository.ts +45 -19
- package/src/db/003-current-fact-semantics.ts +1 -0
- package/src/db/004-package-target-invalidation.ts +10 -0
- package/src/db/006-relative-symbol-resolution.ts +28 -7
- package/src/db/008-relative-fact-semantics.ts +6 -3
- package/src/db/009-binding-fact-semantics.ts +5 -0
- package/src/db/012-binding-reference-proof.ts +4 -0
- package/src/db/013-index-publication-failure.ts +91 -0
- package/src/db/014-binding-helper-provenance.ts +17 -0
- package/src/db/repositories.ts +22 -5
- package/src/indexer/cds-extension-resolver.ts +27 -3
- package/src/indexer/repository-indexer.ts +66 -5
- package/src/indexer/workspace-indexer.ts +141 -29
- package/src/linker/004-event-subscription-handler-linker.ts +32 -11
- package/src/linker/006-event-template-link.ts +72 -0
- package/src/linker/007-call-edge-insertion.ts +568 -0
- package/src/linker/cross-repo-linker.ts +2 -165
- package/src/parsers/000-direct-query-execution.ts +11 -0
- package/src/parsers/006-binding-identity.ts +6 -1
- package/src/parsers/014-service-binding-helper-flow.ts +70 -4
- package/src/parsers/021-binding-visibility.ts +17 -1
- package/src/parsers/022-outbound-expression-analysis.ts +700 -0
- package/src/parsers/023-outbound-call-classifier.ts +692 -0
- package/src/parsers/outbound-call-parser.ts +146 -509
- package/src/parsers/symbol-parser.ts +37 -5
- package/src/trace/007-implementation-start-diagnostic.ts +1 -0
- package/src/trace/011-event-subscriber-traversal.ts +100 -8
- package/src/trace/013-trace-root-scopes.ts +2 -3
- package/src/trace/014-compact-contract.ts +6 -0
- package/src/trace/015-trace-edge-recorder.ts +61 -4
- package/src/trace/016-compact-projector.ts +2 -3
- package/src/trace/019-trace-edge-semantics.ts +6 -10
- package/src/trace/020-compact-field-projection.ts +74 -1
- package/src/trace/021-compact-decision-normalization.ts +75 -9
- package/src/trace/024-compact-observation-decision.ts +7 -2
- package/src/trace/026-trace-start-scope.ts +1 -0
- package/src/trace/027-trace-scope-execution.ts +33 -33
- package/src/trace/030-event-runtime-resolution.ts +151 -0
- package/src/trace/031-local-call-expansion.ts +37 -0
- package/src/trace/implementation-hints.ts +9 -6
- package/src/trace/selectors.ts +1 -0
- package/src/types.ts +2 -1
- package/src/version.ts +1 -1
- package/dist/chunk-AEM4JY22.js.map +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -130,7 +130,7 @@ interface ServiceBindingReference {
|
|
|
130
130
|
bindingSourceFile?: string;
|
|
131
131
|
bindingSiteStartOffset?: number;
|
|
132
132
|
bindingSiteEndOffset?: number;
|
|
133
|
-
resolutionStrategy?: 'lexical_declaration' | 'lexical_alias_declaration' | 'deterministic_reaching_assignment';
|
|
133
|
+
resolutionStrategy?: 'lexical_declaration' | 'lexical_alias_declaration' | 'deterministic_reaching_assignment' | 'single_hop_helper_return';
|
|
134
134
|
lexicalScopeChain?: LexicalScopeFact[];
|
|
135
135
|
bindingScopeIndex?: number;
|
|
136
136
|
scopeChainTotal: number;
|
|
@@ -274,6 +274,7 @@ interface ClassifiedOutboundCall {
|
|
|
274
274
|
fact: OutboundCallFact;
|
|
275
275
|
node: ts.CallExpression;
|
|
276
276
|
}
|
|
277
|
+
|
|
277
278
|
declare function parseOutboundCalls(repoPath: string, filePath: string, context?: RepositorySourceContext, classified?: readonly ClassifiedOutboundCall[], preparedBindings?: readonly ServiceBindingFact[]): Promise<OutboundCallFact[]>;
|
|
278
279
|
|
|
279
280
|
declare function parseGeneratedConstants(repoPath: string, filePath: string): Promise<GeneratedConstantFact[]>;
|
|
@@ -393,6 +394,7 @@ interface CompactDecisionV1 {
|
|
|
393
394
|
implementationStrategy?: string;
|
|
394
395
|
implementationGuided?: boolean;
|
|
395
396
|
implementationContextual?: boolean;
|
|
397
|
+
tiedCandidateRepos?: CompactReferenceGroupV1;
|
|
396
398
|
eventMatchStrategy?: string;
|
|
397
399
|
dispatchCertainty?: string;
|
|
398
400
|
eventSubscriptionCount?: number;
|
|
@@ -413,6 +415,10 @@ interface CompactEdgeDetailsV1 {
|
|
|
413
415
|
}
|
|
414
416
|
interface CompactDiagnosticDetailsV1 {
|
|
415
417
|
reasonCode?: string;
|
|
418
|
+
tiedCandidateRepos?: CompactReferenceGroupV1;
|
|
419
|
+
selectorKind?: string;
|
|
420
|
+
selectorSuggestions?: CompactReferenceGroupV1;
|
|
421
|
+
invalidFactCategories?: CompactReferenceGroupV1;
|
|
416
422
|
missingVariableNames?: string[];
|
|
417
423
|
missingVariableCount?: number;
|
|
418
424
|
shownMissingVariableCount?: number;
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
IndexWorkspaceSummary,
|
|
3
|
+
} from '../indexer/workspace-indexer.js';
|
|
4
|
+
|
|
5
|
+
export interface IndexCommandOutcome {
|
|
6
|
+
stdout: string;
|
|
7
|
+
exitCode: 0 | 1;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function indexCommandOutcome(
|
|
11
|
+
summary: IndexWorkspaceSummary,
|
|
12
|
+
): IndexCommandOutcome {
|
|
13
|
+
const failed = summary.failedCount > 0
|
|
14
|
+
? `, failed ${summary.failedCount} (${
|
|
15
|
+
summary.failedRepos.map(({ name, code }) => `${name}: ${code}`).join(', ')
|
|
16
|
+
})`
|
|
17
|
+
: '';
|
|
18
|
+
return {
|
|
19
|
+
stdout: `Indexed ${summary.indexedCount} repositories, skipped ${summary.skippedCount}${failed}, ${summary.fileCount} files, ${summary.diagnosticCount} diagnostics\n`,
|
|
20
|
+
exitCode: summary.failedCount > 0 ? 1 : 0,
|
|
21
|
+
};
|
|
22
|
+
}
|
package/src/cli.ts
CHANGED
|
@@ -41,6 +41,7 @@ import type {
|
|
|
41
41
|
TraceStart,
|
|
42
42
|
} from './types.js';
|
|
43
43
|
import { cleanWorkspaceState } from './cli/000-clean.js';
|
|
44
|
+
import { indexCommandOutcome } from './cli/001-index-summary.js';
|
|
44
45
|
|
|
45
46
|
const stdout = createStdoutWriter(process.stdout, fail);
|
|
46
47
|
const TRACE_FORMATS = ['table', 'json', 'mermaid', 'compact-json'] as const;
|
|
@@ -245,14 +246,16 @@ function runGraphCommand(opts: GraphCommandOptions): Promise<void> {
|
|
|
245
246
|
});
|
|
246
247
|
}
|
|
247
248
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
program
|
|
249
|
+
function configuredProgram(): Command {
|
|
250
|
+
return new Command()
|
|
251
251
|
.name('service-flow')
|
|
252
252
|
.description(
|
|
253
253
|
'Trace SAP CAP service-to-service flows across multi-repository workspaces',
|
|
254
254
|
)
|
|
255
255
|
.version(VERSION);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function registerInitCommand(program: Command): void {
|
|
256
259
|
program
|
|
257
260
|
.command('init')
|
|
258
261
|
.argument('<workspace>')
|
|
@@ -262,6 +265,9 @@ export function createProgram(): Command {
|
|
|
262
265
|
(workspace: string, opts: { db?: string; ignore?: string[] }) =>
|
|
263
266
|
void init(workspace, opts).catch(fail),
|
|
264
267
|
);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function registerIndexCommand(program: Command): void {
|
|
265
271
|
program
|
|
266
272
|
.command('index')
|
|
267
273
|
.option('--workspace <path>')
|
|
@@ -274,11 +280,14 @@ export function createProgram(): Command {
|
|
|
274
280
|
repo: opts.repo,
|
|
275
281
|
force: Boolean(opts.force),
|
|
276
282
|
});
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
);
|
|
283
|
+
const outcome = indexCommandOutcome(r);
|
|
284
|
+
writeStdout(outcome.stdout);
|
|
285
|
+
if (outcome.exitCode !== 0) process.exitCode = outcome.exitCode;
|
|
280
286
|
}).catch(fail),
|
|
281
287
|
);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function registerLinkCommand(program: Command): void {
|
|
282
291
|
program
|
|
283
292
|
.command('link')
|
|
284
293
|
.option('--workspace <path>')
|
|
@@ -293,6 +302,9 @@ export function createProgram(): Command {
|
|
|
293
302
|
);
|
|
294
303
|
}).catch(fail),
|
|
295
304
|
);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function registerTraceCommand(program: Command): void {
|
|
296
308
|
program
|
|
297
309
|
.command('trace')
|
|
298
310
|
.option('--workspace <path>')
|
|
@@ -312,23 +324,91 @@ export function createProgram(): Command {
|
|
|
312
324
|
.option('--dynamic-mode <mode>', 'strict|candidates|infer', 'strict')
|
|
313
325
|
.option('--max-dynamic-candidates <n>', 'maximum dynamic candidates to show', '5')
|
|
314
326
|
.action((opts: TraceCommandOptions) => void runTraceCommand(opts).catch(fail));
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function listRepositoriesCommand(
|
|
330
|
+
opts: { workspace?: string },
|
|
331
|
+
): Promise<void> {
|
|
332
|
+
return withReadOnlyWorkspace(opts.workspace, (db, workspaceId) =>
|
|
333
|
+
writeStdout(
|
|
334
|
+
renderJson(
|
|
335
|
+
listRepositories(db, workspaceId).map((repo) => ({
|
|
336
|
+
name: repo.name,
|
|
337
|
+
kind: repo.kind,
|
|
338
|
+
packageName: repo.package_name,
|
|
339
|
+
})),
|
|
340
|
+
),
|
|
341
|
+
));
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function listServicesCommand(
|
|
345
|
+
opts: { workspace?: string; repo?: string },
|
|
346
|
+
): Promise<void> {
|
|
347
|
+
return withReadOnlyWorkspace(opts.workspace, (db, workspaceId) => {
|
|
348
|
+
const selection = opts.repo
|
|
349
|
+
? selectRepository(db, opts.repo, workspaceId) : {};
|
|
350
|
+
if (selection.diagnostic) {
|
|
351
|
+
writeStdout(renderJson([selection.diagnostic]));
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
const repo = selection.repo;
|
|
355
|
+
const rows = db.prepare(
|
|
356
|
+
'SELECT r.name repo,s.service_path servicePath,s.qualified_name qualifiedName FROM cds_services s JOIN repositories r ON r.id=s.repo_id WHERE r.workspace_id=? AND (? IS NULL OR s.repo_id=?) ORDER BY r.name,s.service_path',
|
|
357
|
+
).all(workspaceId, repo?.id, repo?.id);
|
|
358
|
+
writeStdout(renderJson(rows));
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function listOperationsCommand(
|
|
363
|
+
opts: { workspace?: string; repo?: string; service?: string },
|
|
364
|
+
): Promise<void> {
|
|
365
|
+
return withReadOnlyWorkspace(opts.workspace, (db, workspaceId) => {
|
|
366
|
+
const selection = opts.repo
|
|
367
|
+
? selectRepository(db, opts.repo, workspaceId) : {};
|
|
368
|
+
if (selection.diagnostic) {
|
|
369
|
+
writeStdout(renderJson([selection.diagnostic]));
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
const repo = selection.repo;
|
|
373
|
+
const rows = db.prepare(
|
|
374
|
+
'SELECT r.name repo,s.service_path servicePath,o.operation_name operation,o.operation_path path FROM cds_operations o JOIN cds_services s ON s.id=o.service_id JOIN repositories r ON r.id=s.repo_id WHERE r.workspace_id=? AND (? IS NULL OR s.repo_id=?) AND (? IS NULL OR s.service_path=?)',
|
|
375
|
+
).all(
|
|
376
|
+
workspaceId, repo?.id, repo?.id, opts.service, opts.service,
|
|
377
|
+
);
|
|
378
|
+
writeStdout(renderJson(rows));
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function listCallsCommand(
|
|
383
|
+
opts: { workspace?: string; repo?: string; operation?: string },
|
|
384
|
+
): Promise<void> {
|
|
385
|
+
return withReadOnlyWorkspace(opts.workspace, (db, workspaceId) => {
|
|
386
|
+
const selection = opts.repo
|
|
387
|
+
? selectRepository(db, opts.repo, workspaceId) : {};
|
|
388
|
+
if (selection.diagnostic) {
|
|
389
|
+
writeStdout(renderJson([selection.diagnostic]));
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
const repo = selection.repo;
|
|
393
|
+
const rows = db.prepare(
|
|
394
|
+
'SELECT r.name repo,c.call_type type,c.operation_path_expr path,c.source_file file,c.source_line line FROM outbound_calls c JOIN repositories r ON r.id=c.repo_id WHERE r.workspace_id=? AND (? IS NULL OR c.repo_id=?) AND (? IS NULL OR c.operation_path_expr=? OR c.operation_path_expr=? OR c.payload_summary LIKE ?)',
|
|
395
|
+
).all(
|
|
396
|
+
workspaceId, repo?.id, repo?.id, opts.operation, opts.operation,
|
|
397
|
+
opts.operation ? `/${opts.operation}` : undefined,
|
|
398
|
+
opts.operation ? `%${opts.operation}%` : undefined,
|
|
399
|
+
);
|
|
400
|
+
writeStdout(renderJson(rows));
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
function registerListCommands(program: Command): void {
|
|
315
405
|
const list = program.command('list');
|
|
316
406
|
list
|
|
317
407
|
.command('repos')
|
|
318
408
|
.option('--workspace <path>')
|
|
319
409
|
.action(
|
|
320
410
|
(opts: { workspace?: string }) =>
|
|
321
|
-
void
|
|
322
|
-
writeStdout(
|
|
323
|
-
renderJson(
|
|
324
|
-
listRepositories(db, workspaceId).map((r) => ({
|
|
325
|
-
name: r.name,
|
|
326
|
-
kind: r.kind,
|
|
327
|
-
packageName: r.package_name,
|
|
328
|
-
})),
|
|
329
|
-
),
|
|
330
|
-
),
|
|
331
|
-
).catch(fail),
|
|
411
|
+
void listRepositoriesCommand(opts).catch(fail),
|
|
332
412
|
);
|
|
333
413
|
list
|
|
334
414
|
.command('services')
|
|
@@ -336,22 +416,7 @@ export function createProgram(): Command {
|
|
|
336
416
|
.option('--repo <name>')
|
|
337
417
|
.action(
|
|
338
418
|
(opts: { workspace?: string; repo?: string }) =>
|
|
339
|
-
void
|
|
340
|
-
const selection = opts.repo
|
|
341
|
-
? selectRepository(db, opts.repo, workspaceId)
|
|
342
|
-
: {};
|
|
343
|
-
if (selection.diagnostic) {
|
|
344
|
-
writeStdout(renderJson([selection.diagnostic]));
|
|
345
|
-
return;
|
|
346
|
-
}
|
|
347
|
-
const repo = selection.repo;
|
|
348
|
-
const rows = db
|
|
349
|
-
.prepare(
|
|
350
|
-
'SELECT r.name repo,s.service_path servicePath,s.qualified_name qualifiedName FROM cds_services s JOIN repositories r ON r.id=s.repo_id WHERE r.workspace_id=? AND (? IS NULL OR s.repo_id=?) ORDER BY r.name,s.service_path',
|
|
351
|
-
)
|
|
352
|
-
.all(workspaceId, repo?.id, repo?.id);
|
|
353
|
-
writeStdout(renderJson(rows));
|
|
354
|
-
}).catch(fail),
|
|
419
|
+
void listServicesCommand(opts).catch(fail),
|
|
355
420
|
);
|
|
356
421
|
list
|
|
357
422
|
.command('operations')
|
|
@@ -360,22 +425,7 @@ export function createProgram(): Command {
|
|
|
360
425
|
.option('--service <path>')
|
|
361
426
|
.action(
|
|
362
427
|
(opts: { workspace?: string; repo?: string; service?: string }) =>
|
|
363
|
-
void
|
|
364
|
-
const selection = opts.repo
|
|
365
|
-
? selectRepository(db, opts.repo, workspaceId)
|
|
366
|
-
: {};
|
|
367
|
-
if (selection.diagnostic) {
|
|
368
|
-
writeStdout(renderJson([selection.diagnostic]));
|
|
369
|
-
return;
|
|
370
|
-
}
|
|
371
|
-
const repo = selection.repo;
|
|
372
|
-
const rows = db
|
|
373
|
-
.prepare(
|
|
374
|
-
'SELECT r.name repo,s.service_path servicePath,o.operation_name operation,o.operation_path path FROM cds_operations o JOIN cds_services s ON s.id=o.service_id JOIN repositories r ON r.id=s.repo_id WHERE r.workspace_id=? AND (? IS NULL OR s.repo_id=?) AND (? IS NULL OR s.service_path=?)',
|
|
375
|
-
)
|
|
376
|
-
.all(workspaceId, repo?.id, repo?.id, opts.service, opts.service);
|
|
377
|
-
writeStdout(renderJson(rows));
|
|
378
|
-
}).catch(fail),
|
|
428
|
+
void listOperationsCommand(opts).catch(fail),
|
|
379
429
|
);
|
|
380
430
|
list
|
|
381
431
|
.command('calls')
|
|
@@ -384,31 +434,11 @@ export function createProgram(): Command {
|
|
|
384
434
|
.option('--operation <name>')
|
|
385
435
|
.action(
|
|
386
436
|
(opts: { workspace?: string; repo?: string; operation?: string }) =>
|
|
387
|
-
void
|
|
388
|
-
const selection = opts.repo
|
|
389
|
-
? selectRepository(db, opts.repo, workspaceId)
|
|
390
|
-
: {};
|
|
391
|
-
if (selection.diagnostic) {
|
|
392
|
-
writeStdout(renderJson([selection.diagnostic]));
|
|
393
|
-
return;
|
|
394
|
-
}
|
|
395
|
-
const repo = selection.repo;
|
|
396
|
-
const rows = db
|
|
397
|
-
.prepare(
|
|
398
|
-
'SELECT r.name repo,c.call_type type,c.operation_path_expr path,c.source_file file,c.source_line line FROM outbound_calls c JOIN repositories r ON r.id=c.repo_id WHERE r.workspace_id=? AND (? IS NULL OR c.repo_id=?) AND (? IS NULL OR c.operation_path_expr=? OR c.operation_path_expr=? OR c.payload_summary LIKE ?)',
|
|
399
|
-
)
|
|
400
|
-
.all(
|
|
401
|
-
workspaceId,
|
|
402
|
-
repo?.id,
|
|
403
|
-
repo?.id,
|
|
404
|
-
opts.operation,
|
|
405
|
-
opts.operation,
|
|
406
|
-
opts.operation ? `/${opts.operation}` : undefined,
|
|
407
|
-
opts.operation ? `%${opts.operation}%` : undefined,
|
|
408
|
-
);
|
|
409
|
-
writeStdout(renderJson(rows));
|
|
410
|
-
}).catch(fail),
|
|
437
|
+
void listCallsCommand(opts).catch(fail),
|
|
411
438
|
);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
function registerGraphCommand(program: Command): void {
|
|
412
442
|
program
|
|
413
443
|
.command('graph')
|
|
414
444
|
.option('--workspace <path>')
|
|
@@ -423,6 +453,33 @@ export function createProgram(): Command {
|
|
|
423
453
|
.option('--dynamic-mode <mode>', 'strict|candidates|infer', 'strict')
|
|
424
454
|
.option('--max-dynamic-candidates <n>', 'maximum dynamic candidates to show', '5')
|
|
425
455
|
.action((opts: GraphCommandOptions) => void runGraphCommand(opts).catch(fail));
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
function inspectRepositoryCommand(
|
|
459
|
+
name: string,
|
|
460
|
+
opts: { workspace?: string },
|
|
461
|
+
): Promise<void> {
|
|
462
|
+
return withReadOnlyWorkspace(opts.workspace, (db, workspaceId) => {
|
|
463
|
+
const selection = selectRepository(db, name, workspaceId);
|
|
464
|
+
writeStdout(renderJson(
|
|
465
|
+
selection.repo ?? selection.diagnostic ?? { error: 'repo not found' },
|
|
466
|
+
));
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
function inspectOperationCommand(
|
|
471
|
+
selector: string,
|
|
472
|
+
opts: { workspace?: string },
|
|
473
|
+
): Promise<void> {
|
|
474
|
+
return withReadOnlyWorkspace(opts.workspace, (db, workspaceId) => {
|
|
475
|
+
const rows = db.prepare(
|
|
476
|
+
'SELECT o.* FROM cds_operations o JOIN cds_services s ON s.id=o.service_id JOIN repositories r ON r.id=s.repo_id WHERE r.workspace_id=? AND (o.operation_name=? OR o.operation_path=?)',
|
|
477
|
+
).all(workspaceId, selector, selector);
|
|
478
|
+
writeStdout(renderJson(rows));
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
function registerInspectCommands(program: Command): void {
|
|
426
483
|
const inspect = program.command('inspect');
|
|
427
484
|
inspect
|
|
428
485
|
.command('repo')
|
|
@@ -430,12 +487,7 @@ export function createProgram(): Command {
|
|
|
430
487
|
.option('--workspace <path>')
|
|
431
488
|
.action(
|
|
432
489
|
(name: string, opts: { workspace?: string }) =>
|
|
433
|
-
void
|
|
434
|
-
const selection = selectRepository(db, name, workspaceId);
|
|
435
|
-
writeStdout(renderJson(
|
|
436
|
-
selection.repo ?? selection.diagnostic ?? { error: 'repo not found' },
|
|
437
|
-
));
|
|
438
|
-
}).catch(fail),
|
|
490
|
+
void inspectRepositoryCommand(name, opts).catch(fail),
|
|
439
491
|
);
|
|
440
492
|
inspect
|
|
441
493
|
.command('operation')
|
|
@@ -443,15 +495,11 @@ export function createProgram(): Command {
|
|
|
443
495
|
.option('--workspace <path>')
|
|
444
496
|
.action(
|
|
445
497
|
(selector: string, opts: { workspace?: string }) =>
|
|
446
|
-
void
|
|
447
|
-
const rows = db
|
|
448
|
-
.prepare(
|
|
449
|
-
'SELECT o.* FROM cds_operations o JOIN cds_services s ON s.id=o.service_id JOIN repositories r ON r.id=s.repo_id WHERE r.workspace_id=? AND (o.operation_name=? OR o.operation_path=?)',
|
|
450
|
-
)
|
|
451
|
-
.all(workspaceId, selector, selector);
|
|
452
|
-
writeStdout(renderJson(rows));
|
|
453
|
-
}).catch(fail),
|
|
498
|
+
void inspectOperationCommand(selector, opts).catch(fail),
|
|
454
499
|
);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
function registerDoctorCommand(program: Command): void {
|
|
455
503
|
program
|
|
456
504
|
.command('doctor')
|
|
457
505
|
.option('--workspace <path>')
|
|
@@ -467,6 +515,9 @@ export function createProgram(): Command {
|
|
|
467
515
|
writeStdout(renderDoctorDiagnostics(allDiagnostics, opts.format));
|
|
468
516
|
}).catch(fail),
|
|
469
517
|
);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
function registerCleanCommand(program: Command): void {
|
|
470
521
|
program
|
|
471
522
|
.command('clean')
|
|
472
523
|
.option('--workspace <path>')
|
|
@@ -479,6 +530,19 @@ export function createProgram(): Command {
|
|
|
479
530
|
writeStdout('Cleaned service-flow state\n');
|
|
480
531
|
})().catch(fail),
|
|
481
532
|
);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
export function createProgram(): Command {
|
|
536
|
+
const program = configuredProgram();
|
|
537
|
+
registerInitCommand(program);
|
|
538
|
+
registerIndexCommand(program);
|
|
539
|
+
registerLinkCommand(program);
|
|
540
|
+
registerTraceCommand(program);
|
|
541
|
+
registerListCommands(program);
|
|
542
|
+
registerGraphCommand(program);
|
|
543
|
+
registerInspectCommands(program);
|
|
544
|
+
registerDoctorCommand(program);
|
|
545
|
+
registerCleanCommand(program);
|
|
482
546
|
return program;
|
|
483
547
|
}
|
|
484
548
|
function collect(value: string, previous: string[]): string[] {
|
|
@@ -16,6 +16,12 @@ import {
|
|
|
16
16
|
type BindingProofCall,
|
|
17
17
|
type BindingProofTarget,
|
|
18
18
|
} from './012-binding-reference-proof.js';
|
|
19
|
+
import {
|
|
20
|
+
preparedCallSnapshotError,
|
|
21
|
+
} from './013-index-publication-failure.js';
|
|
22
|
+
import {
|
|
23
|
+
hasSingleHopHelperReturn,
|
|
24
|
+
} from './014-binding-helper-provenance.js';
|
|
19
25
|
|
|
20
26
|
export function insertSymbolCalls(db: Db, repoId: number, rows: SymbolCallFact[]): void {
|
|
21
27
|
const insertStmt = db.prepare('INSERT INTO symbol_calls(repo_id,caller_symbol_id,callee_symbol_id,callee_expression,import_source,source_file,source_line,call_site_start_offset,call_site_end_offset,call_role,status,confidence,evidence_json,unresolved_reason) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?)');
|
|
@@ -58,8 +64,8 @@ function assertImportProvenance(call: SymbolCallFact): void {
|
|
|
58
64
|
const direct = directPackageProvenanceValid(call);
|
|
59
65
|
const derived = derivedPackageProvenanceValid(call);
|
|
60
66
|
if (!direct && !derived)
|
|
61
|
-
throw
|
|
62
|
-
'
|
|
67
|
+
throw preparedCallSnapshotError(
|
|
68
|
+
'package_import_provenance_missing', 'symbol_call', call,
|
|
63
69
|
);
|
|
64
70
|
}
|
|
65
71
|
|
|
@@ -156,7 +162,9 @@ function requiredSymbolCallOwnerId(
|
|
|
156
162
|
);
|
|
157
163
|
if (selected.status !== 'resolved'
|
|
158
164
|
|| selected.owner?.qualifiedName !== call.callerQualifiedName)
|
|
159
|
-
throw
|
|
165
|
+
throw preparedCallSnapshotError(
|
|
166
|
+
'symbol_call_owner_mismatch', 'symbol_call', call,
|
|
167
|
+
);
|
|
160
168
|
return selected.owner.id;
|
|
161
169
|
}
|
|
162
170
|
|
|
@@ -265,9 +273,11 @@ function exportedSymbolRows(db: Db, repoId: number, r: SymbolCallFact): SymbolTa
|
|
|
265
273
|
));
|
|
266
274
|
}
|
|
267
275
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
276
|
+
const sameFileEligibleRelations = new Set([
|
|
277
|
+
'indexed_local_symbol',
|
|
278
|
+
'indexed_local_symbol_unproven',
|
|
279
|
+
'indexed_this_method',
|
|
280
|
+
]);
|
|
271
281
|
|
|
272
282
|
function eligibleSymbolResolution(
|
|
273
283
|
rows: SymbolTargetRow[],
|
|
@@ -292,8 +302,7 @@ function sameFileResolution(
|
|
|
292
302
|
r: SymbolCallFact,
|
|
293
303
|
relation: unknown,
|
|
294
304
|
): SymbolCallResolution | undefined {
|
|
295
|
-
if (
|
|
296
|
-
return undefined;
|
|
305
|
+
if (!sameFileEligibleRelations.has(String(relation))) return undefined;
|
|
297
306
|
if (relation === 'indexed_local_symbol_unproven')
|
|
298
307
|
return unresolvedSymbol(
|
|
299
308
|
'exact_symbol_match', 'no_local_symbol_target', 0,
|
|
@@ -452,6 +461,7 @@ interface PersistedBinding {
|
|
|
452
461
|
ownerResolution: string;
|
|
453
462
|
ownerStartOffset: number | null;
|
|
454
463
|
ownerEndOffset: number | null;
|
|
464
|
+
singleHopHelperReturn: boolean;
|
|
455
465
|
}
|
|
456
466
|
|
|
457
467
|
function outboundOwnerId(
|
|
@@ -471,13 +481,18 @@ function outboundOwnerId(
|
|
|
471
481
|
);
|
|
472
482
|
const resolution = call.evidence?.sourceOwnerResolution;
|
|
473
483
|
if (resolution === 'ownerless_file_scope')
|
|
474
|
-
return ownerlessOutboundOwner(selected.status);
|
|
484
|
+
return ownerlessOutboundOwner(call, selected.status);
|
|
475
485
|
return ownedOutboundOwner(call, resolution, selected);
|
|
476
486
|
}
|
|
477
487
|
|
|
478
|
-
function ownerlessOutboundOwner(
|
|
488
|
+
function ownerlessOutboundOwner(
|
|
489
|
+
call: OutboundCallFact,
|
|
490
|
+
status: string,
|
|
491
|
+
): null {
|
|
479
492
|
if (status !== 'none')
|
|
480
|
-
throw
|
|
493
|
+
throw preparedCallSnapshotError(
|
|
494
|
+
'outbound_owner_mismatch', 'outbound_call', call,
|
|
495
|
+
);
|
|
481
496
|
return null;
|
|
482
497
|
}
|
|
483
498
|
|
|
@@ -488,10 +503,14 @@ function ownedOutboundOwner(
|
|
|
488
503
|
): number {
|
|
489
504
|
if (resolution !== 'owned_exact' || selected.status !== 'resolved'
|
|
490
505
|
|| selected.owner?.qualifiedName !== call.sourceSymbolQualifiedName)
|
|
491
|
-
throw
|
|
506
|
+
throw preparedCallSnapshotError(
|
|
507
|
+
'outbound_owner_mismatch', 'outbound_call', call,
|
|
508
|
+
);
|
|
492
509
|
const owner = selected.owner;
|
|
493
510
|
if (!owner)
|
|
494
|
-
throw
|
|
511
|
+
throw preparedCallSnapshotError(
|
|
512
|
+
'outbound_owner_mismatch', 'outbound_call', call,
|
|
513
|
+
);
|
|
495
514
|
return owner.id;
|
|
496
515
|
}
|
|
497
516
|
|
|
@@ -506,14 +525,18 @@ function resolvePersistedBinding(
|
|
|
506
525
|
} {
|
|
507
526
|
const reference = call.serviceBindingReference;
|
|
508
527
|
if (!reference)
|
|
509
|
-
throw
|
|
528
|
+
throw preparedCallSnapshotError(
|
|
529
|
+
'binding_reference_missing', 'outbound_call', call,
|
|
530
|
+
);
|
|
510
531
|
if (reference.status !== 'resolved_exact')
|
|
511
532
|
return unresolvedBinding(reference);
|
|
512
533
|
const candidates = exactBindingRows(db, repoId, reference);
|
|
513
534
|
const selected = candidates[0];
|
|
514
535
|
if (candidates.length !== 1 || !selected
|
|
515
536
|
|| selected.variableName !== call.serviceVariableName)
|
|
516
|
-
throw
|
|
537
|
+
throw preparedCallSnapshotError(
|
|
538
|
+
'binding_reference_mismatch', 'outbound_call', call,
|
|
539
|
+
);
|
|
517
540
|
assertResolvedBindingProof(repoId, call, selected, reference);
|
|
518
541
|
return {
|
|
519
542
|
bindingId: selected.id,
|
|
@@ -533,8 +556,8 @@ function bindingProofCall(
|
|
|
533
556
|
): BindingProofCall {
|
|
534
557
|
if (call.callSiteStartOffset === undefined
|
|
535
558
|
|| call.callSiteEndOffset === undefined)
|
|
536
|
-
throw
|
|
537
|
-
'
|
|
559
|
+
throw preparedCallSnapshotError(
|
|
560
|
+
'binding_lexical_proof_invalid', 'outbound_call', call,
|
|
538
561
|
);
|
|
539
562
|
return {
|
|
540
563
|
repoId,
|
|
@@ -561,6 +584,7 @@ function bindingProofTarget(
|
|
|
561
584
|
ownerResolution: binding.ownerResolution,
|
|
562
585
|
ownerStartOffset: binding.ownerStartOffset,
|
|
563
586
|
ownerEndOffset: binding.ownerEndOffset,
|
|
587
|
+
singleHopHelperReturn: binding.singleHopHelperReturn,
|
|
564
588
|
};
|
|
565
589
|
}
|
|
566
590
|
|
|
@@ -576,8 +600,8 @@ function assertResolvedBindingProof(
|
|
|
576
600
|
bindingProofTarget(repoId, binding),
|
|
577
601
|
);
|
|
578
602
|
if (!valid)
|
|
579
|
-
throw
|
|
580
|
-
'
|
|
603
|
+
throw preparedCallSnapshotError(
|
|
604
|
+
'binding_lexical_proof_invalid', 'outbound_call', call,
|
|
581
605
|
);
|
|
582
606
|
}
|
|
583
607
|
|
|
@@ -608,6 +632,7 @@ function exactBindingRows(
|
|
|
608
632
|
binding.binding_site_start_offset siteStart,
|
|
609
633
|
binding.binding_site_end_offset siteEnd,
|
|
610
634
|
binding.owner_resolution ownerResolution,
|
|
635
|
+
binding.helper_chain_json helperChainJson,
|
|
611
636
|
owner.start_offset ownerStartOffset,owner.end_offset ownerEndOffset
|
|
612
637
|
FROM service_bindings binding
|
|
613
638
|
LEFT JOIN symbols owner ON owner.id=binding.symbol_id
|
|
@@ -642,6 +667,7 @@ function persistedBindingRow(
|
|
|
642
667
|
ownerResolution: row.ownerResolution,
|
|
643
668
|
ownerStartOffset,
|
|
644
669
|
ownerEndOffset,
|
|
670
|
+
singleHopHelperReturn: hasSingleHopHelperReturn(row.helperChainJson),
|
|
645
671
|
}];
|
|
646
672
|
}
|
|
647
673
|
|
|
@@ -71,6 +71,7 @@ const symbolReasons = [
|
|
|
71
71
|
'relative_import_type_only',
|
|
72
72
|
'relative_import_module_resolution_ambiguous',
|
|
73
73
|
'relative_import_requested_module_has_no_executable_body',
|
|
74
|
+
'relative_import_proxy_alias_targets_package_unsupported',
|
|
74
75
|
'relative_import_requested_module_has_no_target',
|
|
75
76
|
'multiple_relative_class_targets_in_requested_module',
|
|
76
77
|
'multiple_namespace_targets_in_requested_module',
|
|
@@ -157,6 +157,16 @@ export function createPackageInvalidationBatch(
|
|
|
157
157
|
};
|
|
158
158
|
}
|
|
159
159
|
|
|
160
|
+
export function mergePackageInvalidationEffects(
|
|
161
|
+
target: PackageInvalidationBatch,
|
|
162
|
+
source: PackageInvalidationBatch,
|
|
163
|
+
): void {
|
|
164
|
+
for (const repoId of source.affectedCallerRepoIds)
|
|
165
|
+
target.affectedCallerRepoIds.add(repoId);
|
|
166
|
+
for (const workspaceId of source.affectedWorkspaceIds)
|
|
167
|
+
target.affectedWorkspaceIds.add(workspaceId);
|
|
168
|
+
}
|
|
169
|
+
|
|
160
170
|
export function finalizePackageTargetInvalidations(
|
|
161
171
|
db: Db,
|
|
162
172
|
batch: PackageInvalidationBatch,
|