@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
|
@@ -1,153 +1,153 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Refresh an existing Guard lock after package-manager updates.
|
|
3
|
-
*
|
|
4
|
-
* Lifecycle scripts run from inside the installed package, so projectRoot
|
|
5
|
-
* defaults to INIT_CWD when Bun provides it. This helper is intentionally
|
|
6
|
-
* best-effort: installs must not fail because a project has a stale, invalid,
|
|
7
|
-
* or temporarily unreadable Mandu config.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import path from "node:path";
|
|
11
|
-
import {
|
|
12
|
-
validateConfig,
|
|
13
|
-
type ValidatedManduConfig,
|
|
14
|
-
} from "../src/config/validate.js";
|
|
15
|
-
import { CONFIG_FILES } from "../src/config/mandu.js";
|
|
16
|
-
import {
|
|
17
|
-
generateLockfile,
|
|
18
|
-
readLockfile,
|
|
19
|
-
readMcpConfig,
|
|
20
|
-
writeLockfile,
|
|
21
|
-
LOCKFILE_PATH,
|
|
22
|
-
} from "../src/lockfile/index.js";
|
|
23
|
-
|
|
24
|
-
export type PostinstallLockAction =
|
|
25
|
-
| "updated"
|
|
26
|
-
| "skipped-disabled"
|
|
27
|
-
| "skipped-no-project-config"
|
|
28
|
-
| "skipped-no-lockfile"
|
|
29
|
-
| "skipped-invalid-lockfile"
|
|
30
|
-
| "skipped-invalid-config"
|
|
31
|
-
| "skipped-invalid-mcp-config"
|
|
32
|
-
| "skipped-write-failed";
|
|
33
|
-
|
|
34
|
-
export interface PostinstallLockResult {
|
|
35
|
-
action: PostinstallLockAction;
|
|
36
|
-
projectRoot: string;
|
|
37
|
-
hash?: string;
|
|
38
|
-
error?: string;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export interface PostinstallLockOptions {
|
|
42
|
-
projectRoot?: string;
|
|
43
|
-
env?: NodeJS.ProcessEnv;
|
|
44
|
-
verbose?: boolean;
|
|
45
|
-
log?: (message: string) => void;
|
|
46
|
-
warn?: (message: string) => void;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export async function refreshGuardLockAfterInstall(
|
|
50
|
-
options: PostinstallLockOptions = {},
|
|
51
|
-
): Promise<PostinstallLockResult> {
|
|
52
|
-
const env = options.env ?? process.env;
|
|
53
|
-
const projectRoot = path.resolve(
|
|
54
|
-
options.projectRoot ?? env.INIT_CWD ?? process.cwd(),
|
|
55
|
-
);
|
|
56
|
-
const verbose = options.verbose ?? env.MANDU_POSTINSTALL_VERBOSE === "1";
|
|
57
|
-
const log = options.log ?? console.log;
|
|
58
|
-
const warn = options.warn ?? console.warn;
|
|
59
|
-
|
|
60
|
-
const report = (result: PostinstallLockResult): PostinstallLockResult => {
|
|
61
|
-
if (verbose) {
|
|
62
|
-
if (result.action === "updated") {
|
|
63
|
-
log(`[Mandu] refreshed ${LOCKFILE_PATH} (${result.hash})`);
|
|
64
|
-
} else if (result.error) {
|
|
65
|
-
warn(`[Mandu] ${result.action}: ${result.error}`);
|
|
66
|
-
} else {
|
|
67
|
-
log(`[Mandu] ${result.action}`);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
return result;
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
if (env.MANDU_POSTINSTALL_LOCK === "0") {
|
|
74
|
-
return report({ action: "skipped-disabled", projectRoot });
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
if (!(await hasProjectConfig(projectRoot))) {
|
|
78
|
-
return report({ action: "skipped-no-project-config", projectRoot });
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
let existingLockfile: Awaited<ReturnType<typeof readLockfile>>;
|
|
82
|
-
try {
|
|
83
|
-
existingLockfile = await readLockfile(projectRoot);
|
|
84
|
-
} catch (error) {
|
|
85
|
-
return report({
|
|
86
|
-
action: "skipped-invalid-lockfile",
|
|
87
|
-
projectRoot,
|
|
88
|
-
error: stringifyError(error),
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
if (!existingLockfile) {
|
|
93
|
-
return report({ action: "skipped-no-lockfile", projectRoot });
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const validation = await validateConfig(projectRoot);
|
|
97
|
-
if (!validation.valid || !validation.config) {
|
|
98
|
-
return report({
|
|
99
|
-
action: "skipped-invalid-config",
|
|
100
|
-
projectRoot,
|
|
101
|
-
error:
|
|
102
|
-
validation.errors?.map((entry) => entry.message).join("; ") ??
|
|
103
|
-
"Mandu config validation failed",
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
let mcpConfig: Record<string, unknown> | null;
|
|
108
|
-
try {
|
|
109
|
-
mcpConfig = await readMcpConfig(projectRoot);
|
|
110
|
-
} catch (error) {
|
|
111
|
-
return report({
|
|
112
|
-
action: "skipped-invalid-mcp-config",
|
|
113
|
-
projectRoot,
|
|
114
|
-
error: stringifyError(error),
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
try {
|
|
119
|
-
const lockfile = generateLockfile(
|
|
120
|
-
validation.config as ValidatedManduConfig,
|
|
121
|
-
{
|
|
122
|
-
includeSnapshot: existingLockfile.snapshot !== undefined,
|
|
123
|
-
includeMcpServerHashes: true,
|
|
124
|
-
},
|
|
125
|
-
mcpConfig,
|
|
126
|
-
);
|
|
127
|
-
await writeLockfile(projectRoot, lockfile);
|
|
128
|
-
return report({ action: "updated", projectRoot, hash: lockfile.configHash });
|
|
129
|
-
} catch (error) {
|
|
130
|
-
return report({
|
|
131
|
-
action: "skipped-write-failed",
|
|
132
|
-
projectRoot,
|
|
133
|
-
error: stringifyError(error),
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
async function hasProjectConfig(projectRoot: string): Promise<boolean> {
|
|
139
|
-
for (const fileName of CONFIG_FILES) {
|
|
140
|
-
if (await Bun.file(path.join(projectRoot, fileName)).exists()) {
|
|
141
|
-
return true;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
return false;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function stringifyError(error: unknown): string {
|
|
148
|
-
return error instanceof Error ? error.message : String(error);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
if (import.meta.main) {
|
|
152
|
-
await refreshGuardLockAfterInstall();
|
|
153
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Refresh an existing Guard lock after package-manager updates.
|
|
3
|
+
*
|
|
4
|
+
* Lifecycle scripts run from inside the installed package, so projectRoot
|
|
5
|
+
* defaults to INIT_CWD when Bun provides it. This helper is intentionally
|
|
6
|
+
* best-effort: installs must not fail because a project has a stale, invalid,
|
|
7
|
+
* or temporarily unreadable Mandu config.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import path from "node:path";
|
|
11
|
+
import {
|
|
12
|
+
validateConfig,
|
|
13
|
+
type ValidatedManduConfig,
|
|
14
|
+
} from "../src/config/validate.js";
|
|
15
|
+
import { CONFIG_FILES } from "../src/config/mandu.js";
|
|
16
|
+
import {
|
|
17
|
+
generateLockfile,
|
|
18
|
+
readLockfile,
|
|
19
|
+
readMcpConfig,
|
|
20
|
+
writeLockfile,
|
|
21
|
+
LOCKFILE_PATH,
|
|
22
|
+
} from "../src/lockfile/index.js";
|
|
23
|
+
|
|
24
|
+
export type PostinstallLockAction =
|
|
25
|
+
| "updated"
|
|
26
|
+
| "skipped-disabled"
|
|
27
|
+
| "skipped-no-project-config"
|
|
28
|
+
| "skipped-no-lockfile"
|
|
29
|
+
| "skipped-invalid-lockfile"
|
|
30
|
+
| "skipped-invalid-config"
|
|
31
|
+
| "skipped-invalid-mcp-config"
|
|
32
|
+
| "skipped-write-failed";
|
|
33
|
+
|
|
34
|
+
export interface PostinstallLockResult {
|
|
35
|
+
action: PostinstallLockAction;
|
|
36
|
+
projectRoot: string;
|
|
37
|
+
hash?: string;
|
|
38
|
+
error?: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface PostinstallLockOptions {
|
|
42
|
+
projectRoot?: string;
|
|
43
|
+
env?: NodeJS.ProcessEnv;
|
|
44
|
+
verbose?: boolean;
|
|
45
|
+
log?: (message: string) => void;
|
|
46
|
+
warn?: (message: string) => void;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export async function refreshGuardLockAfterInstall(
|
|
50
|
+
options: PostinstallLockOptions = {},
|
|
51
|
+
): Promise<PostinstallLockResult> {
|
|
52
|
+
const env = options.env ?? process.env;
|
|
53
|
+
const projectRoot = path.resolve(
|
|
54
|
+
options.projectRoot ?? env.INIT_CWD ?? process.cwd(),
|
|
55
|
+
);
|
|
56
|
+
const verbose = options.verbose ?? env.MANDU_POSTINSTALL_VERBOSE === "1";
|
|
57
|
+
const log = options.log ?? console.log;
|
|
58
|
+
const warn = options.warn ?? console.warn;
|
|
59
|
+
|
|
60
|
+
const report = (result: PostinstallLockResult): PostinstallLockResult => {
|
|
61
|
+
if (verbose) {
|
|
62
|
+
if (result.action === "updated") {
|
|
63
|
+
log(`[Mandu] refreshed ${LOCKFILE_PATH} (${result.hash})`);
|
|
64
|
+
} else if (result.error) {
|
|
65
|
+
warn(`[Mandu] ${result.action}: ${result.error}`);
|
|
66
|
+
} else {
|
|
67
|
+
log(`[Mandu] ${result.action}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return result;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
if (env.MANDU_POSTINSTALL_LOCK === "0") {
|
|
74
|
+
return report({ action: "skipped-disabled", projectRoot });
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (!(await hasProjectConfig(projectRoot))) {
|
|
78
|
+
return report({ action: "skipped-no-project-config", projectRoot });
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
let existingLockfile: Awaited<ReturnType<typeof readLockfile>>;
|
|
82
|
+
try {
|
|
83
|
+
existingLockfile = await readLockfile(projectRoot);
|
|
84
|
+
} catch (error) {
|
|
85
|
+
return report({
|
|
86
|
+
action: "skipped-invalid-lockfile",
|
|
87
|
+
projectRoot,
|
|
88
|
+
error: stringifyError(error),
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (!existingLockfile) {
|
|
93
|
+
return report({ action: "skipped-no-lockfile", projectRoot });
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const validation = await validateConfig(projectRoot);
|
|
97
|
+
if (!validation.valid || !validation.config) {
|
|
98
|
+
return report({
|
|
99
|
+
action: "skipped-invalid-config",
|
|
100
|
+
projectRoot,
|
|
101
|
+
error:
|
|
102
|
+
validation.errors?.map((entry) => entry.message).join("; ") ??
|
|
103
|
+
"Mandu config validation failed",
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
let mcpConfig: Record<string, unknown> | null;
|
|
108
|
+
try {
|
|
109
|
+
mcpConfig = await readMcpConfig(projectRoot);
|
|
110
|
+
} catch (error) {
|
|
111
|
+
return report({
|
|
112
|
+
action: "skipped-invalid-mcp-config",
|
|
113
|
+
projectRoot,
|
|
114
|
+
error: stringifyError(error),
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
const lockfile = generateLockfile(
|
|
120
|
+
validation.config as ValidatedManduConfig,
|
|
121
|
+
{
|
|
122
|
+
includeSnapshot: existingLockfile.snapshot !== undefined,
|
|
123
|
+
includeMcpServerHashes: true,
|
|
124
|
+
},
|
|
125
|
+
mcpConfig,
|
|
126
|
+
);
|
|
127
|
+
await writeLockfile(projectRoot, lockfile);
|
|
128
|
+
return report({ action: "updated", projectRoot, hash: lockfile.configHash });
|
|
129
|
+
} catch (error) {
|
|
130
|
+
return report({
|
|
131
|
+
action: "skipped-write-failed",
|
|
132
|
+
projectRoot,
|
|
133
|
+
error: stringifyError(error),
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async function hasProjectConfig(projectRoot: string): Promise<boolean> {
|
|
139
|
+
for (const fileName of CONFIG_FILES) {
|
|
140
|
+
if (await Bun.file(path.join(projectRoot, fileName)).exists()) {
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function stringifyError(error: unknown): string {
|
|
148
|
+
return error instanceof Error ? error.message : String(error);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (import.meta.main) {
|
|
152
|
+
await refreshGuardLockAfterInstall();
|
|
153
|
+
}
|
package/src/a11y/run-audit.ts
CHANGED
|
@@ -69,11 +69,11 @@ interface DomProvider {
|
|
|
69
69
|
fromHtml(html: string, url: string): Promise<{ window: unknown; dispose: () => Promise<void> }>;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
const DEFAULT_MAX_FILES = 500;
|
|
73
|
-
const DEFAULT_MIN_IMPACT: AuditImpact = "minor";
|
|
74
|
-
const AXE_CORE_MODULE = "axe-core";
|
|
75
|
-
const JSDOM_MODULE = "jsdom";
|
|
76
|
-
const HAPPY_DOM_MODULE = "happy-dom";
|
|
72
|
+
const DEFAULT_MAX_FILES = 500;
|
|
73
|
+
const DEFAULT_MIN_IMPACT: AuditImpact = "minor";
|
|
74
|
+
const AXE_CORE_MODULE = "axe-core";
|
|
75
|
+
const JSDOM_MODULE = "jsdom";
|
|
76
|
+
const HAPPY_DOM_MODULE = "happy-dom";
|
|
77
77
|
|
|
78
78
|
/**
|
|
79
79
|
* Zero every entry in an impact-count record. Returned by value so
|
|
@@ -103,10 +103,10 @@ async function resolveAxe(options: RunAuditOptions): Promise<AxeLike | null> {
|
|
|
103
103
|
return null;
|
|
104
104
|
}
|
|
105
105
|
};
|
|
106
|
-
|
|
107
|
-
if (options.axeLoader) return tryLoad(options.axeLoader);
|
|
108
|
-
return tryLoad(() => import(AXE_CORE_MODULE));
|
|
109
|
-
}
|
|
106
|
+
|
|
107
|
+
if (options.axeLoader) return tryLoad(options.axeLoader);
|
|
108
|
+
return tryLoad(() => import(AXE_CORE_MODULE));
|
|
109
|
+
}
|
|
110
110
|
|
|
111
111
|
/**
|
|
112
112
|
* Resolve a DOM provider. Prefers jsdom; falls back to HappyDOM via
|
|
@@ -128,9 +128,9 @@ async function resolveDomProvider(options: RunAuditOptions): Promise<DomProvider
|
|
|
128
128
|
return null;
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
-
// Preferred path — jsdom.
|
|
132
|
-
try {
|
|
133
|
-
const jsdom = await import(JSDOM_MODULE);
|
|
131
|
+
// Preferred path — jsdom.
|
|
132
|
+
try {
|
|
133
|
+
const jsdom = await import(JSDOM_MODULE);
|
|
134
134
|
const JSDOMCtor = (jsdom as { JSDOM?: new (html: string, opts?: unknown) => unknown }).JSDOM;
|
|
135
135
|
if (JSDOMCtor) {
|
|
136
136
|
return {
|
|
@@ -154,9 +154,9 @@ async function resolveDomProvider(options: RunAuditOptions): Promise<DomProvider
|
|
|
154
154
|
// jsdom not installed — fall through to HappyDOM.
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
-
// Fallback path — HappyDOM.
|
|
158
|
-
try {
|
|
159
|
-
const happy = await import(HAPPY_DOM_MODULE);
|
|
157
|
+
// Fallback path — HappyDOM.
|
|
158
|
+
try {
|
|
159
|
+
const happy = await import(HAPPY_DOM_MODULE);
|
|
160
160
|
const WindowCtor = (happy as { Window?: new (opts?: { url?: string; innerWidth?: number }) => unknown }).Window;
|
|
161
161
|
if (WindowCtor) {
|
|
162
162
|
return {
|
|
@@ -33,6 +33,24 @@ async function writeFile(root: string, rel: string, content: string): Promise<vo
|
|
|
33
33
|
await fs.writeFile(abs, content, "utf8");
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
async function runGit(root: string, args: string[]): Promise<{ ok: boolean; stdout: string; stderr: string }> {
|
|
37
|
+
try {
|
|
38
|
+
const proc = Bun.spawn(["git", ...args], {
|
|
39
|
+
cwd: root,
|
|
40
|
+
stdout: "pipe",
|
|
41
|
+
stderr: "pipe",
|
|
42
|
+
});
|
|
43
|
+
const [stdout, stderr, exitCode] = await Promise.all([
|
|
44
|
+
new Response(proc.stdout).text(),
|
|
45
|
+
new Response(proc.stderr).text(),
|
|
46
|
+
proc.exited,
|
|
47
|
+
]);
|
|
48
|
+
return { ok: exitCode === 0, stdout, stderr };
|
|
49
|
+
} catch (error) {
|
|
50
|
+
return { ok: false, stdout: "", stderr: error instanceof Error ? error.message : String(error) };
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
36
54
|
describe("agent context", () => {
|
|
37
55
|
let root: string;
|
|
38
56
|
|
|
@@ -174,7 +192,7 @@ describe("agent context", () => {
|
|
|
174
192
|
|
|
175
193
|
expect(report.framework).toBe("mandu");
|
|
176
194
|
expect(report.ok).toBe(true);
|
|
177
|
-
expect(report.checks.map((check) => check.id)).toEqual(["manifest"]);
|
|
195
|
+
expect(report.checks.map((check) => check.id)).toEqual(["internal-api", "manifest"]);
|
|
178
196
|
expect(report.suggestedCommands.map((cmd) => cmd.command)).toContain("bun run typecheck");
|
|
179
197
|
expect(report.nextRepairInput).toBe(".mandu/agent-verify.json");
|
|
180
198
|
|
|
@@ -184,6 +202,35 @@ describe("agent context", () => {
|
|
|
184
202
|
expect(parsed.project.name).toBe("agent-app");
|
|
185
203
|
});
|
|
186
204
|
|
|
205
|
+
it("records changed file reasons and warns on internal API edits", async () => {
|
|
206
|
+
const version = await runGit(root, ["--version"]);
|
|
207
|
+
if (!version.ok) return;
|
|
208
|
+
|
|
209
|
+
expect((await runGit(root, ["init"])).ok).toBe(true);
|
|
210
|
+
await runGit(root, ["config", "user.email", "agent@example.com"]);
|
|
211
|
+
await runGit(root, ["config", "user.name", "Agent"]);
|
|
212
|
+
expect((await runGit(root, ["add", "."])).ok).toBe(true);
|
|
213
|
+
expect((await runGit(root, ["commit", "-m", "initial"])).ok).toBe(true);
|
|
214
|
+
|
|
215
|
+
await writeFile(root, "packages/core/src/runtime/internal-change.ts", "export const value = 1;\n");
|
|
216
|
+
|
|
217
|
+
const report = await buildAgentVerifyReport(root, {
|
|
218
|
+
includeDiagnose: false,
|
|
219
|
+
includeGuard: false,
|
|
220
|
+
includeContract: false,
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
const reason = report.changedFileReasons.find(
|
|
224
|
+
(entry) => entry.file === "packages/core/src/runtime/internal-change.ts",
|
|
225
|
+
);
|
|
226
|
+
expect(reason?.internalApi).toBe(true);
|
|
227
|
+
expect(reason?.recommendedChecks).toContain("bun run check:public-api && bun run check:target-boundaries");
|
|
228
|
+
expect(report.diagnostics.some((diag) => diag.code === "MANDU_VERIFY_INTERNAL_API_EDIT")).toBe(true);
|
|
229
|
+
expect(report.suggestedCommands.map((cmd) => cmd.command)).toContain(
|
|
230
|
+
"bun run check:public-api && bun run check:target-boundaries",
|
|
231
|
+
);
|
|
232
|
+
});
|
|
233
|
+
|
|
187
234
|
it("turns a verify report into repair actions", async () => {
|
|
188
235
|
await writeAgentVerifyReport(root, {
|
|
189
236
|
schemaVersion: 1,
|
|
@@ -197,6 +244,7 @@ describe("agent context", () => {
|
|
|
197
244
|
configFile: null,
|
|
198
245
|
},
|
|
199
246
|
changedFiles: [],
|
|
247
|
+
changedFileReasons: [],
|
|
200
248
|
gitAvailable: false,
|
|
201
249
|
notes: [],
|
|
202
250
|
ok: false,
|