@saptools/service-flow 0.1.65 → 0.1.67

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 (42) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +67 -11
  3. package/TECHNICAL-NOTE.md +41 -1
  4. package/dist/{chunk-OONNRIDL.js → chunk-ZQABU7MR.js} +3764 -643
  5. package/dist/chunk-ZQABU7MR.js.map +1 -0
  6. package/dist/cli.js +158 -295
  7. package/dist/cli.js.map +1 -1
  8. package/dist/index.d.ts +196 -1
  9. package/dist/index.js +7 -3
  10. package/dist/index.js.map +1 -1
  11. package/package.json +1 -1
  12. package/src/cli/002-doctor-lifecycle.ts +66 -0
  13. package/src/cli/doctor.ts +14 -27
  14. package/src/cli.ts +130 -102
  15. package/src/db/000-call-fact-repository.ts +537 -0
  16. package/src/db/001-fact-lifecycle.ts +111 -0
  17. package/src/db/migrations.ts +16 -1
  18. package/src/db/repositories.ts +1 -315
  19. package/src/db/schema.ts +4 -2
  20. package/src/index.ts +22 -0
  21. package/src/linker/004-event-subscription-handler-linker.ts +300 -0
  22. package/src/linker/cross-repo-linker.ts +23 -2
  23. package/src/output/001-compact-json-output.ts +6 -0
  24. package/src/parsers/imported-wrapper-parser.ts +4 -0
  25. package/src/parsers/outbound-call-parser.ts +11 -1
  26. package/src/parsers/symbol-parser.ts +7 -4
  27. package/src/trace/010-traversal-scope.ts +188 -0
  28. package/src/trace/011-event-subscriber-traversal.ts +366 -0
  29. package/src/trace/012-trace-graph-lookups.ts +74 -0
  30. package/src/trace/013-trace-root-scopes.ts +279 -0
  31. package/src/trace/014-compact-contract.ts +347 -0
  32. package/src/trace/015-trace-edge-recorder.ts +336 -0
  33. package/src/trace/016-compact-projector.ts +697 -0
  34. package/src/trace/017-trace-context.ts +378 -0
  35. package/src/trace/018-compact-trace.ts +87 -0
  36. package/src/trace/019-trace-edge-semantics.ts +328 -0
  37. package/src/trace/020-compact-field-projection.ts +387 -0
  38. package/src/trace/dynamic-branches.ts +35 -13
  39. package/src/trace/trace-engine.ts +271 -245
  40. package/src/types.ts +10 -0
  41. package/src/version.ts +1 -1
  42. package/dist/chunk-OONNRIDL.js.map +0 -1
@@ -1,16 +1,12 @@
1
- import { posix } from 'node:path';
2
1
  import type { Db } from './connection.js';
3
- import { projectBounded } from '../utils/000-bounded-projection.js';
4
2
  import type {
5
3
  CdsRequire,
6
4
  CdsServiceFact,
7
5
  HandlerClassFact,
8
6
  HandlerMethodFact,
9
7
  HandlerRegistrationFact,
10
- OutboundCallFact,
11
8
  ServiceBindingFact,
12
9
  ExecutableSymbolFact,
13
- SymbolCallFact,
14
10
  } from '../types.js';
