@principal-ai/principal-view-react 0.16.49 → 0.16.50

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 (44) hide show
  1. package/dist/graphify/anchor.d.ts +28 -0
  2. package/dist/graphify/anchor.d.ts.map +1 -0
  3. package/dist/graphify/anchor.js +132 -0
  4. package/dist/graphify/anchor.js.map +1 -0
  5. package/dist/graphify/ids.d.ts +19 -0
  6. package/dist/graphify/ids.d.ts.map +1 -0
  7. package/dist/graphify/ids.js +46 -0
  8. package/dist/graphify/ids.js.map +1 -0
  9. package/dist/graphify/index.d.ts +7 -0
  10. package/dist/graphify/index.d.ts.map +1 -1
  11. package/dist/graphify/index.js +4 -0
  12. package/dist/graphify/index.js.map +1 -1
  13. package/dist/graphify/kind.d.ts +23 -0
  14. package/dist/graphify/kind.d.ts.map +1 -0
  15. package/dist/graphify/kind.js +71 -0
  16. package/dist/graphify/kind.js.map +1 -0
  17. package/dist/graphify/signature.d.ts +87 -0
  18. package/dist/graphify/signature.d.ts.map +1 -0
  19. package/dist/graphify/signature.js +276 -0
  20. package/dist/graphify/signature.js.map +1 -0
  21. package/dist/index.d.ts +4 -1
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +2 -1
  24. package/dist/index.js.map +1 -1
  25. package/dist/subsystem/ComponentDeclaration.d.ts +58 -3
  26. package/dist/subsystem/ComponentDeclaration.d.ts.map +1 -1
  27. package/dist/subsystem/ComponentDeclaration.js +168 -35
  28. package/dist/subsystem/ComponentDeclaration.js.map +1 -1
  29. package/dist/subsystem/SubsystemComponentGraph.d.ts +5 -0
  30. package/dist/subsystem/SubsystemComponentGraph.d.ts.map +1 -1
  31. package/dist/subsystem/SubsystemComponentGraph.js +2 -2
  32. package/dist/subsystem/SubsystemComponentGraph.js.map +1 -1
  33. package/package.json +1 -1
  34. package/src/graphify/anchor.test.ts +124 -0
  35. package/src/graphify/anchor.ts +160 -0
  36. package/src/graphify/ids.ts +48 -0
  37. package/src/graphify/index.ts +26 -0
  38. package/src/graphify/kind.test.ts +108 -0
  39. package/src/graphify/kind.ts +114 -0
  40. package/src/graphify/signature.test.ts +278 -0
  41. package/src/graphify/signature.ts +354 -0
  42. package/src/index.ts +26 -0
  43. package/src/subsystem/ComponentDeclaration.tsx +287 -40
  44. package/src/subsystem/SubsystemComponentGraph.tsx +8 -1
