@mandujs/mcp 0.37.1 โ 0.37.3
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 +3 -4
- package/package.json +4 -4
- package/src/prompts.ts +4 -4
- package/src/resources/skills/guides.ts +49 -40
- package/src/resources/skills/mandu-deployment/rules/db-provider-supabase.md +3 -3
- package/src/resources/skills/mandu-hydration/SKILL.md +19 -11
- package/src/resources/skills/mandu-hydration/rules/hydration-island-setup.md +54 -7
- package/src/resources/skills/mandu-hydration/rules/hydration-priority-visible.md +60 -37
- package/src/resources/skills/recipes.ts +28 -19
- package/src/tools/ate.ts +37 -24
- package/src/tools/composite.ts +13 -11
- package/src/tools/guard.ts +162 -68
- package/src/tools/index.ts +61 -30
- package/src/tools/kitchen.ts +72 -32
- package/src/tools/slot-validation.ts +19 -125
package/README.md
CHANGED
|
@@ -136,10 +136,9 @@ bunx @mandujs/mcp --root /path/to/project
|
|
|
136
136
|
|
|
137
137
|
### Semantic Slots (RFC-001) ๐
|
|
138
138
|
|
|
139
|
-
| Tool | Description |
|
|
140
|
-
|------|-------------|
|
|
141
|
-
| `
|
|
142
|
-
| `mandu_validate_slots` | Batch validate multiple slots |
|
|
139
|
+
| Tool | Description |
|
|
140
|
+
|------|-------------|
|
|
141
|
+
| `mandu_get_slot_constraints` | Get recommended slot constraint presets |
|
|
143
142
|
|
|
144
143
|
### Architecture Negotiation (RFC-001) ๐
|
|
145
144
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mandujs/mcp",
|
|
3
|
-
"version": "0.37.
|
|
3
|
+
"version": "0.37.3",
|
|
4
4
|
"description": "Mandu MCP Server - Agent-native interface for Mandu framework operations",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -34,9 +34,9 @@
|
|
|
34
34
|
"access": "public"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@mandujs/core": "^0.54.
|
|
38
|
-
"@mandujs/ate": "^0.26.
|
|
39
|
-
"@mandujs/skills": "^0.20.
|
|
37
|
+
"@mandujs/core": "^0.54.3",
|
|
38
|
+
"@mandujs/ate": "^0.26.1",
|
|
39
|
+
"@mandujs/skills": "^0.20.1",
|
|
40
40
|
"@modelcontextprotocol/sdk": "^1.25.3"
|
|
41
41
|
},
|
|
42
42
|
"engines": {
|
package/src/prompts.ts
CHANGED
|
@@ -48,10 +48,10 @@ Follow these steps using Mandu MCP tools:
|
|
|
48
48
|
1. Read current route manifest: Resource mandu://routes
|
|
49
49
|
2. Read project config: Resource mandu://config
|
|
50
50
|
3. Negotiate the feature spec: Tool mandu.negotiate
|
|
51
|
-
4. Generate scaffold: Tool mandu.negotiate.scaffold
|
|
52
|
-
5. If client interactivity needed, create island: Tool mandu_create_island
|
|
53
|
-
Use
|
|
54
|
-
6. If data requirements exist, create slot: Tool mandu_create_slot
|
|
51
|
+
4. Generate scaffold: Tool mandu.negotiate.scaffold
|
|
52
|
+
5. If client interactivity needed, create island: Tool mandu_create_island
|
|
53
|
+
Use @mandujs/core/client with wrapComponent(Component) or island({ setup, render })
|
|
54
|
+
6. If data requirements exist, create slot: Tool mandu_create_slot
|
|
55
55
|
Slots are server-side data loaders that run before render
|
|
56
56
|
7. If API is exposed, define contract: Tool mandu_create_contract
|
|
57
57
|
Contracts are Zod schemas for validation and OpenAPI generation
|
|
@@ -377,21 +377,21 @@ Island Hydration์ ํ์ด์ง์ ์ผ๋ถ๋ถ๋ง ํด๋ผ์ด์ธํธ์์ ์ธํฐ๋ํฐ
|
|
|
377
377
|
| \`idle\` | ๋ธ๋ผ์ฐ์ ์ ํด ์ | ๋น์ค์ ๊ธฐ๋ฅ |
|
|
378
378
|
| \`interaction\` | ์ฌ์ฉ์ ์ํธ์์ฉ ์ | ํด๋ฆญํด์ผ ํ์ฑํ |
|
|
379
379
|
|
|
380
|
-
##
|
|
381
|
-
|
|
382
|
-
### 1. ํด๋ผ์ด์ธํธ ์ปดํฌ๋ํธ ์์ฑ
|
|
383
|
-
|
|
384
|
-
\`\`\`tsx
|
|
385
|
-
// app/counter/client.tsx
|
|
386
|
-
|
|
387
|
-
"use client";
|
|
388
|
-
|
|
389
|
-
import { useState } from "react";
|
|
390
|
-
|
|
391
|
-
export
|
|
392
|
-
const [count, setCount] = useState(initial);
|
|
393
|
-
|
|
394
|
-
return (
|
|
380
|
+
## Inline client region ๋ง๋ค๊ธฐ
|
|
381
|
+
|
|
382
|
+
### 1. ํด๋ผ์ด์ธํธ ์ปดํฌ๋ํธ ์์ฑ
|
|
383
|
+
|
|
384
|
+
\`\`\`tsx
|
|
385
|
+
// app/counter/client.tsx
|
|
386
|
+
|
|
387
|
+
"use client";
|
|
388
|
+
|
|
389
|
+
import { useState } from "react";
|
|
390
|
+
|
|
391
|
+
export function Counter({ initial = 0 }: { initial?: number }) {
|
|
392
|
+
const [count, setCount] = useState(initial);
|
|
393
|
+
|
|
394
|
+
return (
|
|
395
395
|
<div>
|
|
396
396
|
<p>Count: {count}</p>
|
|
397
397
|
<button onClick={() => setCount(c => c - 1)}>-</button>
|
|
@@ -399,34 +399,43 @@ export default function Counter({ initial = 0 }: { initial?: number }) {
|
|
|
399
399
|
</div>
|
|
400
400
|
);
|
|
401
401
|
}
|
|
402
|
-
\`\`\`
|
|
403
|
-
|
|
404
|
-
### 2. ํ์ด์ง์์ ์ฌ์ฉ
|
|
405
|
-
|
|
406
|
-
\`\`\`tsx
|
|
407
|
-
// app/counter/page.tsx
|
|
408
|
-
|
|
409
|
-
import
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
402
|
+
\`\`\`
|
|
403
|
+
|
|
404
|
+
### 2. ์๋ฒ ํ์ด์ง์์ partial๋ก ์ฌ์ฉ
|
|
405
|
+
|
|
406
|
+
\`\`\`tsx
|
|
407
|
+
// app/counter/page.tsx
|
|
408
|
+
|
|
409
|
+
import { partial } from "@mandujs/core/client";
|
|
410
|
+
import { Counter } from "./client";
|
|
411
|
+
|
|
412
|
+
const CounterPartial = partial({
|
|
413
|
+
component: Counter,
|
|
414
|
+
priority: "visible",
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
export default function CounterPage() {
|
|
418
|
+
return (
|
|
413
419
|
<div>
|
|
414
420
|
<h1>Counter Demo</h1>
|
|
415
421
|
<p>์ด ํ
์คํธ๋ ์ ์ HTML์
๋๋ค.</p>
|
|
416
|
-
|
|
417
|
-
{/* ์ด ๋ถ๋ถ๋ง hydration๋ฉ๋๋ค */}
|
|
418
|
-
<
|
|
419
|
-
</div>
|
|
420
|
-
);
|
|
421
|
-
}
|
|
422
|
-
\`\`\`
|
|
423
|
-
|
|
424
|
-
## Mandu.island() API
|
|
425
|
-
|
|
426
|
-
๊ณ ๊ธ Island ํจํด์ ์ํ API
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
422
|
+
|
|
423
|
+
{/* ์ด ๋ถ๋ถ๋ง hydration๋ฉ๋๋ค */}
|
|
424
|
+
<CounterPartial.Render initial={10} />
|
|
425
|
+
</div>
|
|
426
|
+
);
|
|
427
|
+
}
|
|
428
|
+
\`\`\`
|
|
429
|
+
|
|
430
|
+
## Mandu.island() API
|
|
431
|
+
|
|
432
|
+
๊ณ ๊ธ page-level Island ํจํด์ ์ํ API์
๋๋ค. \`Mandu.island()\`๋ ๋จ์ผ
|
|
433
|
+
์ ์ ๊ฐ์ฒด๋ง ๋ฐ์ต๋๋ค. \`island("visible", Component)\` ํํ๋ ์ง์ํ์ง
|
|
434
|
+
์์ผ๋ฉฐ, ์๋ฒ ํ์ด์ง ์์ inline์ผ๋ก ๋ ๋๋งํ ์์ญ์ \`partial()\`์
|
|
435
|
+
์ฌ์ฉํ์ธ์.
|
|
436
|
+
|
|
437
|
+
\`\`\`typescript
|
|
438
|
+
// spec/slots/todos.client.ts
|
|
430
439
|
|
|
431
440
|
import { Mandu } from "@mandujs/core/client";
|
|
432
441
|
import { useState, useCallback } from "react";
|
|
@@ -138,8 +138,8 @@ export function createServerSupabase() {
|
|
|
138
138
|
Mandu ์ client island ์์ Realtime ๊ตฌ๋
:
|
|
139
139
|
|
|
140
140
|
```tsx
|
|
141
|
-
// app/messages/MessagesIsland.client.tsx
|
|
142
|
-
import {
|
|
141
|
+
// app/messages/MessagesIsland.client.tsx
|
|
142
|
+
import { wrapComponent } from "@mandujs/core/client";
|
|
143
143
|
import { useEffect, useState } from "react";
|
|
144
144
|
import { supabase } from "@/lib/supabase";
|
|
145
145
|
|
|
@@ -174,7 +174,7 @@ function Messages() {
|
|
|
174
174
|
);
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
-
export default
|
|
177
|
+
export default wrapComponent(Messages);
|
|
178
178
|
```
|
|
179
179
|
|
|
180
180
|
### 3.3 Storage
|
|
@@ -57,17 +57,25 @@ Reference these guidelines when:
|
|
|
57
57
|
- `hydration-data-server` - Access server data with useServerData
|
|
58
58
|
- `hydration-data-event` - Communicate between Islands with useIslandEvent
|
|
59
59
|
|
|
60
|
-
## Hydration Strategies
|
|
61
|
-
|
|
62
|
-
| Strategy | Description | Use Case |
|
|
63
|
-
|----------|-------------|----------|
|
|
64
|
-
| `none` | No JavaScript | Pure static pages |
|
|
65
|
-
| `island` | Partial hydration (default) | Static + interactive mix |
|
|
66
|
-
| `full` | Full hydration | SPA-style pages |
|
|
67
|
-
|
|
68
|
-
##
|
|
69
|
-
|
|
70
|
-
|
|
60
|
+
## Hydration Strategies
|
|
61
|
+
|
|
62
|
+
| Strategy | Description | Use Case |
|
|
63
|
+
|----------|-------------|----------|
|
|
64
|
+
| `none` | No JavaScript | Pure static pages |
|
|
65
|
+
| `island` | Partial hydration (default) | Static + interactive mix |
|
|
66
|
+
| `full` | Full hydration | SPA-style pages |
|
|
67
|
+
|
|
68
|
+
## Runtime API Constraints
|
|
69
|
+
|
|
70
|
+
- `island()` / `Mandu.island()` takes one definition object: `island({ setup, render })`.
|
|
71
|
+
- Do not call `island("visible", Component)`; use `wrapComponent(Component)` for a simple page-level island wrapper.
|
|
72
|
+
- Islands are page-level client bundles. Do not render them as inline JSX like `<MyIsland />`.
|
|
73
|
+
- For an embedded interactive region inside a server page, use `partial()`: put it in `*.partial.tsx`, export `partial({ id, component })`, and render the returned `.Render` component from the server page.
|
|
74
|
+
- A server page that renders partials must opt into hydration, for example `export const hydration = { strategy: "island", priority: "visible", preload: false }`.
|
|
75
|
+
|
|
76
|
+
## Client Hooks
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
71
79
|
import {
|
|
72
80
|
useServerData,
|
|
73
81
|
useHydrated,
|
|
@@ -5,13 +5,60 @@ impactDescription: Proper Island structure
|
|
|
5
5
|
tags: hydration, island, setup
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
## Use Mandu.island() with Setup Function
|
|
9
|
-
|
|
10
|
-
For complex Islands, use `Mandu.island()` API to separate state logic from rendering.
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
8
|
+
## Use Mandu.island() with Setup Function
|
|
9
|
+
|
|
10
|
+
For complex Islands, use `Mandu.island()` API to separate state logic from rendering.
|
|
11
|
+
`Mandu.island()` takes a single definition object. The shorthand
|
|
12
|
+
`island("visible", Component)` is not a supported Mandu runtime API.
|
|
13
|
+
|
|
14
|
+
Islands are page-level client bundles. They are discovered and rendered by
|
|
15
|
+
the framework wrapper, not embedded directly as inline JSX. Use `partial()`
|
|
16
|
+
when a server page needs an inline interactive region.
|
|
17
|
+
|
|
18
|
+
```tsx
|
|
19
|
+
// Simple page-level island wrapper
|
|
20
|
+
"use client";
|
|
21
|
+
|
|
22
|
+
import { wrapComponent } from "@mandujs/core/client";
|
|
23
|
+
|
|
24
|
+
function Counter() {
|
|
25
|
+
return <button>Count</button>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export default wrapComponent(Counter);
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
```tsx
|
|
32
|
+
// Inline client region inside a server-rendered page
|
|
33
|
+
// app/Counter.partial.tsx
|
|
34
|
+
import { partial } from "@mandujs/core/client";
|
|
35
|
+
import { Counter } from "./counter.client";
|
|
36
|
+
|
|
37
|
+
export default partial({
|
|
38
|
+
id: "Counter",
|
|
39
|
+
component: Counter,
|
|
40
|
+
priority: "visible",
|
|
41
|
+
});
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
```tsx
|
|
45
|
+
// app/page.tsx
|
|
46
|
+
import CounterPartial from "./Counter.partial";
|
|
47
|
+
|
|
48
|
+
export const hydration = {
|
|
49
|
+
strategy: "island",
|
|
50
|
+
priority: "visible",
|
|
51
|
+
preload: false,
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export default function Page() {
|
|
55
|
+
return <CounterPartial.Render initial={0} />;
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
**Incorrect (mixed concerns):**
|
|
60
|
+
|
|
61
|
+
```tsx
|
|
15
62
|
"use client";
|
|
16
63
|
|
|
17
64
|
export default function TodoList({ initialTodos }) {
|
|
@@ -18,43 +18,66 @@ Set hydration priority based on when the component needs to be interactive.
|
|
|
18
18
|
| `idle` | Browser idle | Non-critical features (analytics widgets) |
|
|
19
19
|
| `interaction` | User action | Click-to-activate components |
|
|
20
20
|
|
|
21
|
-
## Examples
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
21
|
+
## Examples
|
|
22
|
+
|
|
23
|
+
Inline client regions live in `*.partial.tsx` files and use
|
|
24
|
+
`partial({ id, component, priority })`, then render the returned `.Render`
|
|
25
|
+
component from the server page. The page must export a non-`none` hydration
|
|
26
|
+
config.
|
|
27
|
+
|
|
28
|
+
### Immediate: Always-visible interactions
|
|
29
|
+
|
|
30
|
+
```tsx
|
|
31
|
+
// Header with navigation - needs to work immediately
|
|
32
|
+
import { partial } from "@mandujs/core/client";
|
|
33
|
+
|
|
34
|
+
const HeaderNavPartial = partial({
|
|
35
|
+
id: "HeaderNav",
|
|
36
|
+
component: HeaderNav,
|
|
37
|
+
priority: "immediate",
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
<HeaderNavPartial.Render />
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Visible: Below-fold content (default)
|
|
44
|
+
|
|
45
|
+
```tsx
|
|
46
|
+
// Comments section - load when scrolled into view
|
|
47
|
+
const CommentsPartial = partial({
|
|
48
|
+
id: "Comments",
|
|
49
|
+
component: CommentsSection,
|
|
50
|
+
priority: "visible",
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
<CommentsPartial.Render postId={postId} />
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Idle: Background features
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
// Chat widget - can wait until browser is idle
|
|
60
|
+
const ChatPartial = partial({
|
|
61
|
+
id: "Chat",
|
|
62
|
+
component: ChatWidget,
|
|
63
|
+
priority: "idle",
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
<ChatPartial.Render />
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Interaction: On-demand activation
|
|
70
|
+
|
|
71
|
+
```tsx
|
|
72
|
+
// Video player - only hydrate when user clicks play
|
|
73
|
+
const VideoPartial = partial({
|
|
74
|
+
id: "Video",
|
|
75
|
+
component: VideoPlayer,
|
|
76
|
+
priority: "interaction",
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
<VideoPartial.Render videoId={videoId} />
|
|
80
|
+
```
|
|
58
81
|
|
|
59
82
|
## Performance Impact
|
|
60
83
|
|
|
@@ -469,24 +469,30 @@ export default function Counter({ initial = 0, step = 1 }: CounterProps) {
|
|
|
469
469
|
}
|
|
470
470
|
\`\`\`
|
|
471
471
|
|
|
472
|
-
## Step 2: ํ์ด์ง์์ ์ฌ์ฉ
|
|
473
|
-
|
|
474
|
-
\`\`\`tsx
|
|
475
|
-
// app/counter/page.tsx
|
|
476
|
-
|
|
477
|
-
import
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
472
|
+
## Step 2: ์๋ฒ ํ์ด์ง์์ partial๋ก ์ฌ์ฉ
|
|
473
|
+
|
|
474
|
+
\`\`\`tsx
|
|
475
|
+
// app/counter/page.tsx
|
|
476
|
+
|
|
477
|
+
import { partial } from "@mandujs/core/client";
|
|
478
|
+
import Counter from "./client";
|
|
479
|
+
|
|
480
|
+
const CounterPartial = partial({
|
|
481
|
+
component: Counter,
|
|
482
|
+
priority: "visible",
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
export default function CounterPage() {
|
|
486
|
+
return (
|
|
481
487
|
<div style={{ padding: "20px" }}>
|
|
482
488
|
<h1>Counter Demo</h1>
|
|
483
489
|
<p>์๋ ์นด์ดํฐ๋ ํด๋ผ์ด์ธํธ์์ hydration๋ฉ๋๋ค.</p>
|
|
484
|
-
|
|
485
|
-
{/*
|
|
486
|
-
<
|
|
487
|
-
|
|
488
|
-
<p style={{ marginTop: "20px", color: "#666" }}>
|
|
489
|
-
์ด ํ
์คํธ๋ ์ ์ HTML์
๋๋ค.
|
|
490
|
+
|
|
491
|
+
{/* Inline client region */}
|
|
492
|
+
<CounterPartial.Render initial={10} step={5} />
|
|
493
|
+
|
|
494
|
+
<p style={{ marginTop: "20px", color: "#666" }}>
|
|
495
|
+
์ด ํ
์คํธ๋ ์ ์ HTML์
๋๋ค.
|
|
490
496
|
</p>
|
|
491
497
|
</div>
|
|
492
498
|
);
|
|
@@ -618,10 +624,13 @@ export default function UserList() {
|
|
|
618
624
|
|
|
619
625
|
## Mandu.island() API (๊ณ ๊ธ)
|
|
620
626
|
|
|
621
|
-
์๋ฒ ๋ฐ์ดํฐ์ ํด๋ผ์ด์ธํธ ์ํ๋ฅผ
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
627
|
+
์๋ฒ ๋ฐ์ดํฐ์ ํด๋ผ์ด์ธํธ ์ํ๋ฅผ ๋ถ๋ฆฌํ๋ page-level Island๊ฐ ํ์ํ๋ฉด
|
|
628
|
+
\`Mandu.island({ setup, render })\`๋ฅผ ์ฌ์ฉํฉ๋๋ค. \`island("visible",
|
|
629
|
+
Component)\`๋ ์ง์ํ์ง ์์ต๋๋ค. ์๋ฒ ํ์ด์ง ์์ inline์ผ๋ก ๋ ๋๋งํ
|
|
630
|
+
์์ญ์ \`partial()\`์ ์ฌ์ฉํ์ธ์.
|
|
631
|
+
|
|
632
|
+
\`\`\`typescript
|
|
633
|
+
// spec/slots/todos.client.ts
|
|
625
634
|
|
|
626
635
|
import { Mandu } from "@mandujs/core/client";
|
|
627
636
|
import { useState } from "react";
|
package/src/tools/ate.ts
CHANGED
|
@@ -105,7 +105,11 @@ export const ateToolDefinitions: Tool[] = [
|
|
|
105
105
|
type: "string",
|
|
106
106
|
description: "Dev server URL (default: http://localhost:3333). Must match the running mandu dev server.",
|
|
107
107
|
},
|
|
108
|
-
ci: { type: "boolean", description: "CI mode: stricter timeouts, no interactive prompts" },
|
|
108
|
+
ci: { type: "boolean", description: "CI mode: stricter timeouts, no interactive prompts" },
|
|
109
|
+
timeoutMs: {
|
|
110
|
+
type: "number",
|
|
111
|
+
description: "Hard timeout for the whole Playwright process in milliseconds (default: 600000).",
|
|
112
|
+
},
|
|
109
113
|
headless: { type: "boolean", description: "Run browsers headlessly (default: true)" },
|
|
110
114
|
browsers: {
|
|
111
115
|
type: "array",
|
|
@@ -260,7 +264,11 @@ export const ateToolDefinitions: Tool[] = [
|
|
|
260
264
|
enum: ["L0", "L1", "L2", "L3"],
|
|
261
265
|
description: "Assertion depth: L0=smoke, L1=HTTP status, L2=contract schema, L3=full behavioral",
|
|
262
266
|
},
|
|
263
|
-
ci: { type: "boolean", description: "CI mode: stricter timeouts" },
|
|
267
|
+
ci: { type: "boolean", description: "CI mode: stricter timeouts" },
|
|
268
|
+
timeoutMs: {
|
|
269
|
+
type: "number",
|
|
270
|
+
description: "Hard timeout for the whole Playwright process in milliseconds (default: 600000).",
|
|
271
|
+
},
|
|
264
272
|
useImpactAnalysis: {
|
|
265
273
|
type: "boolean",
|
|
266
274
|
description: "Run impact analysis first and only test changed routes (faster in CI)",
|
|
@@ -378,7 +386,7 @@ export function ateTools(projectRoot: string, server?: Server) {
|
|
|
378
386
|
return await ateRun(input);
|
|
379
387
|
} catch (err) {
|
|
380
388
|
const message = err instanceof Error ? err.message : String(err);
|
|
381
|
-
const isTimeout = /timed out
|
|
389
|
+
const isTimeout = /timeout|timed out|ํ์์์/i.test(message);
|
|
382
390
|
const snap = tracker.snapshot();
|
|
383
391
|
const partial: PartialRunResults = {
|
|
384
392
|
runId: snap.runId ?? `unknown-${Date.now()}`,
|
|
@@ -439,19 +447,21 @@ export function ateTools(projectRoot: string, server?: Server) {
|
|
|
439
447
|
browsers,
|
|
440
448
|
onlyFiles,
|
|
441
449
|
onlyRoutes,
|
|
442
|
-
grep,
|
|
443
|
-
|
|
444
|
-
|
|
450
|
+
grep,
|
|
451
|
+
timeoutMs,
|
|
452
|
+
progressToken,
|
|
453
|
+
} = args as {
|
|
445
454
|
repoRoot: string;
|
|
446
455
|
baseURL?: string;
|
|
447
456
|
ci?: boolean;
|
|
448
457
|
headless?: boolean;
|
|
449
458
|
browsers?: ("chromium" | "firefox" | "webkit")[];
|
|
450
459
|
onlyFiles?: string[];
|
|
451
|
-
onlyRoutes?: string[];
|
|
452
|
-
grep?: string;
|
|
453
|
-
|
|
454
|
-
|
|
460
|
+
onlyRoutes?: string[];
|
|
461
|
+
grep?: string;
|
|
462
|
+
timeoutMs?: number;
|
|
463
|
+
progressToken?: string | number;
|
|
464
|
+
};
|
|
455
465
|
return await runWithObservability(
|
|
456
466
|
{
|
|
457
467
|
repoRoot,
|
|
@@ -459,10 +469,11 @@ export function ateTools(projectRoot: string, server?: Server) {
|
|
|
459
469
|
ci,
|
|
460
470
|
headless,
|
|
461
471
|
browsers,
|
|
462
|
-
onlyFiles,
|
|
463
|
-
onlyRoutes,
|
|
464
|
-
grep,
|
|
465
|
-
|
|
472
|
+
onlyFiles,
|
|
473
|
+
onlyRoutes,
|
|
474
|
+
grep,
|
|
475
|
+
timeoutMs,
|
|
476
|
+
},
|
|
466
477
|
{ progressToken },
|
|
467
478
|
);
|
|
468
479
|
},
|
|
@@ -518,9 +529,10 @@ export function ateTools(projectRoot: string, server?: Server) {
|
|
|
518
529
|
},
|
|
519
530
|
"mandu.ate.auto_pipeline": async (args: Record<string, unknown>) => {
|
|
520
531
|
const {
|
|
521
|
-
repoRoot, baseURL, oracleLevel, ci, useImpactAnalysis,
|
|
522
|
-
base, head, autoHeal, tsconfigPath, routeGlobs, buildSalt,
|
|
523
|
-
|
|
532
|
+
repoRoot, baseURL, oracleLevel, ci, useImpactAnalysis,
|
|
533
|
+
base, head, autoHeal, tsconfigPath, routeGlobs, buildSalt,
|
|
534
|
+
timeoutMs,
|
|
535
|
+
} = args as {
|
|
524
536
|
repoRoot: string;
|
|
525
537
|
baseURL?: string;
|
|
526
538
|
oracleLevel?: OracleLevel;
|
|
@@ -530,13 +542,14 @@ export function ateTools(projectRoot: string, server?: Server) {
|
|
|
530
542
|
head?: string;
|
|
531
543
|
autoHeal?: boolean;
|
|
532
544
|
tsconfigPath?: string;
|
|
533
|
-
routeGlobs?: string[];
|
|
534
|
-
buildSalt?: string;
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
545
|
+
routeGlobs?: string[];
|
|
546
|
+
buildSalt?: string;
|
|
547
|
+
timeoutMs?: number;
|
|
548
|
+
};
|
|
549
|
+
return await runFullPipeline({
|
|
550
|
+
repoRoot, baseURL, oracleLevel, ci, useImpactAnalysis,
|
|
551
|
+
base, head, autoHeal, tsconfigPath, routeGlobs, buildSalt, timeoutMs,
|
|
552
|
+
});
|
|
540
553
|
},
|
|
541
554
|
"mandu.ate.feedback": async (args: Record<string, unknown>) => {
|
|
542
555
|
const { repoRoot, runId, autoApply } = args as {
|
package/src/tools/composite.ts
CHANGED
|
@@ -60,10 +60,10 @@ export const compositeToolDefinitions: Tool[] = [
|
|
|
60
60
|
},
|
|
61
61
|
},
|
|
62
62
|
{
|
|
63
|
-
name: "mandu.island.add",
|
|
64
|
-
description:
|
|
65
|
-
"Create an island component with correct @mandujs/core/client imports and hydration strategy. " +
|
|
66
|
-
"Generates a .island.tsx file in app/{route}/ with the
|
|
63
|
+
name: "mandu.island.add",
|
|
64
|
+
description:
|
|
65
|
+
"Create an island component with correct @mandujs/core/client imports and hydration strategy. " +
|
|
66
|
+
"Generates a .island.tsx file in app/{route}/ with the wrapComponent() island definition.",
|
|
67
67
|
annotations: {
|
|
68
68
|
destructiveHint: true,
|
|
69
69
|
readOnlyHint: false,
|
|
@@ -474,10 +474,10 @@ function generateMiddlewareSource(preset: string, options: Record<string, string
|
|
|
474
474
|
return templates[preset] ?? templates.default;
|
|
475
475
|
}
|
|
476
476
|
|
|
477
|
-
function generateIslandSource(name: string, strategy: string): string {
|
|
478
|
-
return `"use client";
|
|
479
|
-
import {
|
|
480
|
-
import { useState } from "react";
|
|
477
|
+
function generateIslandSource(name: string, strategy: string): string {
|
|
478
|
+
return `"use client";
|
|
479
|
+
import { wrapComponent } from "@mandujs/core/client";
|
|
480
|
+
import { useState } from "react";
|
|
481
481
|
|
|
482
482
|
interface ${name}Props {
|
|
483
483
|
[key: string]: unknown;
|
|
@@ -493,6 +493,8 @@ function ${name}Inner(props: ${name}Props) {
|
|
|
493
493
|
);
|
|
494
494
|
}
|
|
495
495
|
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
}
|
|
496
|
+
// Route hydration priority should be set on the page/manifest route.
|
|
497
|
+
// Suggested priority for this island: "${strategy}".
|
|
498
|
+
export default wrapComponent(${name}Inner);
|
|
499
|
+
`;
|
|
500
|
+
}
|