@mandujs/core 0.54.9 → 0.54.11

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 (63) hide show
  1. package/package.json +1 -1
  2. package/scripts/postinstall-lock.ts +153 -153
  3. package/src/a11y/run-audit.ts +15 -15
  4. package/src/agent/context.ts +535 -535
  5. package/src/agent/index.ts +6 -6
  6. package/src/agent/plan.ts +282 -282
  7. package/src/agent/repair.ts +171 -171
  8. package/src/agent/sync.ts +200 -200
  9. package/src/brain/doctor/analyzer.ts +7 -7
  10. package/src/bundler/__tests__/build-runner.ts +81 -62
  11. package/src/bundler/__tests__/cold-start.test.ts +60 -60
  12. package/src/bundler/__tests__/css.test.ts +20 -20
  13. package/src/bundler/analyzer.ts +15 -15
  14. package/src/bundler/build.test.ts +120 -88
  15. package/src/bundler/build.ts +511 -511
  16. package/src/bundler/css.ts +42 -42
  17. package/src/bundler/manifest-schema.ts +21 -21
  18. package/src/bundler/plugins/__tests__/block-generated-imports.test.ts +13 -13
  19. package/src/bundler/plugins/block-generated-imports.ts +13 -13
  20. package/src/bundler/types.ts +31 -31
  21. package/src/client/island.ts +79 -79
  22. package/src/config/validate.ts +1 -1
  23. package/src/deploy/inference/context.ts +82 -82
  24. package/src/devtools/client/components/panel/islands-panel.tsx +16 -16
  25. package/src/devtools/client/components/panel/panel-container.tsx +1 -1
  26. package/src/filling/context.ts +17 -17
  27. package/src/generator/generate.ts +30 -30
  28. package/src/generator/index.ts +3 -3
  29. package/src/generator/templates.test.ts +66 -65
  30. package/src/generator/templates.ts +210 -210
  31. package/src/guard/check.ts +9 -9
  32. package/src/guard/config-guard.ts +13 -13
  33. package/src/guard/fs-routes-policy.ts +51 -51
  34. package/src/guard/index.ts +11 -11
  35. package/src/index.ts +3 -3
  36. package/src/kitchen/api/file-api.ts +11 -11
  37. package/src/report/index.ts +1 -1
  38. package/src/resource/__tests__/generator.test.ts +6 -6
  39. package/src/resource/__tests__/schema.test.ts +14 -14
  40. package/src/resource/ddl/__tests__/emit.test.ts +165 -165
  41. package/src/resource/ddl/emit.ts +146 -146
  42. package/src/resource/generator-schema.ts +11 -11
  43. package/src/resource/generators/slot.ts +72 -72
  44. package/src/resource/schema.ts +21 -21
  45. package/src/router/client-entry.test.ts +192 -89
  46. package/src/router/client-entry.ts +516 -400
  47. package/src/router/fs-routes.ts +16 -16
  48. package/src/router/fs-scanner.ts +16 -27
  49. package/src/runtime/__tests__/devtools-adapter.test.ts +68 -68
  50. package/src/runtime/__tests__/observability-lifecycle.test.ts +103 -103
  51. package/src/runtime/__tests__/page-render-response.test.ts +103 -103
  52. package/src/runtime/__tests__/request-middleware.test.ts +70 -70
  53. package/src/runtime/devtools-adapter.ts +68 -68
  54. package/src/runtime/escape.ts +34 -34
  55. package/src/runtime/observability-lifecycle.ts +290 -290
  56. package/src/runtime/page-render-response.ts +106 -106
  57. package/src/runtime/request-middleware.ts +31 -31
  58. package/src/runtime/server.ts +243 -243
  59. package/src/runtime/ssr.ts +59 -59
  60. package/src/runtime/static-files.ts +289 -289
  61. package/src/runtime/streaming-ssr.ts +22 -22
  62. package/src/watcher/__tests__/watcher.test.ts +59 -59
  63. package/src/watcher/watcher.ts +61 -61