@@ -0,0 +1,160 @@
1
+ /**
2
+ * Resolve a subsystem component (file + symbol) to a graphify definition node.
3
+ *
4
+ * This is component *anchoring*, not type-ref stub rewiring (`resolve.ts`).
5
+ * Conservative: never bind via corpus-wide same-name alone.
6
+ */
7
+
8
+ import type { GraphifyNode } from './types';
9
+ import type { GraphifyAnchorResolution } from './consolidated';
10
+ import { normalizeGraphifyLabel } from './resolve';
11
+ import {
12
+ graphifyFileStem,
13
+ makeGraphifyId,
14
+ normalizeSourcePath,
15
+ } from './ids';
16
+
17
+ export interface ComponentAnchorInput {
18
+ file?: string;
19
+ symbol?: string;
20
+ kind?: string;
21
+ purl?: string;
22
+ }
23
+
24
+ export interface ComponentAnchorResult {
25
+ resolution: GraphifyAnchorResolution;
26
+ node: GraphifyNode | null;
27
+ candidates: GraphifyNode[];
28
+ /** make_id strings attempted on the fast path. */
29
+ triedIds: string[];
30
+ }
31
+
32
+ const FILE_SUFFIX_RE =
33
+ /\.(py|js|jsx|ts|tsx|mjs|cjs|java|go|rs|rb|php|cs|cpp|cc|c|h|hpp|pas|pp|dpr|swift|kt|scala|dart|lua|pl|pm|ex|exs|zig|vue|svelte)$/i;
34
+
35
+ function isDefinition(node: GraphifyNode): boolean {
36
+ if (node.file_type !== 'code') return false;
37
+ const file = typeof node.source_file === 'string' ? node.source_file : '';
38
+ if (!file) return false;
39
+ const label = typeof node.label === 'string' ? node.label.trim() : '';
40
+ return !!label && !FILE_SUFFIX_RE.test(label);
41
+ }
42
+
43
+ /** Label forms that might match a subsystem symbol on a graphify node. */
44
+ export function symbolLabelVariants(symbol: string): string[] {
45
+ const raw = symbol.trim();
46
+ if (!raw) return [];
47
+ const last = raw.includes('.') ? (raw.split('.').pop() ?? raw) : raw;
48
+ const variants = new Set<string>([
49
+ raw,
50
+ last,
51
+ `${raw}()`,
52
+ `${last}()`,
53
+ `.${last}()`,
54
+ `.${last}`,
55
+ ]);
56
+ return [...variants];
57
+ }
58
+
59
+ function labelMatchesSymbol(label: string, symbol: string): boolean {
60
+ const variants = symbolLabelVariants(symbol);
61
+ const trimmed = label.trim();
62
+ if (variants.includes(trimmed)) return true;
63
+ const foldedLabel = normalizeGraphifyLabel(trimmed);
64
+ return variants.some((v) => normalizeGraphifyLabel(v) === foldedLabel);
65
+ }
66
+
67
+ function candidateIdsFor(file: string, symbol: string): string[] {
68
+ const stem = graphifyFileStem(file);
69
+ if (!stem) return [];
70
+ const parts = symbol.split('.').map((p) => p.trim()).filter(Boolean);
71
+ const ids = new Set<string>();
72
+ ids.add(makeGraphifyId(stem, symbol));
73
+ if (parts.length > 0) ids.add(makeGraphifyId(stem, ...parts));
74
+ if (parts.length === 2) {
75
+ // Class method: make_id(class_id, method) ≈ make_id(stem, Class, method)
76
+ ids.add(makeGraphifyId(stem, parts[0]!, parts[1]!));
77
+ }
78
+ return [...ids];
79
+ }
80
+
81
+ /**
82
+ * Anchor a subsystem component onto a graphify definition node.
83
+ */
84
+ export function resolveComponentAnchor(
85
+ nodes: readonly GraphifyNode[],
86
+ input: ComponentAnchorInput,
87
+ ): ComponentAnchorResult {
88
+ const file = input.file?.trim() ? normalizeSourcePath(input.file) : '';
89
+ const symbol = input.symbol?.trim() ?? '';
90
+ if (!file || !symbol) {
91
+ return { resolution: 'missing', node: null, candidates: [], triedIds: [] };
92
+ }
93
+
94
+ const byId = new Map<string, GraphifyNode>();
95
+ const inFile: GraphifyNode[] = [];
96
+ for (const node of nodes) {
97
+ byId.set(String(node.id), node);
98
+ if (!isDefinition(node)) continue;
99
+ const sf = normalizeSourcePath(String(node.source_file ?? ''));
100
+ if (sf === file) inFile.push(node);
101
+ }
102
+
103
+ const triedIds = candidateIdsFor(file, symbol);
104
+ const idHits: GraphifyNode[] = [];
105
+ for (const id of triedIds) {
106
+ const hit = byId.get(id);
107
+ if (!hit || !isDefinition(hit)) continue;
108
+ const sf = normalizeSourcePath(String(hit.source_file ?? ''));
109
+ if (sf === file) idHits.push(hit);
110
+ }
111
+ const uniqueIdHits = [...new Map(idHits.map((n) => [String(n.id), n])).values()];
112
+ if (uniqueIdHits.length === 1) {
113
+ return {
114
+ resolution: 'exact',
115
+ node: uniqueIdHits[0]!,
116
+ candidates: [],
117
+ triedIds,
118
+ };
119
+ }
120
+ if (uniqueIdHits.length > 1) {
121
+ return {
122
+ resolution: 'ambiguous',
123
+ node: null,
124
+ candidates: uniqueIdHits,
125
+ triedIds,
126
+ };
127
+ }
128
+
129
+ if (inFile.length === 0) {
130
+ return { resolution: 'missing', node: null, candidates: [], triedIds };
131
+ }
132
+
133
+ const labelHits = inFile.filter((n) =>
134
+ labelMatchesSymbol(String(n.label ?? ''), symbol),
135
+ );
136
+ if (labelHits.length === 1) {
137
+ return {
138
+ resolution: 'exact',
139
+ node: labelHits[0]!,
140
+ candidates: [],
141
+ triedIds,
142
+ };
143
+ }
144
+ if (labelHits.length > 1) {
145
+ return {
146
+ resolution: 'ambiguous',
147
+ node: null,
148
+ candidates: labelHits,
149
+ triedIds,
150
+ };
151
+ }
152
+
153
+ // File has code nodes but none matched the symbol.
154
+ return {
155
+ resolution: 'file-only',
156
+ node: null,
157
+ candidates: inFile.slice(0, 8),
158
+ triedIds,
159
+ };
160
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Mirror of graphify/ids.py — NFKC + casefold-ish + non-word → `_`.
3
+ *
4
+ * Definition node ids are `makeId(fileStem, ...symbolParts)`. Stub ids are
5
+ * intentionally different (name-only / `ref_…`); this module is for the
6
+ * definition path used by component anchoring.
7
+ */
8
+
9
+ /** Normalize one id string to graphify's canonical form. */
10
+ export function normalizeGraphifyId(s: string): string {
11
+ let out = s.normalize("NFKC");
12
+ // JS has no Unicode casefold; lowercasing + second NFKC is close enough
13
+ // for the Latin/ASCII identifiers we resolve in practice.
14
+ out = out.toLowerCase().normalize("NFKC");
15
+ out = out.replace(/[^\w]+/gu, "_");
16
+ out = out.replace(/_+/g, "_");
17
+ return out.replace(/^_|_$/g, "");
18
+ }
19
+
20
+ /** Build a canonical node id from path/symbol parts (graphify `make_id`). */
21
+ export function makeGraphifyId(...parts: string[]): string {
22
+ const joined = parts
23
+ .filter((p) => typeof p === "string" && p.length > 0)
24
+ .map((p) => p.replace(/^[_.]+|[_.]+$/g, ""))
25
+ .filter(Boolean)
26
+ .join("_");
27
+ return normalizeGraphifyId(joined);
28
+ }
29
+
30
+ /**
31
+ * File stem used as the node-id prefix (graphify `_file_stem`):
32
+ * full path with extension dropped, posix separators.
33
+ */
34
+ export function graphifyFileStem(file: string): string {
35
+ const posix = file.replace(/\\/g, "/").replace(/^\.\//, "");
36
+ const lastSlash = posix.lastIndexOf("/");
37
+ const base = lastSlash >= 0 ? posix.slice(lastSlash + 1) : posix;
38
+ const dir = lastSlash >= 0 ? posix.slice(0, lastSlash + 1) : "";
39
+ if (!base || base === ".") return "";
40
+ const dot = base.lastIndexOf(".");
41
+ const stemBase = dot > 0 ? base.slice(0, dot) : base;
42
+ return `${dir}${stemBase}`;
43
+ }
44
+
45
+ /** Normalize a repo-relative path for source_file comparison. */
46
+ export function normalizeSourcePath(file: string): string {
47
+ return file.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/+/g, "/");
48
+ }
@@ -43,3 +43,29 @@ export {
43
43
  createGraphifyTypeResolver,
44
44
  resolveGraphifyTypeRef,
45
45
  } from './resolve';
46
+ export {
47
+ normalizeGraphifyId,
48
+ makeGraphifyId,
49
+ graphifyFileStem,
50
+ normalizeSourcePath,
51
+ } from './ids';
52
+ export type {
53
+ ComponentAnchorInput,
54
+ ComponentAnchorResult,
55
+ } from './anchor';
56
+ export {
57
+ resolveComponentAnchor,
58
+ symbolLabelVariants,
59
+ } from './anchor';
60
+ export type { InferredGraphifyKind, InferGraphifyKindResult } from './kind';
61
+ export { inferGraphifyKind, kindsMatch } from './kind';
62
+ export type {
63
+ GraphifyInferredSignature,
64
+ ClaimedSignature,
65
+ SignatureCompareResult,
66
+ } from './signature';
67
+ export {
68
+ extractNamedTypes,
69
+ extractGraphifySignature,
70
+ compareSignatures,
71
+ } from './signature';
@@ -0,0 +1,108 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+ import type { GraphifyEdge, GraphifyNode } from './types';
3
+ import { inferGraphifyKind, kindsMatch } from './kind';
4
+
5
+ function node(
6
+ id: string,
7
+ label: string,
8
+ sourceFile = 'src/Foo.ts',
9
+ extra?: Partial<GraphifyNode>,
10
+ ): GraphifyNode {
11
+ return {
12
+ id,
13
+ label,
14
+ file_type: 'code',
15
+ source_file: sourceFile,
16
+ source_location: 'L1',
17
+ ...extra,
18
+ };
19
+ }
20
+
21
+ function edge(
22
+ source: string,
23
+ target: string,
24
+ relation: string,
25
+ ): GraphifyEdge {
26
+ return {
27
+ source,
28
+ target,
29
+ relation,
30
+ confidence: 'EXTRACTED',
31
+ source_file: 'src/Foo.ts',
32
+ };
33
+ }
34
+
35
+ describe('inferGraphifyKind', () => {
36
+ test('outgoing method edges → class', () => {
37
+ const n = node('cls', 'SessionReader');
38
+ const edges = [edge('cls', 'cls_get', 'method')];
39
+ const r = inferGraphifyKind(n, edges);
40
+ expect(r.kind).toBe('class');
41
+ expect(r.evidence[0]).toContain('outgoing method');
42
+ });
43
+
44
+ test('call-style label + no methods → function', () => {
45
+ const n = node('fn', 'SubsystemGraphView()');
46
+ const r = inferGraphifyKind(n, []);
47
+ expect(r.kind).toBe('function');
48
+ });
49
+
50
+ test('method-style label + incoming method → method', () => {
51
+ const n = node('m', '.validate()');
52
+ const edges = [edge('cls', 'm', 'method')];
53
+ const r = inferGraphifyKind(n, edges);
54
+ expect(r.kind).toBe('method');
55
+ });
56
+
57
+ test('method takes priority over call-style function', () => {
58
+ const n = node('m', '.generate()');
59
+ const edges = [edge('CodeGenerator', 'm', 'method')];
60
+ expect(inferGraphifyKind(n, edges).kind).toBe('method');
61
+ });
62
+
63
+ test('incoming implements → type', () => {
64
+ const n = node('iface', 'StoryboardRegistryInterface');
65
+ const edges = [edge('MockRegistry', 'iface', 'implements')];
66
+ expect(inferGraphifyKind(n, edges).kind).toBe('type');
67
+ });
68
+
69
+ test('filename label → module', () => {
70
+ const n = node(
71
+ 'mod',
72
+ 'SubsystemGraphView.tsx',
73
+ 'packages/trail-viewer/src/mainview/views/SubsystemGraphView.tsx',
74
+ );
75
+ expect(inferGraphifyKind(n, []).kind).toBe('module');
76
+ });
77
+
78
+ test('bare symbol with no edges → unknown', () => {
79
+ const n = node('x', 'INSPECTOR_KEYS');
80
+ expect(inferGraphifyKind(n, []).kind).toBe('unknown');
81
+ });
82
+
83
+ test('class wins over implements on same node', () => {
84
+ const n = node('cls', 'TypeScriptGenerator');
85
+ const edges = [
86
+ edge('cls', 'cls_gen', 'method'),
87
+ edge('Other', 'cls', 'implements'),
88
+ ];
89
+ expect(inferGraphifyKind(n, edges).kind).toBe('class');
90
+ });
91
+ });
92
+
93
+ describe('kindsMatch', () => {
94
+ test('exact string equality', () => {
95
+ expect(kindsMatch('function', 'function')).toBe(true);
96
+ expect(kindsMatch('class', 'function')).toBe(false);
97
+ });
98
+
99
+ test('external / missing claimed skips', () => {
100
+ expect(kindsMatch('external', 'function')).toBe(true);
101
+ expect(kindsMatch(undefined, 'unknown')).toBe(true);
102
+ });
103
+
104
+ test('no class≈function alias', () => {
105
+ expect(kindsMatch('class', 'function')).toBe(false);
106
+ expect(kindsMatch('function', 'class')).toBe(false);
107
+ });
108
+ });
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Infer a subsystem-style kind from a graphify node + edges.
3
+ *
4
+ * Graphify does not store `kind` on nodes (everything is `file_type: "code"`).
5
+ * Class vs function vs type is derived from structure — same rules as
6
+ * `GraphifyComponentDetail` in consolidated.ts.
7
+ */
8
+
9
+ import type { GraphifyEdge, GraphifyNode } from './types';
10
+
11
+ /** Kind inferred from graph structure (never read off the node). */
12
+ export type InferredGraphifyKind =
13
+ | 'class'
14
+ | 'function'
15
+ | 'method'
16
+ | 'type'
17
+ | 'module'
18
+ | 'unknown';
19
+
20
+ export interface InferGraphifyKindResult {
21
+ kind: InferredGraphifyKind;
22
+ evidence: string[];
23
+ }
24
+
25
+ const FILE_LABEL_RE =
26
+ /\.(py|js|jsx|ts|tsx|mjs|cjs|java|go|rs|rb|php|cs|cpp|cc|c|h|hpp|pas|pp|dpr|swift|kt|scala|dart|lua|pl|pm|ex|exs|zig|vue|svelte)$/i;
27
+
28
+ function isMethodStyleLabel(label: string): boolean {
29
+ return label.trim().startsWith('.');
30
+ }
31
+
32
+ function isCallStyleLabel(label: string): boolean {
33
+ return label.trim().endsWith('()');
34
+ }
35
+
36
+ function isFilenameLabel(label: string, sourceFile: string): boolean {
37
+ const trimmed = label.trim();
38
+ if (FILE_LABEL_RE.test(trimmed)) return true;
39
+ if (!sourceFile) return false;
40
+ const base = sourceFile.replace(/\\/g, '/').split('/').pop() ?? '';
41
+ return !!base && trimmed === base;
42
+ }
43
+
44
+ /**
45
+ * Infer kind for a definition node from its label and incident edges.
46
+ *
47
+ * Priority: class → method → function → type → module → unknown.
48
+ */
49
+ export function inferGraphifyKind(
50
+ node: GraphifyNode,
51
+ edges: readonly GraphifyEdge[],
52
+ ): InferGraphifyKindResult {
53
+ const id = String(node.id);
54
+ const label = String(node.label ?? '').trim();
55
+ const sourceFile =
56
+ typeof node.source_file === 'string' ? node.source_file : '';
57
+ const evidence: string[] = [];
58
+
59
+ const outgoingMethod = edges.filter(
60
+ (e) => String(e.source) === id && e.relation === 'method',
61
+ );
62
+ if (outgoingMethod.length > 0) {
63
+ evidence.push(`${outgoingMethod.length} outgoing method edge(s)`);
64
+ return { kind: 'class', evidence };
65
+ }
66
+
67
+ const incomingMethod = edges.filter(
68
+ (e) => String(e.target) === id && e.relation === 'method',
69
+ );
70
+ if (incomingMethod.length > 0 && isMethodStyleLabel(label)) {
71
+ evidence.push(
72
+ `incoming method edge from ${String(incomingMethod[0]!.source)}`,
73
+ `label ${label}`,
74
+ );
75
+ return { kind: 'method', evidence };
76
+ }
77
+
78
+ if (isCallStyleLabel(label)) {
79
+ evidence.push(`call-style label ${label}`);
80
+ return { kind: 'function', evidence };
81
+ }
82
+
83
+ const incomingImplements = edges.filter(
84
+ (e) => String(e.target) === id && e.relation === 'implements',
85
+ );
86
+ if (incomingImplements.length > 0) {
87
+ evidence.push(
88
+ `${incomingImplements.length} incoming implements edge(s)`,
89
+ );
90
+ return { kind: 'type', evidence };
91
+ }
92
+
93
+ if (node.type === 'module' || isFilenameLabel(label, sourceFile)) {
94
+ evidence.push(
95
+ node.type === 'module'
96
+ ? 'node.type=module'
97
+ : `filename label ${label}`,
98
+ );
99
+ return { kind: 'module', evidence };
100
+ }
101
+
102
+ if (label) evidence.push(`unclassified label ${label}`);
103
+ else evidence.push('no classifying signals');
104
+ return { kind: 'unknown', evidence };
105
+ }
106
+
107
+ /** Strict claimed-vs-inferred check (no class≈function alias). */
108
+ export function kindsMatch(
109
+ claimed: string | undefined,
110
+ inferred: InferredGraphifyKind,
111
+ ): boolean {
112
+ if (!claimed || claimed === 'external') return true;
113
+ return claimed === inferred;
114
+ }