swallowkit 1.0.0-beta.6 → 1.0.0-beta.61

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 (296) hide show
  1. package/README.ja.md +87 -175
  2. package/README.md +87 -176
  3. package/dist/__tests__/fixtures.d.ts +12 -0
  4. package/dist/__tests__/fixtures.d.ts.map +1 -1
  5. package/dist/__tests__/fixtures.js +101 -0
  6. package/dist/__tests__/fixtures.js.map +1 -1
  7. package/dist/cli/commands/add-auth.d.ts +15 -0
  8. package/dist/cli/commands/add-auth.d.ts.map +1 -0
  9. package/dist/cli/commands/add-auth.js +790 -0
  10. package/dist/cli/commands/add-auth.js.map +1 -0
  11. package/dist/cli/commands/add-connector.d.ts +20 -0
  12. package/dist/cli/commands/add-connector.d.ts.map +1 -0
  13. package/dist/cli/commands/add-connector.js +164 -0
  14. package/dist/cli/commands/add-connector.js.map +1 -0
  15. package/dist/cli/commands/cosmos-worktree-isolation.d.ts +25 -0
  16. package/dist/cli/commands/cosmos-worktree-isolation.d.ts.map +1 -0
  17. package/dist/cli/commands/cosmos-worktree-isolation.js +124 -0
  18. package/dist/cli/commands/cosmos-worktree-isolation.js.map +1 -0
  19. package/dist/cli/commands/create-model.d.ts +1 -4
  20. package/dist/cli/commands/create-model.d.ts.map +1 -1
  21. package/dist/cli/commands/create-model.js +20 -82
  22. package/dist/cli/commands/create-model.js.map +1 -1
  23. package/dist/cli/commands/dev-seeds.d.ts +62 -0
  24. package/dist/cli/commands/dev-seeds.d.ts.map +1 -0
  25. package/dist/cli/commands/dev-seeds.js +474 -0
  26. package/dist/cli/commands/dev-seeds.js.map +1 -0
  27. package/dist/cli/commands/dev.d.ts +38 -0
  28. package/dist/cli/commands/dev.d.ts.map +1 -1
  29. package/dist/cli/commands/dev.js +733 -155
  30. package/dist/cli/commands/dev.js.map +1 -1
  31. package/dist/cli/commands/index.d.ts +1 -0
  32. package/dist/cli/commands/index.d.ts.map +1 -1
  33. package/dist/cli/commands/index.js +3 -1
  34. package/dist/cli/commands/index.js.map +1 -1
  35. package/dist/cli/commands/init.d.ts +74 -0
  36. package/dist/cli/commands/init.d.ts.map +1 -1
  37. package/dist/cli/commands/init.js +1376 -930
  38. package/dist/cli/commands/init.js.map +1 -1
  39. package/dist/cli/commands/scaffold.d.ts +24 -0
  40. package/dist/cli/commands/scaffold.d.ts.map +1 -1
  41. package/dist/cli/commands/scaffold.js +473 -90
  42. package/dist/cli/commands/scaffold.js.map +1 -1
  43. package/dist/cli/index.d.ts +5 -1
  44. package/dist/cli/index.d.ts.map +1 -1
  45. package/dist/cli/index.js +335 -55
  46. package/dist/cli/index.js.map +1 -1
  47. package/dist/core/config.d.ts +14 -3
  48. package/dist/core/config.d.ts.map +1 -1
  49. package/dist/core/config.js +179 -8
  50. package/dist/core/config.js.map +1 -1
  51. package/dist/core/mock/connector-mock-server.d.ts +101 -0
  52. package/dist/core/mock/connector-mock-server.d.ts.map +1 -0
  53. package/dist/core/mock/connector-mock-server.js +480 -0
  54. package/dist/core/mock/connector-mock-server.js.map +1 -0
  55. package/dist/core/mock/zod-mock-generator.d.ts +14 -0
  56. package/dist/core/mock/zod-mock-generator.d.ts.map +1 -0
  57. package/dist/core/mock/zod-mock-generator.js +163 -0
  58. package/dist/core/mock/zod-mock-generator.js.map +1 -0
  59. package/dist/core/operations/auth-operations.d.ts +61 -0
  60. package/dist/core/operations/auth-operations.d.ts.map +1 -0
  61. package/dist/core/operations/auth-operations.js +272 -0
  62. package/dist/core/operations/auth-operations.js.map +1 -0
  63. package/dist/core/operations/create-model.d.ts +15 -0
  64. package/dist/core/operations/create-model.d.ts.map +1 -0
  65. package/dist/core/operations/create-model.js +185 -0
  66. package/dist/core/operations/create-model.js.map +1 -0
  67. package/dist/core/operations/file-session.d.ts +56 -0
  68. package/dist/core/operations/file-session.d.ts.map +1 -0
  69. package/dist/core/operations/file-session.js +203 -0
  70. package/dist/core/operations/file-session.js.map +1 -0
  71. package/dist/core/operations/provision-operations.d.ts +52 -0
  72. package/dist/core/operations/provision-operations.d.ts.map +1 -0
  73. package/dist/core/operations/provision-operations.js +211 -0
  74. package/dist/core/operations/provision-operations.js.map +1 -0
  75. package/dist/core/operations/runtime.d.ts +37 -0
  76. package/dist/core/operations/runtime.d.ts.map +1 -0
  77. package/dist/core/operations/runtime.js +248 -0
  78. package/dist/core/operations/runtime.js.map +1 -0
  79. package/dist/core/operations/scaffold-apply.d.ts +21 -0
  80. package/dist/core/operations/scaffold-apply.d.ts.map +1 -0
  81. package/dist/core/operations/scaffold-apply.js +57 -0
  82. package/dist/core/operations/scaffold-apply.js.map +1 -0
  83. package/dist/core/operations/scaffold-machine.d.ts +16 -0
  84. package/dist/core/operations/scaffold-machine.d.ts.map +1 -0
  85. package/dist/core/operations/scaffold-machine.js +51 -0
  86. package/dist/core/operations/scaffold-machine.js.map +1 -0
  87. package/dist/core/operations/scaffold-plan.d.ts +43 -0
  88. package/dist/core/operations/scaffold-plan.d.ts.map +1 -0
  89. package/dist/core/operations/scaffold-plan.js +187 -0
  90. package/dist/core/operations/scaffold-plan.js.map +1 -0
  91. package/dist/core/project/agent-instructions.d.ts +29 -0
  92. package/dist/core/project/agent-instructions.d.ts.map +1 -0
  93. package/dist/core/project/agent-instructions.js +160 -0
  94. package/dist/core/project/agent-instructions.js.map +1 -0
  95. package/dist/core/project/artifacts.d.ts +58 -0
  96. package/dist/core/project/artifacts.d.ts.map +1 -0
  97. package/dist/core/project/artifacts.js +179 -0
  98. package/dist/core/project/artifacts.js.map +1 -0
  99. package/dist/core/project/boundaries.d.ts +50 -0
  100. package/dist/core/project/boundaries.d.ts.map +1 -0
  101. package/dist/core/project/boundaries.js +117 -0
  102. package/dist/core/project/boundaries.js.map +1 -0
  103. package/dist/core/project/capabilities.d.ts +58 -0
  104. package/dist/core/project/capabilities.d.ts.map +1 -0
  105. package/dist/core/project/capabilities.js +135 -0
  106. package/dist/core/project/capabilities.js.map +1 -0
  107. package/dist/core/project/drift.d.ts +29 -0
  108. package/dist/core/project/drift.d.ts.map +1 -0
  109. package/dist/core/project/drift.js +188 -0
  110. package/dist/core/project/drift.js.map +1 -0
  111. package/dist/core/project/infra.d.ts +49 -0
  112. package/dist/core/project/infra.d.ts.map +1 -0
  113. package/dist/core/project/infra.js +167 -0
  114. package/dist/core/project/infra.js.map +1 -0
  115. package/dist/core/project/manifest.d.ts +92 -0
  116. package/dist/core/project/manifest.d.ts.map +1 -0
  117. package/dist/core/project/manifest.js +321 -0
  118. package/dist/core/project/manifest.js.map +1 -0
  119. package/dist/core/project/state.d.ts +15 -0
  120. package/dist/core/project/state.d.ts.map +1 -0
  121. package/dist/core/project/state.js +106 -0
  122. package/dist/core/project/state.js.map +1 -0
  123. package/dist/core/project/validation.d.ts +20 -0
  124. package/dist/core/project/validation.d.ts.map +1 -0
  125. package/dist/core/project/validation.js +274 -0
  126. package/dist/core/project/validation.js.map +1 -0
  127. package/dist/core/project/workflows.d.ts +43 -0
  128. package/dist/core/project/workflows.d.ts.map +1 -0
  129. package/dist/core/project/workflows.js +299 -0
  130. package/dist/core/project/workflows.js.map +1 -0
  131. package/dist/core/scaffold/auth-generator.d.ts +63 -0
  132. package/dist/core/scaffold/auth-generator.d.ts.map +1 -0
  133. package/dist/core/scaffold/auth-generator.js +1774 -0
  134. package/dist/core/scaffold/auth-generator.js.map +1 -0
  135. package/dist/core/scaffold/connector-functions-generator.d.ts +41 -0
  136. package/dist/core/scaffold/connector-functions-generator.d.ts.map +1 -0
  137. package/dist/core/scaffold/connector-functions-generator.js +1029 -0
  138. package/dist/core/scaffold/connector-functions-generator.js.map +1 -0
  139. package/dist/core/scaffold/functions-generator.d.ts +7 -1
  140. package/dist/core/scaffold/functions-generator.d.ts.map +1 -1
  141. package/dist/core/scaffold/functions-generator.js +771 -69
  142. package/dist/core/scaffold/functions-generator.js.map +1 -1
  143. package/dist/core/scaffold/model-parser.d.ts +21 -1
  144. package/dist/core/scaffold/model-parser.d.ts.map +1 -1
  145. package/dist/core/scaffold/model-parser.js +230 -79
  146. package/dist/core/scaffold/model-parser.js.map +1 -1
  147. package/dist/core/scaffold/native-schema-generator.d.ts +19 -0
  148. package/dist/core/scaffold/native-schema-generator.d.ts.map +1 -0
  149. package/dist/core/scaffold/native-schema-generator.js +753 -0
  150. package/dist/core/scaffold/native-schema-generator.js.map +1 -0
  151. package/dist/core/scaffold/nextjs-generator.d.ts +8 -0
  152. package/dist/core/scaffold/nextjs-generator.d.ts.map +1 -1
  153. package/dist/core/scaffold/nextjs-generator.js +153 -9
  154. package/dist/core/scaffold/nextjs-generator.js.map +1 -1
  155. package/dist/core/scaffold/openapi-generator.d.ts +3 -0
  156. package/dist/core/scaffold/openapi-generator.d.ts.map +1 -0
  157. package/dist/core/scaffold/openapi-generator.js +190 -0
  158. package/dist/core/scaffold/openapi-generator.js.map +1 -0
  159. package/dist/core/scaffold/ui-generator.d.ts +17 -4
  160. package/dist/core/scaffold/ui-generator.d.ts.map +1 -1
  161. package/dist/core/scaffold/ui-generator.js +174 -34
  162. package/dist/core/scaffold/ui-generator.js.map +1 -1
  163. package/dist/core/verify/index.d.ts +69 -0
  164. package/dist/core/verify/index.d.ts.map +1 -0
  165. package/dist/core/verify/index.js +412 -0
  166. package/dist/core/verify/index.js.map +1 -0
  167. package/dist/database/base-model.d.ts +3 -3
  168. package/dist/database/base-model.js +3 -3
  169. package/dist/index.d.ts +2 -2
  170. package/dist/index.d.ts.map +1 -1
  171. package/dist/index.js +2 -1
  172. package/dist/index.js.map +1 -1
  173. package/dist/machine/contracts.d.ts +24 -0
  174. package/dist/machine/contracts.d.ts.map +1 -0
  175. package/dist/machine/contracts.js +3 -0
  176. package/dist/machine/contracts.js.map +1 -0
  177. package/dist/machine/errors.d.ts +14 -0
  178. package/dist/machine/errors.d.ts.map +1 -0
  179. package/dist/machine/errors.js +42 -0
  180. package/dist/machine/errors.js.map +1 -0
  181. package/dist/machine/index.d.ts +3 -0
  182. package/dist/machine/index.d.ts.map +1 -0
  183. package/dist/machine/index.js +566 -0
  184. package/dist/machine/index.js.map +1 -0
  185. package/dist/mcp/index.d.ts +25 -0
  186. package/dist/mcp/index.d.ts.map +1 -0
  187. package/dist/mcp/index.js +425 -0
  188. package/dist/mcp/index.js.map +1 -0
  189. package/dist/types/index.d.ts +109 -0
  190. package/dist/types/index.d.ts.map +1 -1
  191. package/dist/utils/package-manager.d.ts +9 -12
  192. package/dist/utils/package-manager.d.ts.map +1 -1
  193. package/dist/utils/package-manager.js +51 -22
  194. package/dist/utils/package-manager.js.map +1 -1
  195. package/dist/utils/python-uv.d.ts +21 -0
  196. package/dist/utils/python-uv.d.ts.map +1 -0
  197. package/dist/utils/python-uv.js +111 -0
  198. package/dist/utils/python-uv.js.map +1 -0
  199. package/dist/version.d.ts +2 -0
  200. package/dist/version.d.ts.map +1 -0
  201. package/dist/version.js +49 -0
  202. package/dist/version.js.map +1 -0
  203. package/package.json +16 -17
  204. package/src/__tests__/__snapshots__/functions-generator.test.ts.snap +1139 -0
  205. package/src/__tests__/__snapshots__/nextjs-generator.test.ts.snap +206 -0
  206. package/src/__tests__/__snapshots__/ui-generator.test.ts.snap +532 -0
  207. package/src/__tests__/agent-instructions.test.ts +237 -0
  208. package/src/__tests__/agent-loop-phase34.test.ts +513 -0
  209. package/src/__tests__/auth.test.ts +770 -0
  210. package/src/__tests__/bff-swa-auth.test.ts +275 -0
  211. package/src/__tests__/config.test.ts +295 -0
  212. package/src/__tests__/connector-functions-generator.test.ts +288 -0
  213. package/src/__tests__/connector-mock-server.test.ts +459 -0
  214. package/src/__tests__/connector-model-bff.test.ts +162 -0
  215. package/src/__tests__/cosmos-worktree-isolation.test.ts +149 -0
  216. package/src/__tests__/dev-seeds.test.ts +236 -0
  217. package/src/__tests__/dev.test.ts +296 -0
  218. package/src/__tests__/file-session.test.ts +98 -0
  219. package/src/__tests__/fixtures.ts +184 -0
  220. package/src/__tests__/functions-generator.test.ts +237 -0
  221. package/src/__tests__/init.test.ts +363 -0
  222. package/src/__tests__/jest-globals.d.ts +8 -0
  223. package/src/__tests__/machine.test.ts +371 -0
  224. package/src/__tests__/mcp.test.ts +253 -0
  225. package/src/__tests__/model-parser.test.ts +142 -0
  226. package/src/__tests__/multi-auth.test.ts +160 -0
  227. package/src/__tests__/nextjs-generator.test.ts +105 -0
  228. package/src/__tests__/openapi-generator.test.ts +43 -0
  229. package/src/__tests__/package-manager.test.ts +244 -0
  230. package/src/__tests__/plan-apply.test.ts +499 -0
  231. package/src/__tests__/python-uv.test.ts +48 -0
  232. package/src/__tests__/scaffold.test.ts +302 -0
  233. package/src/__tests__/string-utils.test.ts +75 -0
  234. package/src/__tests__/tsconfig.json +15 -0
  235. package/src/__tests__/ui-generator.test.ts +248 -0
  236. package/src/__tests__/zod-mock-generator.test.ts +132 -0
  237. package/src/cli/commands/add-auth.ts +853 -0
  238. package/src/cli/commands/add-connector.ts +159 -0
  239. package/src/cli/commands/cosmos-worktree-isolation.ts +130 -0
  240. package/src/cli/commands/create-model.ts +62 -0
  241. package/src/cli/commands/dev-seeds.ts +629 -0
  242. package/src/cli/commands/dev.ts +1252 -0
  243. package/src/cli/commands/index.ts +9 -0
  244. package/src/cli/commands/init.ts +3414 -0
  245. package/src/cli/commands/provision.ts +193 -0
  246. package/src/cli/commands/scaffold.ts +1081 -0
  247. package/src/cli/index.ts +352 -0
  248. package/src/core/config.ts +380 -0
  249. package/src/core/mock/connector-mock-server.ts +555 -0
  250. package/src/core/mock/zod-mock-generator.ts +205 -0
  251. package/src/core/operations/auth-operations.ts +360 -0
  252. package/src/core/operations/create-model.ts +191 -0
  253. package/src/core/operations/file-session.ts +222 -0
  254. package/src/core/operations/provision-operations.ts +297 -0
  255. package/src/core/operations/runtime.ts +258 -0
  256. package/src/core/operations/scaffold-apply.ts +106 -0
  257. package/src/core/operations/scaffold-machine.ts +77 -0
  258. package/src/core/operations/scaffold-plan.ts +215 -0
  259. package/src/core/project/agent-instructions.ts +167 -0
  260. package/src/core/project/agent-templates/AGENTS.md +504 -0
  261. package/src/core/project/agent-templates/CLAUDE.md +31 -0
  262. package/src/core/project/agent-templates/azure-functions.instructions.md +22 -0
  263. package/src/core/project/agent-templates/bff-routes.instructions.md +18 -0
  264. package/src/core/project/agent-templates/copilot-instructions.md +27 -0
  265. package/src/core/project/agent-templates/shared-models.instructions.md +72 -0
  266. package/src/core/project/artifacts.ts +205 -0
  267. package/src/core/project/boundaries.ts +170 -0
  268. package/src/core/project/capabilities.ts +193 -0
  269. package/src/core/project/drift.ts +202 -0
  270. package/src/core/project/infra.ts +196 -0
  271. package/src/core/project/manifest.ts +402 -0
  272. package/src/core/project/state.ts +77 -0
  273. package/src/core/project/validation.ts +295 -0
  274. package/src/core/project/workflows.ts +295 -0
  275. package/src/core/scaffold/auth-generator.ts +1824 -0
  276. package/src/core/scaffold/connector-functions-generator.ts +1133 -0
  277. package/src/core/scaffold/functions-generator.ts +972 -0
  278. package/src/core/scaffold/model-parser.ts +781 -0
  279. package/src/core/scaffold/native-schema-generator.ts +904 -0
  280. package/src/core/scaffold/nextjs-generator.ts +382 -0
  281. package/src/core/scaffold/openapi-generator.ts +212 -0
  282. package/src/core/scaffold/ui-generator.ts +1100 -0
  283. package/src/core/verify/index.ts +468 -0
  284. package/src/database/base-model.ts +184 -0
  285. package/src/database/client.ts +140 -0
  286. package/src/database/repository.ts +104 -0
  287. package/src/database/runtime-check.ts +25 -0
  288. package/src/index.ts +27 -0
  289. package/src/machine/contracts.ts +33 -0
  290. package/src/machine/errors.ts +45 -0
  291. package/src/machine/index.ts +690 -0
  292. package/src/mcp/index.ts +423 -0
  293. package/src/types/index.ts +182 -0
  294. package/src/utils/package-manager.ts +251 -0
  295. package/src/utils/python-uv.ts +96 -0
  296. package/src/version.ts +13 -0
