@mandujs/core 0.54.11 → 0.54.13

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 (76) hide show
  1. package/package.json +208 -200
  2. package/scripts/postinstall-lock.ts +153 -153
  3. package/src/a11y/run-audit.ts +15 -15
  4. package/src/agent/__tests__/context.test.ts +49 -1
  5. package/src/agent/context.ts +535 -535
  6. package/src/agent/index.ts +6 -6
  7. package/src/agent/plan.ts +282 -282
  8. package/src/agent/repair.ts +171 -171
  9. package/src/agent/sync.ts +200 -200
  10. package/src/agent/types.ts +8 -0
  11. package/src/agent/verify.ts +100 -2
  12. package/src/brain/doctor/analyzer.ts +7 -7
  13. package/src/bundler/__tests__/build-runner.ts +5 -4
  14. package/src/bundler/__tests__/cold-start.test.ts +60 -60
  15. package/src/bundler/__tests__/css.test.ts +20 -20
  16. package/src/bundler/analyzer.ts +15 -15
  17. package/src/bundler/build.test.ts +73 -7
  18. package/src/bundler/build.ts +139 -31
  19. package/src/bundler/css.ts +42 -42
  20. package/src/bundler/manifest-schema.ts +21 -21
  21. package/src/bundler/plugins/__tests__/block-generated-imports.test.ts +13 -13
  22. package/src/bundler/plugins/block-generated-imports.ts +13 -13
  23. package/src/bundler/types.ts +31 -31
  24. package/src/client/island.ts +79 -79
  25. package/src/config/validate.ts +1 -1
  26. package/src/contract/schema.ts +7 -0
  27. package/src/deploy/inference/context.ts +82 -82
  28. package/src/devtools/client/components/panel/islands-panel.tsx +16 -16
  29. package/src/devtools/client/components/panel/panel-container.tsx +1 -1
  30. package/src/error/formatter.ts +10 -1
  31. package/src/experimental/index.ts +10 -0
  32. package/src/filling/context.ts +17 -17
  33. package/src/filling/filling.ts +22 -1
  34. package/src/filling/index.ts +15 -1
  35. package/src/generator/generate.ts +30 -30
  36. package/src/generator/index.ts +3 -3
  37. package/src/generator/templates.ts +210 -210
  38. package/src/guard/check.ts +9 -9
  39. package/src/guard/config-guard.ts +13 -13
  40. package/src/guard/fs-routes-policy.ts +51 -51
  41. package/src/guard/index.ts +11 -11
  42. package/src/index.ts +0 -10
  43. package/src/internal/index.ts +25 -0
  44. package/src/kitchen/api/file-api.ts +11 -11
  45. package/src/report/index.ts +1 -1
  46. package/src/resource/__tests__/generator.test.ts +6 -6
  47. package/src/resource/__tests__/schema.test.ts +14 -14
  48. package/src/resource/ddl/__tests__/emit.test.ts +165 -165
  49. package/src/resource/ddl/emit.ts +146 -146
  50. package/src/resource/generator-schema.ts +11 -11
  51. package/src/resource/generators/slot.ts +72 -72
  52. package/src/resource/schema.ts +21 -21
  53. package/src/router/client-entry.test.ts +69 -33
  54. package/src/router/client-entry.ts +134 -74
  55. package/src/router/fs-routes.ts +24 -22
  56. package/src/router/fs-scanner.ts +21 -17
  57. package/src/router/fs-types.ts +8 -5
  58. package/src/runtime/__tests__/devtools-adapter.test.ts +68 -68
  59. package/src/runtime/__tests__/observability-lifecycle.test.ts +103 -103
  60. package/src/runtime/__tests__/page-render-response.test.ts +103 -103
  61. package/src/runtime/__tests__/request-middleware.test.ts +70 -70
  62. package/src/runtime/devtools-adapter.ts +68 -68
  63. package/src/runtime/escape.ts +34 -34
  64. package/src/runtime/image-feature.ts +15 -0
  65. package/src/runtime/observability-lifecycle.ts +290 -290
  66. package/src/runtime/page-render-response.ts +106 -106
  67. package/src/runtime/rate-limit.ts +231 -0
  68. package/src/runtime/request-middleware.ts +31 -31
  69. package/src/runtime/scheduler-lifecycle.ts +64 -0
  70. package/src/runtime/server.ts +27 -295
  71. package/src/runtime/ssr.ts +59 -59
  72. package/src/runtime/static-files.ts +289 -289
  73. package/src/runtime/streaming-ssr.ts +22 -22
  74. package/src/spec/schema.ts +4 -3
  75. package/src/watcher/__tests__/watcher.test.ts +59 -59
  76. 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