package/src/agent/sync.ts CHANGED
@@ -1,200 +1,200 @@
1
- import fs from "fs/promises";
2
- import path from "path";
3
- import type {
4
- AgentSuggestedCommand,
5
- AgentSyncFile,
6
- AgentSyncReport,
7
- AgentSyncTarget,
8
- BuildAgentSyncOptions,
9
- } from "./types";
10
-
11
- const SYNC_ROOT = path.join(".mandu", "agent-sync");
12
- const TARGETS = ["codex", "claude", "gemini"] as const;
13
- type ConcreteTarget = (typeof TARGETS)[number];
14
-
15
- function toPosix(value: string): string {
16
- return value.split(path.sep).join("/");
17
- }
18
-
19
- function isTarget(value: string | undefined): value is AgentSyncTarget {
20
- return value === "codex" || value === "claude" || value === "gemini" || value === "all";
21
- }
22
-
23
- function concreteTargets(target: AgentSyncTarget): ConcreteTarget[] {
24
- return target === "all" ? [...TARGETS] : [target];
25
- }
26
-
27
- function targetFile(target: ConcreteTarget): string {
28
- if (target === "codex") return path.join(SYNC_ROOT, "codex", "AGENTS.md");
29
- if (target === "claude") return path.join(SYNC_ROOT, "claude", "CLAUDE.md");
30
- return path.join(SYNC_ROOT, "gemini", "GEMINI.md");
31
- }
32
-
33
- function renderInstructions(target: ConcreteTarget): string {
34
- const title =
35
- target === "codex"
36
- ? "Codex"
37
- : target === "claude"
38
- ? "Claude Code"
39
- : "Gemini CLI";
40
- return [
41
- `# Mandu Agent Workflow for ${title}`,
42
- "",
43
- "Mandu is an agent-native fullstack framework. Use the official agent surface before direct source edits.",
44
- "",
45
- "## Canonical Loop",
46
- "",
47
- "```text",
48
- "context -> plan -> apply -> verify -> repair",
49
- "```",
50
- "",
51
- "## Required First Choices",
52
- "",
53
- "1. Start with `mandu.agent.context` or `mandu agent context --json`.",
54
- "2. Create a plan with `mandu.agent.plan` or `mandu agent plan \"<task>\" --json --write`.",
55
- "3. Prefer `mandu.agent.apply` and domain MCP tools before direct file edits.",
56
- "4. End code-changing work with `mandu.agent.verify` or `mandu agent verify --changed --json --write`.",
57
- "5. If verification fails, run `mandu.agent.repair` or `mandu agent repair --from .mandu/agent-verify.json --json`, then verify again.",
58
- "",
59
- "## MCP Profile",
60
- "",
61
- "Use the reduced default profile:",
62
- "",
63
- "```bash",
64
- "MANDU_MCP_PROFILE=agent-core",
65
- "```",
66
- "",
67
- "Escalate to `agent-full` only when the plan selects route, API, slot, hydration, contract, guard, testing, or lint domains. Use `internal` only for framework maintenance.",
68
- "",
69
- "## Domain Skill Escalation",
70
- "",
71
- "- route/api: `mandu-fs-routes`",
72
- "- hydration/island/partial: `mandu-hydration`",
73
- "- slot/filling: `mandu-slot`",
74
- "- guard/import boundary: `mandu-guard`",
75
- "- test/e2e/ATE: `mandu-testing`",
76
- "- deploy: `mandu-deployment`",
77
- "- security/auth/session: `mandu-security`",
78
- "- styling/ui/design: `mandu-styling`, `mandu-ui`, `mandu-composition`",
79
- "",
80
- "Domain skills are addenda. They do not replace the canonical loop.",
81
- "",
82
- ].join("\n");
83
- }
84
-
85
- function renderClaudeSkill(): string {
86
- return [
87
- "---",
88
- "name: mandu-agent-workflow",
89
- "description: Canonical context -> plan -> apply -> verify -> repair workflow for Mandu projects.",
90
- "---",
91
- "",
92
- "# Mandu Agent Workflow",
93
- "",
94
- "Use this skill first in Mandu projects. Follow `context -> plan -> apply -> verify -> repair`.",
95
- "",
96
- "Preferred tools: `mandu.agent.context`, `mandu.agent.plan`, `mandu.agent.apply`, `mandu.agent.verify`, `mandu.agent.repair`.",
97
- "",
98
- "CLI fallback:",
99
- "",
100
- "```bash",
101
- "mandu agent context --json",
102
- "mandu agent plan \"<task>\" --json --write",
103
- "mandu agent apply --from .mandu/agent-plan.json --json",
104
- "mandu agent verify --changed --json --write",
105
- "mandu agent repair --from .mandu/agent-verify.json --json",
106
- "```",
107
- "",
108
- ].join("\n");
109
- }
110
-
111
- function syncEntries(target: ConcreteTarget): Array<{ target: ConcreteTarget; relPath: string; content: string }> {
112
- const entries = [
113
- {
114
- target,
115
- relPath: targetFile(target),
116
- content: renderInstructions(target),
117
- },
118
- ];
119
- if (target === "claude") {
120
- entries.push({
121
- target,
122
- relPath: path.join(SYNC_ROOT, "claude", "skills", "mandu-agent-workflow", "SKILL.md"),
123
- content: renderClaudeSkill(),
124
- });
125
- }
126
- return entries;
127
- }
128
-
129
- async function writeEntry(
130
- rootDir: string,
131
- entry: { target: ConcreteTarget; relPath: string; content: string },
132
- dryRun: boolean,
133
- ): Promise<AgentSyncFile> {
134
- const absPath = path.join(rootDir, entry.relPath);
135
- let action: AgentSyncFile["action"] = "created";
136
- try {
137
- const existing = await fs.readFile(absPath, "utf8");
138
- action = existing === entry.content ? "unchanged" : "updated";
139
- } catch {
140
- action = "created";
141
- }
142
-
143
- if (dryRun) {
144
- action = "planned";
145
- } else if (action !== "unchanged") {
146
- await fs.mkdir(path.dirname(absPath), { recursive: true });
147
- await fs.writeFile(absPath, entry.content, "utf8");
148
- }
149
-
150
- return {
151
- target: entry.target,
152
- path: toPosix(entry.relPath),
153
- action,
154
- bytes: Buffer.byteLength(entry.content, "utf8"),
155
- };
156
- }
157
-
158
- function nextCommands(): AgentSuggestedCommand[] {
159
- return [
160
- {
161
- command: "mandu agent context --json",
162
- reason: "Confirm the generated workflow matches the current project state.",
163
- required: true,
164
- },
165
- {
166
- command: "MANDU_MCP_PROFILE=agent-core",
167
- reason: "Use the reduced default MCP exposure for coding agents.",
168
- required: true,
169
- },
170
- ];
171
- }
172
-
173
- export async function buildAgentSyncReport(
174
- rootDir: string = process.cwd(),
175
- options: BuildAgentSyncOptions = {},
176
- ): Promise<AgentSyncReport> {
177
- const target = isTarget(options.target) ? options.target : "all";
178
- const dryRun = options.dryRun === true;
179
- const root = path.resolve(rootDir);
180
- const files: AgentSyncFile[] = [];
181
-
182
- for (const concrete of concreteTargets(target)) {
183
- for (const entry of syncEntries(concrete)) {
184
- files.push(await writeEntry(root, entry, dryRun));
185
- }
186
- }
187
-
188
- return {
189
- schemaVersion: 1,
190
- framework: "mandu",
191
- generatedAt: new Date().toISOString(),
192
- ok: true,
193
- target,
194
- profile: "agent-core",
195
- workflow: ["context", "plan", "apply", "verify", "repair"],
196
- files,
197
- warnings: dryRun ? ["Dry-run only. No files were written."] : [],
198
- nextCommands: nextCommands(),
199
- };
200
- }
1
+ import fs from "fs/promises";
2
+ import path from "path";
3
+ import type {
4
+ AgentSuggestedCommand,
5
+ AgentSyncFile,
6
+ AgentSyncReport,
7
+ AgentSyncTarget,
8
+ BuildAgentSyncOptions,
9
+ } from "./types";
10
+
11
+ const SYNC_ROOT = path.join(".mandu", "agent-sync");
12
+ const TARGETS = ["codex", "claude", "gemini"] as const;
13
+ type ConcreteTarget = (typeof TARGETS)[number];
14
+
15
+ function toPosix(value: string): string {
16
+ return value.split(path.sep).join("/");
17
+ }
18
+
19
+ function isTarget(value: string | undefined): value is AgentSyncTarget {
20
+ return value === "codex" || value === "claude" || value === "gemini" || value === "all";
21
+ }
22
+
23
+ function concreteTargets(target: AgentSyncTarget): ConcreteTarget[] {
24
+ return target === "all" ? [...TARGETS] : [target];
25
+ }
26
+
27
+ function targetFile(target: ConcreteTarget): string {
28
+ if (target === "codex") return path.join(SYNC_ROOT, "codex", "AGENTS.md");
29
+ if (target === "claude") return path.join(SYNC_ROOT, "claude", "CLAUDE.md");
30
+ return path.join(SYNC_ROOT, "gemini", "GEMINI.md");
31
+ }
32
+
33
+ function renderInstructions(target: ConcreteTarget): string {
34
+ const title =
35
+ target === "codex"
36
+ ? "Codex"
37
+ : target === "claude"
38
+ ? "Claude Code"
39
+ : "Gemini CLI";
40
+ return [
41
+ `# Mandu Agent Workflow for ${title}`,
42
+ "",
43
+ "Mandu is an agent-native fullstack framework. Use the official agent surface before direct source edits.",
44
+ "",
45
+ "## Canonical Loop",
46
+ "",
47
+ "```text",
48
+ "context -> plan -> apply -> verify -> repair",
49
+ "```",
50
+ "",
51
+ "## Required First Choices",
52
+ "",
53
+ "1. Start with `mandu.agent.context` or `mandu agent context --json`.",
54
+ "2. Create a plan with `mandu.agent.plan` or `mandu agent plan \"<task>\" --json --write`.",
55
+ "3. Prefer `mandu.agent.apply` and domain MCP tools before direct file edits.",
56
+ "4. End code-changing work with `mandu.agent.verify` or `mandu agent verify --changed --json --write`.",
57
+ "5. If verification fails, run `mandu.agent.repair` or `mandu agent repair --from .mandu/agent-verify.json --json`, then verify again.",
58
+ "",
59
+ "## MCP Profile",
60
+ "",
61
+ "Use the reduced default profile:",
62
+ "",
63
+ "```bash",
64
+ "MANDU_MCP_PROFILE=agent-core",
65
+ "```",
66
+ "",
67
+ "Escalate to `agent-full` only when the plan selects route, API, slot, hydration, contract, guard, testing, or lint domains. Use `internal` only for framework maintenance.",
68
+ "",
69
+ "## Domain Skill Escalation",
70
+ "",
71
+ "- route/api: `mandu-fs-routes`",
72
+ "- hydration/island/partial: `mandu-hydration`",
73
+ "- slot/filling: `mandu-slot`",
74
+ "- guard/import boundary: `mandu-guard`",
75
+ "- test/e2e/ATE: `mandu-testing`",
76
+ "- deploy: `mandu-deployment`",
77
+ "- security/auth/session: `mandu-security`",
78
+ "- styling/ui/design: `mandu-styling`, `mandu-ui`, `mandu-composition`",
79
+ "",
80
+ "Domain skills are addenda. They do not replace the canonical loop.",
81
+ "",
82
+ ].join("\n");
83
+ }
84
+
85
+ function renderClaudeSkill(): string {
86
+ return [
87
+ "---",
88
+ "name: mandu-agent-workflow",
89
+ "description: Canonical context -> plan -> apply -> verify -> repair workflow for Mandu projects.",
90
+ "---",
91
+ "",
92
+ "# Mandu Agent Workflow",
93
+ "",
94
+ "Use this skill first in Mandu projects. Follow `context -> plan -> apply -> verify -> repair`.",
95
+ "",
96
+ "Preferred tools: `mandu.agent.context`, `mandu.agent.plan`, `mandu.agent.apply`, `mandu.agent.verify`, `mandu.agent.repair`.",
97
+ "",
98
+ "CLI fallback:",
99
+ "",
100
+ "```bash",
101
+ "mandu agent context --json",
102
+ "mandu agent plan \"<task>\" --json --write",
103
+ "mandu agent apply --from .mandu/agent-plan.json --json",
104
+ "mandu agent verify --changed --json --write",
105
+ "mandu agent repair --from .mandu/agent-verify.json --json",
106
+ "```",
107
+ "",
108
+ ].join("\n");
109
+ }
110
+
111
+ function syncEntries(target: ConcreteTarget): Array<{ target: ConcreteTarget; relPath: string; content: string }> {
112
+ const entries = [
113
+ {
114
+ target,
115
+ relPath: targetFile(target),
116
+ content: renderInstructions(target),
117
+ },
118
+ ];
119
+ if (target === "claude") {
120
+ entries.push({
121
+ target,
122
+ relPath: path.join(SYNC_ROOT, "claude", "skills", "mandu-agent-workflow", "SKILL.md"),
123
+ content: renderClaudeSkill(),
124
+ });
125
+ }
126
+ return entries;
127
+ }
128
+
129
+ async function writeEntry(
130
+ rootDir: string,
131
+ entry: { target: ConcreteTarget; relPath: string; content: string },
132
+ dryRun: boolean,
133
+ ): Promise<AgentSyncFile> {
134
+ const absPath = path.join(rootDir, entry.relPath);
135
+ let action: AgentSyncFile["action"] = "created";
136
+ try {
137
+ const existing = await fs.readFile(absPath, "utf8");
138
+ action = existing === entry.content ? "unchanged" : "updated";
139
+ } catch {
140
+ action = "created";
141
+ }
142
+
143
+ if (dryRun) {
144
+ action = "planned";
145
+ } else if (action !== "unchanged") {
146
+ await fs.mkdir(path.dirname(absPath), { recursive: true });
147
+ await fs.writeFile(absPath, entry.content, "utf8");
148
+ }
149
+
150
+ return {
151
+ target: entry.target,
152
+ path: toPosix(entry.relPath),
153
+ action,
154
+ bytes: Buffer.byteLength(entry.content, "utf8"),
155
+ };
156
+ }
157
+
158
+ function nextCommands(): AgentSuggestedCommand[] {
159
+ return [
160
+ {
161
+ command: "mandu agent context --json",
162
+ reason: "Confirm the generated workflow matches the current project state.",
163
+ required: true,
164
+ },
165
+ {
166
+ command: "MANDU_MCP_PROFILE=agent-core",
167
+ reason: "Use the reduced default MCP exposure for coding agents.",
168
+ required: true,
169
+ },
170
+ ];
171
+ }
172
+
173
+ export async function buildAgentSyncReport(
174
+ rootDir: string = process.cwd(),
175
+ options: BuildAgentSyncOptions = {},
176
+ ): Promise<AgentSyncReport> {
177
+ const target = isTarget(options.target) ? options.target : "all";
178
+ const dryRun = options.dryRun === true;
179
+ const root = path.resolve(rootDir);
180
+ const files: AgentSyncFile[] = [];
181
+
182
+ for (const concrete of concreteTargets(target)) {
183
+ for (const entry of syncEntries(concrete)) {
184
+ files.push(await writeEntry(root, entry, dryRun));
185
+ }
186
+ }
187
+
188
+ return {
189
+ schemaVersion: 1,
190
+ framework: "mandu",
191
+ generatedAt: new Date().toISOString(),
192
+ ok: true,
193
+ target,
194
+ profile: "agent-core",
195
+ workflow: ["context", "plan", "apply", "verify", "repair"],
196
+ files,
197
+ warnings: dryRun ? ["Dry-run only. No files were written."] : [],
198
+ nextCommands: nextCommands(),
199
+ };
200
+ }
@@ -206,13 +206,13 @@ export function generateTemplatePatches(
206
206
  "Do NOT import or re-export the island in page.tsx — island() returns " +
207
207
  "a config object, not a React component. Use data-island attributes instead.",
208
208
  type: "modify",
209
- content:
210
- `// Example: app/my-feature.island.tsx\n` +
211
- `import { wrapComponent } from "@mandujs/core/client";\n\n` +
212
- `export default wrapComponent(MyComponent);\n\n` +
213
- `// In page.tsx, reference via: <div data-island="my-feature">...</div>`,
214
- confidence: 0.9,
215
- });
209
+ content:
210
+ `// Example: app/my-feature.island.tsx\n` +
211
+ `import { wrapComponent } from "@mandujs/core/client";\n\n` +
212
+ `export default wrapComponent(MyComponent);\n\n` +
213
+ `// In page.tsx, reference via: <div data-island="my-feature">...</div>`,
214
+ confidence: 0.9,
215
+ });
216
216
  break;