@@ -0,0 +1,258 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+
4
+ export interface CapturedConsoleMessages {
5
+ logs: string[];
6
+ warnings: string[];
7
+ errors: string[];
8
+ }
9
+
10
+ export interface FileMutationSummary {
11
+ createdFiles: string[];
12
+ updatedFiles: string[];
13
+ appendedFiles: string[];
14
+ deletedFiles: string[];
15
+ createdDirectories: string[];
16
+ }
17
+
18
+ export class ProcessExitInterceptError extends Error {
19
+ readonly exitCode: number;
20
+
21
+ constructor(exitCode: number) {
22
+ super(`Process exited with code ${exitCode}`);
23
+ this.name = "ProcessExitInterceptError";
24
+ this.exitCode = exitCode;
25
+ }
26
+ }
27
+
28
+ /**
29
+ * Captured console 出力からエラーメッセージ全文を組み立てる。
30
+ * 最終行だけでなく全 error 行を保持し(装飾絵文字は除去)、機械可読な診断に使う。
31
+ */
32
+ export function deriveCapturedErrorMessage(messages: CapturedConsoleMessages, fallback: string): string {
33
+ const errorText = messages.errors
34
+ .map((entry) => entry.replace(/[❌💡]\s*/gu, "").trim())
35
+ .filter((entry) => entry.length > 0)
36
+ .join("\n");
37
+ if (errorText) {
38
+ return errorText;
39
+ }
40
+ const warningText = messages.warnings[messages.warnings.length - 1];
41
+ if (warningText) {
42
+ return warningText.trim();
43
+ }
44
+ const logText = messages.logs[messages.logs.length - 1];
45
+ if (logText) {
46
+ return logText.trim();
47
+ }
48
+ return fallback;
49
+ }
50
+
51
+ function formatConsoleArgs(args: unknown[]): string {
52
+ return args
53
+ .map((arg) => {
54
+ if (typeof arg === "string") {
55
+ return arg;
56
+ }
57
+
58
+ if (arg instanceof Error) {
59
+ return arg.stack || arg.message;
60
+ }
61
+
62
+ try {
63
+ return JSON.stringify(arg);
64
+ } catch {
65
+ return String(arg);
66
+ }
67
+ })
68
+ .join(" ");
69
+ }
70
+
71
+ function normalizePathForSummary(targetPath: string): string {
72
+ const relative = path.relative(process.cwd(), targetPath);
73
+ return relative && !relative.startsWith("..") ? relative.replace(/\\/g, "/") : targetPath.replace(/\\/g, "/");
74
+ }
75
+
76
+ interface FileSystemEntrySnapshot {
77
+ type: "file" | "directory";
78
+ mtimeMs: number;
79
+ size: number;
80
+ }
81
+
82
+ function snapshotFileSystem(rootDirectory: string): Map<string, FileSystemEntrySnapshot> {
83
+ const snapshot = new Map<string, FileSystemEntrySnapshot>();
84
+ const excludedDirectoryNames = new Set([".git", "node_modules"]);
85
+
86
+ function walk(currentDirectory: string): void {
87
+ for (const entry of fs.readdirSync(currentDirectory, { withFileTypes: true })) {
88
+ if (entry.isDirectory() && excludedDirectoryNames.has(entry.name)) {
89
+ continue;
90
+ }
91
+
92
+ const entryPath = path.join(currentDirectory, entry.name);
93
+ const stats = fs.statSync(entryPath);
94
+ snapshot.set(entryPath, {
95
+ type: entry.isDirectory() ? "directory" : "file",
96
+ mtimeMs: stats.mtimeMs,
97
+ size: stats.size,
98
+ });
99
+
100
+ if (entry.isDirectory()) {
101
+ walk(entryPath);
102
+ }
103
+ }
104
+ }
105
+
106
+ if (fs.existsSync(rootDirectory)) {
107
+ walk(rootDirectory);
108
+ }
109
+
110
+ return snapshot;
111
+ }
112
+
113
+ export async function withWorkingDirectory<T>(directory: string, action: () => Promise<T>): Promise<T> {
114
+ const originalDirectory = process.cwd();
115
+ process.chdir(directory);
116
+
117
+ try {
118
+ return await action();
119
+ } finally {
120
+ process.chdir(originalDirectory);
121
+ }
122
+ }
123
+
124
+ export async function captureConsoleMessages<T>(
125
+ action: () => Promise<T>
126
+ ): Promise<{ result: T; messages: CapturedConsoleMessages }> {
127
+ const messages: CapturedConsoleMessages = {
128
+ logs: [],
129
+ warnings: [],
130
+ errors: [],
131
+ };
132
+
133
+ const originalLog = console.log;
134
+ const originalWarn = console.warn;
135
+ const originalError = console.error;
136
+
137
+ console.log = (...args: unknown[]) => {
138
+ messages.logs.push(formatConsoleArgs(args));
139
+ };
140
+ console.warn = (...args: unknown[]) => {
141
+ messages.warnings.push(formatConsoleArgs(args));
142
+ };
143
+ console.error = (...args: unknown[]) => {
144
+ messages.errors.push(formatConsoleArgs(args));
145
+ };
146
+
147
+ try {
148
+ const result = await action();
149
+ return { result, messages };
150
+ } finally {
151
+ console.log = originalLog;
152
+ console.warn = originalWarn;
153
+ console.error = originalError;
154
+ }
155
+ }
156
+
157
+ export async function captureConsoleMessagesWithError<T>(
158
+ action: () => Promise<T>
159
+ ): Promise<{ result?: T; messages: CapturedConsoleMessages; error?: unknown }> {
160
+ const messages: CapturedConsoleMessages = {
161
+ logs: [],
162
+ warnings: [],
163
+ errors: [],
164
+ };
165
+
166
+ const originalLog = console.log;
167
+ const originalWarn = console.warn;
168
+ const originalError = console.error;
169
+
170
+ console.log = (...args: unknown[]) => {
171
+ messages.logs.push(formatConsoleArgs(args));
172
+ };
173
+ console.warn = (...args: unknown[]) => {
174
+ messages.warnings.push(formatConsoleArgs(args));
175
+ };
176
+ console.error = (...args: unknown[]) => {
177
+ messages.errors.push(formatConsoleArgs(args));
178
+ };
179
+
180
+ try {
181
+ return { result: await action(), messages };
182
+ } catch (error) {
183
+ return { messages, error };
184
+ } finally {
185
+ console.log = originalLog;
186
+ console.warn = originalWarn;
187
+ console.error = originalError;
188
+ }
189
+ }
190
+
191
+ export async function interceptProcessExit<T>(action: () => Promise<T>): Promise<T> {
192
+ const originalExit = process.exit;
193
+
194
+ process.exit = ((code?: number | string | null) => {
195
+ const normalizedCode = typeof code === "number"
196
+ ? code
197
+ : typeof process.exitCode === "number"
198
+ ? process.exitCode
199
+ : 1;
200
+ throw new ProcessExitInterceptError(normalizedCode);
201
+ }) as typeof process.exit;
202
+
203
+ try {
204
+ return await action();
205
+ } finally {
206
+ process.exit = originalExit;
207
+ }
208
+ }
209
+
210
+ export async function trackFileMutations<T>(
211
+ action: () => Promise<T>
212
+ ): Promise<{ result: T; mutations: FileMutationSummary }> {
213
+ const rootDirectory = process.cwd();
214
+ const before = snapshotFileSystem(rootDirectory);
215
+ const result = await action();
216
+ const after = snapshotFileSystem(rootDirectory);
217
+
218
+ const createdFiles = new Set<string>();
219
+ const updatedFiles = new Set<string>();
220
+ const deletedFiles = new Set<string>();
221
+ const createdDirectories = new Set<string>();
222
+
223
+ for (const [entryPath, afterEntry] of after.entries()) {
224
+ const beforeEntry = before.get(entryPath);
225
+ if (!beforeEntry) {
226
+ if (afterEntry.type === "directory") {
227
+ createdDirectories.add(normalizePathForSummary(entryPath));
228
+ } else {
229
+ createdFiles.add(normalizePathForSummary(entryPath));
230
+ }
231
+ continue;
232
+ }
233
+
234
+ if (
235
+ afterEntry.type === "file" &&
236
+ (beforeEntry.mtimeMs !== afterEntry.mtimeMs || beforeEntry.size !== afterEntry.size)
237
+ ) {
238
+ updatedFiles.add(normalizePathForSummary(entryPath));
239
+ }
240
+ }
241
+
242
+ for (const [entryPath, beforeEntry] of before.entries()) {
243
+ if (!after.has(entryPath) && beforeEntry.type === "file") {
244
+ deletedFiles.add(normalizePathForSummary(entryPath));
245
+ }
246
+ }
247
+
248
+ return {
249
+ result,
250
+ mutations: {
251
+ createdFiles: Array.from(createdFiles).sort(),
252
+ updatedFiles: Array.from(updatedFiles).sort(),
253
+ appendedFiles: [],
254
+ deletedFiles: Array.from(deletedFiles).sort(),
255
+ createdDirectories: Array.from(createdDirectories).sort(),
256
+ },
257
+ };
258
+ }
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Apply Scaffold — Plan の検証(stale 検出・承認チェック)を経て scaffold を実際に適用する。
3
+ */
4
+
5
+ import { MachineCommandError } from "../../machine/errors";
6
+ import { deletePlanState, loadPlanState } from "../project/state";
7
+ import { runMachineScaffoldOperation, MachineScaffoldOperationResult } from "./scaffold-machine";
8
+ import {
9
+ planScaffoldOperation,
10
+ ScaffoldPlanData,
11
+ ScaffoldPlanOperation,
12
+ verifyPlanFingerprints,
13
+ } from "./scaffold-plan";
14
+
15
+ export interface ApplyScaffoldOptions {
16
+ model?: string;
17
+ planId?: string;
18
+ approve?: boolean;
19
+ functionsDir?: string;
20
+ apiDir?: string;
21
+ apiOnly?: boolean;
22
+ }
23
+
24
+ export interface ApplyScaffoldResult extends MachineScaffoldOperationResult {
25
+ model: string;
26
+ planId: string;
27
+ approvedConflicts: ScaffoldPlanOperation[];
28
+ warnings: string[];
29
+ }
30
+
31
+ export async function applyScaffoldOperation(options: ApplyScaffoldOptions): Promise<ApplyScaffoldResult> {
32
+ const projectRoot = process.cwd();
33
+
34
+ let basePlan: ScaffoldPlanData | null = null;
35
+
36
+ if (options.planId) {
37
+ basePlan = loadPlanState<ScaffoldPlanData>(options.planId, projectRoot);
38
+ if (!basePlan) {
39
+ throw new MachineCommandError(
40
+ "plan-not-found",
41
+ `Plan "${options.planId}" was not found. Run "plan scaffold" to create a new plan.`,
42
+ { planId: options.planId },
43
+ "blocked"
44
+ );
45
+ }
46
+
47
+ const freshness = verifyPlanFingerprints(basePlan, projectRoot);
48
+ if (!freshness.fresh) {
49
+ throw new MachineCommandError(
50
+ "stale-plan",
51
+ `Plan "${options.planId}" is stale: ${freshness.changedFiles.length} file(s) changed after the plan was created. Re-run "plan scaffold".`,
52
+ { planId: options.planId, changedFiles: freshness.changedFiles },
53
+ "blocked"
54
+ );
55
+ }
56
+ }
57
+
58
+ const model = options.model ?? basePlan?.model;
59
+ if (!model) {
60
+ throw new MachineCommandError(
61
+ "invalid-arguments",
62
+ "Either a model name or --plan <planId> must be provided.",
63
+ undefined,
64
+ "failed"
65
+ );
66
+ }
67
+
68
+ const effectiveOptions = {
69
+ model,
70
+ functionsDir: options.functionsDir ?? basePlan?.options.functionsDir,
71
+ apiDir: options.apiDir ?? basePlan?.options.apiDir,
72
+ apiOnly: options.apiOnly ?? basePlan?.options.apiOnly,
73
+ };
74
+
75
+ // 常に最新状態で collect し、競合を検出する(plan 指定時も現況を再確認)
76
+ const currentPlan = await planScaffoldOperation(effectiveOptions);
77
+
78
+ if (currentPlan.requiresApproval && !options.approve) {
79
+ // 承認待ちの plan は残し、参照できるようにする
80
+ throw new MachineCommandError(
81
+ "approval-required",
82
+ `${currentPlan.conflicts.length} file(s) were modified after generation and would be overwritten. Review the conflicts and re-run with --approve to proceed.`,
83
+ {
84
+ planId: currentPlan.planId,
85
+ conflicts: currentPlan.conflicts,
86
+ },
87
+ "requires-human"
88
+ );
89
+ }
90
+
91
+ const result = await runMachineScaffoldOperation(effectiveOptions);
92
+
93
+ // 適用済み plan の状態を破棄
94
+ deletePlanState(currentPlan.planId, projectRoot);
95
+ if (options.planId && options.planId !== currentPlan.planId) {
96
+ deletePlanState(options.planId, projectRoot);
97
+ }
98
+
99
+ return {
100
+ ...result,
101
+ model,
102
+ planId: currentPlan.planId,
103
+ approvedConflicts: options.approve ? currentPlan.conflicts : [],
104
+ warnings: currentPlan.warnings,
105
+ };
106
+ }
@@ -0,0 +1,77 @@
1
+ import { scaffoldCommand } from "../../cli/commands/scaffold";
2
+ import {
3
+ captureConsoleMessagesWithError,
4
+ deriveCapturedErrorMessage,
5
+ interceptProcessExit,
6
+ ProcessExitInterceptError,
7
+ trackFileMutations,
8
+ } from "./runtime";
9
+
10
+ export interface MachineScaffoldOperationOptions {
11
+ model: string;
12
+ functionsDir?: string;
13
+ apiDir?: string;
14
+ apiOnly?: boolean;
15
+ }
16
+
17
+ export interface MachineScaffoldOperationResult {
18
+ createdFiles: string[];
19
+ updatedFiles: string[];
20
+ appendedFiles: string[];
21
+ deletedFiles: string[];
22
+ createdDirectories: string[];
23
+ diagnostics: string[];
24
+ }
25
+
26
+ function deriveErrorMessage(messages: { errors: string[]; warnings: string[]; logs: string[] }): string {
27
+ return deriveCapturedErrorMessage(messages, "Scaffold failed.");
28
+ }
29
+
30
+ export async function runMachineScaffoldOperation(
31
+ options: MachineScaffoldOperationOptions
32
+ ): Promise<MachineScaffoldOperationResult> {
33
+ const originalMachineOutput = process.env.SWALLOWKIT_MACHINE_OUTPUT;
34
+ process.env.SWALLOWKIT_MACHINE_OUTPUT = "1";
35
+
36
+ try {
37
+ const tracked = await trackFileMutations(async () => {
38
+ const captured = await captureConsoleMessagesWithError(async () => {
39
+ await interceptProcessExit(async () => {
40
+ await scaffoldCommand(options);
41
+ });
42
+ });
43
+
44
+ if (captured.error) {
45
+ if (captured.error instanceof ProcessExitInterceptError) {
46
+ throw new Error(deriveErrorMessage(captured.messages));
47
+ }
48
+
49
+ if (captured.error instanceof Error) {
50
+ throw captured.error;
51
+ }
52
+
53
+ throw new Error(deriveErrorMessage(captured.messages));
54
+ }
55
+
56
+ return captured.messages;
57
+ });
58
+
59
+ return {
60
+ createdFiles: tracked.mutations.createdFiles,
61
+ updatedFiles: tracked.mutations.updatedFiles,
62
+ appendedFiles: tracked.mutations.appendedFiles,
63
+ deletedFiles: tracked.mutations.deletedFiles,
64
+ createdDirectories: tracked.mutations.createdDirectories,
65
+ diagnostics: [
66
+ ...tracked.result.warnings.map((warning) => `warning:${warning}`),
67
+ ...tracked.result.errors.map((error) => `error:${error}`),
68
+ ],
69
+ };
70
+ } finally {
71
+ if (originalMachineOutput === undefined) {
72
+ delete process.env.SWALLOWKIT_MACHINE_OUTPUT;
73
+ } else {
74
+ process.env.SWALLOWKIT_MACHINE_OUTPUT = originalMachineOutput;
75
+ }
76
+ }
77
+ }
@@ -0,0 +1,215 @@
1
+ /**
2
+ * Plan Scaffold — scaffold を collect モードで実行し、書き込み予定を差分計画として返す。
3
+ * ディスクへの書き込みは一切行わない。
4
+ */
5
+
6
+ import * as crypto from "crypto";
7
+ import * as path from "path";
8
+ import { scaffoldCommand } from "../../cli/commands/scaffold";
9
+ import { getSwallowKitVersion } from "../../version";
10
+ import { findArtifactRecord, loadArtifactLedger } from "../project/artifacts";
11
+ import { savePlanState } from "../project/state";
12
+ import {
13
+ ArtifactOwnership,
14
+ FileOperationSession,
15
+ hashFileIfExists,
16
+ runWithFileSession,
17
+ } from "./file-session";
18
+ import {
19
+ captureConsoleMessagesWithError,
20
+ deriveCapturedErrorMessage,
21
+ interceptProcessExit,
22
+ ProcessExitInterceptError,
23
+ } from "./runtime";
24
+
25
+ export interface PlanScaffoldOptions {
26
+ model: string;
27
+ functionsDir?: string;
28
+ apiDir?: string;
29
+ apiOnly?: boolean;
30
+ }
31
+
32
+ export type ScaffoldPlanAction = "create" | "update" | "overwrite" | "append" | "delete" | "skip";
33
+
34
+ export interface ScaffoldPlanOperation {
35
+ path: string;
36
+ action: ScaffoldPlanAction;
37
+ ownership: ArtifactOwnership;
38
+ conflict: boolean;
39
+ conflictReason?: string;
40
+ }
41
+
42
+ export interface ScaffoldPlanData {
43
+ planId: string;
44
+ planType: "scaffold";
45
+ model: string;
46
+ options: {
47
+ functionsDir?: string;
48
+ apiDir?: string;
49
+ apiOnly?: boolean;
50
+ };
51
+ createdAt: string;
52
+ swallowkitVersion: string;
53
+ operations: ScaffoldPlanOperation[];
54
+ warnings: string[];
55
+ conflicts: ScaffoldPlanOperation[];
56
+ requiresApproval: boolean;
57
+ /** project-relative path -> pre-plan content hash (null = did not exist). Used for stale-plan detection. */
58
+ fingerprints: Record<string, string | null>;
59
+ }
60
+
61
+ function deriveScaffoldErrorMessage(messages: { errors: string[]; warnings: string[]; logs: string[] }): string {
62
+ return deriveCapturedErrorMessage(messages, "Scaffold plan failed.");
63
+ }
64
+
65
+ /**
66
+ * scaffold を collect セッションで実行し、記録された操作を返す。
67
+ */
68
+ async function collectScaffoldOperations(options: PlanScaffoldOptions): Promise<FileOperationSession> {
69
+ const session = new FileOperationSession("collect");
70
+ const originalMachineOutput = process.env.SWALLOWKIT_MACHINE_OUTPUT;
71
+ process.env.SWALLOWKIT_MACHINE_OUTPUT = "1";
72
+
73
+ try {
74
+ const captured = await captureConsoleMessagesWithError(async () => {
75
+ await interceptProcessExit(async () => {
76
+ await runWithFileSession(session, async () => {
77
+ await scaffoldCommand({
78
+ model: options.model,
79
+ functionsDir: options.functionsDir,
80
+ apiDir: options.apiDir,
81
+ apiOnly: options.apiOnly,
82
+ });
83
+ });
84
+ });
85
+ });
86
+
87
+ if (captured.error) {
88
+ if (captured.error instanceof ProcessExitInterceptError) {
89
+ throw new Error(deriveScaffoldErrorMessage(captured.messages));
90
+ }
91
+ if (captured.error instanceof Error) {
92
+ throw captured.error;
93
+ }
94
+ throw new Error(deriveScaffoldErrorMessage(captured.messages));
95
+ }
96
+
97
+ return session;
98
+ } finally {
99
+ if (originalMachineOutput === undefined) {
100
+ delete process.env.SWALLOWKIT_MACHINE_OUTPUT;
101
+ } else {
102
+ process.env.SWALLOWKIT_MACHINE_OUTPUT = originalMachineOutput;
103
+ }
104
+ }
105
+ }
106
+
107
+ export async function planScaffoldOperation(options: PlanScaffoldOptions): Promise<ScaffoldPlanData> {
108
+ const projectRoot = process.cwd();
109
+ const session = await collectScaffoldOperations(options);
110
+ const ledger = loadArtifactLedger(projectRoot);
111
+
112
+ const operations: ScaffoldPlanOperation[] = [];
113
+ const fingerprints: Record<string, string | null> = { ...session.inputFingerprints };
114
+ const warnings = [...session.warnings];
115
+
116
+ for (const op of session.operations) {
117
+ fingerprints[op.path] = op.previousHash;
118
+
119
+ let action: ScaffoldPlanAction;
120
+ let conflict = false;
121
+ let conflictReason: string | undefined;
122
+
123
+ switch (op.action) {
124
+ case "create":
125
+ action = "create";
126
+ break;
127
+ case "skip":
128
+ action = "skip";
129
+ break;
130
+ case "append":
131
+ action = "append";
132
+ break;
133
+ case "delete":
134
+ action = "delete";
135
+ break;
136
+ case "modify": {
137
+ const record = ledger ? findArtifactRecord(ledger, op.path) : undefined;
138
+ if (record && record.contentHash === op.previousHash) {
139
+ // 生成後に変更されていない managed ファイルの再生成 — 安全
140
+ action = "update";
141
+ } else if (record) {
142
+ action = "overwrite";
143
+ conflict = true;
144
+ conflictReason = "modified-after-generation";
145
+ } else {
146
+ action = "overwrite";
147
+ conflict = true;
148
+ conflictReason = "unknown-provenance";
149
+ warnings.push(
150
+ `unknown-provenance: ${op.path} exists but is not tracked in .swallowkit/artifacts.json; regenerating will overwrite it.`
151
+ );
152
+ }
153
+ break;
154
+ }
155
+ }
156
+
157
+ operations.push({
158
+ path: op.path,
159
+ action,
160
+ ownership: op.ownership,
161
+ conflict,
162
+ ...(conflictReason ? { conflictReason } : {}),
163
+ });
164
+ }
165
+
166
+ const conflicts = operations.filter((op) => op.conflict);
167
+
168
+ const planId = crypto
169
+ .createHash("sha256")
170
+ .update(
171
+ JSON.stringify({
172
+ model: options.model,
173
+ options: { functionsDir: options.functionsDir, apiDir: options.apiDir, apiOnly: options.apiOnly },
174
+ operations: operations.map((op) => ({ path: op.path, action: op.action })),
175
+ fingerprints,
176
+ })
177
+ )
178
+ .digest("hex")
179
+ .slice(0, 12);
180
+
181
+ const plan: ScaffoldPlanData = {
182
+ planId,
183
+ planType: "scaffold",
184
+ model: options.model,
185
+ options: {
186
+ ...(options.functionsDir ? { functionsDir: options.functionsDir } : {}),
187
+ ...(options.apiDir ? { apiDir: options.apiDir } : {}),
188
+ ...(options.apiOnly !== undefined ? { apiOnly: options.apiOnly } : {}),
189
+ },
190
+ createdAt: new Date().toISOString(),
191
+ swallowkitVersion: getSwallowKitVersion(),
192
+ operations,
193
+ warnings,
194
+ conflicts,
195
+ requiresApproval: conflicts.length > 0,
196
+ fingerprints,
197
+ };
198
+
199
+ savePlanState(plan, projectRoot);
200
+ return plan;
201
+ }
202
+
203
+ export function verifyPlanFingerprints(
204
+ plan: ScaffoldPlanData,
205
+ projectRoot: string = process.cwd()
206
+ ): { fresh: boolean; changedFiles: string[] } {
207
+ const changedFiles: string[] = [];
208
+ for (const [relativePath, expectedHash] of Object.entries(plan.fingerprints)) {
209
+ const currentHash = hashFileIfExists(path.join(projectRoot, relativePath));
210
+ if (currentHash !== expectedHash) {
211
+ changedFiles.push(relativePath);
212
+ }
213
+ }
214
+ return { fresh: changedFiles.length === 0, changedFiles };
215
+ }