+ }
@@ -159,12 +159,20 @@ export interface AgentSuggestedCommand {
159
159
  required: boolean;
160
160
  }
161
161
 
162
+ export interface AgentChangedFileReason {
163
+ file: string;
164
+ reasons: string[];
165
+ recommendedChecks: string[];
166
+ internalApi: boolean;
167
+ }
168
+
162
169
  export interface AgentVerifyReport {
163
170
  schemaVersion: 1;
164
171
  framework: "mandu";
165
172
  generatedAt: string;
166
173
  project: AgentProjectSummary;
167
174
  changedFiles: string[];
175
+ changedFileReasons: AgentChangedFileReason[];
168
176
  gitAvailable: boolean;
169
177
  notes: string[];
170
178
  ok: boolean;
@@ -8,6 +8,7 @@ import {
8
8
  buildAgentContext,
9
9
  } from "./context";
10
10
  import type {
11
+ AgentChangedFileReason,
11
12
  AgentDiagnostic,
12
13
  AgentDiagnosticSeverity,
13
14
  AgentSuggestedCommand,
@@ -208,7 +209,11 @@ function matchesChanged(
208
209
  return candidates.some((file) => changed.has(file));
209
210
  }
210
211
 
211
- function commandSuggestions(changedFiles: string[], diagnostics: AgentDiagnostic[]): AgentSuggestedCommand[] {
212
+ function commandSuggestions(
213
+ changedFiles: string[],
214
+ diagnostics: AgentDiagnostic[],
215
+ changedFileReasons: AgentChangedFileReason[] = [],
216
+ ): AgentSuggestedCommand[] {
212
217
  const files = changedFiles.map(normalizePath).filter((value): value is string => Boolean(value));
213
218
  const out: AgentSuggestedCommand[] = [];
214
219
  const add = (command: string, reason: string, required: boolean) => {
@@ -243,6 +248,9 @@ function commandSuggestions(changedFiles: string[], diagnostics: AgentDiagnostic
243
248
  if (files.some((file) => file.includes("package.json") || file === "bun.lock")) {
244
249
  add("bun run check:publish", "Package metadata or lockfile changed.", true);
245
250
  }
251
+ if (changedFileReasons.some((entry) => entry.internalApi)) {
252
+ add("bun run check:public-api && bun run check:target-boundaries", "Internal framework boundaries changed.", true);
253
+ }
246
254
  if (diagnostics.some((d) => d.severity === "error" || d.severity === "fatal")) {
247
255
  add("mandu agent repair --from .mandu/agent-verify.json", "Verification produced repairable diagnostics.", false);
248
256
  }
@@ -250,6 +258,89 @@ function commandSuggestions(changedFiles: string[], diagnostics: AgentDiagnostic
250
258
  return out;
251
259
  }
252
260
 
261
+ function isInternalApiEdit(file: string): boolean {
262
+ return [
263
+ "packages/core/src/runtime/",
264
+ "packages/core/src/bundler/",
265
+ "packages/core/src/server/",
266
+ "packages/core/src/guard/",
267
+ "packages/core/src/spec/",
268
+ "packages/core/src/router/",
269
+ "packages/core/src/internal/",
270
+ ].some((prefix) => file.startsWith(prefix));
271
+ }
272
+
273
+ function changedFileReason(file: string): AgentChangedFileReason {
274
+ const normalized = normalizePath(file) ?? file;
275
+ const reasons: string[] = [];
276
+ const recommendedChecks: string[] = [];
277
+
278
+ if (/\.(ts|tsx|js|jsx)$/.test(normalized)) {
279
+ reasons.push("Source code changed.");
280
+ recommendedChecks.push("bun run typecheck");
281
+ }
282
+ if (/\.test\.(ts|tsx|js|jsx)$/.test(normalized)) {
283
+ reasons.push("Test code changed.");
284
+ recommendedChecks.push(`bun test ${normalized}`);
285
+ }
286
+ if (normalized.startsWith("packages/cli/")) {
287
+ reasons.push("CLI behavior or documentation changed.");
288
+ recommendedChecks.push("bun test packages/cli/src");
289
+ }
290
+ if (normalized.startsWith("packages/mcp/")) {
291
+ reasons.push("MCP tool surface changed.");
292
+ recommendedChecks.push("bun test packages/mcp/tests");
293
+ }
294
+ if (normalized.startsWith("docs/") || normalized.endsWith("README.md") || normalized.endsWith("README.ko.md")) {
295
+ reasons.push("User-facing documentation changed.");
296
+ recommendedChecks.push("bun run check:docs-drift");
297
+ }
298
+ if (normalized === "package.json" || normalized === "bun.lock" || normalized.endsWith("/package.json")) {
299
+ reasons.push("Package metadata or dependency graph changed.");
300
+ recommendedChecks.push("bun run check:publish");
301
+ }
302
+
303
+ const internalApi = isInternalApiEdit(normalized);
304
+ if (internalApi) {
305
+ reasons.push("Framework internal API changed.");
306
+ recommendedChecks.push("bun run check:public-api && bun run check:target-boundaries");
307
+ }
308
+
309
+ return {
310
+ file: normalized,
311
+ reasons: reasons.length > 0 ? reasons : ["Changed file requires standard verification."],
312
+ recommendedChecks: [...new Set(recommendedChecks)],
313
+ internalApi,
314
+ };
315
+ }
316
+
317
+ function buildChangedFileReasons(changedFiles: string[]): AgentChangedFileReason[] {
318
+ return changedFiles
319
+ .map(normalizePath)
320
+ .filter((file): file is string => Boolean(file))
321
+ .map(changedFileReason);
322
+ }
323
+
324
+ function internalApiDiagnostics(changedFileReasons: AgentChangedFileReason[]): AgentDiagnostic[] {
325
+ return changedFileReasons
326
+ .filter((entry) => entry.internalApi)
327
+ .map((entry) => ({
328
+ code: "MANDU_VERIFY_INTERNAL_API_EDIT",
329
+ severity: "warning" as const,
330
+ title: "Internal framework API changed",
331
+ file: entry.file,
332
+ cause: "This file is part of Mandu's internal runtime/bundler/guard surface and can affect public behavior indirectly.",
333
+ suggestedFix: {
334
+ type: "run_command" as const,
335
+ command: "bun run check:public-api && bun run check:target-boundaries",
336
+ description: "Verify public API classification and target-safe import boundaries.",
337
+ },
338
+ docs: "docs/architect/public-api-boundary.md",
339
+ repairable: false,
340
+ source: "agent.verify",
341
+ }));
342
+ }
343
+
253
344
  function check(
254
345
  id: string,
255
346
  label: string,
@@ -284,6 +375,7 @@ export async function buildAgentVerifyReport(
284
375
  const root = path.resolve(rootDir);
285
376
  const changed = await collectChangedFiles(root, options);
286
377
  const changedSet = new Set(changed.files.map(normalizePath).filter((value): value is string => Boolean(value)));
378
+ const changedFileReasons = buildChangedFileReasons(changed.files);
287
379
  const context = await buildAgentContext(root, {
288
380
  includeDiagnose: false,
289
381
  includeGit: false,
@@ -291,6 +383,11 @@ export async function buildAgentVerifyReport(
291
383
  const notes = [...changed.notes];
292
384
  const checks: AgentVerifyCheck[] = [];
293
385
  const diagnostics: AgentDiagnostic[] = [];
386
+ const internalDiagnostics = internalApiDiagnostics(changedFileReasons);
387
+ diagnostics.push(...internalDiagnostics);
388
+ checks.push(check("internal-api", "Internal API boundary", internalDiagnostics, {
389
+ changedFiles: changedFileReasons.filter((entry) => entry.internalApi).length,
390
+ }));
294
391
 
295
392
  if (options.includeDiagnose !== false) {
296
393
  try {
@@ -384,12 +481,13 @@ export async function buildAgentVerifyReport(
384
481
  generatedAt: new Date().toISOString(),
385
482
  project: context.project,
386
483
  changedFiles: changed.files,
484
+ changedFileReasons,
387
485
  gitAvailable: changed.gitAvailable,
388
486
  notes,
389
487
  ok,
390
488
  checks,
391
489
  diagnostics,
392
- suggestedCommands: commandSuggestions(changed.files, diagnostics),
490
+ suggestedCommands: commandSuggestions(changed.files, diagnostics, changedFileReasons),
393
491
  nextRepairInput: AGENT_VERIFY_RELATIVE_PATH,
394
492
  };
395
493
  }
@@ -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:
@@ -80,10 +80,11 @@ const manifest: RoutesManifest = mode === "server-page-client-module"
80
80
  },
81
81
  ],
82
82
  }
83
- : mode === "server-page-route-client-import"
84
- ? {
85
- version: 1,
86
- routes: [
83
+ : mode === "server-page-route-client-import"
84
+ || mode === "server-page-route-named-client-import"
85
+ ? {
86
+ version: 1,
87
+ routes: [
87
88
  {
88
89
  id: "login",
89
90
  kind: "page",