swallowkit 1.0.0-beta.6 → 1.0.0-beta.60

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 +86 -177
  2. package/README.md +86 -178
  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 +163 -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 +19 -81
  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 +78 -0
  36. package/dist/cli/commands/init.d.ts.map +1 -1
  37. package/dist/cli/commands/init.js +1453 -898
  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 +333 -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 +159 -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 +243 -43
  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 +5 -2
  192. package/dist/utils/package-manager.d.ts.map +1 -1
  193. package/dist/utils/package-manager.js +30 -9
  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 +9 -5
  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 +439 -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 +411 -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 +81 -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 +227 -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 +158 -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 +3541 -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 +350 -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 +502 -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 +854 -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 +246 -0
  295. package/src/utils/python-uv.ts +96 -0
  296. package/src/version.ts +13 -0
@@ -0,0 +1,295 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ import { loadProjectManifest, buildProjectManifest, ProjectManifest, ProjectManifestEntity, SWALLOWKIT_MANIFEST_PATH } from "./manifest";
4
+ import { toCamelCase, toKebabCase } from "../scaffold/model-parser";
5
+
6
+ export type ProjectViolationSeverity = "error" | "warning";
7
+
8
+ export interface ProjectViolation {
9
+ code: string;
10
+ severity: ProjectViolationSeverity;
11
+ message: string;
12
+ location?: {
13
+ path?: string;
14
+ entity?: string;
15
+ };
16
+ suggestedFix?: string;
17
+ }
18
+
19
+ export interface ProjectValidationResult {
20
+ manifest: ProjectManifest;
21
+ manifestSource: "file" | "reconstructed";
22
+ diagnostics: string[];
23
+ violations: ProjectViolation[];
24
+ }
25
+
26
+ function pushViolation(
27
+ violations: ProjectViolation[],
28
+ violation: ProjectViolation
29
+ ): void {
30
+ violations.push(violation);
31
+ }
32
+
33
+ function hasScaffoldedEntities(manifest: ProjectManifest): boolean {
34
+ return manifest.entities.length > 0;
35
+ }
36
+
37
+ function validateRequiredPaths(manifest: ProjectManifest, violations: ProjectViolation[]): void {
38
+ const requiredModules = manifest.modules.filter((module) =>
39
+ ["shared-models", "bff", "functions"].includes(module.kind)
40
+ );
41
+
42
+ for (const module of requiredModules) {
43
+ if (!module.exists) {
44
+ pushViolation(violations, {
45
+ code: "missing-required-module",
46
+ severity: "error",
47
+ message: `Required module path is missing: ${module.rootPath}`,
48
+ location: { path: module.rootPath },
49
+ suggestedFix: "Initialize or restore the standard SwallowKit project directories before generating code.",
50
+ });
51
+ }
52
+ }
53
+
54
+ if (hasScaffoldedEntities(manifest) && !manifest.artifacts.callFunctionHelperExists) {
55
+ pushViolation(violations, {
56
+ code: "missing-call-function-helper",
57
+ severity: "warning",
58
+ message: "BFF helper lib/api/call-function.ts is missing.",
59
+ location: { path: manifest.artifacts.callFunctionHelperPath },
60
+ suggestedFix: "Run scaffold to regenerate the BFF helper.",
61
+ });
62
+ }
63
+ }
64
+
65
+ function validateSchemaNaming(entity: ProjectManifestEntity, violations: ProjectViolation[]): void {
66
+ const expectedFileName = `${toKebabCase(entity.name)}.ts`;
67
+ if (!entity.filePath.endsWith(expectedFileName)) {
68
+ pushViolation(violations, {
69
+ code: "naming-model-file",
70
+ severity: "warning",
71
+ message: `Model file should match the entity name in kebab-case: expected ${expectedFileName}.`,
72
+ location: { path: entity.filePath, entity: entity.name },
73
+ suggestedFix: `Rename the model file to ${expectedFileName} to keep generator conventions deterministic.`,
74
+ });
75
+ }
76
+
77
+ const camelSchema = `${toCamelCase(entity.name)}Schema`;
78
+ if (entity.schemaName !== entity.name && entity.schemaName !== camelSchema) {
79
+ pushViolation(violations, {
80
+ code: "naming-schema-export",
81
+ severity: "warning",
82
+ message: `Schema export should be ${entity.name} or ${camelSchema}. Found ${entity.schemaName}.`,
83
+ location: { path: entity.filePath, entity: entity.name },
84
+ suggestedFix: "Use either the PascalCase Zod export or the camelCase Schema suffix pattern supported by SwallowKit.",
85
+ });
86
+ }
87
+ }
88
+
89
+ function validateGeneratedArtifacts(manifest: ProjectManifest, violations: ProjectViolation[]): void {
90
+ for (const route of manifest.routes.filter((candidate) => candidate.kind === "entity")) {
91
+ if (!route.exists) {
92
+ pushViolation(violations, {
93
+ code: route.surface === "bff" ? "missing-bff-route" : "missing-functions-artifact",
94
+ severity: "warning",
95
+ message: `${route.surface === "bff" ? "BFF route" : "Functions artifact"} is missing for ${route.entityName}.`,
96
+ location: {
97
+ path: route.filePath,
98
+ entity: route.entityName,
99
+ },
100
+ suggestedFix: `Run scaffold for ${route.entityName} to regenerate ${route.surface === "bff" ? "API routes" : "Functions code"}.`,
101
+ });
102
+ }
103
+ }
104
+
105
+ if (
106
+ hasScaffoldedEntities(manifest) &&
107
+ manifest.backendLanguage !== "typescript" &&
108
+ manifest.artifacts.openApiSpecs.length === 0
109
+ ) {
110
+ pushViolation(violations, {
111
+ code: "missing-openapi-artifacts",
112
+ severity: "warning",
113
+ message: `No OpenAPI export or native schema artifacts were found for ${manifest.backendLanguage} backend generation.`,
114
+ location: { path: "functions/openapi" },
115
+ suggestedFix: "Run scaffold to regenerate the OpenAPI export and native backend schema artifacts.",
116
+ });
117
+ }
118
+ }
119
+
120
+ function validateLayeringViolations(projectRoot: string, violations: ProjectViolation[]): void {
121
+ const apiDir = path.join(projectRoot, "app", "api");
122
+ const modelsDir = path.join(projectRoot, "shared", "models");
123
+
124
+ if (fs.existsSync(apiDir)) {
125
+ for (const filePath of walkFiles(apiDir, [".ts", ".tsx"])) {
126
+ const content = fs.readFileSync(filePath, "utf-8");
127
+ if (content.includes("@azure/cosmos") || content.match(/from\s+['"].*functions\//)) {
128
+ pushViolation(violations, {
129
+ code: "forbidden-bff-dependency",
130
+ severity: "error",
131
+ message: "BFF files must not depend directly on Azure Cosmos or Functions implementation paths.",
132
+ location: { path: path.relative(projectRoot, filePath).replace(/\\/g, "/") },
133
+ suggestedFix: "Keep app/api as a pure BFF layer and call the backend through generated helpers instead.",
134
+ });
135
+ }
136
+ }
137
+ }
138
+
139
+ if (fs.existsSync(modelsDir)) {
140
+ for (const filePath of walkFiles(modelsDir, [".ts", ".tsx"])) {
141
+ const content = fs.readFileSync(filePath, "utf-8");
142
+ if (content.match(/from\s+['"].*(app\/|functions\/|lib\/api\/)/)) {
143
+ pushViolation(violations, {
144
+ code: "forbidden-model-layer-import",
145
+ severity: "error",
146
+ message: "Shared models must not import from app/, functions/, or lib/api/ layers.",
147
+ location: { path: path.relative(projectRoot, filePath).replace(/\\/g, "/") },
148
+ suggestedFix: "Keep shared/models framework-agnostic so generators can reuse them across all layers.",
149
+ });
150
+ }
151
+ }
152
+ }
153
+ }
154
+
155
+ function validateManifestDrift(
156
+ projectRoot: string,
157
+ loadedManifest: ProjectManifest,
158
+ rebuiltManifest: ProjectManifest,
159
+ violations: ProjectViolation[]
160
+ ): void {
161
+ const sameEntitySet = JSON.stringify(loadedManifest.entities.map((entity) => entity.name).sort()) ===
162
+ JSON.stringify(rebuiltManifest.entities.map((entity) => entity.name).sort());
163
+ const sameRouteSet = JSON.stringify(loadedManifest.routes.map((route) => route.name).sort()) ===
164
+ JSON.stringify(rebuiltManifest.routes.map((route) => route.name).sort());
165
+
166
+ if (!sameEntitySet || !sameRouteSet) {
167
+ pushViolation(violations, {
168
+ code: "manifest-out-of-date",
169
+ severity: "warning",
170
+ message: `${SWALLOWKIT_MANIFEST_PATH} is out of date with the current project structure.`,
171
+ location: { path: SWALLOWKIT_MANIFEST_PATH },
172
+ suggestedFix: "Run a SwallowKit generator command or refresh the manifest so inspection and validation use current metadata.",
173
+ });
174
+ }
175
+ }
176
+
177
+ function walkFiles(directory: string, extensions: string[]): string[] {
178
+ const collected: string[] = [];
179
+
180
+ for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
181
+ const entryPath = path.join(directory, entry.name);
182
+ if (entry.isDirectory()) {
183
+ collected.push(...walkFiles(entryPath, extensions));
184
+ continue;
185
+ }
186
+
187
+ if (extensions.some((extension) => entry.name.endsWith(extension))) {
188
+ collected.push(entryPath);
189
+ }
190
+ }
191
+
192
+ return collected;
193
+ }
194
+
195
+ const VERIFIER_STUB_MARKER = /External token verifier(?: for \S+)? is not configured/;
196
+
197
+ /**
198
+ * external-token スキームの verifier が生成スタブのまま (fail-closed throw のみ) だと
199
+ * typecheck / verify は通るのに実行時は必ず 401/503 になる。
200
+ * 「認証は生成されたが動かない」状態を warning として可視化する。
201
+ */
202
+ function validateExternalTokenVerifierStubs(projectRoot: string, manifest: ProjectManifest, violations: ProjectViolation[]): void {
203
+ const auth = manifest.auth;
204
+ const hasExternalToken =
205
+ auth?.provider === "external-token" ||
206
+ Object.values(auth?.schemes ?? {}).some((scheme) => scheme.provider === "external-token");
207
+ if (!hasExternalToken) return;
208
+
209
+ const candidates: string[] = [
210
+ path.join("functions", "src", "auth", "external-token-verifier.ts"),
211
+ path.join("functions", "Auth", "ExternalTokenVerifier.cs"),
212
+ path.join("functions", "auth", "external_token_verifier.py"),
213
+ ];
214
+ const schemeDirs = [
215
+ { root: path.join("functions", "src", "auth", "schemes"), file: "verifier.ts" },
216
+ { root: path.join("functions", "Auth", "Schemes"), file: "ExternalTokenVerifier.cs" },
217
+ { root: path.join("functions", "auth", "schemes"), file: "verifier.py" },
218
+ ];
219
+ for (const { root, file } of schemeDirs) {
220
+ const absoluteRoot = path.join(projectRoot, root);
221
+ if (!fs.existsSync(absoluteRoot)) continue;
222
+ for (const entry of fs.readdirSync(absoluteRoot, { withFileTypes: true })) {
223
+ if (entry.isDirectory()) candidates.push(path.join(root, entry.name, file));
224
+ }
225
+ }
226
+
227
+ for (const relativePath of candidates) {
228
+ const absolutePath = path.join(projectRoot, relativePath);
229
+ if (!fs.existsSync(absolutePath)) continue;
230
+ const content = fs.readFileSync(absolutePath, "utf-8");
231
+ if (VERIFIER_STUB_MARKER.test(content)) {
232
+ pushViolation(violations, {
233
+ code: "auth-verifier-stub",
234
+ severity: "warning",
235
+ message:
236
+ "External token verifier is still the generated stub; it fails closed, so external-token authentication cannot succeed at runtime.",
237
+ location: { path: relativePath.replace(/\\/g, "/") },
238
+ suggestedFix: "Implement the verifier against your identity provider. The file is yours to edit (generated-once).",
239
+ });
240
+ }
241
+ }
242
+ }
243
+
244
+ export async function validateProject(projectRoot: string = process.cwd()): Promise<ProjectValidationResult> {
245
+ const loaded = await loadProjectManifest(projectRoot);
246
+ const rebuilt = await buildProjectManifest(projectRoot);
247
+ const violations: ProjectViolation[] = [];
248
+
249
+ validateRequiredPaths(loaded.manifest, violations);
250
+
251
+ for (const error of loaded.manifest.configValidation.errors) {
252
+ pushViolation(violations, {
253
+ code: "config-validation",
254
+ severity: "error",
255
+ message: error,
256
+ location: loaded.manifest.configPath ? { path: loaded.manifest.configPath } : undefined,
257
+ suggestedFix: "Fix the SwallowKit config so generators and validators have a stable project definition.",
258
+ });
259
+ }
260
+
261
+ const namedPolicies = loaded.manifest.auth?.authorization?.policies ?? {};
262
+ for (const entity of loaded.manifest.entities) {
263
+ const policy = entity.authPolicy;
264
+ if (!policy) continue;
265
+ const references = [policy.policy, typeof policy.read === "string" ? policy.read : undefined, typeof policy.write === "string" ? policy.write : undefined].filter((value): value is string => Boolean(value));
266
+ for (const name of references) {
267
+ if (!namedPolicies[name]) pushViolation(violations, {
268
+ code: "undefined-auth-policy",
269
+ severity: "error",
270
+ message: `Model '${entity.name}' references undefined authorization policy '${name}'.`,
271
+ location: { path: entity.filePath, entity: entity.name },
272
+ suggestedFix: `Define auth.authorization.policies.${name} or update the model authPolicy reference.`,
273
+ });
274
+ }
275
+ }
276
+
277
+ for (const entity of loaded.manifest.entities) {
278
+ validateSchemaNaming(entity, violations);
279
+ }
280
+
281
+ validateGeneratedArtifacts(loaded.manifest, violations);
282
+ validateLayeringViolations(projectRoot, violations);
283
+ validateExternalTokenVerifierStubs(projectRoot, loaded.manifest, violations);
284
+
285
+ if (loaded.source === "file") {
286
+ validateManifestDrift(projectRoot, loaded.manifest, rebuilt.manifest, violations);
287
+ }
288
+
289
+ return {
290
+ manifest: loaded.manifest,
291
+ manifestSource: loaded.source,
292
+ diagnostics: [...loaded.diagnostics, ...rebuilt.diagnostics],
293
+ violations: violations.sort((left, right) => left.code.localeCompare(right.code)),
294
+ };
295
+ }
@@ -0,0 +1,295 @@
1
+ /**
2
+ * Workflow Docs & Agent Skills
3
+ *
4
+ * 同一のランブック コンテンツから 2 つの表面を生成する:
5
+ * - `.swallowkit/workflows/*.md` — Coding Agent 非依存の Markdown ランブック
6
+ * (AGENTS.md / CLAUDE.md / copilot-instructions.md から参照される)
7
+ * - `.github/skills/<name>/SKILL.md` — Agent Skills (agentskills.io open standard)。
8
+ * GitHub Copilot (VS Code / CLI / cloud agent) 等が name/description で自動発見し、
9
+ * タスクに関連するときだけ本文をロードする (progressive disclosure)。
10
+ */
11
+
12
+ import * as fs from "fs";
13
+ import * as path from "path";
14
+
15
+ export interface WorkflowDoc {
16
+ /** `.swallowkit/workflows/` 配下のファイル名 */
17
+ fileName: string;
18
+ title: string;
19
+ content: string;
20
+ /**
21
+ * Agent Skills のスキル名 (ディレクトリ名と一致必須)。
22
+ * agentskills.io 仕様: 小文字英数字とハイフンのみ・64 文字以内。
23
+ * undefined の場合はスキルとして出力しない (README など)。
24
+ */
25
+ skillName?: string;
26
+ /**
27
+ * Agent Skills の description (最大 1024 文字)。
28
+ * 「何をするか」と「いつ使うか」の両方を書く。
29
+ */
30
+ skillDescription?: string;
31
+ }
32
+
33
+ export function buildWorkflowDocs(runCmd: string): WorkflowDoc[] {
34
+ const addModel: WorkflowDoc = {
35
+ fileName: "add-model.md",
36
+ title: "Add a new model (autonomous loop)",
37
+ skillName: "swallowkit-add-model",
38
+ skillDescription:
39
+ "Add a new data model to a SwallowKit project and deterministically derive CRUD Functions, BFF routes, UI pages, and Cosmos DB infra from it using the plan/apply/verify loop. Use when asked to add a model, entity, resource, table, or a new CRUD feature.",
40
+ content: `# Workflow: Add a new model
41
+
42
+ Goal: add a data model and derive CRUD/BFF/UI/infra deterministically.
43
+
44
+ ## Responsibility boundary
45
+
46
+ - **You author freely**: the model schema (\`shared/models/<name>.ts\`) and any business logic.
47
+ - **SwallowKit generates deterministically**: Functions CRUD, BFF routes, UI pages, OpenAPI, Bicep containers.
48
+ - Query the full contract: \`${runCmd} swallowkit machine inspect boundaries\`
49
+
50
+ ## Steps
51
+
52
+ 1. Create the model file (deterministic template):
53
+ \`${runCmd} swallowkit machine generate model <name> --overwrite never\`
54
+ 2. Edit \`shared/models/<name>.ts\` — add domain fields (free-form authoring).
55
+ 3. Compute the change plan (writes nothing):
56
+ \`${runCmd} swallowkit machine plan scaffold <name>\`
57
+ - \`status: "requires-human"\` → review \`data.conflicts\`; only proceed with \`--approve\` if overwriting is intended.
58
+ 4. Apply:
59
+ \`${runCmd} swallowkit machine apply scaffold --plan <planId>\`
60
+ 5. Verify:
61
+ \`${runCmd} swallowkit machine verify project\`
62
+ - \`summary.done: false\` → \`${runCmd} swallowkit machine explain failure\` and repair, then re-verify.
63
+ 6. If \`dev-seeds/\` exists, update seed JSON for the new model. Seeds are only
64
+ applied by \`${runCmd} swallowkit dev --seed-env <environment>\` — editing the
65
+ JSON alone does nothing.
66
+
67
+ ## Terminal states
68
+
69
+ | status | meaning | next |
70
+ |--------|---------|------|
71
+ | complete | operation finished | continue |
72
+ | in-progress | more work remains (e.g. failing checks) | follow nextActions |
73
+ | blocked | prerequisite missing (e.g. stale plan) | re-plan |
74
+ | requires-human | approval needed | ask the human, or pass --approve when instructed |
75
+ | failed | operation failed | inspect error.details |
76
+ `,
77
+ };
78
+
79
+ const modifyModel: WorkflowDoc = {
80
+ fileName: "modify-model.md",
81
+ title: "Modify an existing model schema",
82
+ skillName: "swallowkit-modify-model",
83
+ skillDescription:
84
+ "Change an existing Zod model schema in a SwallowKit project and re-align every generated artifact (Functions, BFF, UI, infra) via drift inspection and plan/apply regeneration. Use when asked to add/remove/rename model fields, change validation rules, or when inspect drift reports schema-drift.",
85
+ content: `# Workflow: Modify an existing model
86
+
87
+ Goal: change a schema and re-align every generated artifact.
88
+
89
+ ## Steps
90
+
91
+ 1. Edit \`shared/models/<name>.ts\` (free-form authoring).
92
+ 2. Check what drifted:
93
+ \`${runCmd} swallowkit machine inspect drift\`
94
+ - \`schema-drift\` findings list the models whose artifacts are stale.
95
+ 3. Plan the regeneration:
96
+ \`${runCmd} swallowkit machine plan scaffold <name>\`
97
+ - Conflicts mean generated files were hand-edited after generation. Decide: keep the hand-edit (move the logic to an ai-authored file first) or overwrite with \`--approve\`.
98
+ 4. Apply: \`${runCmd} swallowkit machine apply scaffold --plan <planId>\`
99
+ 5. Verify: \`${runCmd} swallowkit machine verify project\`
100
+
101
+ ## Guardrail
102
+
103
+ Never hand-edit managed artifacts to implement schema changes — always change the model
104
+ and regenerate. Hand-edits are detected as drift and block unattended overwrites.
105
+ `,
106
+ };
107
+
108
+ const addAuth: WorkflowDoc = {
109
+ fileName: "add-auth.md",
110
+ title: "Add authentication (plan/apply with config ownership rules)",
111
+ skillName: "swallowkit-add-auth",
112
+ skillDescription:
113
+ "Add authentication (custom-jwt, Azure Static Web Apps built-in, or external-token) to a SwallowKit project via plan/apply auth, including the config ownership rules (never hand-write auth.schemes; hand-edit policies and allowedProviders after apply) and post-apply steps. Use when asked to add login, authentication, authorization, roles, or a named auth scheme.",
114
+ content: `# Workflow: Add authentication
115
+
116
+ Goal: introduce an auth scheme deterministically and finish the manual follow-ups.
117
+
118
+ ## Config ownership (critical)
119
+
120
+ - \`auth.schemes\` is **owned by plan/apply auth. Never hand-write scheme entries** —
121
+ a pre-written scheme makes planning fail with \`already exists\`.
122
+ - \`auth.authorization.policies\` and \`swa.allowedProviders\` are **hand-edited after
123
+ apply** (the config is an extension point; this is the expected workflow).
124
+
125
+ ## Steps
126
+
127
+ 1. Check what already exists: \`${runCmd} swallowkit machine inspect capabilities\`
128
+ (authentication section) and the current \`auth\` block in swallowkit.config.
129
+ 2. Plan (writes nothing):
130
+ \`${runCmd} swallowkit machine plan auth --provider <custom-jwt|swa|external-token> [--scheme <name>] [--allowed-providers github,aad]\`
131
+ 3. Apply:
132
+ \`${runCmd} swallowkit machine apply auth --plan <planId>\`
133
+ 4. Follow the returned \`nextActions\`:
134
+ - Define \`auth.authorization.policies\` referencing the scheme (hand-edit).
135
+ - swa: confirm \`swa.allowedProviders\` — generated login URLs use the first entry.
136
+ - external-token: implement the generated verifier stub. It fails closed and
137
+ **verify passes with the stub**, so a green verify does not mean the auth
138
+ flow works (a \`auth-verifier-stub\` warning is surfaced while unmodified).
139
+ 5. Add \`authPolicy\` to models that need guards, then re-run plan/apply scaffold
140
+ for them.
141
+ 6. Verify: \`${runCmd} swallowkit machine verify project\`
142
+
143
+ ## Scope guardrail
144
+
145
+ Generated CRUD enforces authentication and roles but does **not** scope rows to
146
+ the caller. Owner-scoped behavior belongs in ai-authored endpoints that derive
147
+ the scoping key from the verified principal, never from the request body.
148
+ `,
149
+ };
150
+
151
+ const verifyRepair: WorkflowDoc = {
152
+ fileName: "verify-and-repair.md",
153
+ title: "Verify and repair loop",
154
+ skillName: "swallowkit-verify-repair",
155
+ skillDescription:
156
+ "Converge a SwallowKit project to a verified state: run structure/drift/typecheck (and build/lint/test) checks, read evidence with explain failure, repair, and repeat. Use after any code or schema change, when verification fails, when the build/lint/tests are broken, or before finishing a task.",
157
+ content: `# Workflow: Verify and repair
158
+
159
+ Goal: converge the project to a verified state after any change.
160
+
161
+ ## Loop
162
+
163
+ 1. \`${runCmd} swallowkit machine verify project\`
164
+ - default checks: structure, drift, typecheck (+ project-specific checks from swallowkit.config \`verify.checks\`)
165
+ - opt-in heavier checks: \`--checks structure,drift,typecheck,build,lint,test\`
166
+ 2. If \`summary.done\` is false:
167
+ - \`${runCmd} swallowkit machine explain failure\` → evidence (logTail, findings, violations) and suggestedActions per check.
168
+ - Repair:
169
+ - \`drift\` failures → follow each finding's \`repairAction\` (usually re-run plan/apply scaffold).
170
+ - \`structure\` failures → follow \`suggestedFix\` per violation.
171
+ - \`typecheck\`/\`build\`/\`lint\`/\`test\` failures → fix the code in ai-authored files.
172
+ 3. Repeat until \`summary.done: true\`.
173
+
174
+ ## Exit criteria
175
+
176
+ Stop and ask a human when:
177
+ - the same check fails 3 times in a row with the same evidence, or
178
+ - a repair would require overwriting hand-edited files (\`requires-human\`).
179
+ `,
180
+ };
181
+
182
+ const provision: WorkflowDoc = {
183
+ fileName: "provision.md",
184
+ title: "Provision Azure resources (plan/apply with approval gate)",
185
+ skillName: "swallowkit-provision",
186
+ skillDescription:
187
+ "Provision Azure resources (Static Web Apps, Functions, Cosmos DB) for a SwallowKit project using the two-phase plan/apply flow with a mandatory human approval gate. Use when asked to deploy infrastructure, provision Azure, create resource groups, or run Bicep deployments.",
188
+ content: `# Workflow: Provision Azure resources
189
+
190
+ Goal: deploy the Bicep infrastructure with an explicit human approval gate.
191
+
192
+ ## Rules
193
+
194
+ - Provisioning **always** requires human approval — \`plan provision\` returns
195
+ \`status: "requires-human"\` and \`requiresApproval: true\` by design.
196
+ - Never pass \`--approve\` to \`apply provision\` unless a human explicitly
197
+ instructed you to provision in this session.
198
+ - Inspect infra locally first — no \`az\` calls are needed for inspection.
199
+
200
+ ## Steps
201
+
202
+ 1. Inspect the infrastructure definition (pure local Bicep analysis):
203
+ \`${runCmd} swallowkit machine inspect infra\`
204
+ - review parameters, modules, outputs, and container wiring warnings.
205
+ 2. Compute the provisioning plan (no resources are created):
206
+ \`${runCmd} swallowkit machine plan provision -g <resource-group> --location <region>\`
207
+ - optional: \`--what-if\` to include an \`az deployment ... what-if\` preview.
208
+ - the plan lists the exact \`az\` commands that would run.
209
+ 3. Present the plan to the human and wait for explicit approval.
210
+ 4. Only after approval:
211
+ \`${runCmd} swallowkit machine apply provision --plan <planId> --approve\`
212
+ 5. Verify the deployment outputs and report them.
213
+
214
+ ## Terminal states
215
+
216
+ \`apply provision\` without \`--approve\` fails with \`approval-required\` —
217
+ this is the guardrail working as intended, not an error to work around.
218
+ `,
219
+ };
220
+
221
+ const index: WorkflowDoc = {
222
+ fileName: "README.md",
223
+ title: "SwallowKit agent workflows",
224
+ content: `# SwallowKit Agent Workflows
225
+
226
+ Agent-agnostic runbooks for autonomous development loops. Machine-readable
227
+ interfaces (\`swallowkit machine ...\` / MCP \`swallowkit_*\` tools) return a
228
+ \`status\` terminal state and \`nextActions\`, so agents can chain steps without
229
+ parsing human-oriented output.
230
+
231
+ These runbooks are also installed as Agent Skills (agentskills.io format)
232
+ under \`.github/skills/\` so skills-aware agents load them automatically.
233
+
234
+ | Workflow | File | Skill |
235
+ |----------|------|-------|
236
+ | Add a new model | [add-model.md](./add-model.md) | \`swallowkit-add-model\` |
237
+ | Modify an existing model | [modify-model.md](./modify-model.md) | \`swallowkit-modify-model\` |
238
+ | Add authentication | [add-auth.md](./add-auth.md) | \`swallowkit-add-auth\` |
239
+ | Verify and repair | [verify-and-repair.md](./verify-and-repair.md) | \`swallowkit-verify-repair\` |
240
+ | Provision Azure resources | [provision.md](./provision.md) | \`swallowkit-provision\` |
241
+
242
+ ## Responsibility boundary (summary)
243
+
244
+ - **ai-authored** (write freely): \`shared/models/\`, business logic, custom pages/components, tests.
245
+ - **deterministic** (never hand-edit): artifacts generated by SwallowKit (see \`.swallowkit/artifacts.json\`) and \`.swallowkit/\` metadata. Change models/config and regenerate via plan/apply.
246
+ - **shared** (edit outside markers): \`infra/main.bicep\`, \`functions/function_app.py\`, \`shared/index.ts\`.
247
+
248
+ Full machine-readable contract: \`${runCmd} swallowkit machine inspect boundaries\`
249
+ `,
250
+ };
251
+
252
+ return [index, addModel, modifyModel, addAuth, verifyRepair, provision];
253
+ }
254
+
255
+ export const WORKFLOWS_DIR = path.join(".swallowkit", "workflows");
256
+ export const SKILLS_DIR = path.join(".github", "skills");
257
+
258
+ /** `.swallowkit/workflows/*.md` を書き出し、書き込んだ相対パスを返す。 */
259
+ export function writeWorkflowDocs(projectDir: string, runCmd: string): string[] {
260
+ const dir = path.join(projectDir, WORKFLOWS_DIR);
261
+ fs.mkdirSync(dir, { recursive: true });
262
+ const written: string[] = [];
263
+ for (const doc of buildWorkflowDocs(runCmd)) {
264
+ fs.writeFileSync(path.join(dir, doc.fileName), doc.content, "utf-8");
265
+ written.push(path.posix.join(".swallowkit", "workflows", doc.fileName));
266
+ }
267
+ return written;
268
+ }
269
+
270
+ /** Agent Skills (agentskills.io) の SKILL.md コンテンツを組み立てる。 */
271
+ export function buildAgentSkills(runCmd: string): Array<{ skillName: string; content: string }> {
272
+ return buildWorkflowDocs(runCmd)
273
+ .filter((doc): doc is WorkflowDoc & { skillName: string; skillDescription: string } =>
274
+ Boolean(doc.skillName && doc.skillDescription)
275
+ )
276
+ .map((doc) => ({
277
+ skillName: doc.skillName,
278
+ content: `---\nname: ${doc.skillName}\ndescription: ${doc.skillDescription}\nmetadata:\n generator: swallowkit\n---\n\n${doc.content}`,
279
+ }));
280
+ }
281
+
282
+ /**
283
+ * `.github/skills/<name>/SKILL.md` を書き出し、書き込んだ相対パスを返す。
284
+ * agentskills.io 仕様: ディレクトリ名は frontmatter の name と一致必須。
285
+ */
286
+ export function writeAgentSkills(projectDir: string, runCmd: string): string[] {
287
+ const written: string[] = [];
288
+ for (const skill of buildAgentSkills(runCmd)) {
289
+ const dir = path.join(projectDir, SKILLS_DIR, skill.skillName);
290
+ fs.mkdirSync(dir, { recursive: true });
291
+ fs.writeFileSync(path.join(dir, "SKILL.md"), skill.content, "utf-8");
292
+ written.push(path.posix.join(".github", "skills", skill.skillName, "SKILL.md"));
293
+ }
294
+ return written;
295
+ }