@principal-ai/principal-view-react 0.16.47 → 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 (57) 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/consolidated.d.ts +12 -1
  6. package/dist/graphify/consolidated.d.ts.map +1 -1
  7. package/dist/graphify/ids.d.ts +19 -0
  8. package/dist/graphify/ids.d.ts.map +1 -0
  9. package/dist/graphify/ids.js +46 -0
  10. package/dist/graphify/ids.js.map +1 -0
  11. package/dist/graphify/index.d.ts +7 -0
  12. package/dist/graphify/index.d.ts.map +1 -1
  13. package/dist/graphify/index.js +4 -0
  14. package/dist/graphify/index.js.map +1 -1
  15. package/dist/graphify/kind.d.ts +23 -0
  16. package/dist/graphify/kind.d.ts.map +1 -0
  17. package/dist/graphify/kind.js +71 -0
  18. package/dist/graphify/kind.js.map +1 -0
  19. package/dist/graphify/signature.d.ts +87 -0
  20. package/dist/graphify/signature.d.ts.map +1 -0
  21. package/dist/graphify/signature.js +276 -0
  22. package/dist/graphify/signature.js.map +1 -0
  23. package/dist/index.d.ts +4 -1
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +2 -1
  26. package/dist/index.js.map +1 -1
  27. package/dist/subsystem/ComponentDeclaration.d.ts +58 -3
  28. package/dist/subsystem/ComponentDeclaration.d.ts.map +1 -1
  29. package/dist/subsystem/ComponentDeclaration.js +186 -53
  30. package/dist/subsystem/ComponentDeclaration.js.map +1 -1
  31. package/dist/subsystem/SubsystemComponentGraph.d.ts +5 -0
  32. package/dist/subsystem/SubsystemComponentGraph.d.ts.map +1 -1
  33. package/dist/subsystem/SubsystemComponentGraph.js +2 -2
  34. package/dist/subsystem/SubsystemComponentGraph.js.map +1 -1
  35. package/dist/subsystem/formatDeclaration.d.ts.map +1 -1
  36. package/dist/subsystem/formatDeclaration.js +12 -1
  37. package/dist/subsystem/formatDeclaration.js.map +1 -1
  38. package/dist/subsystem/model.d.ts +1 -1
  39. package/dist/subsystem/model.d.ts.map +1 -1
  40. package/dist/subsystem/model.js +1 -0
  41. package/dist/subsystem/model.js.map +1 -1
  42. package/package.json +1 -1
  43. package/src/graphify/anchor.test.ts +124 -0
  44. package/src/graphify/anchor.ts +160 -0
  45. package/src/graphify/consolidated.ts +13 -0
  46. package/src/graphify/ids.ts +48 -0
  47. package/src/graphify/index.ts +26 -0
  48. package/src/graphify/kind.test.ts +108 -0
  49. package/src/graphify/kind.ts +114 -0
  50. package/src/graphify/signature.test.ts +278 -0
  51. package/src/graphify/signature.ts +354 -0
  52. package/src/index.ts +26 -0
  53. package/src/stories/ComponentDeclarationAudit.stories.tsx +32 -0
  54. package/src/subsystem/ComponentDeclaration.tsx +306 -56
  55. package/src/subsystem/SubsystemComponentGraph.tsx +8 -1
  56. package/src/subsystem/formatDeclaration.ts +13 -1
  57. package/src/subsystem/model.ts +2 -0