15
11
  export interface RepoRow {
16
12
  id: number;
@@ -385,314 +381,4 @@ export function insertExecutableSymbols(db: Db, repoId: number, rows: Executable
385
381
  const stmt = db.prepare('INSERT INTO symbols(repo_id,file_id,kind,name,qualified_name,exported,start_line,end_line,start_offset,end_offset,source_file,exported_name,evidence_json) VALUES(?,(SELECT id FROM files WHERE repo_id=? AND relative_path=?),?,?,?,?,?,?,?,?,?,?,?)');
386
382
  for (const r of rows) stmt.run(repoId, repoId, r.sourceFile, r.kind, r.localName, r.qualifiedName, r.exported ? 1 : 0, r.startLine, r.endLine, r.startOffset, r.endOffset, r.sourceFile, r.exportedName, r.importExportEvidence ? JSON.stringify(r.importExportEvidence) : null);
387
383
  }
388
- export function insertSymbolCalls(db: Db, repoId: number, rows: SymbolCallFact[]): void {
389
- const callerStmt = db.prepare('SELECT id FROM symbols WHERE repo_id=? AND source_file=? AND qualified_name=? ORDER BY id LIMIT 1');
390
- const insertStmt = db.prepare('INSERT INTO symbol_calls(repo_id,caller_symbol_id,callee_symbol_id,callee_expression,import_source,source_file,source_line,status,confidence,evidence_json,unresolved_reason) VALUES(?,?,?,?,?,?,?,?,?,?,?)');
391
- for (const r of rows) {
392
- const caller = callerStmt.get(repoId, r.sourceFile, r.callerQualifiedName) as { id?: number } | undefined;
393
- const target = resolveSymbolCallTarget(db, repoId, r);
394
- insertStmt.run(repoId, caller?.id, target.id, r.calleeExpression, r.importSource, r.sourceFile, r.sourceLine, target.status, 0.8, JSON.stringify({ ...r.evidence, candidateStrategy: target.strategy, candidateCount: target.candidateCount, resolvedModulePath: target.resolvedModulePath }), target.reason);
395
- }
396
- }
397
- interface SymbolTargetRow { id: number; kind?: string; sourceFile?: string | null; evidenceJson?: string | null }
398
- interface SymbolCallResolution { id: number | null; status: 'resolved' | 'ambiguous' | 'unresolved'; reason: string | null; strategy: string; candidateCount: number; resolvedModulePath?: string }
399
- const stripExt = (value: string): string => value.replace(/\.(ts|tsx|js|jsx|cds)$/, '');
400
- function symbolTargetRows(rows: Array<Record<string, unknown>>): SymbolTargetRow[] {
401
- return rows.flatMap((row) => typeof row.id === 'number' ? [{ id: row.id, kind: typeof row.kind === 'string' ? row.kind : undefined, sourceFile: nullableString(row.sourceFile), evidenceJson: nullableString(row.evidenceJson) }] : []);
402
- }
403
- function relativeModuleTargets(callerSourceFile: string, importSource: string): Set<string> {
404
- const base = posix.dirname(callerSourceFile);
405
- const joined = stripExt(posix.normalize(posix.join(base, importSource)));
406
- return new Set([joined, `${joined}/index`]);
407
- }
408
- function moduleRows(rows: SymbolTargetRow[], r: SymbolCallFact): SymbolTargetRow[] {
409
- if (!r.importSource) return [];
410
- const targets = relativeModuleTargets(r.sourceFile, r.importSource);
411
- return rows.filter((row) => typeof row.sourceFile === 'string' && targets.has(stripExt(row.sourceFile)));
412
- }
413
- function resolvedSymbol(row: SymbolTargetRow, strategy: string, candidateCount: number, moduleScoped = false): SymbolCallResolution {
414
- return { id: row.id, status: 'resolved', reason: null, strategy, candidateCount, resolvedModulePath: moduleScoped && row.sourceFile ? stripExt(row.sourceFile) : undefined };
415
- }
416
- function exportedSymbolRows(db: Db, repoId: number, r: SymbolCallFact): SymbolTargetRow[] {
417
- return symbolTargetRows(db.prepare('SELECT id,kind,source_file sourceFile,evidence_json evidenceJson FROM symbols WHERE repo_id=? AND source_file<>? AND exported=1 AND (exported_name=? OR name=? OR qualified_name=?) ORDER BY id').all(repoId, r.sourceFile, r.calleeLocalName, r.calleeLocalName, r.calleeLocalName));
418
- }
419
- function isRelativeImportedSymbolCall(r: SymbolCallFact): boolean {
420
- return Boolean(r.importSource?.startsWith('.'));
421
- }
422
- function sameFileResolution(db: Db, repoId: number, r: SymbolCallFact, relation: unknown): SymbolCallResolution | undefined {
423
- const bareImport = relation === 'relative_import' && isRelativeImportedSymbolCall(r) && !String(r.calleeLocalName).includes('.');
424
- if (bareImport || relation === 'relative_import_namespace_member' || relation === 'package_import') return undefined;
425
- const rows = symbolTargetRows(db.prepare('SELECT id FROM symbols WHERE repo_id=? AND source_file=? AND (name=? OR qualified_name=?) ORDER BY id').all(repoId, r.sourceFile, r.calleeLocalName, r.calleeLocalName));
426
- if (rows.length === 1 && rows[0]) return resolvedSymbol(rows[0], 'same_file_exact', 1);
427
- return rows.length > 1 ? { id: null, status: 'ambiguous', reason: 'Multiple same-file symbol targets matched exactly', strategy: 'same_file_exact', candidateCount: rows.length } : undefined;
428
- }
429
- function classInstanceResolution(db: Db, repoId: number, r: SymbolCallFact, relation: unknown): SymbolCallResolution | undefined {
430
- if (relation !== 'class_instance_method' || !isRelativeImportedSymbolCall(r)) return undefined;
431
- const rows = symbolTargetRows(db.prepare('SELECT id FROM symbols WHERE repo_id=? AND source_file<>? AND qualified_name=? ORDER BY id').all(repoId, r.sourceFile, r.calleeLocalName));
432
- if (rows.length === 1 && rows[0]) return resolvedSymbol(rows[0], 'relative_import_class_instance_method', 1);
433
- return rows.length > 1 ? { id: null, status: 'ambiguous', reason: 'Multiple relative class instance method targets matched exactly', strategy: 'relative_import_class_instance_method', candidateCount: rows.length } : undefined;
434
- }
435
- function namespaceResolution(db: Db, repoId: number, r: SymbolCallFact, relation: unknown): SymbolCallResolution | undefined {
436
- if (relation !== 'relative_import_namespace_member' || !isRelativeImportedSymbolCall(r)) return undefined;
437
- const rows = moduleRows(exportedSymbolRows(db, repoId, r), r);
438
- if (rows.length === 1 && rows[0]) return resolvedSymbol(rows[0], 'relative_import_namespace_member', 1, true);
439
- if (rows.length > 1) return { id: null, status: 'ambiguous', reason: 'Multiple namespace member targets matched the imported module', strategy: 'relative_import_namespace_member', candidateCount: rows.length };
440
- return { id: null, status: 'unresolved', reason: 'No namespace member target matched the imported module', strategy: 'relative_import_namespace_member', candidateCount: 0 };
441
- }
442
- function proxyResolution(rows: SymbolTargetRow[], r: SymbolCallFact, relation: unknown): SymbolCallResolution | undefined {
443
- if (relation !== 'relative_import_proxy_member' || rows.length <= 1) return undefined;
444
- const mapped = rows.filter((row) => String(row.evidenceJson ?? '').includes('exported_object_shorthand') || String(row.evidenceJson ?? '').includes('exported_object_literal'));
445
- if (mapped.length > 0) {
446
- const concrete = rows.find((row) => row.kind !== 'object_alias') ?? mapped[0];
447
- return { id: concrete?.id ?? null, status: 'resolved', reason: null, strategy: 'proxy_member_exported_object_map', candidateCount: rows.length };
448
- }
449
- const scoped = moduleRows(rows, r);
450
- if (scoped.length === 1 && scoped[0]) return resolvedSymbol(scoped[0], 'relative_import_path_disambiguated', rows.length, true);
451
- return { id: null, status: 'ambiguous', reason: 'Proxy member target requires explicit factory/module/type evidence; global member name is ambiguous', strategy: 'proxy_member_no_global_name_fallback', candidateCount: rows.length };
452
- }
453
- function exportedResolution(rows: SymbolTargetRow[], r: SymbolCallFact, relation: unknown): SymbolCallResolution | undefined {
454
- if (rows.length === 1 && rows[0]) return resolvedSymbol(rows[0], relation === 'relative_import_proxy_member' ? 'proxy_member_unique_exported_candidate' : 'relative_import_exported_exact', 1, moduleRows(rows, r).length === 1);
455
- if (rows.length <= 1) return undefined;
456
- const scoped = isRelativeImportedSymbolCall(r) ? moduleRows(rows, r) : [];
457
- if (scoped.length === 1 && scoped[0]) return resolvedSymbol(scoped[0], 'relative_import_path_disambiguated', rows.length, true);
458
- return { id: null, status: 'ambiguous', reason: 'Multiple exported symbol targets matched exactly', strategy: 'exported_exact', candidateCount: rows.length };
459
- }
460
- function accessorResolution(db: Db, repoId: number, r: SymbolCallFact, relation: unknown): SymbolCallResolution | undefined {
461
- if (relation !== 'relative_import' || !isRelativeImportedSymbolCall(r) || !/^[^.]+\.[^.]+$/.test(String(r.calleeLocalName))) return undefined;
462
- const methodRows = symbolTargetRows(db.prepare("SELECT id,kind,source_file sourceFile FROM symbols WHERE repo_id=? AND source_file<>? AND kind='method' AND qualified_name=? ORDER BY id").all(repoId, r.sourceFile, r.calleeLocalName));
463
- const scoped = moduleRows(methodRows, r);
464
- if (scoped.length === 1 && scoped[0]) return resolvedSymbol(scoped[0], 'relative_import_static_accessor_instance_method', 1, true);
465
- return scoped.length > 1 ? { id: null, status: 'ambiguous', reason: 'Multiple static-accessor instance method targets matched the imported module', strategy: 'relative_import_static_accessor_instance_method', candidateCount: scoped.length } : undefined;
466
- }
467
- function resolveSymbolCallTarget(db: Db, repoId: number, r: SymbolCallFact): SymbolCallResolution {
468
- const relation = r.evidence.relation;
469
- const early = sameFileResolution(db, repoId, r, relation) ?? classInstanceResolution(db, repoId, r, relation) ?? namespaceResolution(db, repoId, r, relation);
470
- if (early) return early;
471
- const rows = relation === 'package_import' ? [] : exportedSymbolRows(db, repoId, r);
472
- const matched = proxyResolution(rows, r, relation) ?? exportedResolution(rows, r, relation) ?? accessorResolution(db, repoId, r, relation);
473
- if (matched) return matched;
474
- if (relation === 'package_import') return { id: null, status: 'unresolved', reason: 'Package import target resolution requires a post-publication workspace pass', strategy: 'package_import_unresolved', candidateCount: 0 };
475
- return { id: null, status: 'unresolved', reason: 'No local symbol target matched exactly', strategy: relation === 'relative_import_proxy_member' ? 'proxy_member_no_global_name_fallback' : 'exact_symbol_match', candidateCount: 0 };
476
- }
477
- export function insertCalls(
478
- db: Db,
479
- repoId: number,
480
- rows: OutboundCallFact[],
481
- ): void {
482
- const stmt = db.prepare(
483
- 'INSERT INTO outbound_calls(repo_id,source_symbol_id,call_type,method,operation_path_expr,query_entity,event_name_expr,payload_summary,source_file,source_line,confidence,unresolved_reason,local_service_name,local_service_lookup,alias_chain_json,evidence_json,external_target_kind,external_target_id,external_target_label,external_target_dynamic,service_binding_id) VALUES(?,COALESCE((SELECT id FROM symbols WHERE repo_id=? AND source_file=? AND qualified_name=? ORDER BY id LIMIT 1),(SELECT id FROM symbols WHERE repo_id=? AND source_file=? AND start_line<=? AND end_line>=? ORDER BY (end_line-start_line),id LIMIT 1)),?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)',
484
- );
485
- for (const r of rows) {
486
- const binding = resolvePersistedBinding(db, repoId, r);
487
- const evidence = {
488
- ...(r.evidence ?? {}),
489
- serviceBindingResolution: binding.evidence,
490
- };
491
- stmt.run(
492
- repoId,
493
- repoId,
494
- r.sourceFile,
495
- r.sourceSymbolQualifiedName,
496
- repoId,
497
- r.sourceFile,
498
- r.sourceLine,
499
- r.sourceLine,
500
- r.callType,
501
- r.method,
502
- r.operationPathExpr,
503
- r.queryEntity,
504
- r.eventNameExpr,
505
- r.payloadSummary,
506
- r.sourceFile,
507
- r.sourceLine,
508
- r.confidence,
509
- r.unresolvedReason ?? binding.unresolvedReason,
510
- r.localServiceName,
511
- r.localServiceLookup,
512
- r.aliasChain ? JSON.stringify(r.aliasChain) : null,
513
- JSON.stringify(evidence),
514
- r.externalTarget?.kind ?? null,
515
- r.externalTarget?.stableId ?? null,
516
- r.externalTarget?.label ?? null,
517
- r.externalTarget?.dynamic ? 1 : 0,
518
- binding.bindingId,
519
- );
520
- }
521
- }
522
-
523
- interface BindingCandidate {
524
- id: number;
525
- symbolId?: number | null;
526
- variableName: string;
527
- alias?: string | null;
528
- aliasExpr?: string | null;
529
- destinationExpr?: string | null;
530
- servicePathExpr?: string | null;
531
- sourceFile: string;
532
- sourceLine: number;
533
- helperChainJson?: string | null;
534
- }
535
-
536
- function resolvePersistedBinding(
537
- db: Db,
538
- repoId: number,
539
- call: OutboundCallFact,
540
- ): {
541
- bindingId: number | null;
542
- unresolvedReason?: string;
543
- evidence: Record<string, unknown>;
544
- } {
545
- if (!call.serviceVariableName)
546
- return { bindingId: null, evidence: { status: 'not_applicable', candidateCount: 0 } };
547
- const candidates = bindingCandidates(db, repoId, call);
548
- const prior = candidates.filter((candidate) => candidate.sourceLine <= call.sourceLine);
549
- const families = new Set(prior.map(bindingSignature));
550
- if (prior.length > 0 && families.size === 1) {
551
- const selected = prior.at(-1);
552
- return {
553
- bindingId: selected?.id ?? null,
554
- evidence: bindingEvidence('selected', prior, selected),
555
- };
556
- }
557
- if (prior.length > 1) {
558
- return {
559
- bindingId: null,
560
- unresolvedReason: 'ambiguous_service_binding_candidates',
561
- evidence: bindingEvidence('ambiguous', prior),
562
- };
563
- }
564
- if (candidates.length > 0) {
565
- return {
566
- bindingId: null,
567
- unresolvedReason: 'service_binding_declared_after_call',
568
- evidence: bindingEvidence('rejected_future_binding', candidates),
569
- };
570
- }
571
- return {
572
- bindingId: null,
573
- evidence: bindingEvidence('unrecoverable', []),
574
- };
575
- }
576
-
577
- function bindingCandidates(
578
- db: Db,
579
- repoId: number,
580
- call: OutboundCallFact,
581
- ): BindingCandidate[] {
582
- const ownerId = callSymbolId(db, repoId, call);
583
- const rows = db.prepare(`
584
- SELECT id,symbol_id symbolId,variable_name variableName,alias,alias_expr aliasExpr,
585
- destination_expr destinationExpr,service_path_expr servicePathExpr,
586
- source_file sourceFile,source_line sourceLine,helper_chain_json helperChainJson
587
- FROM service_bindings
588
- WHERE repo_id=? AND variable_name=? AND source_file=?
589
- AND (? IS NULL OR symbol_id IS NULL OR symbol_id=?)
590
- ORDER BY source_line,id
591
- `).all(
592
- repoId,
593
- call.serviceVariableName,
594
- call.sourceFile,
595
- ownerId,
596
- ownerId,
597
- ) as Array<Record<string, unknown>>;
598
- return rows.flatMap((row) => {
599
- if (typeof row.id !== 'number' || typeof row.variableName !== 'string'
600
- || typeof row.sourceFile !== 'string' || typeof row.sourceLine !== 'number')
601
- return [];
602
- return [{
603
- id: row.id,
604
- symbolId: nullableNumber(row.symbolId),
605
- variableName: row.variableName,
606
- alias: nullableString(row.alias),
607
- aliasExpr: nullableString(row.aliasExpr),
608
- destinationExpr: nullableString(row.destinationExpr),
609
- servicePathExpr: nullableString(row.servicePathExpr),
610
- sourceFile: row.sourceFile,
611
- sourceLine: row.sourceLine,
612
- helperChainJson: nullableString(row.helperChainJson),
613
- }];
614
- });
615
- }
616
-
617
- function nullableString(value: unknown): string | null | undefined {
618
- return value === null || typeof value === 'string' ? value : undefined;
619
- }
620
-
621
- function nullableNumber(value: unknown): number | null | undefined {
622
- return value === null || typeof value === 'number' ? value : undefined;
623
- }
624
-
625
- function callSymbolId(
626
- db: Db,
627
- repoId: number,
628
- call: OutboundCallFact,
629
- ): number | undefined {
630
- const row = db.prepare(`
631
- SELECT id FROM symbols
632
- WHERE repo_id=? AND source_file=?
633
- AND ((? IS NOT NULL AND qualified_name=?)
634
- OR (start_line<=? AND end_line>=?))
635
- ORDER BY CASE WHEN qualified_name=? THEN 0 ELSE 1 END,
636
- (end_line-start_line),id
637
- LIMIT 1
638
- `).get(
639
- repoId,
640
- call.sourceFile,
641
- call.sourceSymbolQualifiedName,
642
- call.sourceSymbolQualifiedName,
643
- call.sourceLine,
644
- call.sourceLine,
645
- call.sourceSymbolQualifiedName,
646
- );
647
- return typeof row?.id === 'number' ? row.id : undefined;
648
- }
649
-
650
- function bindingEvidence(
651
- status: string,
652
- candidates: BindingCandidate[],
653
- selected?: BindingCandidate,
654
- ): Record<string, unknown> {
655
- const projection = projectBounded(candidates, (left, right) =>
656
- Number(right.id === selected?.id) - Number(left.id === selected?.id)
657
- || left.sourceFile.localeCompare(right.sourceFile)
658
- || left.sourceLine - right.sourceLine
659
- || left.id - right.id);
660
- return {
661
- status,
662
- candidateCount: projection.totalCount,
663
- shownCandidateCount: projection.shownCount,
664
- omittedCandidateCount: projection.omittedCount,
665
- selectedBindingId: selected?.id,
666
- sourceOrderRule: 'binding_source_line_must_not_follow_call',
667
- candidates: projection.items.map((candidate) => ({
668
- bindingId: candidate.id,
669
- symbolId: candidate.symbolId,
670
- variableName: candidate.variableName,
671
- alias: candidate.alias,
672
- aliasExpr: candidate.aliasExpr,
673
- destinationExpr: candidate.destinationExpr,
674
- servicePathExpr: candidate.servicePathExpr,
675
- sourceFile: candidate.sourceFile,
676
- sourceLine: candidate.sourceLine,
677
- helperChain: parseBindingChain(candidate.helperChainJson),
678
- })),
679
- };
680
- }
681
-
682
- function bindingSignature(candidate: BindingCandidate): string {
683
- return JSON.stringify([
684
- candidate.alias,
685
- candidate.aliasExpr,
686
- candidate.destinationExpr,
687
- candidate.servicePathExpr,
688
- ]);
689
- }
690
-
691
- function parseBindingChain(value: string | null | undefined): unknown {
692
- if (!value) return undefined;
693
- try {
694
- return JSON.parse(value) as unknown;
695
- } catch {
696
- return undefined;
697
- }
698
- }
384
+ export { insertCalls, insertSymbolCalls } from './000-call-fact-repository.js';
package/src/db/schema.ts CHANGED
@@ -10,8 +10,8 @@ CREATE TABLE IF NOT EXISTS handler_classes (id INTEGER PRIMARY KEY, repo_id INTE
10
10
  CREATE TABLE IF NOT EXISTS handler_methods (id INTEGER PRIMARY KEY, handler_class_id INTEGER NOT NULL, method_name TEXT NOT NULL, decorator_kind TEXT NOT NULL, decorator_value TEXT, decorator_raw_expression TEXT NOT NULL, decorator_resolution_json TEXT NOT NULL DEFAULT '{}', source_file TEXT NOT NULL, source_line INTEGER NOT NULL, FOREIGN KEY(handler_class_id) REFERENCES handler_classes(id) ON DELETE CASCADE);
11
11
  CREATE TABLE IF NOT EXISTS handler_registrations (id INTEGER PRIMARY KEY, repo_id INTEGER NOT NULL, handler_class_id INTEGER, class_name TEXT, import_source TEXT, registration_file TEXT NOT NULL, registration_line INTEGER NOT NULL, registration_kind TEXT NOT NULL, confidence REAL NOT NULL, FOREIGN KEY(repo_id) REFERENCES repositories(id) ON DELETE CASCADE, FOREIGN KEY(handler_class_id) REFERENCES handler_classes(id) ON DELETE SET NULL);
12
12
  CREATE TABLE IF NOT EXISTS service_bindings (id INTEGER PRIMARY KEY, repo_id INTEGER NOT NULL, symbol_id INTEGER, variable_name TEXT NOT NULL, alias TEXT, alias_expr TEXT, destination_expr TEXT, service_path_expr TEXT, is_dynamic INTEGER NOT NULL, placeholders_json TEXT NOT NULL, source_file TEXT NOT NULL, source_line INTEGER NOT NULL, helper_chain_json TEXT, FOREIGN KEY(repo_id) REFERENCES repositories(id) ON DELETE CASCADE, FOREIGN KEY(symbol_id) REFERENCES symbols(id) ON DELETE SET NULL);
13
- CREATE TABLE IF NOT EXISTS outbound_calls (id INTEGER PRIMARY KEY, repo_id INTEGER NOT NULL, source_symbol_id INTEGER, call_type TEXT NOT NULL, service_binding_id INTEGER, method TEXT, operation_path_expr TEXT, query_entity TEXT, event_name_expr TEXT, payload_summary TEXT, source_file TEXT NOT NULL, source_line INTEGER NOT NULL, confidence REAL NOT NULL, unresolved_reason TEXT, local_service_name TEXT, local_service_lookup TEXT, alias_chain_json TEXT, evidence_json TEXT, external_target_kind TEXT, external_target_id TEXT, external_target_label TEXT, external_target_dynamic INTEGER NOT NULL DEFAULT 0, FOREIGN KEY(repo_id) REFERENCES repositories(id) ON DELETE CASCADE, FOREIGN KEY(source_symbol_id) REFERENCES symbols(id) ON DELETE SET NULL, FOREIGN KEY(service_binding_id) REFERENCES service_bindings(id) ON DELETE SET NULL);
14
- CREATE TABLE IF NOT EXISTS symbol_calls (id INTEGER PRIMARY KEY, repo_id INTEGER NOT NULL, caller_symbol_id INTEGER NOT NULL, callee_symbol_id INTEGER, callee_expression TEXT NOT NULL, import_source TEXT, source_file TEXT NOT NULL, source_line INTEGER NOT NULL, status TEXT NOT NULL, confidence REAL NOT NULL, evidence_json TEXT NOT NULL, unresolved_reason TEXT, FOREIGN KEY(repo_id) REFERENCES repositories(id) ON DELETE CASCADE, FOREIGN KEY(caller_symbol_id) REFERENCES symbols(id) ON DELETE CASCADE, FOREIGN KEY(callee_symbol_id) REFERENCES symbols(id) ON DELETE SET NULL);
13
+ CREATE TABLE IF NOT EXISTS outbound_calls (id INTEGER PRIMARY KEY, repo_id INTEGER NOT NULL, source_symbol_id INTEGER, call_type TEXT NOT NULL, service_binding_id INTEGER, method TEXT, operation_path_expr TEXT, query_entity TEXT, event_name_expr TEXT, payload_summary TEXT, source_file TEXT NOT NULL, source_line INTEGER NOT NULL, call_site_start_offset INTEGER, call_site_end_offset INTEGER, confidence REAL NOT NULL, unresolved_reason TEXT, local_service_name TEXT, local_service_lookup TEXT, alias_chain_json TEXT, evidence_json TEXT, external_target_kind TEXT, external_target_id TEXT, external_target_label TEXT, external_target_dynamic INTEGER NOT NULL DEFAULT 0, FOREIGN KEY(repo_id) REFERENCES repositories(id) ON DELETE CASCADE, FOREIGN KEY(source_symbol_id) REFERENCES symbols(id) ON DELETE SET NULL, FOREIGN KEY(service_binding_id) REFERENCES service_bindings(id) ON DELETE SET NULL);
14
+ CREATE TABLE IF NOT EXISTS symbol_calls (id INTEGER PRIMARY KEY, repo_id INTEGER NOT NULL, caller_symbol_id INTEGER NOT NULL, callee_symbol_id INTEGER, callee_expression TEXT NOT NULL, import_source TEXT, source_file TEXT NOT NULL, source_line INTEGER NOT NULL, call_site_start_offset INTEGER, call_site_end_offset INTEGER, call_role TEXT NOT NULL DEFAULT 'legacy_unknown', status TEXT NOT NULL, confidence REAL NOT NULL, evidence_json TEXT NOT NULL, unresolved_reason TEXT, FOREIGN KEY(repo_id) REFERENCES repositories(id) ON DELETE CASCADE, FOREIGN KEY(caller_symbol_id) REFERENCES symbols(id) ON DELETE CASCADE, FOREIGN KEY(callee_symbol_id) REFERENCES symbols(id) ON DELETE SET NULL);
15
15
  CREATE TABLE IF NOT EXISTS graph_edges (id INTEGER PRIMARY KEY, workspace_id INTEGER NOT NULL, edge_type TEXT NOT NULL, status TEXT NOT NULL DEFAULT 'unresolved', from_kind TEXT NOT NULL, from_id TEXT NOT NULL, to_kind TEXT NOT NULL, to_id TEXT NOT NULL, confidence REAL NOT NULL, evidence_json TEXT NOT NULL, is_dynamic INTEGER NOT NULL, unresolved_reason TEXT, generation INTEGER NOT NULL DEFAULT 0, FOREIGN KEY(workspace_id) REFERENCES workspaces(id) ON DELETE CASCADE);
16
16
  CREATE TABLE IF NOT EXISTS index_runs (id INTEGER PRIMARY KEY, workspace_id INTEGER NOT NULL, started_at TEXT NOT NULL, finished_at TEXT, status TEXT NOT NULL, repo_count INTEGER NOT NULL, file_count INTEGER NOT NULL, diagnostic_count INTEGER NOT NULL, error_message TEXT, owner_pid INTEGER, FOREIGN KEY(workspace_id) REFERENCES workspaces(id) ON DELETE CASCADE);
17
17
  CREATE TABLE IF NOT EXISTS diagnostics (id INTEGER PRIMARY KEY, repo_id INTEGER, file_id INTEGER, severity TEXT NOT NULL, code TEXT NOT NULL, message TEXT NOT NULL, source_file TEXT, source_line INTEGER, FOREIGN KEY(repo_id) REFERENCES repositories(id) ON DELETE CASCADE, FOREIGN KEY(file_id) REFERENCES files(id) ON DELETE SET NULL);
@@ -25,6 +25,8 @@ CREATE INDEX IF NOT EXISTS idx_operation_name ON cds_operations(operation_name,
25
25
  CREATE INDEX IF NOT EXISTS idx_operation_base ON cds_operations(base_operation_id);
26
26
  CREATE INDEX IF NOT EXISTS idx_calls_repo ON outbound_calls(repo_id, call_type);
27
27
  CREATE INDEX IF NOT EXISTS idx_symbol_calls_caller ON symbol_calls(repo_id, caller_symbol_id);
28
+ CREATE INDEX IF NOT EXISTS idx_outbound_call_site ON outbound_calls(repo_id, source_file, call_site_start_offset, call_site_end_offset, call_type);
29
+ CREATE INDEX IF NOT EXISTS idx_symbol_call_site_role ON symbol_calls(repo_id, source_file, call_site_start_offset, call_site_end_offset, call_role);
28
30
  CREATE VIRTUAL TABLE IF NOT EXISTS search_index USING fts5(kind, name, path, repo);
29
31
  `;
30
32
 
package/src/index.ts CHANGED
@@ -10,6 +10,28 @@ export { linkWorkspace } from './linker/cross-repo-linker.js';
10
10
  export { applyVariables, extractPlaceholders, substituteVariables } from './linker/dynamic-edge-resolver.js';
11
11
  export type { RuntimeSubstitution } from './linker/dynamic-edge-resolver.js';
12
12
  export { trace } from './trace/trace-engine.js';
13
+ export {
14
+ compactTrace,
15
+ traceAndCompact,
16
+ } from './trace/018-compact-trace.js';
17
+ export type { CompactTraceExecution } from './trace/018-compact-trace.js';
18
+ export type {
19
+ CompactDecisionV1,
20
+ CompactDiagnosticDetailsV1,
21
+ CompactDiagnosticRowV1,
22
+ CompactEdgeDetailsV1,
23
+ CompactEdgeRowV1,
24
+ CompactGraphV1,
25
+ CompactHintV1,
26
+ CompactNodeRowV1,
27
+ CompactQueryV1,
28
+ CompactReferenceGroupV1,
29
+ CompactReferencesV1,
30
+ CompactSourceContext,
31
+ CompactStartV1,
32
+ CompactStatus,
33
+ CompactStatusCountsV1,
34
+ } from './trace/014-compact-contract.js';
13
35
  export { parseImplementationHint } from './trace/implementation-hints.js';
14
36
  export type { DynamicMode, ImplementationHint, TraceOptions } from './types.js';
15
37
  export { redactValue, redactText } from './utils/redaction.js';