@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.
- package/package.json +208 -200
- package/scripts/postinstall-lock.ts +153 -153
- package/src/a11y/run-audit.ts +15 -15
- package/src/agent/__tests__/context.test.ts +49 -1
- package/src/agent/context.ts +535 -535
- package/src/agent/index.ts +6 -6
- package/src/agent/plan.ts +282 -282
- package/src/agent/repair.ts +171 -171
- package/src/agent/sync.ts +200 -200
- package/src/agent/types.ts +8 -0
- package/src/agent/verify.ts +100 -2
- package/src/brain/doctor/analyzer.ts +7 -7
- package/src/bundler/__tests__/build-runner.ts +5 -4
- package/src/bundler/__tests__/cold-start.test.ts +60 -60
- package/src/bundler/__tests__/css.test.ts +20 -20
- package/src/bundler/analyzer.ts +15 -15
- package/src/bundler/build.test.ts +73 -7
- package/src/bundler/build.ts +139 -31
- package/src/bundler/css.ts +42 -42
- package/src/bundler/manifest-schema.ts +21 -21
- package/src/bundler/plugins/__tests__/block-generated-imports.test.ts +13 -13
- package/src/bundler/plugins/block-generated-imports.ts +13 -13
- package/src/bundler/types.ts +31 -31
- package/src/client/island.ts +79 -79
- package/src/config/validate.ts +1 -1
- package/src/contract/schema.ts +7 -0
- package/src/deploy/inference/context.ts +82 -82
- package/src/devtools/client/components/panel/islands-panel.tsx +16 -16
- package/src/devtools/client/components/panel/panel-container.tsx +1 -1
- package/src/error/formatter.ts +10 -1
- package/src/experimental/index.ts +10 -0
- package/src/filling/context.ts +17 -17
- package/src/filling/filling.ts +22 -1
- package/src/filling/index.ts +15 -1
- package/src/generator/generate.ts +30 -30
- package/src/generator/index.ts +3 -3
- package/src/generator/templates.ts +210 -210
- package/src/guard/check.ts +9 -9
- package/src/guard/config-guard.ts +13 -13
- package/src/guard/fs-routes-policy.ts +51 -51
- package/src/guard/index.ts +11 -11
- package/src/index.ts +0 -10
- package/src/internal/index.ts +25 -0
- package/src/kitchen/api/file-api.ts +11 -11
- package/src/report/index.ts +1 -1
- package/src/resource/__tests__/generator.test.ts +6 -6
- package/src/resource/__tests__/schema.test.ts +14 -14
- package/src/resource/ddl/__tests__/emit.test.ts +165 -165
- package/src/resource/ddl/emit.ts +146 -146
- package/src/resource/generator-schema.ts +11 -11
- package/src/resource/generators/slot.ts +72 -72
- package/src/resource/schema.ts +21 -21
- package/src/router/client-entry.test.ts +69 -33
- package/src/router/client-entry.ts +134 -74
- package/src/router/fs-routes.ts +24 -22
- package/src/router/fs-scanner.ts +21 -17
- package/src/router/fs-types.ts +8 -5
- package/src/runtime/__tests__/devtools-adapter.test.ts +68 -68
- package/src/runtime/__tests__/observability-lifecycle.test.ts +103 -103
- package/src/runtime/__tests__/page-render-response.test.ts +103 -103
- package/src/runtime/__tests__/request-middleware.test.ts +70 -70
- package/src/runtime/devtools-adapter.ts +68 -68
- package/src/runtime/escape.ts +34 -34
- package/src/runtime/image-feature.ts +15 -0
- package/src/runtime/observability-lifecycle.ts +290 -290
- package/src/runtime/page-render-response.ts +106 -106
- package/src/runtime/rate-limit.ts +231 -0
- package/src/runtime/request-middleware.ts +31 -31
- package/src/runtime/scheduler-lifecycle.ts +64 -0
- package/src/runtime/server.ts +27 -295
- package/src/runtime/ssr.ts +59 -59
- package/src/runtime/static-files.ts +289 -289
- package/src/runtime/streaming-ssr.ts +22 -22
- package/src/spec/schema.ts +4 -3
- package/src/watcher/__tests__/watcher.test.ts +59 -59
- 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
|
+
}
|
package/src/agent/types.ts
CHANGED
|
@@ -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;
|
package/src/agent/verify.ts
CHANGED
|
@@ -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(
|
|
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
|
-
|
|
86
|
-
|
|
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",
|