217
217
 
218
218
  default:
@@ -54,68 +54,87 @@
54
54
  import { buildClientBundles } from "../build";
55
55
  import type { RoutesManifest } from "../../spec/schema";
56
56
 
57
- const rootDir = process.argv[2];
58
- if (!rootDir) {
59
- console.error("usage: build-runner.ts <rootDir>");
60
- process.exit(2);
61
- }
62
- const mode = process.argv[3] ?? "default";
63
-
64
- const manifest: RoutesManifest = mode === "server-page-client-module"
65
- ? {
66
- version: 1,
67
- routes: [
68
- {
69
- id: "index",
70
- kind: "page",
71
- pattern: "/",
72
- module: "app/page.tsx",
73
- componentModule: "app/page.tsx",
74
- clientModule: "app/page.tsx",
75
- hydration: {
76
- strategy: "island",
77
- priority: "visible",
78
- preload: false,
79
- },
80
- },
81
- ],
82
- }
83
- : mode === "hydration-no-client-module"
84
- ? {
85
- version: 1,
86
- routes: [
87
- {
88
- id: "login",
89
- kind: "page",
90
- pattern: "/login",
91
- module: "app/login/page.tsx",
92
- componentModule: "app/login/page.tsx",
93
- hydration: {
94
- strategy: "full",
95
- priority: "immediate",
96
- preload: false,
97
- },
98
- },
99
- ],
100
- }
101
- : {
102
- version: 1,
103
- routes: [
104
- {
105
- id: "demo",
106
- kind: "page",
107
- pattern: "/",
108
- module: "app/page.tsx",
109
- componentModule: "app/page.tsx",
110
- clientModule: "app/demo.client.tsx",
111
- hydration: {
112
- strategy: "island",
113
- priority: "visible",
114
- preload: false,
115
- },
116
- },
117
- ],
118
- };
57
+ const rootDir = process.argv[2];
58
+ if (!rootDir) {
59
+ console.error("usage: build-runner.ts <rootDir>");
60
+ process.exit(2);
61
+ }
62
+ const mode = process.argv[3] ?? "default";
63
+
64
+ const manifest: RoutesManifest = mode === "server-page-client-module"
65
+ ? {
66
+ version: 1,
67
+ routes: [
68
+ {
69
+ id: "index",
70
+ kind: "page",
71
+ pattern: "/",
72
+ module: "app/page.tsx",
73
+ componentModule: "app/page.tsx",
74
+ clientModule: "app/page.tsx",
75
+ hydration: {
76
+ strategy: "island",
77
+ priority: "visible",
78
+ preload: false,
79
+ },
80
+ },
81
+ ],
82
+ }
83
+ : mode === "server-page-route-client-import"
84
+ ? {
85
+ version: 1,
86
+ routes: [
87
+ {
88
+ id: "login",
89
+ kind: "page",
90
+ pattern: "/login",
91
+ module: "app/login/page.tsx",
92
+ componentModule: "app/login/page.tsx",
93
+ clientModule: "app/login/page.tsx",
94
+ hydration: {
95
+ strategy: "island",
96
+ priority: "visible",
97
+ preload: false,
98
+ },
99
+ },
100
+ ],
101
+ }
102
+ : mode === "hydration-no-client-module"
103
+ ? {
104
+ version: 1,
105
+ routes: [
106
+ {
107
+ id: "login",
108
+ kind: "page",
109
+ pattern: "/login",
110
+ module: "app/login/page.tsx",
111
+ componentModule: "app/login/page.tsx",
112
+ hydration: {
113
+ strategy: "full",
114
+ priority: "immediate",
115
+ preload: false,
116
+ },
117
+ },
118
+ ],
119
+ }
120
+ : {
121
+ version: 1,
122
+ routes: [
123
+ {
124
+ id: "demo",
125
+ kind: "page",
126
+ pattern: "/",
127
+ module: "app/page.tsx",
128
+ componentModule: "app/page.tsx",
129
+ clientModule: "app/demo.client.tsx",
130
+ hydration: {
131
+ strategy: "island",
132
+ priority: "visible",
133
+ preload: false,
134
+ },
135
+ },
136
+ ],
137
+ };
119
138
 
120
139
  try {
121
140
  const result = await buildClientBundles(manifest, rootDir, {