@principal-ai/subsystems-react 0.21.1 → 0.22.0
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/dist/graphify/consolidated.d.ts +5 -24
- package/dist/graphify/consolidated.d.ts.map +1 -1
- package/dist/graphify/index.d.ts +1 -1
- package/dist/graphify/index.d.ts.map +1 -1
- package/dist/graphify/index.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/pierre/PierreSnippetView.d.ts.map +1 -1
- package/dist/pierre/PierreSnippetView.js +4 -19
- package/dist/pierre/PierreSnippetView.js.map +1 -1
- package/dist/pierre/PierreWalkthroughCodeView.d.ts.map +1 -1
- package/dist/pierre/PierreWalkthroughCodeView.js +20 -3
- package/dist/pierre/PierreWalkthroughCodeView.js.map +1 -1
- package/dist/pierre/constructColors.d.ts.map +1 -1
- package/dist/pierre/constructColors.js +0 -5
- package/dist/pierre/constructColors.js.map +1 -1
- package/dist/pierre/index.d.ts +1 -1
- package/dist/pierre/index.d.ts.map +1 -1
- package/dist/pierre/index.js +1 -1
- package/dist/pierre/index.js.map +1 -1
- package/dist/pierre/sliceSnippet.d.ts +6 -0
- package/dist/pierre/sliceSnippet.d.ts.map +1 -1
- package/dist/pierre/sliceSnippet.js +25 -0
- package/dist/pierre/sliceSnippet.js.map +1 -1
- package/dist/stories/Subsystem/ComponentGraph/fixtures.d.ts.map +1 -1
- package/dist/stories/Subsystem/ComponentGraph/fixtures.js +0 -3
- package/dist/stories/Subsystem/ComponentGraph/fixtures.js.map +1 -1
- package/dist/subsystem/formatDeclaration.d.ts.map +1 -1
- package/dist/subsystem/formatDeclaration.js +0 -15
- package/dist/subsystem/formatDeclaration.js.map +1 -1
- package/dist/subsystem/model.d.ts +61 -22
- package/dist/subsystem/model.d.ts.map +1 -1
- package/dist/subsystem/model.js +186 -63
- package/dist/subsystem/model.js.map +1 -1
- package/dist/subsystem/nodes.d.ts +3 -4
- package/dist/subsystem/nodes.d.ts.map +1 -1
- package/dist/subsystem/nodes.js +5 -6
- package/dist/subsystem/nodes.js.map +1 -1
- package/dist/utils/elkLayout.d.ts +6 -4
- package/dist/utils/elkLayout.d.ts.map +1 -1
- package/dist/utils/elkLayout.js +144 -54
- package/dist/utils/elkLayout.js.map +1 -1
- package/package.json +3 -3
- package/src/graphify/consolidated.ts +4 -26
- package/src/graphify/index.ts +0 -2
- package/src/index.ts +0 -2
- package/src/pierre/PierreSnippetView.tsx +4 -18
- package/src/pierre/PierreWalkthroughCodeView.tsx +39 -3
- package/src/pierre/constructColors.test.ts +1 -2
- package/src/pierre/constructColors.ts +0 -5
- package/src/pierre/index.ts +1 -1
- package/src/pierre/sliceSnippet.test.ts +44 -1
- package/src/pierre/sliceSnippet.ts +28 -0
- package/src/stories/Subsystem/ComponentDeclarationAudit.stories.tsx +0 -31
- package/src/stories/Subsystem/ComponentGraph/Appearance.stories.tsx +2 -46
- package/src/stories/Subsystem/ComponentGraph/Captures.stories.tsx +9 -24
- package/src/stories/Subsystem/ComponentGraph/DetailPanel.stories.tsx +2 -2
- package/src/stories/Subsystem/ComponentGraph/Modules.stories.tsx +287 -0
- package/src/stories/Subsystem/ComponentGraph/Processes.stories.tsx +1 -1
- package/src/stories/Subsystem/ComponentGraph/Scenarios.stories.tsx +6 -6
- package/src/stories/Subsystem/ComponentGraph/fixtures.ts +0 -3
- package/src/subsystem/formatDeclaration.ts +0 -19
- package/src/subsystem/model.test.ts +190 -9
- package/src/subsystem/model.ts +219 -73
- package/src/subsystem/nodes.tsx +6 -6
- package/src/utils/elkLayout.ts +148 -58
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import '@xyflow/react/dist/style.css';
|
|
2
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
3
|
+
import { ThemeProvider, defaultEditorTheme } from '@principal-ade/industry-theme';
|
|
4
|
+
import { SubsystemComponentGraph } from '../../../subsystem/SubsystemComponentGraph';
|
|
5
|
+
import type { SubsystemComponent } from '../../../subsystem/model';
|
|
6
|
+
import { graphSpecFromEdges } from './fixtures';
|
|
7
|
+
|
|
8
|
+
const meta = {
|
|
9
|
+
title: 'Subsystem/ComponentGraph/Modules',
|
|
10
|
+
component: SubsystemComponentGraph,
|
|
11
|
+
parameters: { layout: 'fullscreen' },
|
|
12
|
+
tags: ['autodocs'],
|
|
13
|
+
decorators: [
|
|
14
|
+
(Story) => (
|
|
15
|
+
<ThemeProvider theme={defaultEditorTheme}>
|
|
16
|
+
<Story />
|
|
17
|
+
</ThemeProvider>
|
|
18
|
+
),
|
|
19
|
+
],
|
|
20
|
+
} satisfies Meta<typeof SubsystemComponentGraph>;
|
|
21
|
+
|
|
22
|
+
export default meta;
|
|
23
|
+
type Story = StoryObj<typeof meta>;
|
|
24
|
+
|
|
25
|
+
const PURL = 'pkg:github/principal-ai/agent-monitoring';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Module frames replace `construct: module`. Each export keeps its real
|
|
29
|
+
* construct; shared `module` (the source path) draws the file as a boundary.
|
|
30
|
+
*/
|
|
31
|
+
const moduleComponents: SubsystemComponent[] = [
|
|
32
|
+
{
|
|
33
|
+
id: 'record',
|
|
34
|
+
name: 'CodexRolloutRecord',
|
|
35
|
+
construct: 'type_alias',
|
|
36
|
+
symbol: 'CodexRolloutRecord',
|
|
37
|
+
file: 'src/session/transcript.ts',
|
|
38
|
+
module: 'src/session/transcript.ts',
|
|
39
|
+
purl: PURL,
|
|
40
|
+
purpose: 'parsed rollout record shape',
|
|
41
|
+
layer: 1,
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
id: 'is-rollout',
|
|
45
|
+
name: 'isCodexRollout',
|
|
46
|
+
construct: 'function',
|
|
47
|
+
symbol: 'isCodexRollout',
|
|
48
|
+
file: 'src/session/transcript.ts',
|
|
49
|
+
module: 'src/session/transcript.ts',
|
|
50
|
+
purl: PURL,
|
|
51
|
+
purpose: 'type guard for rollout records',
|
|
52
|
+
layer: 1,
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
id: 'parse',
|
|
56
|
+
name: 'parseTranscript',
|
|
57
|
+
construct: 'function',
|
|
58
|
+
symbol: 'parseTranscript',
|
|
59
|
+
file: 'src/session/transcript.ts',
|
|
60
|
+
module: 'src/session/transcript.ts',
|
|
61
|
+
purl: PURL,
|
|
62
|
+
purpose: 'parses session transcript lines',
|
|
63
|
+
layer: 1,
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
id: 'tool-name',
|
|
67
|
+
name: 'extractToolName',
|
|
68
|
+
construct: 'function',
|
|
69
|
+
symbol: 'extractToolName',
|
|
70
|
+
file: 'src/session/paths.ts',
|
|
71
|
+
module: 'src/session/paths.ts',
|
|
72
|
+
purl: PURL,
|
|
73
|
+
purpose: 'pulls tool names from path-like strings',
|
|
74
|
+
layer: 2,
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
id: 'file-path',
|
|
78
|
+
name: 'extractFilePath',
|
|
79
|
+
construct: 'function',
|
|
80
|
+
symbol: 'extractFilePath',
|
|
81
|
+
file: 'src/session/paths.ts',
|
|
82
|
+
module: 'src/session/paths.ts',
|
|
83
|
+
purl: PURL,
|
|
84
|
+
purpose: 'pulls file paths from tool args',
|
|
85
|
+
layer: 2,
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
id: 'reader',
|
|
89
|
+
name: 'SessionReader',
|
|
90
|
+
construct: 'class',
|
|
91
|
+
symbol: 'SessionReader',
|
|
92
|
+
file: 'src/session/SessionReader.ts',
|
|
93
|
+
module: 'src/session/SessionReader.ts',
|
|
94
|
+
purl: PURL,
|
|
95
|
+
purpose: 'normalizes a session into universal events',
|
|
96
|
+
layer: 3,
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
id: 'normalize',
|
|
100
|
+
name: 'normalize',
|
|
101
|
+
construct: 'method',
|
|
102
|
+
symbol: 'SessionReader.normalize',
|
|
103
|
+
file: 'src/session/SessionReader.ts',
|
|
104
|
+
module: 'src/session/SessionReader.ts',
|
|
105
|
+
purl: PURL,
|
|
106
|
+
purpose: 'maps a session into universal events',
|
|
107
|
+
layer: 3,
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
id: 'registry',
|
|
111
|
+
name: 'registerAgent',
|
|
112
|
+
construct: 'function',
|
|
113
|
+
symbol: 'registerAgent',
|
|
114
|
+
file: 'src/supported-agents.ts',
|
|
115
|
+
// Singleton module — no frame (same rule as process: need 2+ members).
|
|
116
|
+
module: 'src/supported-agents.ts',
|
|
117
|
+
purl: PURL,
|
|
118
|
+
purpose: 'registers a supported agent (singleton file — no module frame)',
|
|
119
|
+
layer: 4,
|
|
120
|
+
},
|
|
121
|
+
];
|
|
122
|
+
|
|
123
|
+
const moduleEdges = graphSpecFromEdges([
|
|
124
|
+
['parse', 'record', 'references'],
|
|
125
|
+
['is-rollout', 'record', 'references'],
|
|
126
|
+
['reader', 'normalize', 'method'],
|
|
127
|
+
['normalize', 'parse', 'calls'],
|
|
128
|
+
['normalize', 'tool-name', 'calls'],
|
|
129
|
+
['normalize', 'file-path', 'calls'],
|
|
130
|
+
['reader', 'registry', 'registers-into'],
|
|
131
|
+
]);
|
|
132
|
+
|
|
133
|
+
export const ModuleBoundaries: Story = {
|
|
134
|
+
render: () => (
|
|
135
|
+
<div style={{ width: '100%', height: '100vh' }}>
|
|
136
|
+
<SubsystemComponentGraph
|
|
137
|
+
title="Module boundaries"
|
|
138
|
+
description="Concrete exports keep their real construct. Shared `module` (the source path) draws the file as a dashed frame — not `construct: module`. Singleton modules (registerAgent) stay unframed."
|
|
139
|
+
components={moduleComponents}
|
|
140
|
+
relations={moduleEdges.relations}
|
|
141
|
+
walkthroughs={moduleEdges.walkthroughs}
|
|
142
|
+
/>
|
|
143
|
+
</div>
|
|
144
|
+
),
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
/** Process → module → export nesting (host / renderer). */
|
|
148
|
+
const nestedComponents: SubsystemComponent[] = [
|
|
149
|
+
{
|
|
150
|
+
id: 'boot',
|
|
151
|
+
name: 'boot',
|
|
152
|
+
construct: 'function',
|
|
153
|
+
symbol: 'boot',
|
|
154
|
+
role: 'entry',
|
|
155
|
+
file: 'src/host/main.ts',
|
|
156
|
+
module: 'src/host/main.ts',
|
|
157
|
+
process: 'principal-studio/host',
|
|
158
|
+
purl: 'pkg:github/principal-ai/principal-studio',
|
|
159
|
+
purpose: 'Boots the host process',
|
|
160
|
+
layer: 1,
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
id: 'create-host',
|
|
164
|
+
name: 'createHost',
|
|
165
|
+
construct: 'function',
|
|
166
|
+
symbol: 'createHost',
|
|
167
|
+
file: 'src/host/main.ts',
|
|
168
|
+
module: 'src/host/main.ts',
|
|
169
|
+
process: 'principal-studio/host',
|
|
170
|
+
purl: 'pkg:github/principal-ai/principal-studio',
|
|
171
|
+
purpose: 'Constructs the host runtime',
|
|
172
|
+
layer: 1,
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
id: 'session-store',
|
|
176
|
+
name: 'SessionStore',
|
|
177
|
+
construct: 'store',
|
|
178
|
+
symbol: 'SessionStore',
|
|
179
|
+
file: 'src/host/store.ts',
|
|
180
|
+
module: 'src/host/store.ts',
|
|
181
|
+
process: 'principal-studio/host',
|
|
182
|
+
purl: 'pkg:github/principal-ai/principal-studio',
|
|
183
|
+
purpose: 'Retained host session state',
|
|
184
|
+
layer: 2,
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
id: 'write-session',
|
|
188
|
+
name: 'writeSession',
|
|
189
|
+
construct: 'function',
|
|
190
|
+
symbol: 'writeSession',
|
|
191
|
+
file: 'src/host/store.ts',
|
|
192
|
+
module: 'src/host/store.ts',
|
|
193
|
+
process: 'principal-studio/host',
|
|
194
|
+
purl: 'pkg:github/principal-ai/principal-studio',
|
|
195
|
+
purpose: 'Mutates SessionStore',
|
|
196
|
+
layer: 2,
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
id: 'bridge',
|
|
200
|
+
name: 'bridge',
|
|
201
|
+
construct: 'function',
|
|
202
|
+
symbol: 'bridge',
|
|
203
|
+
role: 'entry',
|
|
204
|
+
file: 'src/renderer/bridge.ts',
|
|
205
|
+
module: 'src/renderer/bridge.ts',
|
|
206
|
+
process: 'principal-studio/renderer',
|
|
207
|
+
purl: 'pkg:github/principal-ai/principal-studio',
|
|
208
|
+
purpose: 'Renderer IPC entry',
|
|
209
|
+
layer: 3,
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
id: 'post-to-host',
|
|
213
|
+
name: 'postToHost',
|
|
214
|
+
construct: 'function',
|
|
215
|
+
symbol: 'postToHost',
|
|
216
|
+
file: 'src/renderer/bridge.ts',
|
|
217
|
+
module: 'src/renderer/bridge.ts',
|
|
218
|
+
process: 'principal-studio/renderer',
|
|
219
|
+
purl: 'pkg:github/principal-ai/principal-studio',
|
|
220
|
+
purpose: 'Sends a message to the host',
|
|
221
|
+
layer: 3,
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
id: 'trail-view',
|
|
225
|
+
name: 'TrailView',
|
|
226
|
+
construct: 'function',
|
|
227
|
+
symbol: 'TrailView',
|
|
228
|
+
framework: 'react',
|
|
229
|
+
stereotype: 'component',
|
|
230
|
+
file: 'src/renderer/view.tsx',
|
|
231
|
+
module: 'src/renderer/view.tsx',
|
|
232
|
+
process: 'principal-studio/renderer',
|
|
233
|
+
purl: 'pkg:github/principal-ai/principal-studio',
|
|
234
|
+
purpose: 'Renders the trail',
|
|
235
|
+
layer: 4,
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
id: 'use-trail',
|
|
239
|
+
name: 'useTrail',
|
|
240
|
+
construct: 'function',
|
|
241
|
+
symbol: 'useTrail',
|
|
242
|
+
framework: 'react',
|
|
243
|
+
stereotype: 'hook',
|
|
244
|
+
file: 'src/renderer/view.tsx',
|
|
245
|
+
module: 'src/renderer/view.tsx',
|
|
246
|
+
process: 'principal-studio/renderer',
|
|
247
|
+
purl: 'pkg:github/principal-ai/principal-studio',
|
|
248
|
+
purpose: 'Subscribes the view to trail updates',
|
|
249
|
+
layer: 4,
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
id: 'telemetry',
|
|
253
|
+
name: 'telemetry',
|
|
254
|
+
construct: 'external',
|
|
255
|
+
role: 'service',
|
|
256
|
+
file: '',
|
|
257
|
+
purl: 'external',
|
|
258
|
+
purpose: 'External sink — outside every frame',
|
|
259
|
+
layer: 5,
|
|
260
|
+
},
|
|
261
|
+
];
|
|
262
|
+
|
|
263
|
+
const nestedEdges = graphSpecFromEdges([
|
|
264
|
+
['boot', 'write-session', 'imports'],
|
|
265
|
+
['trail-view', 'bridge', 'imports'],
|
|
266
|
+
['trail-view', 'use-trail', 'references'],
|
|
267
|
+
['boot', 'create-host', 'calls'],
|
|
268
|
+
['create-host', 'write-session', 'calls'],
|
|
269
|
+
['write-session', 'session-store', 'writes'],
|
|
270
|
+
['bridge', 'boot', 'calls'],
|
|
271
|
+
['trail-view', 'post-to-host', 'calls'],
|
|
272
|
+
['boot', 'telemetry', 'uses'],
|
|
273
|
+
]);
|
|
274
|
+
|
|
275
|
+
export const ProcessAndModuleNesting: Story = {
|
|
276
|
+
render: () => (
|
|
277
|
+
<div style={{ width: '100%', height: '100vh' }}>
|
|
278
|
+
<SubsystemComponentGraph
|
|
279
|
+
title="Process × module nesting"
|
|
280
|
+
description="process frames wrap module frames wrap exports. Leaves parent to module:; module groups parent to process: when every member shares that process."
|
|
281
|
+
components={nestedComponents}
|
|
282
|
+
relations={nestedEdges.relations}
|
|
283
|
+
walkthroughs={nestedEdges.walkthroughs}
|
|
284
|
+
/>
|
|
285
|
+
</div>
|
|
286
|
+
),
|
|
287
|
+
};
|
|
@@ -28,7 +28,7 @@ const processComponents = [
|
|
|
28
28
|
['main', 'main', 'function', 'src/host/main.ts', 'pkg:github/principal-ai/principal-studio', 'boots the host process', 'main'],
|
|
29
29
|
['store', 'SessionStore', 'store', 'src/host/store.ts', 'pkg:github/principal-ai/principal-studio', 'retained host state', 'SessionStore'],
|
|
30
30
|
['view', 'TrailView', 'function', 'src/renderer/view.tsx', 'pkg:github/principal-ai/principal-studio', 'renders the trail', 'TrailView'],
|
|
31
|
-
['bridge', 'bridge', '
|
|
31
|
+
['bridge', 'bridge', 'function', 'src/renderer/bridge.ts', 'pkg:github/principal-ai/principal-studio', 'IPC bridge to the host', 'bridge'],
|
|
32
32
|
]),
|
|
33
33
|
{
|
|
34
34
|
id: 'svc',
|
|
@@ -84,7 +84,7 @@ const mermaidComponents: SubsystemComponent[] = [
|
|
|
84
84
|
{
|
|
85
85
|
id: 'helpers',
|
|
86
86
|
name: 'beautifulMermaid',
|
|
87
|
-
construct: '
|
|
87
|
+
construct: 'function',
|
|
88
88
|
file: 'industryMarkdown/utils/beautifulMermaid.ts',
|
|
89
89
|
purl: mermaidPurl,
|
|
90
90
|
purpose: 'engine detection, theme\u2192options mapping, SVG post-processing',
|
|
@@ -130,11 +130,11 @@ const coreLibPurl = 'pkg:github/principal-ai/principal-view-core-library';
|
|
|
130
130
|
const graphifyPurl = 'pkg:github/Graphify-Labs/graphify';
|
|
131
131
|
|
|
132
132
|
const multiRepoComponents = components([
|
|
133
|
-
['resolver', 'resolve.ts', '
|
|
134
|
-
['reftypes', 'consolidated.ts', '
|
|
135
|
-
['detail', 'ComponentDeclaration.tsx', '
|
|
136
|
-
['engine', 'engine.py', '
|
|
137
|
-
['rewire', 'extract.py', '
|
|
133
|
+
['resolver', 'resolve.ts', 'function', 'packages/subsystems-react/src/graphify/resolve.ts', coreLibPurl, 'consumer-side resolution mirroring graphify rewire tiers', 'createGraphifyTypeResolver'],
|
|
134
|
+
['reftypes', 'consolidated.ts', 'function', 'packages/subsystems-react/src/graphify/consolidated.ts', coreLibPurl, 'drill-down payload types carrying ref.nodeId', 'GraphifyParamInfo'],
|
|
135
|
+
['detail', 'ComponentDeclaration.tsx', 'function', 'packages/subsystems-react/src/subsystem/ComponentDeclaration.tsx', coreLibPurl, 'declaration panel rendering the selected component as code, with clickable type tokens', 'ComponentDeclaration'],
|
|
136
|
+
['engine', 'engine.py', 'function', 'graphify/extractors/engine.py', graphifyPurl, 'tree-sitter walk emitting references[parameter_type] edges + sourceless stubs', 'ensure_named_node'],
|
|
137
|
+
['rewire', 'extract.py', 'function', 'graphify/extract.py', graphifyPurl, 'corpus pass folding unique-label stubs onto definitions', '_rewire_unique_stub_nodes'],
|
|
138
138
|
]);
|
|
139
139
|
|
|
140
140
|
const multiRepoEdges = graphSpecFromEdges([
|
|
@@ -39,8 +39,6 @@ export function generateDeclarationString(component: SubsystemComponent): string
|
|
|
39
39
|
case 'type_alias':
|
|
40
40
|
case 'enum':
|
|
41
41
|
return generateType(name, component.construct, declaration);
|
|
42
|
-
case 'module':
|
|
43
|
-
return generateModule(declaration);
|
|
44
42
|
case 'store':
|
|
45
43
|
return generateStore(name, declaration);
|
|
46
44
|
case 'external':
|
|
@@ -175,23 +173,6 @@ function generateType(
|
|
|
175
173
|
}
|
|
176
174
|
}
|
|
177
175
|
|
|
178
|
-
function generateModule(declaration?: GraphifyComponentDetail): string {
|
|
179
|
-
const mod = declaration?.kind === 'module' ? declaration : undefined;
|
|
180
|
-
if (!mod) return 'module {}';
|
|
181
|
-
|
|
182
|
-
const parts: string[] = [];
|
|
183
|
-
|
|
184
|
-
for (const imp of mod.imports ?? []) {
|
|
185
|
-
parts.push(`import '${imp.name}';`);
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
if ((mod.exports ?? []).length > 0) {
|
|
189
|
-
parts.push(`export { ${mod.exports!.join(', ')} };`);
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
return parts.join('\n') || `module {}`;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
176
|
/**
|
|
196
177
|
* A store renders as its retained state — ambient `declare const` lines for
|
|
197
178
|
* the state members, never a class/method stub. The node's name labels the
|
|
@@ -4,7 +4,9 @@ import {
|
|
|
4
4
|
convertSubsystemToEdges,
|
|
5
5
|
convertSubsystemToGroups,
|
|
6
6
|
getSubsystemRegions,
|
|
7
|
+
getSubsystemModuleRegions,
|
|
7
8
|
processGroupNodeId,
|
|
9
|
+
moduleGroupNodeId,
|
|
8
10
|
buildSubsystemGraph,
|
|
9
11
|
deriveNameFromSymbol,
|
|
10
12
|
constructBadgeLabel,
|
|
@@ -94,10 +96,10 @@ describe('subsystem graph model', () => {
|
|
|
94
96
|
});
|
|
95
97
|
|
|
96
98
|
test('deriveNameFromSymbol is consistent per kind', () => {
|
|
97
|
-
// brace-bodied constructs wear {}; classes render bare
|
|
99
|
+
// brace-bodied constructs wear {}; classes render bare.
|
|
98
100
|
expect(deriveNameFromSymbol('SessionReader', 'class')).toBe('SessionReader');
|
|
99
101
|
expect(deriveNameFromSymbol('SessionRecord', 'type_alias')).toBe('SessionRecord {}');
|
|
100
|
-
expect(deriveNameFromSymbol('transcript', '
|
|
102
|
+
expect(deriveNameFromSymbol('transcript', 'function')).toBe('transcript()');
|
|
101
103
|
// falls back to existing name when no symbol (class stays bare).
|
|
102
104
|
expect(deriveNameFromSymbol(undefined, 'class', 'SessionReader')).toBe('SessionReader');
|
|
103
105
|
expect(deriveNameFromSymbol('', 'external', 'principal-studio-host')).toBe('principal-studio-host');
|
|
@@ -187,13 +189,13 @@ describe('subsystem graph model', () => {
|
|
|
187
189
|
expect(rightBadgeLabel({ role: 'service', proposed: true })).toBe('proposed');
|
|
188
190
|
});
|
|
189
191
|
|
|
190
|
-
test('deriveNameFromSymbol falls back to
|
|
191
|
-
expect(deriveNameFromSymbol(undefined, '
|
|
192
|
-
expect(deriveNameFromSymbol(undefined, '
|
|
193
|
-
// Symbol wins over
|
|
194
|
-
expect(deriveNameFromSymbol('CodexRolloutRecord', '
|
|
195
|
-
|
|
196
|
-
|
|
192
|
+
test('deriveNameFromSymbol falls back to existing name when no symbol', () => {
|
|
193
|
+
expect(deriveNameFromSymbol(undefined, 'class', undefined, 'transcript.ts')).toBe('untitled');
|
|
194
|
+
expect(deriveNameFromSymbol(undefined, 'function', 'MyFn', 'x.ts')).toBe('MyFn()');
|
|
195
|
+
// Symbol wins over a supplied name.
|
|
196
|
+
expect(deriveNameFromSymbol('CodexRolloutRecord', 'function', undefined, 'transcript.ts')).toBe(
|
|
197
|
+
'CodexRolloutRecord()',
|
|
198
|
+
);
|
|
197
199
|
});
|
|
198
200
|
|
|
199
201
|
test('formatPurl renders the human identity', () => {
|
|
@@ -216,9 +218,49 @@ describe('subsystem graph model', () => {
|
|
|
216
218
|
],
|
|
217
219
|
});
|
|
218
220
|
expect(regions.map((r) => r.key)).toEqual(['app/host', 'app/renderer']);
|
|
221
|
+
expect(regions.every((r) => r.kind === 'process')).toBe(true);
|
|
219
222
|
expect(regions[0]!.memberIds).toEqual(['a', 'b']);
|
|
220
223
|
});
|
|
221
224
|
|
|
225
|
+
test('getSubsystemModuleRegions groups by module, skipping module-less nodes', () => {
|
|
226
|
+
const regions = getSubsystemModuleRegions({
|
|
227
|
+
components: [
|
|
228
|
+
{ id: 'a', name: 'a', construct: 'function', file: 'transcript.ts', purl: 'pkg:github/acme/app', module: 'src/session/transcript.ts' },
|
|
229
|
+
{ id: 'b', name: 'b', construct: 'type_alias', file: 'transcript.ts', purl: 'pkg:github/acme/app', module: 'src/session/transcript.ts' },
|
|
230
|
+
{ id: 'c', name: 'c', construct: 'function', file: 'paths.ts', purl: 'pkg:github/acme/app', module: 'src/session/paths.ts' },
|
|
231
|
+
{ id: 'd', name: 'd', construct: 'function', file: 'other.ts', purl: 'pkg:github/acme/app' },
|
|
232
|
+
],
|
|
233
|
+
});
|
|
234
|
+
expect(regions.map((r) => r.key)).toEqual([
|
|
235
|
+
'src/session/transcript.ts',
|
|
236
|
+
'src/session/paths.ts',
|
|
237
|
+
]);
|
|
238
|
+
expect(regions.every((r) => r.kind === 'module')).toBe(true);
|
|
239
|
+
expect(regions[0]!.memberIds).toEqual(['a', 'b']);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
test('convertSubsystemToNodes prefers module parentId over process', () => {
|
|
243
|
+
const nodes = convertSubsystemToNodes({
|
|
244
|
+
components: [
|
|
245
|
+
{
|
|
246
|
+
id: 'a',
|
|
247
|
+
name: 'a',
|
|
248
|
+
construct: 'function',
|
|
249
|
+
file: 'a.ts',
|
|
250
|
+
purl: 'pkg:github/acme/app',
|
|
251
|
+
process: 'app/host',
|
|
252
|
+
module: 'src/a.ts',
|
|
253
|
+
},
|
|
254
|
+
{ id: 'd', name: 'd', construct: 'function', file: 'd.ts', purl: 'pkg:github/acme/app' },
|
|
255
|
+
],
|
|
256
|
+
relations: [],
|
|
257
|
+
});
|
|
258
|
+
expect((nodes.find((n) => n.id === 'a') as { parentId?: string }).parentId).toBe(
|
|
259
|
+
moduleGroupNodeId('src/a.ts'),
|
|
260
|
+
);
|
|
261
|
+
expect((nodes.find((n) => n.id === 'd') as { parentId?: string }).parentId).toBeUndefined();
|
|
262
|
+
});
|
|
263
|
+
|
|
222
264
|
test('convertSubsystemToNodes stamps parentId for process members only', () => {
|
|
223
265
|
const nodes = convertSubsystemToNodes({
|
|
224
266
|
components: [
|
|
@@ -245,6 +287,103 @@ describe('subsystem graph model', () => {
|
|
|
245
287
|
expect(groups[0]!.type).toBe('subsystem-group');
|
|
246
288
|
});
|
|
247
289
|
|
|
290
|
+
test('convertSubsystemToGroups nests module frames under a shared process', () => {
|
|
291
|
+
const groups = convertSubsystemToGroups({
|
|
292
|
+
components: [
|
|
293
|
+
{
|
|
294
|
+
id: 'a',
|
|
295
|
+
name: 'a',
|
|
296
|
+
construct: 'function',
|
|
297
|
+
file: 'a.ts',
|
|
298
|
+
purl: 'pkg:github/acme/app',
|
|
299
|
+
process: 'app/host',
|
|
300
|
+
module: 'src/a.ts',
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
id: 'b',
|
|
304
|
+
name: 'b',
|
|
305
|
+
construct: 'function',
|
|
306
|
+
file: 'a.ts',
|
|
307
|
+
purl: 'pkg:github/acme/app',
|
|
308
|
+
process: 'app/host',
|
|
309
|
+
module: 'src/a.ts',
|
|
310
|
+
},
|
|
311
|
+
],
|
|
312
|
+
});
|
|
313
|
+
expect(groups.map((g) => g.id).sort()).toEqual(
|
|
314
|
+
[moduleGroupNodeId('src/a.ts'), processGroupNodeId('app/host')].sort(),
|
|
315
|
+
);
|
|
316
|
+
const mod = groups.find((g) => g.id === moduleGroupNodeId('src/a.ts'));
|
|
317
|
+
expect((mod as { parentId?: string }).parentId).toBe(processGroupNodeId('app/host'));
|
|
318
|
+
const proc = groups.find((g) => g.id === processGroupNodeId('app/host'));
|
|
319
|
+
expect((proc as { parentId?: string }).parentId).toBeUndefined();
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
test('buildSubsystemGraph nests module frames inside process frames', async () => {
|
|
323
|
+
const { nodes, regions } = await buildSubsystemGraph({
|
|
324
|
+
components: [
|
|
325
|
+
{
|
|
326
|
+
id: 'boot',
|
|
327
|
+
name: 'boot',
|
|
328
|
+
construct: 'function',
|
|
329
|
+
file: 'main.ts',
|
|
330
|
+
purl: 'pkg:github/acme/app',
|
|
331
|
+
process: 'app/host',
|
|
332
|
+
module: 'src/main.ts',
|
|
333
|
+
symbol: 'boot',
|
|
334
|
+
},
|
|
335
|
+
{
|
|
336
|
+
id: 'create',
|
|
337
|
+
name: 'createHost',
|
|
338
|
+
construct: 'function',
|
|
339
|
+
file: 'main.ts',
|
|
340
|
+
purl: 'pkg:github/acme/app',
|
|
341
|
+
process: 'app/host',
|
|
342
|
+
module: 'src/main.ts',
|
|
343
|
+
symbol: 'createHost',
|
|
344
|
+
},
|
|
345
|
+
{
|
|
346
|
+
id: 'write',
|
|
347
|
+
name: 'writeSession',
|
|
348
|
+
construct: 'function',
|
|
349
|
+
file: 'store.ts',
|
|
350
|
+
purl: 'pkg:github/acme/app',
|
|
351
|
+
process: 'app/host',
|
|
352
|
+
module: 'src/store.ts',
|
|
353
|
+
symbol: 'writeSession',
|
|
354
|
+
},
|
|
355
|
+
{
|
|
356
|
+
id: 'store',
|
|
357
|
+
name: 'SessionStore',
|
|
358
|
+
construct: 'store',
|
|
359
|
+
file: 'store.ts',
|
|
360
|
+
purl: 'pkg:github/acme/app',
|
|
361
|
+
process: 'app/host',
|
|
362
|
+
module: 'src/store.ts',
|
|
363
|
+
symbol: 'SessionStore',
|
|
364
|
+
},
|
|
365
|
+
],
|
|
366
|
+
relations: [],
|
|
367
|
+
});
|
|
368
|
+
expect(regions.map((r) => r.key).sort()).toEqual(
|
|
369
|
+
['app/host', 'src/main.ts', 'src/store.ts'].sort(),
|
|
370
|
+
);
|
|
371
|
+
const processNode = nodes.find((n) => n.id === processGroupNodeId('app/host'));
|
|
372
|
+
const mainMod = nodes.find((n) => n.id === moduleGroupNodeId('src/main.ts'));
|
|
373
|
+
const storeMod = nodes.find((n) => n.id === moduleGroupNodeId('src/store.ts'));
|
|
374
|
+
expect(processNode).toBeDefined();
|
|
375
|
+
expect(mainMod).toBeDefined();
|
|
376
|
+
expect(storeMod).toBeDefined();
|
|
377
|
+
expect((mainMod as { parentId?: string }).parentId).toBe(processGroupNodeId('app/host'));
|
|
378
|
+
expect((storeMod as { parentId?: string }).parentId).toBe(processGroupNodeId('app/host'));
|
|
379
|
+
expect((nodes.find((n) => n.id === 'boot') as { parentId?: string }).parentId).toBe(
|
|
380
|
+
moduleGroupNodeId('src/main.ts'),
|
|
381
|
+
);
|
|
382
|
+
expect((nodes.find((n) => n.id === 'store') as { parentId?: string }).parentId).toBe(
|
|
383
|
+
moduleGroupNodeId('src/store.ts'),
|
|
384
|
+
);
|
|
385
|
+
});
|
|
386
|
+
|
|
248
387
|
test('buildSubsystemGraph drops singleton process frames (no parentId, no group)', async () => {
|
|
249
388
|
const { nodes, regions } = await buildSubsystemGraph({
|
|
250
389
|
components: [
|
|
@@ -264,6 +403,48 @@ describe('subsystem graph model', () => {
|
|
|
264
403
|
expect(nodes.find((n) => n.id === processGroupNodeId('app/lonely'))).toBeUndefined();
|
|
265
404
|
});
|
|
266
405
|
|
|
406
|
+
test('buildSubsystemGraph frames multi-member modules', async () => {
|
|
407
|
+
const { nodes, regions } = await buildSubsystemGraph({
|
|
408
|
+
components: [
|
|
409
|
+
{
|
|
410
|
+
id: 'rec',
|
|
411
|
+
name: 'CodexRolloutRecord',
|
|
412
|
+
construct: 'type_alias',
|
|
413
|
+
file: 'transcript.ts',
|
|
414
|
+
purl: 'pkg:github/acme/app',
|
|
415
|
+
module: 'src/session/transcript.ts',
|
|
416
|
+
symbol: 'CodexRolloutRecord',
|
|
417
|
+
},
|
|
418
|
+
{
|
|
419
|
+
id: 'parse',
|
|
420
|
+
name: 'parseTranscript',
|
|
421
|
+
construct: 'function',
|
|
422
|
+
file: 'transcript.ts',
|
|
423
|
+
purl: 'pkg:github/acme/app',
|
|
424
|
+
module: 'src/session/transcript.ts',
|
|
425
|
+
symbol: 'parseTranscript',
|
|
426
|
+
},
|
|
427
|
+
{
|
|
428
|
+
id: 'solo',
|
|
429
|
+
name: 'lonely',
|
|
430
|
+
construct: 'function',
|
|
431
|
+
file: 'solo.ts',
|
|
432
|
+
purl: 'pkg:github/acme/app',
|
|
433
|
+
module: 'src/solo.ts',
|
|
434
|
+
symbol: 'lonely',
|
|
435
|
+
},
|
|
436
|
+
],
|
|
437
|
+
relations: [],
|
|
438
|
+
});
|
|
439
|
+
expect(regions.map((r) => r.key)).toEqual(['src/session/transcript.ts']);
|
|
440
|
+
expect(regions[0]!.kind).toBe('module');
|
|
441
|
+
expect((nodes.find((n) => n.id === 'rec') as { parentId?: string }).parentId).toBe(
|
|
442
|
+
moduleGroupNodeId('src/session/transcript.ts'),
|
|
443
|
+
);
|
|
444
|
+
expect((nodes.find((n) => n.id === 'solo') as { parentId?: string }).parentId).toBeUndefined();
|
|
445
|
+
expect(nodes.find((n) => n.id === moduleGroupNodeId('src/session/transcript.ts'))).toBeDefined();
|
|
446
|
+
});
|
|
447
|
+
|
|
267
448
|
test('subsystemGraphLayoutKey ignores declarationRef-only changes', () => {
|
|
268
449
|
const base = doc;
|
|
269
450
|
const withRef = {
|