@mandujs/core 0.54.10 → 0.54.12
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 +1 -1
- package/scripts/postinstall-lock.ts +153 -153
- package/src/a11y/run-audit.ts +15 -15
- package/src/agent/__tests__/context.test.ts +17 -65
- 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 +10 -18
- package/src/agent/verify.ts +17 -115
- package/src/brain/doctor/analyzer.ts +7 -7
- package/src/bundler/__tests__/build-runner.ts +81 -62
- 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 +130 -83
- package/src/bundler/build.ts +570 -524
- 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/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 +22 -31
- package/src/filling/context.ts +17 -17
- package/src/generator/generate.ts +30 -30
- package/src/generator/index.ts +3 -3
- package/src/generator/templates.test.ts +66 -65
- 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 +3 -3
- 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 +192 -140
- package/src/router/client-entry.ts +516 -486
- package/src/router/fs-routes.ts +16 -16
- package/src/router/fs-scanner.ts +16 -28
- 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/observability-lifecycle.ts +290 -290
- package/src/runtime/page-render-response.ts +106 -106
- package/src/runtime/request-middleware.ts +31 -31
- package/src/runtime/server.ts +243 -243
- 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/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
|
@@ -153,27 +153,19 @@ export interface AgentVerifyCheck {
|
|
|
153
153
|
details?: Record<string, unknown>;
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
-
export interface AgentSuggestedCommand {
|
|
157
|
-
command: string;
|
|
158
|
-
reason: string;
|
|
159
|
-
required: boolean;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
export interface
|
|
163
|
-
file: string;
|
|
164
|
-
reasons: string[];
|
|
165
|
-
recommendedChecks: string[];
|
|
166
|
-
internalApi: boolean;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
export interface AgentVerifyReport {
|
|
156
|
+
export interface AgentSuggestedCommand {
|
|
157
|
+
command: string;
|
|
158
|
+
reason: string;
|
|
159
|
+
required: boolean;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export interface AgentVerifyReport {
|
|
170
163
|
schemaVersion: 1;
|
|
171
164
|
framework: "mandu";
|
|
172
165
|
generatedAt: string;
|
|
173
|
-
project: AgentProjectSummary;
|
|
174
|
-
changedFiles: string[];
|
|
175
|
-
|
|
176
|
-
gitAvailable: boolean;
|
|
166
|
+
project: AgentProjectSummary;
|
|
167
|
+
changedFiles: string[];
|
|
168
|
+
gitAvailable: boolean;
|
|
177
169
|
notes: string[];
|
|
178
170
|
ok: boolean;
|
|
179
171
|
checks: AgentVerifyCheck[];
|
package/src/agent/verify.ts
CHANGED
|
@@ -7,9 +7,8 @@ import type { ContractViolation } from "../guard/contract-guard";
|
|
|
7
7
|
import {
|
|
8
8
|
buildAgentContext,
|
|
9
9
|
} from "./context";
|
|
10
|
-
import type {
|
|
11
|
-
|
|
12
|
-
AgentDiagnostic,
|
|
10
|
+
import type {
|
|
11
|
+
AgentDiagnostic,
|
|
13
12
|
AgentDiagnosticSeverity,
|
|
14
13
|
AgentSuggestedCommand,
|
|
15
14
|
AgentVerifyCheck,
|
|
@@ -209,11 +208,7 @@ function matchesChanged(
|
|
|
209
208
|
return candidates.some((file) => changed.has(file));
|
|
210
209
|
}
|
|
211
210
|
|
|
212
|
-
function commandSuggestions(
|
|
213
|
-
changedFiles: string[],
|
|
214
|
-
diagnostics: AgentDiagnostic[],
|
|
215
|
-
changedFileReasons: AgentChangedFileReason[] = [],
|
|
216
|
-
): AgentSuggestedCommand[] {
|
|
211
|
+
function commandSuggestions(changedFiles: string[], diagnostics: AgentDiagnostic[]): AgentSuggestedCommand[] {
|
|
217
212
|
const files = changedFiles.map(normalizePath).filter((value): value is string => Boolean(value));
|
|
218
213
|
const out: AgentSuggestedCommand[] = [];
|
|
219
214
|
const add = (command: string, reason: string, required: boolean) => {
|
|
@@ -245,101 +240,15 @@ function commandSuggestions(
|
|
|
245
240
|
if (files.some((file) => file.startsWith("packages/mcp/"))) {
|
|
246
241
|
add("bun test packages/mcp/tests", "MCP package files changed.", true);
|
|
247
242
|
}
|
|
248
|
-
if (files.some((file) => file.includes("package.json") || file === "bun.lock")) {
|
|
249
|
-
add("bun run check:publish", "Package metadata or lockfile changed.", true);
|
|
250
|
-
}
|
|
251
|
-
if (
|
|
252
|
-
add("bun run check:public-api && bun run check:target-boundaries", "Internal framework boundaries changed.", true);
|
|
253
|
-
}
|
|
254
|
-
if (diagnostics.some((d) => d.severity === "error" || d.severity === "fatal")) {
|
|
243
|
+
if (files.some((file) => file.includes("package.json") || file === "bun.lock")) {
|
|
244
|
+
add("bun run check:publish", "Package metadata or lockfile changed.", true);
|
|
245
|
+
}
|
|
246
|
+
if (diagnostics.some((d) => d.severity === "error" || d.severity === "fatal")) {
|
|
255
247
|
add("mandu agent repair --from .mandu/agent-verify.json", "Verification produced repairable diagnostics.", false);
|
|
256
248
|
}
|
|
257
249
|
|
|
258
250
|
return out;
|
|
259
|
-
}
|
|
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
|
-
}
|
|
251
|
+
}
|
|
343
252
|
|
|
344
253
|
function check(
|
|
345
254
|
id: string,
|
|
@@ -372,22 +281,16 @@ export async function buildAgentVerifyReport(
|
|
|
372
281
|
rootDir: string = process.cwd(),
|
|
373
282
|
options: BuildAgentVerifyOptions = {},
|
|
374
283
|
): Promise<AgentVerifyReport> {
|
|
375
|
-
const root = path.resolve(rootDir);
|
|
376
|
-
const changed = await collectChangedFiles(root, options);
|
|
377
|
-
const changedSet = new Set(changed.files.map(normalizePath).filter((value): value is string => Boolean(value)));
|
|
378
|
-
const changedFileReasons = buildChangedFileReasons(changed.files);
|
|
284
|
+
const root = path.resolve(rootDir);
|
|
285
|
+
const changed = await collectChangedFiles(root, options);
|
|
286
|
+
const changedSet = new Set(changed.files.map(normalizePath).filter((value): value is string => Boolean(value)));
|
|
379
287
|
const context = await buildAgentContext(root, {
|
|
380
288
|
includeDiagnose: false,
|
|
381
289
|
includeGit: false,
|
|
382
290
|
});
|
|
383
291
|
const notes = [...changed.notes];
|
|
384
|
-
const checks: AgentVerifyCheck[] = [];
|
|
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
|
-
}));
|
|
292
|
+
const checks: AgentVerifyCheck[] = [];
|
|
293
|
+
const diagnostics: AgentDiagnostic[] = [];
|
|
391
294
|
|
|
392
295
|
if (options.includeDiagnose !== false) {
|
|
393
296
|
try {
|
|
@@ -479,15 +382,14 @@ export async function buildAgentVerifyReport(
|
|
|
479
382
|
schemaVersion: 1,
|
|
480
383
|
framework: "mandu",
|
|
481
384
|
generatedAt: new Date().toISOString(),
|
|
482
|
-
project: context.project,
|
|
483
|
-
changedFiles: changed.files,
|
|
484
|
-
|
|
485
|
-
gitAvailable: changed.gitAvailable,
|
|
385
|
+
project: context.project,
|
|
386
|
+
changedFiles: changed.files,
|
|
387
|
+
gitAvailable: changed.gitAvailable,
|
|
486
388
|
notes,
|
|
487
389
|
ok,
|
|
488
390
|
checks,
|
|
489
391
|
diagnostics,
|
|
490
|
-
suggestedCommands: commandSuggestions(changed.files, diagnostics
|
|
392
|
+
suggestedCommands: commandSuggestions(changed.files, diagnostics),
|
|
491
393
|
nextRepairInput: AGENT_VERIFY_RELATIVE_PATH,
|
|
492
394
|
};
|
|
493
395
|
}
|
|
@@ -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:
|