@agent-scope/manifest 1.17.0 → 1.17.2
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/README.md +522 -0
- package/package.json +4 -3
package/README.md
ADDED
|
@@ -0,0 +1,522 @@
|
|
|
1
|
+
# @agent-scope/manifest
|
|
2
|
+
|
|
3
|
+
TypeScript AST parser that generates a machine-readable React component registry (manifest) from source code.
|
|
4
|
+
|
|
5
|
+
Walks TypeScript/TSX source files using [ts-morph](https://ts-morph.com/), extracts every React component (function, arrow, class), resolves props types, detects hooks, context dependencies, side effects, renders complexity, and builds a full bi-directional composition tree.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install @agent-scope/manifest
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## What it does / when to use it
|
|
18
|
+
|
|
19
|
+
| Need | Use |
|
|
20
|
+
|------|-----|
|
|
21
|
+
| Generate a JSON registry of all React components in a project | `generateManifest(config)` |
|
|
22
|
+
| Know which components are composed by / compose which others | `manifest.tree` |
|
|
23
|
+
| Determine if a component can be rendered via Satori (SVG) or needs a browser | `complexityClass` |
|
|
24
|
+
| Find which React contexts a component depends on | `requiredContexts` |
|
|
25
|
+
| Know which hooks a component calls | `detectedHooks` |
|
|
26
|
+
| Detect fetch calls, timers, event listener subscriptions | `sideEffects` |
|
|
27
|
+
| Understand all props (types, defaults, required status) | `props` in `ComponentDescriptor` |
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Manifest JSON schema
|
|
32
|
+
|
|
33
|
+
### Top-level `Manifest`
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
interface Manifest {
|
|
37
|
+
version: "0.1"; // schema version
|
|
38
|
+
generatedAt: string; // ISO 8601 timestamp
|
|
39
|
+
components: Record<string, ComponentDescriptor>; // keyed by component name
|
|
40
|
+
tree: Record<string, TreeNode>; // keyed by component name
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### `ComponentDescriptor` — every field
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
interface ComponentDescriptor {
|
|
48
|
+
// -----------------------------------------------------------------------
|
|
49
|
+
// Identity
|
|
50
|
+
// -----------------------------------------------------------------------
|
|
51
|
+
filePath: string; // relative path from rootDir, e.g. "src/components/Button.tsx"
|
|
52
|
+
exportType: ExportType; // "named" | "default" | "none"
|
|
53
|
+
displayName: string; // displayName property if set, otherwise function/class name
|
|
54
|
+
loc: { start: number; end: number }; // 1-based line numbers in the source file
|
|
55
|
+
|
|
56
|
+
// -----------------------------------------------------------------------
|
|
57
|
+
// Props
|
|
58
|
+
// -----------------------------------------------------------------------
|
|
59
|
+
props: Record<string, PropDescriptor>; // keyed by prop name
|
|
60
|
+
|
|
61
|
+
// -----------------------------------------------------------------------
|
|
62
|
+
// Composition
|
|
63
|
+
// -----------------------------------------------------------------------
|
|
64
|
+
composes: string[]; // component names rendered directly in this component's JSX
|
|
65
|
+
composedBy: string[]; // component names that render this component in their JSX
|
|
66
|
+
|
|
67
|
+
// -----------------------------------------------------------------------
|
|
68
|
+
// Wrappers
|
|
69
|
+
// -----------------------------------------------------------------------
|
|
70
|
+
forwardedRef: boolean; // true if wrapped with React.forwardRef
|
|
71
|
+
memoized: boolean; // true if wrapped with React.memo
|
|
72
|
+
hocWrappers: string[]; // HOC wrapper names (excluding memo/forwardRef)
|
|
73
|
+
|
|
74
|
+
// -----------------------------------------------------------------------
|
|
75
|
+
// Phase 6: rendering & runtime analysis
|
|
76
|
+
// -----------------------------------------------------------------------
|
|
77
|
+
complexityClass: ComplexityClass; // "simple" | "complex"
|
|
78
|
+
requiredContexts: string[]; // sorted context variable names (e.g. ["AuthCtx", "ThemeCtx"])
|
|
79
|
+
detectedHooks: string[]; // sorted hook names (e.g. ["useCallback", "useEffect", "useState"])
|
|
80
|
+
sideEffects: SideEffects;
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### `PropDescriptor`
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
interface PropDescriptor {
|
|
88
|
+
type: PropKind; // resolved TypeScript type category
|
|
89
|
+
values?: string[]; // for union types: expanded literal values, e.g. ["primary", "secondary"]
|
|
90
|
+
default?: string; // default value as source-code string, e.g. "'primary'"
|
|
91
|
+
required: boolean; // false if prop is optional (?) or has a default
|
|
92
|
+
rawType: string; // raw TypeScript type string from source
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
type PropKind =
|
|
96
|
+
| "string" | "number" | "boolean"
|
|
97
|
+
| "union" | "object" | "array"
|
|
98
|
+
| "function" | "node" | "element"
|
|
99
|
+
| "any" | "unknown" | "never"
|
|
100
|
+
| "null" | "undefined" | "literal" | "other";
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### `SideEffects`
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
interface SideEffects {
|
|
107
|
+
fetches: string[]; // detected fetch callee names: "fetch", "axios", "useQuery", etc.
|
|
108
|
+
timers: boolean; // setTimeout / setInterval / requestAnimationFrame detected
|
|
109
|
+
subscriptions: string[]; // subscription methods: "subscribe", "onSnapshot", "addListener", etc.
|
|
110
|
+
globalListeners: boolean; // window.addEventListener or document.addEventListener detected
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### `TreeNode`
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
interface TreeNode {
|
|
118
|
+
children: string[]; // names of components this component renders directly
|
|
119
|
+
parents: string[]; // names of components that render this component directly
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## How complexity classification works
|
|
126
|
+
|
|
127
|
+
Each component receives a `complexityClass` of `"simple"` or `"complex"`. The classification drives rendering strategy:
|
|
128
|
+
|
|
129
|
+
- **`simple`** — flexbox-only layout, standard box model, no animations. Safe to render via Satori (SVG-based renderer, fast).
|
|
130
|
+
- **`complex`** — must render via a full browser pool. Slower but handles all CSS.
|
|
131
|
+
|
|
132
|
+
### Static CSS analysis
|
|
133
|
+
|
|
134
|
+
The parser scans all inline `style={{ ... }}` objects in JSX for these triggers:
|
|
135
|
+
|
|
136
|
+
| Trigger | Example | Result |
|
|
137
|
+
|---------|---------|--------|
|
|
138
|
+
| CSS Grid properties | `gridTemplateColumns`, `gridArea`, `grid` | `complex` |
|
|
139
|
+
| Absolute / fixed / sticky positioning | `position: "absolute"` | `complex` |
|
|
140
|
+
| CSS animations | `animation`, `animationName`, `animationDuration` | `complex` |
|
|
141
|
+
| CSS transitions | `transition` | `complex` |
|
|
142
|
+
| CSS transforms | `transform`, `transformOrigin` | `complex` |
|
|
143
|
+
| `clipPath`, `willChange`, `contain` | any of these keys | `complex` |
|
|
144
|
+
| Styled-components / `css` template literals | `` styled.div`...` ``, `` css`...` `` | `complex` |
|
|
145
|
+
| Opaque `className` references | `className={styles.root}` (identifier/call) | `complex` |
|
|
146
|
+
|
|
147
|
+
Anything not matching the above stays `"simple"`.
|
|
148
|
+
|
|
149
|
+
**Class components** always default to `"complex"` (safe fallback; lifecycle methods / state patterns are too complex to analyze statically).
|
|
150
|
+
|
|
151
|
+
### Complexity propagation through the composition tree
|
|
152
|
+
|
|
153
|
+
After all components are classified, complexity propagates **upward** through the tree:
|
|
154
|
+
|
|
155
|
+
> A component is `simple` only if it **and every descendant** are also `simple`. If any child anywhere in the subtree is `complex`, all ancestors are also marked `complex`.
|
|
156
|
+
|
|
157
|
+
Algorithm: bottom-up BFS starting from every initially-complex component, following `composedBy` links upward.
|
|
158
|
+
|
|
159
|
+
**Example from the `basic-tree` fixture:**
|
|
160
|
+
|
|
161
|
+
```
|
|
162
|
+
App (simple own CSS)
|
|
163
|
+
└── Layout (simple own CSS)
|
|
164
|
+
└── Sidebar (complex — uses `transition: "width 0.2s"`)
|
|
165
|
+
└── NavItem (simple leaf — no complex CSS)
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
After propagation:
|
|
169
|
+
- `NavItem` → `simple` (leaf, no complex descendants)
|
|
170
|
+
- `Sidebar` → `complex` (own CSS triggers it)
|
|
171
|
+
- `Layout` → `complex` (propagated from Sidebar)
|
|
172
|
+
- `App` → `complex` (propagated from Layout → Sidebar)
|
|
173
|
+
|
|
174
|
+
Propagation unit-test examples (from `manifest.test.ts`):
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
// Direct propagation
|
|
178
|
+
// Input: Parent(simple) ← Child(complex)
|
|
179
|
+
// Result: Parent becomes complex
|
|
180
|
+
propagateComplexity({ Parent: { complexityClass: "simple", composedBy: [] },
|
|
181
|
+
Child: { complexityClass: "complex", composedBy: ["Parent"] } });
|
|
182
|
+
// Parent.complexityClass === "complex"
|
|
183
|
+
|
|
184
|
+
// Transitive: A → B → C where C is complex → A and B both become complex
|
|
185
|
+
// Diamond: A → B, A → C, both B and C → D(complex) → A, B, C all become complex
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## Context detection
|
|
191
|
+
|
|
192
|
+
The parser detects which React contexts a component depends on via two strategies:
|
|
193
|
+
|
|
194
|
+
### 1. Direct `useContext()` calls
|
|
195
|
+
|
|
196
|
+
```tsx
|
|
197
|
+
// Inside a component body:
|
|
198
|
+
const theme = useContext(ThemeCtx);
|
|
199
|
+
// → requiredContexts: ["ThemeCtx"]
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### 2. Custom hook resolution
|
|
203
|
+
|
|
204
|
+
```tsx
|
|
205
|
+
// Component calls a custom hook:
|
|
206
|
+
const theme = useTheme();
|
|
207
|
+
|
|
208
|
+
// In hooks/useTheme.ts:
|
|
209
|
+
export function useTheme() {
|
|
210
|
+
return useContext(ThemeCtx); // ← resolved transitively
|
|
211
|
+
}
|
|
212
|
+
// → requiredContexts: ["ThemeCtx"]
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
The parser follows relative imports (`"./hooks/useTheme"`) and scans the hook's source file for `useContext(...)` calls. Results are de-duplicated and sorted alphabetically.
|
|
216
|
+
|
|
217
|
+
**`deep-context` fixture** — 5 custom hooks each wrapping a different context:
|
|
218
|
+
|
|
219
|
+
```typescript
|
|
220
|
+
// DeepConsumer calls: useTheme, useAuth, useLocale, useFeatureFlags, useUserPrefs
|
|
221
|
+
manifest.components.DeepConsumer.requiredContexts;
|
|
222
|
+
// ["AuthCtx", "FeatureFlagsCtx", "LocaleCtx", "ThemeCtx", "UserPrefsCtx"]
|
|
223
|
+
|
|
224
|
+
manifest.components.DeepConsumer.detectedHooks;
|
|
225
|
+
// ["useAuth", "useFeatureFlags", "useLocale", "useTheme", "useUserPrefs"]
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## Side-effect detection
|
|
231
|
+
|
|
232
|
+
All `CallExpression` nodes in the component body are scanned recursively (including inside `useEffect`, `useCallback`, event handlers, etc.).
|
|
233
|
+
|
|
234
|
+
| Category | Detected callees |
|
|
235
|
+
|----------|-----------------|
|
|
236
|
+
| `fetches` | `fetch`, `axios.*`, `useQuery`, `useMutation`, `useSWR`, `useInfiniteQuery`, `request` |
|
|
237
|
+
| `timers` | `setTimeout`, `setInterval`, `requestAnimationFrame`, `clearTimeout`, `clearInterval`, `cancelAnimationFrame` |
|
|
238
|
+
| `subscriptions` | `.subscribe()`, `.onSnapshot()`, `.on()`, `.listen()`, `.addListener()` |
|
|
239
|
+
| `globalListeners` | `window.addEventListener`, `document.addEventListener`, `window.removeEventListener`, `document.removeEventListener`, bare `addEventListener` |
|
|
240
|
+
|
|
241
|
+
`fetches` and `subscriptions` are de-duplicated sorted arrays. `timers` and `globalListeners` are booleans.
|
|
242
|
+
|
|
243
|
+
**Example from the `hooks-showcase` fixture:**
|
|
244
|
+
|
|
245
|
+
```typescript
|
|
246
|
+
// UseEffectDemo uses setInterval inside useEffect
|
|
247
|
+
manifest.components.UseEffectDemo.sideEffects.timers; // true
|
|
248
|
+
|
|
249
|
+
// UseStateDemo has no side effects
|
|
250
|
+
manifest.components.UseStateDemo.sideEffects;
|
|
251
|
+
// { fetches: [], timers: false, subscriptions: [], globalListeners: false }
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
## `generateManifest(config)`
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
function generateManifest(config: ManifestConfig): Manifest
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
Generates the full component manifest by parsing all matching TypeScript/TSX files.
|
|
263
|
+
|
|
264
|
+
### `ManifestConfig`
|
|
265
|
+
|
|
266
|
+
```typescript
|
|
267
|
+
interface ManifestConfig {
|
|
268
|
+
rootDir: string; // absolute path to project/package root
|
|
269
|
+
include?: string[]; // glob patterns — default: ["src/**/*.tsx", "src/**/*.ts"]
|
|
270
|
+
exclude?: string[]; // glob patterns — default: ["**/node_modules/**", "**/*.test.*",
|
|
271
|
+
// "**/*.spec.*", "**/dist/**", "**/*.d.ts"]
|
|
272
|
+
tsConfigFilePath?: string; // path to tsconfig.json — default: "<rootDir>/tsconfig.json"
|
|
273
|
+
}
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### Usage
|
|
277
|
+
|
|
278
|
+
```typescript
|
|
279
|
+
import { generateManifest } from "@agent-scope/manifest";
|
|
280
|
+
|
|
281
|
+
const manifest = generateManifest({
|
|
282
|
+
rootDir: "/path/to/my-project",
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
// Access a specific component
|
|
286
|
+
const button = manifest.components.Button;
|
|
287
|
+
console.log(button.props.variant);
|
|
288
|
+
// { type: "union", values: ["primary", "secondary", "ghost"], required: false, default: "'primary'", rawType: "Variant" }
|
|
289
|
+
|
|
290
|
+
console.log(button.complexityClass); // "simple" or "complex"
|
|
291
|
+
console.log(button.detectedHooks); // ["useCallback", "useState"]
|
|
292
|
+
console.log(button.composedBy); // ["Toolbar", "Form"]
|
|
293
|
+
console.log(button.composes); // ["Icon", "Spinner"]
|
|
294
|
+
|
|
295
|
+
// Walk the composition tree
|
|
296
|
+
const tree = manifest.tree.Button;
|
|
297
|
+
// { children: ["Icon", "Spinner"], parents: ["Toolbar", "Form"] }
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
### Custom include/exclude
|
|
301
|
+
|
|
302
|
+
```typescript
|
|
303
|
+
const manifest = generateManifest({
|
|
304
|
+
rootDir: "/path/to/my-project",
|
|
305
|
+
include: ["src/components/**/*.tsx", "src/features/**/*.tsx"],
|
|
306
|
+
exclude: [
|
|
307
|
+
"**/node_modules/**",
|
|
308
|
+
"**/*.test.*",
|
|
309
|
+
"**/*.stories.*",
|
|
310
|
+
"**/dist/**",
|
|
311
|
+
],
|
|
312
|
+
tsConfigFilePath: "/path/to/my-project/tsconfig.app.json",
|
|
313
|
+
});
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
318
|
+
## Component detection rules
|
|
319
|
+
|
|
320
|
+
The parser discovers components from three declaration forms:
|
|
321
|
+
|
|
322
|
+
### 1. Function declarations
|
|
323
|
+
|
|
324
|
+
```tsx
|
|
325
|
+
// Named PascalCase function that returns JSX
|
|
326
|
+
export function Button({ label, onClick }: ButtonProps) {
|
|
327
|
+
return <button onClick={onClick}>{label}</button>;
|
|
328
|
+
}
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
### 2. Arrow functions / variable declarations (including wrappers)
|
|
332
|
+
|
|
333
|
+
```tsx
|
|
334
|
+
// Arrow function
|
|
335
|
+
export const Card = ({ title }: CardProps) => <div>{title}</div>;
|
|
336
|
+
|
|
337
|
+
// React.memo wrapper
|
|
338
|
+
export const Layout = React.memo(function Layout({ children }: LayoutProps) {
|
|
339
|
+
return <main>{children}</main>;
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
// React.forwardRef wrapper
|
|
343
|
+
export const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|
344
|
+
({ value }, ref) => <input ref={ref} value={value} />,
|
|
345
|
+
);
|
|
346
|
+
|
|
347
|
+
// Export-default memo: export default React.memo(Sidebar)
|
|
348
|
+
export default React.memo(Sidebar);
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
### 3. Class components
|
|
352
|
+
|
|
353
|
+
```tsx
|
|
354
|
+
export class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
|
355
|
+
render() {
|
|
356
|
+
if (this.state.hasError) return <ErrorFallback />;
|
|
357
|
+
return this.props.children;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
// Always gets complexityClass: "complex"
|
|
361
|
+
// detectedHooks: [] (hooks not valid in class components)
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
---
|
|
365
|
+
|
|
366
|
+
## Manifest query patterns
|
|
367
|
+
|
|
368
|
+
```typescript
|
|
369
|
+
// All simple components (Satori-renderable)
|
|
370
|
+
const simpleComponents = Object.entries(manifest.components)
|
|
371
|
+
.filter(([, desc]) => desc.complexityClass === "simple")
|
|
372
|
+
.map(([name]) => name);
|
|
373
|
+
|
|
374
|
+
// All components that use a specific context
|
|
375
|
+
const themeConsumers = Object.entries(manifest.components)
|
|
376
|
+
.filter(([, desc]) => desc.requiredContexts.includes("ThemeCtx"))
|
|
377
|
+
.map(([name]) => name);
|
|
378
|
+
|
|
379
|
+
// Leaf components (no children in the composition tree)
|
|
380
|
+
const leaves = Object.entries(manifest.tree)
|
|
381
|
+
.filter(([, node]) => node.children.length === 0)
|
|
382
|
+
.map(([name]) => name);
|
|
383
|
+
|
|
384
|
+
// Root components (no parents)
|
|
385
|
+
const roots = Object.entries(manifest.tree)
|
|
386
|
+
.filter(([, node]) => node.parents.length === 0)
|
|
387
|
+
.map(([name]) => name);
|
|
388
|
+
|
|
389
|
+
// Components with data fetching
|
|
390
|
+
const fetchers = Object.entries(manifest.components)
|
|
391
|
+
.filter(([, desc]) => desc.sideEffects.fetches.length > 0)
|
|
392
|
+
.map(([name, desc]) => ({ name, fetches: desc.sideEffects.fetches }));
|
|
393
|
+
|
|
394
|
+
// Components with default-exported named exports
|
|
395
|
+
const defaultExports = Object.entries(manifest.components)
|
|
396
|
+
.filter(([, desc]) => desc.exportType === "default")
|
|
397
|
+
.map(([name]) => name);
|
|
398
|
+
|
|
399
|
+
// All components with a specific prop
|
|
400
|
+
const hasVariantProp = Object.entries(manifest.components)
|
|
401
|
+
.filter(([, desc]) => "variant" in desc.props)
|
|
402
|
+
.map(([name, desc]) => ({ name, variant: desc.props.variant }));
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
---
|
|
406
|
+
|
|
407
|
+
## Example payloads
|
|
408
|
+
|
|
409
|
+
### `manifest.components.Sidebar` (from `basic-tree` fixture)
|
|
410
|
+
|
|
411
|
+
```json
|
|
412
|
+
{
|
|
413
|
+
"filePath": "src/Sidebar.tsx",
|
|
414
|
+
"exportType": "default",
|
|
415
|
+
"displayName": "Sidebar",
|
|
416
|
+
"props": {
|
|
417
|
+
"title": { "type": "string", "required": true, "rawType": "string" },
|
|
418
|
+
"collapsed": { "type": "boolean", "required": true, "rawType": "boolean" },
|
|
419
|
+
"itemCount": { "type": "number", "required": true, "rawType": "number" }
|
|
420
|
+
},
|
|
421
|
+
"composes": ["NavItem"],
|
|
422
|
+
"composedBy": ["Layout"],
|
|
423
|
+
"forwardedRef": false,
|
|
424
|
+
"memoized": true,
|
|
425
|
+
"hocWrappers": [],
|
|
426
|
+
"loc": { "start": 10, "end": 40 },
|
|
427
|
+
"complexityClass": "complex",
|
|
428
|
+
"requiredContexts": [],
|
|
429
|
+
"detectedHooks": [],
|
|
430
|
+
"sideEffects": {
|
|
431
|
+
"fetches": [],
|
|
432
|
+
"timers": false,
|
|
433
|
+
"subscriptions": [],
|
|
434
|
+
"globalListeners": false
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
Note: `complexityClass: "complex"` because Sidebar uses `transition: "width 0.2s"` in its inline style.
|
|
440
|
+
|
|
441
|
+
### `manifest.components.DeepConsumer` (from `deep-context` fixture)
|
|
442
|
+
|
|
443
|
+
```json
|
|
444
|
+
{
|
|
445
|
+
"filePath": "src/DeepConsumer.tsx",
|
|
446
|
+
"exportType": "named",
|
|
447
|
+
"displayName": "DeepConsumer",
|
|
448
|
+
"props": {},
|
|
449
|
+
"composes": [],
|
|
450
|
+
"composedBy": ["App"],
|
|
451
|
+
"forwardedRef": false,
|
|
452
|
+
"memoized": false,
|
|
453
|
+
"hocWrappers": [],
|
|
454
|
+
"loc": { "start": 1, "end": 25 },
|
|
455
|
+
"complexityClass": "simple",
|
|
456
|
+
"requiredContexts": ["AuthCtx", "FeatureFlagsCtx", "LocaleCtx", "ThemeCtx", "UserPrefsCtx"],
|
|
457
|
+
"detectedHooks": ["useAuth", "useFeatureFlags", "useLocale", "useTheme", "useUserPrefs"],
|
|
458
|
+
"sideEffects": {
|
|
459
|
+
"fetches": [],
|
|
460
|
+
"timers": false,
|
|
461
|
+
"subscriptions": [],
|
|
462
|
+
"globalListeners": false
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
### `manifest.tree` (from `basic-tree` fixture)
|
|
468
|
+
|
|
469
|
+
```json
|
|
470
|
+
{
|
|
471
|
+
"App": { "children": ["Layout"], "parents": [] },
|
|
472
|
+
"Layout": { "children": ["Sidebar"], "parents": ["App"] },
|
|
473
|
+
"Sidebar": { "children": ["NavItem"], "parents": ["Layout"] },
|
|
474
|
+
"NavItem": { "children": [], "parents": ["Sidebar"] }
|
|
475
|
+
}
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
---
|
|
479
|
+
|
|
480
|
+
## Internal architecture
|
|
481
|
+
|
|
482
|
+
| Module | Responsibility |
|
|
483
|
+
|--------|----------------|
|
|
484
|
+
| `types.ts` | All TypeScript types (`ComponentDescriptor`, `PropDescriptor`, `Manifest`, `ManifestConfig`, etc.) |
|
|
485
|
+
| `analysis.ts` | `analyzeComplexity`, `detectHooks`, `detectRequiredContexts`, `detectSideEffects`, `propagateComplexity` — all static analysis |
|
|
486
|
+
| `parser.ts` | `generateManifest` — ts-morph project setup, source file iteration, per-file extraction (functions, arrow functions, class components), composition inverse linking, complexity propagation, tree building |
|
|
487
|
+
| `index.ts` | Public re-exports |
|
|
488
|
+
|
|
489
|
+
### Processing pipeline
|
|
490
|
+
|
|
491
|
+
```
|
|
492
|
+
ManifestConfig
|
|
493
|
+
│
|
|
494
|
+
▼
|
|
495
|
+
ts-morph Project (tsconfig-aware)
|
|
496
|
+
│
|
|
497
|
+
▼
|
|
498
|
+
Source file filtering (include/exclude globs)
|
|
499
|
+
│
|
|
500
|
+
├─ for each SourceFile:
|
|
501
|
+
│ ├─ processSourceFile()
|
|
502
|
+
│ │ ├─ Function declarations → extract props, JSX compositions, wrappers
|
|
503
|
+
│ │ ├─ Variable declarations (arrow fns, memo, forwardRef)
|
|
504
|
+
│ │ └─ Class components
|
|
505
|
+
│ │
|
|
506
|
+
│ └─ per component:
|
|
507
|
+
│ ├─ analyzeComplexity() — CSS feature scan
|
|
508
|
+
│ ├─ detectHooks() — /^use[A-Z]/ pattern
|
|
509
|
+
│ ├─ detectRequiredContexts() — useContext() + hook resolution
|
|
510
|
+
│ └─ detectSideEffects() — fetch/timer/subscription/global patterns
|
|
511
|
+
│
|
|
512
|
+
├─ Build composedBy (inverse of composes)
|
|
513
|
+
├─ propagateComplexity() — upward BFS from complex leaves
|
|
514
|
+
└─ Build tree (filtered to manifest-known components)
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
---
|
|
518
|
+
|
|
519
|
+
## Used by
|
|
520
|
+
|
|
521
|
+
- `@agent-scope/cli` — manifest commands (`scope manifest generate`, `scope manifest list`, `scope manifest show`), render commands (uses `complexityClass` to choose Satori vs browser pool), CI commands
|
|
522
|
+
- `@agent-scope/render` — `satori.ts` and `matrix.ts` consume `complexityClass` and `ComponentDescriptor` for render strategy decisions
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-scope/manifest",
|
|
3
|
-
"version": "1.17.
|
|
3
|
+
"version": "1.17.2",
|
|
4
4
|
"description": "TypeScript AST parser that generates a machine-readable React component registry for Scope",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -20,7 +20,8 @@
|
|
|
20
20
|
"module": "./dist/index.js",
|
|
21
21
|
"types": "./dist/index.d.ts",
|
|
22
22
|
"files": [
|
|
23
|
-
"dist"
|
|
23
|
+
"dist",
|
|
24
|
+
"README.md"
|
|
24
25
|
],
|
|
25
26
|
"scripts": {
|
|
26
27
|
"build": "tsup",
|
|
@@ -29,7 +30,7 @@
|
|
|
29
30
|
"clean": "rm -rf dist"
|
|
30
31
|
},
|
|
31
32
|
"dependencies": {
|
|
32
|
-
"@agent-scope/core": "1.17.
|
|
33
|
+
"@agent-scope/core": "1.17.2",
|
|
33
34
|
"ts-morph": "^25.0.0"
|
|
34
35
|
},
|
|
35
36
|
"devDependencies": {
|