@@ -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
+ }
@@ -0,0 +1,278 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+ import type { GraphifyEdge, GraphifyNode } from './types';
3
+ import {
4
+ compareSignatures,
5
+ extractGraphifySignature,
6
+ extractNamedTypes,
7
+ } from './signature';
8
+
9
+ function node(id: string, label: string): GraphifyNode {
10
+ return { id, label, file_type: 'code', source_file: 'a.ts' };
11
+ }
12
+
13
+ function ref(
14
+ source: string,
15
+ target: string,
16
+ context: 'parameter_type' | 'return_type' | 'inline_parameter',
17
+ ): GraphifyEdge {
18
+ return {
19
+ source,
20
+ target,
21
+ relation: 'references',
22
+ context,
23
+ confidence: 'EXTRACTED',
24
+ source_file: 'a.ts',
25
+ };
26
+ }
27
+
28
+ describe('extractNamedTypes', () => {
29
+ test('strips primitives and wrappers', () => {
30
+ expect(extractNamedTypes('Promise<BuiltSessionEvents>')).toEqual([
31
+ 'BuiltSessionEvents',
32
+ ]);
33
+ expect(extractNamedTypes('string')).toEqual([]);
34
+ expect(extractNamedTypes('{ tabId: string }')).toEqual([]);
35
+ });
36
+
37
+ test('splits unions and keeps named types', () => {
38
+ expect(extractNamedTypes('Foo | Bar | null')).toEqual(['Bar', 'Foo']);
39
+ });
40
+
41
+ test('drops inline objects but keeps sibling names', () => {
42
+ expect(
43
+ extractNamedTypes('Opts & { includeRaw?: boolean }'),
44
+ ).toEqual(['Opts']);
45
+ });
46
+ });
47
+
48
+ describe('extractGraphifySignature', () => {
49
+ test('collects parameter_type and return_type edges', () => {
50
+ const nodes = new Map([
51
+ ['fn', node('fn', 'build()')],
52
+ ['A', node('A', 'DataProcessor')],
53
+ ['R', node('R', 'Result')],
54
+ ]);
55
+ const edges = [
56
+ ref('fn', 'A', 'parameter_type'),
57
+ ref('fn', 'R', 'return_type'),
58
+ ];
59
+ const sig = extractGraphifySignature('fn', edges, nodes);
60
+ expect(sig.hasSignal).toBe(true);
61
+ expect(sig.parameters.map((p) => p.type)).toEqual(['DataProcessor']);
62
+ expect(sig.returnType).toBe('Result');
63
+ });
64
+
65
+ test('counts inline_parameter self-edges', () => {
66
+ const nodes = new Map([['fn', node('fn', 'build()')]]);
67
+ const sig = extractGraphifySignature(
68
+ 'fn',
69
+ [
70
+ ref('fn', 'fn', 'inline_parameter'),
71
+ ref('fn', 'fn', 'inline_parameter'),
72
+ ],
73
+ nodes,
74
+ );
75
+ expect(sig.inlineParameters).toBe(2);
76
+ expect(sig.hasSignal).toBe(false);
77
+ });
78
+ });
79
+
80
+ describe('compareSignatures', () => {
81
+ test('skips when graphify has no signature edges', () => {
82
+ const r = compareSignatures(
83
+ { parameters: [{ type: 'SessionReader' }], returnType: 'void' },
84
+ { parameters: [], hasSignal: false },
85
+ );
86
+ expect(r.skipped).toBe(true);
87
+ expect(r.match).toBe(true);
88
+ });
89
+
90
+ test('classifies skip: no claimable named types', () => {
91
+ const r = compareSignatures(
92
+ { parameters: [{ type: '{ tabId: string }' }] },
93
+ { parameters: [], hasSignal: false },
94
+ );
95
+ expect(r.skipped).toBe(true);
96
+ expect(r.skipCode).toBe('no_claimed_types');
97
+ });
98
+
99
+ test('classifies skip: claimed types only as generic_arg', () => {
100
+ const r = compareSignatures(
101
+ { returnType: 'Promise<BuiltSessionEvents>' },
102
+ {
103
+ parameters: [],
104
+ hasSignal: false,
105
+ genericArgs: [{ type: 'BuiltSessionEvents', nodeId: 'n' }],
106
+ },
107
+ );
108
+ expect(r.skipped).toBe(true);
109
+ expect(r.skipCode).toBe('generic_arg_only');
110
+ });
111
+
112
+ test('classifies skip: partial generic_arg coverage', () => {
113
+ const r = compareSignatures(
114
+ { parameters: [{ type: 'Foo' }], returnType: 'Bar' },
115
+ {
116
+ parameters: [],
117
+ hasSignal: false,
118
+ genericArgs: [{ type: 'Foo', nodeId: 'n' }],
119
+ },
120
+ );
121
+ expect(r.skipped).toBe(true);
122
+ expect(r.skipCode).toBe('partially_generic_arg');
123
+ });
124
+
125
+ test('classifies skip: claimed names have no edges at all', () => {
126
+ const r = compareSignatures(
127
+ { returnType: 'Element' },
128
+ { parameters: [], hasSignal: false },
129
+ );
130
+ expect(r.skipped).toBe(true);
131
+ expect(r.skipCode).toBe('unresolved_claimed_types');
132
+ });
133
+
134
+ test('matches named type bags', () => {
135
+ const inferred = {
136
+ hasSignal: true,
137
+ parameters: [
138
+ { type: 'DataProcessor', nodeId: 'a' },
139
+ { type: 'LintOptions', nodeId: 'b' },
140
+ ],
141
+ returnType: 'Result',
142
+ returnTypeNodeId: 'r',
143
+ };
144
+ const r = compareSignatures(
145
+ {
146
+ parameters: [
147
+ { type: 'DataProcessor' },
148
+ { type: 'Promise<LintOptions>' },
149
+ ],
150
+ returnType: 'Promise<Result>',
151
+ },
152
+ inferred,
153
+ );
154
+ expect(r.match).toBe(true);
155
+ expect(r.skipped).toBe(false);
156
+ });
157
+
158
+ test('hard-fails on parameter bag mismatch', () => {
159
+ const r = compareSignatures(
160
+ { parameters: [{ type: 'Foo' }], returnType: 'Result' },
161
+ {
162
+ hasSignal: true,
163
+ parameters: [{ type: 'Bar', nodeId: 'b' }],
164
+ returnType: 'Result',
165
+ },
166
+ );
167
+ expect(r.match).toBe(false);
168
+ expect(r.reason).toBe('parameter_types_mismatch');
169
+ });
170
+
171
+ test('empty claimed vs graphify types fails', () => {
172
+ const r = compareSignatures(
173
+ { parameters: [] },
174
+ {
175
+ hasSignal: true,
176
+ parameters: [{ type: 'DataProcessor', nodeId: 'a' }],
177
+ returnType: 'Result',
178
+ },
179
+ );
180
+ expect(r.match).toBe(false);
181
+ });
182
+
183
+ test('anon param claim + marker parity: params verified, unresolved return skips', () => {
184
+ // buildAgentSessionsView shape: 1 anonymous `opts` (marker present),
185
+ // named return type only in the claim (npm type, no graph edge).
186
+ const r = compareSignatures(
187
+ {
188
+ parameters: [
189
+ {
190
+ type: '{ sessionId: string; events: SessionEventRow[] | null }',
191
+ },
192
+ ],
193
+ returnType: 'AgentSessionsView',
194
+ },
195
+ {
196
+ parameters: [],
197
+ hasSignal: false,
198
+ inlineParameters: 1,
199
+ genericArgs: [],
200
+ },
201
+ );
202
+ expect(r.skipped).toBe(true);
203
+ expect(r.match).toBe(true);
204
+ expect(r.skipCode).toBe('unresolved_claimed_types');
205
+ expect(r.reason).toMatch(/params verified/);
206
+ });
207
+
208
+ test('anon params verified + wrapper-indirected return skips as generic_arg', () => {
209
+ // processSessionEvents shape: 1 anonymous opts + Promise-wrapped return.
210
+ const r = compareSignatures(
211
+ {
212
+ parameters: [
213
+ {
214
+ type: '{ sessionId: string; events: SessionEventRow[] | null }',
215
+ },
216
+ ],
217
+ returnType: 'Promise<BuiltSessionEvents>',
218
+ },
219
+ {
220
+ parameters: [],
221
+ hasSignal: false,
222
+ inlineParameters: 1,
223
+ genericArgs: [{ type: 'BuiltSessionEvents', nodeId: 'n' }],
224
+ },
225
+ );
226
+ expect(r.skipped).toBe(true);
227
+ expect(r.match).toBe(true);
228
+ expect(r.skipCode).toBe('generic_arg_only');
229
+ expect(r.reason).toMatch(/params verified/);
230
+ });
231
+
232
+ test('params verified but mixed generic+unresolved return stays a hard mismatch', () => {
233
+ const r = compareSignatures(
234
+ {
235
+ parameters: [{ type: '{ a: string }' }],
236
+ returnType: 'Promise<Foo> | Bar',
237
+ },
238
+ {
239
+ parameters: [],
240
+ hasSignal: false,
241
+ inlineParameters: 1,
242
+ genericArgs: [{ type: 'Foo', nodeId: 'n' }],
243
+ },
244
+ );
245
+ expect(r.skipped).toBe(false);
246
+ expect(r.match).toBe(false);
247
+ expect(r.reason).toBe('return_type_mismatch');
248
+ });
249
+
250
+ test('anon param claim with marker and no return claim: matches', () => {
251
+ // analyzeSessionInBackground shape: inline opts only, no return type.
252
+ const r = compareSignatures(
253
+ { parameters: [{ type: '{ sessionId: string; title: string }' }] },
254
+ { parameters: [], hasSignal: false, inlineParameters: 1 },
255
+ );
256
+ expect(r.skipped).toBe(false);
257
+ expect(r.match).toBe(true);
258
+ });
259
+
260
+ test('graph marker without matching claim is a params mismatch', () => {
261
+ const r = compareSignatures(
262
+ { parameters: [] },
263
+ { parameters: [], hasSignal: false, inlineParameters: 1 },
264
+ );
265
+ expect(r.skipped).toBe(false);
266
+ expect(r.match).toBe(false);
267
+ expect(r.reason).toBe('parameter_types_mismatch');
268
+ });
269
+
270
+ test('anon claim with no marker stays skipped (default graph, no signal)', () => {
271
+ const r = compareSignatures(
272
+ { parameters: [{ type: '{ tabId: string }' }] },
273
+ { parameters: [], hasSignal: false },
274
+ );
275
+ expect(r.skipped).toBe(true);
276
+ expect(r.skipCode).toBe('no_claimed_types');
277
+ });
278
+ });