swallowkit 1.0.0-beta.56 → 1.0.0-beta.58

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 (40) hide show
  1. package/README.ja.md +1 -0
  2. package/README.md +1 -0
  3. package/dist/cli/commands/init.d.ts.map +1 -1
  4. package/dist/cli/commands/init.js +12 -498
  5. package/dist/cli/commands/init.js.map +1 -1
  6. package/dist/cli/commands/scaffold.d.ts.map +1 -1
  7. package/dist/cli/commands/scaffold.js +8 -6
  8. package/dist/cli/commands/scaffold.js.map +1 -1
  9. package/dist/core/operations/file-session.d.ts +3 -0
  10. package/dist/core/operations/file-session.d.ts.map +1 -1
  11. package/dist/core/operations/file-session.js +8 -0
  12. package/dist/core/operations/file-session.js.map +1 -1
  13. package/dist/core/operations/scaffold-plan.d.ts.map +1 -1
  14. package/dist/core/operations/scaffold-plan.js +1 -9
  15. package/dist/core/operations/scaffold-plan.js.map +1 -1
  16. package/dist/core/project/agent-instructions.d.ts +29 -0
  17. package/dist/core/project/agent-instructions.d.ts.map +1 -0
  18. package/dist/core/project/agent-instructions.js +159 -0
  19. package/dist/core/project/agent-instructions.js.map +1 -0
  20. package/dist/core/scaffold/native-schema-generator.d.ts +7 -1
  21. package/dist/core/scaffold/native-schema-generator.d.ts.map +1 -1
  22. package/dist/core/scaffold/native-schema-generator.js +104 -47
  23. package/dist/core/scaffold/native-schema-generator.js.map +1 -1
  24. package/package.json +1 -1
  25. package/src/__tests__/agent-instructions.test.ts +237 -0
  26. package/src/__tests__/init.test.ts +2 -0
  27. package/src/__tests__/plan-apply.test.ts +167 -0
  28. package/src/__tests__/scaffold.test.ts +2 -2
  29. package/src/cli/commands/init.ts +19 -525
  30. package/src/cli/commands/scaffold.ts +15 -8
  31. package/src/core/operations/file-session.ts +12 -0
  32. package/src/core/operations/scaffold-plan.ts +2 -11
  33. package/src/core/project/agent-instructions.ts +167 -0
  34. package/src/core/project/agent-templates/AGENTS.md +442 -0
  35. package/src/core/project/agent-templates/CLAUDE.md +28 -0
  36. package/src/core/project/agent-templates/azure-functions.instructions.md +22 -0
  37. package/src/core/project/agent-templates/bff-routes.instructions.md +18 -0
  38. package/src/core/project/agent-templates/copilot-instructions.md +24 -0
  39. package/src/core/project/agent-templates/shared-models.instructions.md +35 -0
  40. package/src/core/scaffold/native-schema-generator.ts +152 -70
@@ -0,0 +1,237 @@
1
+ import * as fs from "fs";
2
+ import * as os from "os";
3
+ import * as path from "path";
4
+ import {
5
+ AGENT_TEMPLATE_FILES,
6
+ AgentInstructionContext,
7
+ buildAgentsMd,
8
+ buildClaudeMd,
9
+ buildCopilotInstructions,
10
+ buildPathSpecificInstructions,
11
+ readAgentInstructionTemplate,
12
+ writeAgentInstructionFiles,
13
+ } from "../core/project/agent-instructions";
14
+
15
+ const representativeContext: AgentInstructionContext = {
16
+ projectName: "sample-app",
17
+ backendLanguage: "typescript",
18
+ packageManager: "npm",
19
+ };
20
+
21
+ const repositoryRoot = path.resolve(__dirname, "../..");
22
+
23
+ describe("coding-agent instruction contract", () => {
24
+ it("applies SwallowKit before implementation and composes with external processes", () => {
25
+ const agentsMd = buildAgentsMd(representativeContext);
26
+
27
+ expect(agentsMd).toContain("discovery, requirements clarification");
28
+ expect(agentsMd).toContain("specification, planning, ticket decomposition");
29
+ expect(agentsMd).toContain("not only during code generation");
30
+ expect(agentsMd).toContain("How SwallowKit Fits Into the Development Process");
31
+ expect(agentsMd).toContain("does **not** own the overall software development process");
32
+ expect(agentsMd).toContain("External development-process skills and SwallowKit are intended to compose");
33
+ });
34
+
35
+ it("requires inspection before questions or design instead of remembered capabilities", () => {
36
+ const agentsMd = buildAgentsMd(representativeContext);
37
+
38
+ expect(agentsMd).toContain("Do Not Assume SwallowKit Capabilities From Memory");
39
+ expect(agentsMd).toContain("Inspect Before Asking or Designing");
40
+ expect(agentsMd).toContain("should be answered by the agent instead of being delegated to the human");
41
+ expect(agentsMd).toContain("npx swallowkit machine inspect project");
42
+ expect(agentsMd).toContain("npx swallowkit machine inspect entities");
43
+ expect(agentsMd).toContain("npx swallowkit machine inspect routes");
44
+ expect(agentsMd).toContain("npx swallowkit machine inspect boundaries");
45
+ });
46
+
47
+ it("defines ownership, vertical slices, plan/apply, verification, and approval gates", () => {
48
+ const agentsMd = buildAgentsMd(representativeContext);
49
+
50
+ expect(agentsMd).toContain("### ai-authored");
51
+ expect(agentsMd).toContain("### deterministic");
52
+ expect(agentsMd).toContain("### shared");
53
+ expect(agentsMd).toContain("vertical slices");
54
+ expect(agentsMd).toContain("Do not split work into horizontal tickets");
55
+ expect(agentsMd).toContain("Deterministic Changes: Plan → Apply → Verify");
56
+ expect(agentsMd).toContain("npx swallowkit machine plan scaffold <model>");
57
+ expect(agentsMd).toContain("Verification Is a Completion Gate");
58
+ expect(agentsMd).toContain("semantic code review answers different questions");
59
+ expect(agentsMd).toContain("When the status is `requires-human`, stop");
60
+ expect(agentsMd).toContain("never bypass its\napproval gate");
61
+ });
62
+
63
+ it("uses MCP first and the package-manager-specific machine fallback", () => {
64
+ const npmDocument = buildAgentsMd(representativeContext);
65
+ const pnpmDocument = buildAgentsMd({
66
+ ...representativeContext,
67
+ packageManager: "pnpm",
68
+ });
69
+
70
+ expect(npmDocument).toContain("Prefer the `swallowkit_*` MCP tools when available");
71
+ expect(npmDocument).toContain("npx swallowkit machine ...");
72
+ expect(pnpmDocument).toContain("pnpm swallowkit machine ...");
73
+ expect(pnpmDocument).not.toContain("npx swallowkit machine ...");
74
+ });
75
+
76
+ it("keeps Claude and Copilot as self-identifying adapters to canonical AGENTS.md", () => {
77
+ const adapters = [
78
+ buildClaudeMd(representativeContext),
79
+ buildCopilotInstructions(representativeContext),
80
+ ];
81
+
82
+ for (const adapter of adapters) {
83
+ expect(adapter).toContain("This is a **SwallowKit** project");
84
+ expect(adapter).toContain("root `AGENTS.md`");
85
+ expect(adapter).toContain("canonical project contract");
86
+ expect(adapter).toContain("discovery");
87
+ expect(adapter).toContain("specification");
88
+ expect(adapter).toContain("planning");
89
+ expect(adapter).toContain("Never hand-edit deterministic SwallowKit-managed artifacts");
90
+ expect(adapter).toContain("SwallowKit verification");
91
+ }
92
+ });
93
+
94
+ it("aligns path-specific instructions with the machine plan/apply workflow", () => {
95
+ const instructions = buildPathSpecificInstructions(representativeContext);
96
+ const sharedModels = instructions[AGENT_TEMPLATE_FILES.sharedModelsInstructions];
97
+
98
+ expect(sharedModels).toContain("swallowkit-modify-model");
99
+ expect(sharedModels).toContain(".swallowkit/workflows/modify-model.md");
100
+ expect(sharedModels).toContain("inspect drift → plan scaffold → apply scaffold → verify project");
101
+ expect(sharedModels).toContain("Prefer the equivalent `swallowkit_*` MCP tools");
102
+
103
+ for (const content of Object.values(instructions)) {
104
+ expect(content).toContain("root `AGENTS.md` is the canonical contract");
105
+ expect(content).toMatch(/Never|never|do not/);
106
+ expect(content).toContain("requires-human");
107
+ expect(content).not.toMatch(/run `(?:npx|pnpm )?swallowkit scaffold/);
108
+ expect(content).not.toContain("rerunning `swallowkit scaffold`");
109
+ }
110
+ });
111
+
112
+ it("writes the generated integration files from the builders", () => {
113
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "swallowkit-agent-docs-"));
114
+ try {
115
+ const documents = writeAgentInstructionFiles(tempDir, representativeContext);
116
+ expect(fs.readFileSync(path.join(tempDir, "AGENTS.md"), "utf-8")).toBe(documents.agentsMd);
117
+ expect(fs.readFileSync(path.join(tempDir, "CLAUDE.md"), "utf-8")).toBe(documents.claudeMd);
118
+ expect(
119
+ fs.readFileSync(path.join(tempDir, ".github", "copilot-instructions.md"), "utf-8")
120
+ ).toBe(documents.copilotInstructions);
121
+ for (const [fileName, content] of Object.entries(documents.pathSpecificInstructions)) {
122
+ expect(
123
+ fs.readFileSync(path.join(tempDir, ".github", "instructions", fileName), "utf-8")
124
+ ).toBe(content);
125
+ }
126
+ } finally {
127
+ fs.rmSync(tempDir, { recursive: true, force: true });
128
+ }
129
+ });
130
+
131
+ it("covers the requested model-change, new-feature, BFF, and deployment scenarios", () => {
132
+ const agentsMd = buildAgentsMd(representativeContext);
133
+ const pathInstructions = buildPathSpecificInstructions(representativeContext);
134
+
135
+ // Scenario A: existing Todo field change.
136
+ expect(agentsMd).toContain("For an existing model change, the normal pattern is");
137
+ expect(agentsMd).toContain("edit source-of-truth model");
138
+ expect(agentsMd).toContain("inspect drift");
139
+ expect(agentsMd).toContain("verify before final semantic review");
140
+
141
+ // Scenario B: new Order behavior starts with discovery and the add-model capability.
142
+ expect(agentsMd).toContain("requirements clarification");
143
+ expect(agentsMd).toContain("machine inspect entities");
144
+ expect(agentsMd).toContain("machine inspect routes");
145
+ expect(agentsMd).toContain("Use the `swallowkit-add-model` Agent Skill when available");
146
+
147
+ // Scenario C: generated BFF logic is redirected to the correct ownership zone.
148
+ const bff = pathInstructions[AGENT_TEMPLATE_FILES.bffRoutesInstructions];
149
+ const functions = pathInstructions[AGENT_TEMPLATE_FILES.azureFunctionsInstructions];
150
+ expect(bff).toContain("Never place business logic, database access");
151
+ expect(bff).toContain("Never directly edit a route reported as deterministic");
152
+ expect(functions).toContain("Put custom behavior only in an ai-authored location");
153
+
154
+ // Scenario D: a broad deployment request never implies provisioning approval.
155
+ expect(agentsMd).toContain("A broad request to deploy does not itself grant the explicit");
156
+ expect(agentsMd).toContain("Use the `swallowkit-provision` Agent Skill or runbook");
157
+ expect(agentsMd).toContain("When the status is `requires-human`, stop");
158
+ });
159
+ });
160
+
161
+ describe("coding-agent documentation synchronization", () => {
162
+ const englishGuidePath = path.join(repositoryRoot, "docs", "en", "coding-agent-guide.md");
163
+ const japaneseGuidePath = path.join(repositoryRoot, "docs", "ja", "coding-agent-guide.md");
164
+
165
+ it("publishes each complete English original directly from its canonical template", () => {
166
+ const englishGuide = fs.readFileSync(englishGuidePath, "utf-8");
167
+ const japaneseGuide = fs.readFileSync(japaneseGuidePath, "utf-8");
168
+
169
+ const expectedIncludes = [
170
+ "AGENTS.md",
171
+ "CLAUDE.md",
172
+ "copilot-instructions.md",
173
+ ];
174
+ for (const fileName of expectedIncludes) {
175
+ const include = `<<< ../../src/core/project/agent-templates/${fileName}{md}`;
176
+ expect(englishGuide).toContain(include);
177
+ expect(japaneseGuide).toContain(include);
178
+ expect(readAgentInstructionTemplate(fileName).length).toBeGreaterThan(500);
179
+ }
180
+
181
+ expect(englishGuide).toContain("complete English templates");
182
+ expect(englishGuide).toContain("not\nabridged excerpts");
183
+ expect(japaneseGuide).toContain("抜粋ではありません");
184
+ });
185
+
186
+ it("keeps the Japanese AGENTS.md translation aligned by canonical section", () => {
187
+ const source = readAgentInstructionTemplate(AGENT_TEMPLATE_FILES.agentsMd);
188
+ const translation = fs.readFileSync(
189
+ path.join(repositoryRoot, "docs", "ja", "generated-agent-docs", "AGENTS.ja.md"),
190
+ "utf-8"
191
+ );
192
+ const sourceSections = [...source.matchAll(/^## (.+)$/gm)].map((match) => match[1]);
193
+ const translatedSourceSections = [
194
+ ...translation.matchAll(/^<!-- source-section: (.+) -->$/gm),
195
+ ].map((match) => match[1]);
196
+
197
+ expect(translatedSourceSections).toEqual(sourceSections);
198
+ });
199
+
200
+ it("exposes matching major EN/JA guide topics and both sidebar entries", () => {
201
+ const englishGuide = fs.readFileSync(englishGuidePath, "utf-8");
202
+ const japaneseGuide = fs.readFileSync(japaneseGuidePath, "utf-8");
203
+ const vitepressConfig = fs.readFileSync(
204
+ path.join(repositoryRoot, "docs", ".vitepress", "config.mts"),
205
+ "utf-8"
206
+ );
207
+
208
+ for (const topic of [
209
+ "Generated agent integration files",
210
+ "SwallowKit across the development lifecycle",
211
+ "Inspect before asking or designing",
212
+ "Responsibility boundary",
213
+ "Planning and ticketing",
214
+ "Implementation workflow",
215
+ "Verification versus semantic review",
216
+ "Human approval",
217
+ "Composing with external development processes",
218
+ ]) {
219
+ expect(englishGuide.toLowerCase()).toContain(topic.toLowerCase());
220
+ }
221
+ for (const topic of [
222
+ "生成される Agent integration files",
223
+ "開発ライフサイクル全体での SwallowKit",
224
+ "質問・設計の前に inspect する",
225
+ "Responsibility boundary",
226
+ "Planning と ticketing",
227
+ "Implementation workflow",
228
+ "Verification と semantic review の違い",
229
+ "Human approval",
230
+ "外部 development process との composition",
231
+ ]) {
232
+ expect(japaneseGuide).toContain(topic);
233
+ }
234
+ expect(vitepressConfig).toContain("/en/coding-agent-guide");
235
+ expect(vitepressConfig).toContain("/ja/coding-agent-guide");
236
+ });
237
+ });
@@ -40,6 +40,7 @@ export default nextConfig;
40
40
  const updated = injectSwallowKitNextConfig(original, "sample-app");
41
41
 
42
42
  expect(updated).toContain("output: 'standalone'");
43
+ expect(updated).toContain("agentRules: false");
43
44
  expect(updated).toContain("transpilePackages: ['@sample-app/shared']");
44
45
  expect(updated).toContain("serverExternalPackages: ['applicationinsights', 'diagnostic-channel-publishers']");
45
46
  expect(updated).not.toContain("turbopackUseSystemTlsCerts");
@@ -57,6 +58,7 @@ module.exports = nextConfig;
57
58
  const updated = injectSwallowKitNextConfig(original, "sample-app");
58
59
 
59
60
  expect(updated).toContain("transpilePackages: ['@sample-app/shared']");
61
+ expect(updated).toContain("agentRules: false");
60
62
  expect(updated).toContain("module.exports = nextConfig;");
61
63
  });
62
64
 
@@ -1,6 +1,8 @@
1
1
  import * as fs from "fs";
2
2
  import * as os from "os";
3
3
  import * as path from "path";
4
+ import * as childProcess from "child_process";
5
+ import { EventEmitter } from "events";
4
6
  import { runMachineCli } from "../machine";
5
7
 
6
8
  jest.mock("child_process", () => {
@@ -63,6 +65,49 @@ function createProjectFixture(rootDir: string): void {
63
65
  );
64
66
  }
65
67
 
68
+ function createNativeBackendFixture(rootDir: string, backendLanguage: "csharp" | "python"): void {
69
+ createProjectFixture(rootDir);
70
+ writeFile(
71
+ path.join(rootDir, "swallowkit.config.js"),
72
+ `module.exports = {
73
+ database: {
74
+ connectionString: 'AccountEndpoint=https://example.local;',
75
+ },
76
+ backend: {
77
+ language: '${backendLanguage}',
78
+ },
79
+ api: {
80
+ endpoint: '/api/_swallowkit',
81
+ },
82
+ };
83
+ `
84
+ );
85
+
86
+ if (backendLanguage === "csharp") {
87
+ writeFile(path.join(rootDir, "functions", "SwallowKit.Functions.csproj"), "<Project />\n");
88
+ } else {
89
+ writeFile(
90
+ path.join(rootDir, "functions", "function_app.py"),
91
+ "from azure.functions import FunctionApp\n\napp = FunctionApp()\n\n# SwallowKit scaffold registrations\n"
92
+ );
93
+ }
94
+ }
95
+
96
+ function mockSuccessfulNativeCodegen(): () => void {
97
+ const spawnSyncMock = childProcess.spawnSync as unknown as any;
98
+ const spawnMock = childProcess.spawn as unknown as any;
99
+ spawnSyncMock.mockReturnValue({ status: 0 });
100
+ spawnMock.mockImplementation(() => {
101
+ const child = new EventEmitter() as childProcess.ChildProcess;
102
+ process.nextTick(() => child.emit("close", 0));
103
+ return child;
104
+ });
105
+ return () => {
106
+ spawnMock.mockReset();
107
+ spawnSyncMock.mockReset();
108
+ };
109
+ }
110
+
66
111
  async function runMachine(argv: string[]): Promise<{ response: any; exitCode: number }> {
67
112
  const writes: string[] = [];
68
113
  const originalWrite = process.stdout.write.bind(process.stdout);
@@ -163,6 +208,93 @@ describe("plan / apply / drift / verify machine commands", () => {
163
208
  expect(fs.existsSync(path.join(tempDir, ".swallowkit", "state", "plans", `${planId}.json`))).toBe(false);
164
209
  });
165
210
 
211
+ it.each([
212
+ {
213
+ backendLanguage: "csharp" as const,
214
+ modelPath: "functions/generated/csharp-models/src/SwallowKitBackendModels/Model/Todo.cs",
215
+ },
216
+ {
217
+ backendLanguage: "python" as const,
218
+ modelPath: "functions/generated/python-models/backend_models/models/todo.py",
219
+ },
220
+ ])(
221
+ "includes $backendLanguage native schema artifacts in plan, ledger, and approval checks",
222
+ async ({ backendLanguage, modelPath }) => {
223
+ createNativeBackendFixture(tempDir, backendLanguage);
224
+ const restoreCodegenMocks = mockSuccessfulNativeCodegen();
225
+
226
+ try {
227
+ const plan = await runMachine(machineArgs("plan", "scaffold", "todo", "--api-only"));
228
+
229
+ expect(plan.response).toEqual(expect.objectContaining({ ok: true, status: "complete" }));
230
+ expect(plan.exitCode).toBe(0);
231
+ expect(plan.response.data.warnings).not.toEqual(
232
+ expect.arrayContaining([expect.stringContaining("external-codegen")])
233
+ );
234
+ expect(plan.response.data.operations).toEqual(
235
+ expect.arrayContaining([
236
+ expect.objectContaining({ path: "functions/openapi/todo.openapi.json", action: "create" }),
237
+ expect.objectContaining({ path: modelPath, action: "create", ownership: "managed" }),
238
+ ])
239
+ );
240
+ expect(fs.existsSync(path.join(tempDir, "functions", "openapi"))).toBe(false);
241
+ expect(fs.existsSync(path.join(tempDir, modelPath))).toBe(false);
242
+ expect(childProcess.spawn).not.toHaveBeenCalled();
243
+
244
+ const applied = await runMachine(
245
+ machineArgs("apply", "scaffold", "--plan", plan.response.data.planId)
246
+ );
247
+ expect(applied.exitCode).toBe(0);
248
+ expect(fs.existsSync(path.join(tempDir, modelPath))).toBe(true);
249
+
250
+ const ledger = JSON.parse(
251
+ fs.readFileSync(path.join(tempDir, ".swallowkit", "artifacts.json"), "utf-8")
252
+ );
253
+ expect(ledger.artifacts).toEqual(
254
+ expect.arrayContaining([
255
+ expect.objectContaining({ path: modelPath, ownership: "managed", generator: "native-schema" }),
256
+ ])
257
+ );
258
+
259
+ fs.appendFileSync(path.join(tempDir, modelPath), "\n// customized\n");
260
+ const denied = await runMachine(machineArgs("apply", "scaffold", "todo", "--api-only"));
261
+ expect(denied.exitCode).toBe(1);
262
+ expect(denied.response.error.code).toBe("approval-required");
263
+ expect(denied.response.error.details.conflicts).toEqual(
264
+ expect.arrayContaining([
265
+ expect.objectContaining({ path: modelPath, conflictReason: "modified-after-generation" }),
266
+ ])
267
+ );
268
+ } finally {
269
+ restoreCodegenMocks();
270
+ }
271
+ }
272
+ );
273
+
274
+ it("leaves the project untouched when the native toolchain validation fails on apply", async () => {
275
+ createNativeBackendFixture(tempDir, "csharp");
276
+ const spawnSyncMock = childProcess.spawnSync as unknown as any;
277
+ // dotnet --version が失敗 → .NET SDK 不在としてプリフライトで拒否される
278
+ spawnSyncMock.mockReturnValue({ status: 1 });
279
+
280
+ try {
281
+ const { response, exitCode } = await runMachine(machineArgs("apply", "scaffold", "todo", "--api-only"));
282
+
283
+ expect(exitCode).toBe(1);
284
+ expect(response.ok).toBe(false);
285
+ expect(response.error.message).toContain(".NET SDK is required");
286
+
287
+ expect(fs.existsSync(path.join(tempDir, "app"))).toBe(false);
288
+ expect(fs.existsSync(path.join(tempDir, "lib"))).toBe(false);
289
+ expect(fs.existsSync(path.join(tempDir, "functions", "openapi"))).toBe(false);
290
+ expect(fs.existsSync(path.join(tempDir, "functions", "generated"))).toBe(false);
291
+ expect(fs.existsSync(path.join(tempDir, "functions", "Crud"))).toBe(false);
292
+ expect(fs.existsSync(path.join(tempDir, ".swallowkit", "artifacts.json"))).toBe(false);
293
+ } finally {
294
+ spawnSyncMock.mockReset();
295
+ }
296
+ });
297
+
166
298
  it("rejects a stale plan when files change after planning", async () => {
167
299
  createProjectFixture(tempDir);
168
300
 
@@ -186,6 +318,41 @@ describe("plan / apply / drift / verify machine commands", () => {
186
318
  expect(fs.existsSync(path.join(tempDir, "app"))).toBe(false);
187
319
  });
188
320
 
321
+ it("rejects a native backend plan when a nested model changes after planning", async () => {
322
+ createNativeBackendFixture(tempDir, "csharp");
323
+ writeFile(path.join(tempDir, "shared", "models", "member.ts"), createModelSource("Member"));
324
+ writeFile(
325
+ path.join(tempDir, "shared", "models", "todo.ts"),
326
+ `import { z } from 'zod/v4';
327
+ import { Member } from './member';
328
+
329
+ export const Todo = z.object({
330
+ id: z.string(),
331
+ name: z.string().min(1),
332
+ member: Member,
333
+ });
334
+
335
+ export type Todo = z.infer<typeof Todo>;
336
+ `
337
+ );
338
+
339
+ const plan = await runMachine(machineArgs("plan", "scaffold", "todo", "--api-only"));
340
+ expect(plan.response).toEqual(expect.objectContaining({ ok: true, status: "complete" }));
341
+ expect(plan.response.data.fingerprints["shared/models/member.ts"]).toMatch(/^[0-9a-f]{64}$/);
342
+
343
+ writeFile(
344
+ path.join(tempDir, "shared", "models", "member.ts"),
345
+ createModelSource("Member", "\n role: z.string().optional(),")
346
+ );
347
+
348
+ const applied = await runMachine(
349
+ machineArgs("apply", "scaffold", "--plan", plan.response.data.planId)
350
+ );
351
+ expect(applied.exitCode).toBe(1);
352
+ expect(applied.response.error.code).toBe("stale-plan");
353
+ expect(applied.response.error.details.changedFiles).toContain("shared/models/member.ts");
354
+ });
355
+
189
356
  it("requires approval to overwrite files modified after generation, then applies with --approve", async () => {
190
357
  createProjectFixture(tempDir);
191
358
 
@@ -167,7 +167,7 @@ describe("native schema generators", () => {
167
167
  });
168
168
 
169
169
  it("builds NSwag arguments for model-only C# generation", () => {
170
- const outputPath = path.join("C:\\temp\\generated\\csharp-models", ".native-temp", "Contracts.cs");
170
+ const outputPath = path.join(os.tmpdir(), "swallowkit-csharp-codegen-example", "Contracts.cs");
171
171
  const args = getCSharpNativeGeneratorArgs("C:\\temp\\todo.openapi.json", outputPath);
172
172
 
173
173
  expect(args.slice(0, 4)).toEqual(["tool", "run", "nswag", "openapi2csclient"]);
@@ -178,7 +178,7 @@ describe("native schema generators", () => {
178
178
  });
179
179
 
180
180
  it("builds datamodel-code-generator arguments for python assets", () => {
181
- const outputPath = path.join("C:\\temp\\generated\\python-models", ".native-temp", "models.py");
181
+ const outputPath = path.join(os.tmpdir(), "swallowkit-python-codegen-example", "models.py");
182
182
  const args = getPythonNativeGeneratorArgs("C:\\temp\\todo.openapi.json", outputPath);
183
183
 
184
184
  expect(args).toEqual(