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,18 @@
1
+ ---
2
+ applyTo: "app/api/**"
3
+ ---
4
+
5
+ # BFF API Routes — Rules
6
+
7
+ The root `AGENTS.md` is the canonical contract. Inspect the current
8
+ responsibility boundary before editing files in this layer.
9
+
10
+ - `app/api/` is a BFF/proxy layer. Never place business logic, database access,
11
+ or direct Cosmos DB calls here.
12
+ - Use `callFunction()` from `@/lib/api/call-function` to forward requests to
13
+ Azure Functions.
14
+ - Input/output validation may use schemas imported from `@{{projectName}}/shared`.
15
+ - Never directly edit a route reported as deterministic. Change its source of
16
+ truth and use MCP-first plan/apply, with the machine interface as fallback.
17
+ - Edit shared artifacts only outside managed markers.
18
+ - Respect `requires-human` and run SwallowKit verification after changes.
@@ -0,0 +1,24 @@
1
+ # Copilot Instructions
2
+
3
+ This is a **SwallowKit** project.
4
+
5
+ Read and follow the root `AGENTS.md` as the canonical project contract.
6
+
7
+ SwallowKit applies during discovery, requirements clarification, specification,
8
+ planning, ticket decomposition, implementation, verification, and review — not
9
+ only during code generation.
10
+
11
+ Key rules:
12
+
13
+ - Inspect current SwallowKit capabilities instead of assuming them.
14
+ - Answer repository- and SwallowKit-inspectable questions before asking the human.
15
+ - Prefer `swallowkit_*` MCP tools when available.
16
+ - Otherwise use `{{runCmd}} swallowkit machine ...`.
17
+ - Inspect responsibility boundaries before editing framework-related files.
18
+ - Never hand-edit deterministic SwallowKit-managed artifacts.
19
+ - Use plan/apply for deterministic generation.
20
+ - Respect `requires-human` states and other human approval gates.
21
+ - Run SwallowKit verification before completion.
22
+
23
+ Task-specific SwallowKit Agent Skills live under `.github/skills/`.
24
+ Agent-agnostic equivalents live under `.swallowkit/workflows/`.
@@ -0,0 +1,35 @@
1
+ ---
2
+ applyTo: "shared/models/**"
3
+ ---
4
+
5
+ # Shared Models — Zod Schema Rules
6
+
7
+ The root `AGENTS.md` is the canonical contract. Files here are ai-authored
8
+ source-of-truth models, subject to the current result of
9
+ `{{runCmd}} swallowkit machine inspect boundaries`.
10
+
11
+ ## Rules
12
+
13
+ - Define Zod schemas using `zod/v4` (`import { z } from 'zod/v4'`).
14
+ - Use the Zod pattern where the schema constant and inferred TypeScript type
15
+ share the same name.
16
+ - Include `id`, `createdAt`, and `updatedAt` as optional string fields. The
17
+ backend manages their values.
18
+ - Export a `displayName` string for UI display.
19
+ - Re-export every model from `shared/index.ts`, outside managed markers where
20
+ applicable.
21
+ - For relationships, use nested schemas rather than duplicating model shapes.
22
+
23
+ After changing a model, do not manually patch generated artifacts.
24
+
25
+ Use the `swallowkit-modify-model` Agent Skill when available. Otherwise follow
26
+ `.swallowkit/workflows/modify-model.md`.
27
+
28
+ The normal agent workflow is:
29
+
30
+ ```text
31
+ inspect drift → plan scaffold → apply scaffold → verify project
32
+ ```
33
+
34
+ Prefer the equivalent `swallowkit_*` MCP tools; use the machine interface as the
35
+ fallback. Stop for any `requires-human` result.
@@ -1,7 +1,9 @@
1
1
  import * as fs from "fs";
2
+ import * as os from "os";
2
3
  import * as path from "path";
3
4
  import { spawn, spawnSync } from "child_process";
4
5
  import { BackendLanguage } from "../../types";
6
+ import { FileOperationSession } from "../operations/file-session";
5
7
  import { ModelInfo, toKebabCase } from "./model-parser";
6
8
  import { generateOpenApiDocument } from "./openapi-generator";
7
9
  import {
@@ -621,23 +623,47 @@ function mergePythonInitSource(existingSource: string | undefined, generatedSour
621
623
  return `${normalizedExisting}${missingLines.join("\n")}\n`;
622
624
  }
623
625
 
624
- function writeMergedPythonInitFile(filePath: string, generatedSource: string): void {
625
- const existingSource = fs.existsSync(filePath) ? fs.readFileSync(filePath, "utf-8") : undefined;
626
- fs.writeFileSync(filePath, mergePythonInitSource(existingSource, generatedSource), "utf-8");
626
+ function writeMergedPythonInitFile(
627
+ filePath: string,
628
+ generatedSource: string,
629
+ session: FileOperationSession,
630
+ sourceModel: string
631
+ ): void {
632
+ const existingSource = session.fileExists(filePath) ? session.readFile(filePath) : undefined;
633
+ session.writeFile(filePath, mergePythonInitSource(existingSource, generatedSource), {
634
+ ownership: "extension-point",
635
+ generator: "native-schema",
636
+ sourceModel,
637
+ });
627
638
  }
628
639
 
629
- function ensureCSharpCodegenProjectFiles(functionsRoot: string): void {
640
+ function ensureCSharpCodegenProjectFiles(
641
+ functionsRoot: string,
642
+ session: FileOperationSession,
643
+ sourceModel: string
644
+ ): void {
630
645
  const toolManifestPath = path.join(functionsRoot, ".config", "dotnet-tools.json");
631
- fs.mkdirSync(path.dirname(toolManifestPath), { recursive: true });
632
- if (!fs.existsSync(toolManifestPath)) {
633
- fs.writeFileSync(toolManifestPath, buildCSharpCodegenToolManifestSource(), "utf-8");
646
+ if (!session.fileExists(toolManifestPath)) {
647
+ session.writeFile(toolManifestPath, buildCSharpCodegenToolManifestSource(), {
648
+ ownership: "generated-once",
649
+ generator: "native-schema",
650
+ sourceModel,
651
+ });
634
652
  }
635
653
  }
636
654
 
637
- function ensurePythonCodegenProjectFiles(functionsRoot: string): string {
655
+ function ensurePythonCodegenProjectFiles(
656
+ functionsRoot: string,
657
+ session: FileOperationSession,
658
+ sourceModel: string
659
+ ): string {
638
660
  const requirementsPath = path.join(functionsRoot, "requirements.codegen.txt");
639
- if (!fs.existsSync(requirementsPath)) {
640
- fs.writeFileSync(requirementsPath, buildPythonCodegenRequirementsSource(), "utf-8");
661
+ if (!session.fileExists(requirementsPath)) {
662
+ session.writeFile(requirementsPath, buildPythonCodegenRequirementsSource(), {
663
+ ownership: "generated-once",
664
+ generator: "native-schema",
665
+ sourceModel,
666
+ });
641
667
  }
642
668
  return requirementsPath;
643
669
  }
@@ -676,8 +702,7 @@ async function ensureProjectLocalUvCommand(projectRoot: string): Promise<{ comma
676
702
  return { command: localUvExecutable, env: uvEnv };
677
703
  }
678
704
 
679
- async function ensurePythonCodegenEnvironment(functionsRoot: string): Promise<string> {
680
- const requirementsPath = ensurePythonCodegenProjectFiles(functionsRoot);
705
+ async function ensurePythonCodegenEnvironment(functionsRoot: string, requirementsPath: string): Promise<string> {
681
706
  const projectRoot = getPythonProjectRoot(functionsRoot);
682
707
  const { command: uvCommand, env: uvEnv } = await ensureProjectLocalUvCommand(projectRoot);
683
708
  const venvDir = path.join(functionsRoot, ".codegen-venv");
@@ -711,79 +736,125 @@ async function ensurePythonCodegenEnvironment(functionsRoot: string): Promise<st
711
736
  return venvPython;
712
737
  }
713
738
 
714
- async function generateCSharpSchemaArtifacts(
739
+ /**
740
+ * 外部 codegen toolchain の事前検証。プロジェクトには一切書き込まず、
741
+ * OS 一時ディレクトリ上で NSwag / datamodel-code-generator を実行して失敗を早期検出する。
742
+ */
743
+ export async function validateNativeSchemaToolchain(
715
744
  models: ModelInfo[],
716
- specPath: string,
717
- outputDir: string,
745
+ rootModel: ModelInfo,
746
+ backendLanguage: Exclude<BackendLanguage, "typescript">,
718
747
  functionsRoot: string
719
748
  ): Promise<void> {
720
- ensureCSharpCodegenProjectFiles(functionsRoot);
749
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "swallowkit-native-codegen-"));
750
+ try {
751
+ const specPath = path.join(tempDir, `${toKebabCase(rootModel.name)}.openapi.json`);
752
+ fs.writeFileSync(specPath, generateOpenApiDocument(models, rootModel), "utf-8");
753
+
754
+ if (backendLanguage === "csharp") {
755
+ if (!canRun("dotnet", ["--version"], tempDir)) {
756
+ throw new Error(
757
+ "The .NET SDK is required to generate C# backend schema assets.\n" +
758
+ "Install the .NET 8 SDK and retry."
759
+ );
760
+ }
721
761
 
722
- if (!canRun("dotnet", ["--version"], functionsRoot)) {
723
- throw new Error(
724
- "The .NET SDK is required to generate C# backend schema assets.\n" +
725
- "Install the .NET 8 SDK and retry."
726
- );
727
- }
762
+ // プロジェクトの tool manifest(バージョン固定)があればそれを尊重し、なければ一時 manifest で検証する
763
+ const projectManifestPath = path.join(functionsRoot, ".config", "dotnet-tools.json");
764
+ let toolCwd = functionsRoot;
765
+ if (!fs.existsSync(projectManifestPath)) {
766
+ fs.mkdirSync(path.join(tempDir, ".config"), { recursive: true });
767
+ fs.writeFileSync(path.join(tempDir, ".config", "dotnet-tools.json"), buildCSharpCodegenToolManifestSource(), "utf-8");
768
+ toolCwd = tempDir;
769
+ }
728
770
 
729
- const tempContractsPath = path.join(outputDir, ".native-temp", "Contracts.cs");
730
- fs.mkdirSync(path.dirname(tempContractsPath), { recursive: true });
771
+ await runCommand("dotnet", ["tool", "restore"], toolCwd, "Failed to restore the NSwag dotnet tool.");
772
+ await runCommand(
773
+ "dotnet",
774
+ getCSharpNativeGeneratorArgs(specPath, path.join(tempDir, "Contracts.cs")),
775
+ toolCwd,
776
+ "NSwag failed to generate C# backend schema assets."
777
+ );
778
+ } else {
779
+ const projectRequirementsPath = path.join(functionsRoot, "requirements.codegen.txt");
780
+ let requirementsPath = projectRequirementsPath;
781
+ if (!fs.existsSync(projectRequirementsPath)) {
782
+ requirementsPath = path.join(tempDir, "requirements.codegen.txt");
783
+ fs.writeFileSync(requirementsPath, buildPythonCodegenRequirementsSource(), "utf-8");
784
+ }
731
785
 
732
- await runCommand(
733
- "dotnet",
734
- ["tool", "restore"],
735
- functionsRoot,
736
- "Failed to restore the NSwag dotnet tool."
737
- );
738
- await runCommand(
739
- "dotnet",
740
- getCSharpNativeGeneratorArgs(specPath, tempContractsPath),
741
- functionsRoot,
742
- "NSwag failed to generate C# backend schema assets."
743
- );
786
+ // .codegen-venv は environment cache であり managed artifact ではない(既存挙動どおり functionsRoot 配下)
787
+ fs.mkdirSync(functionsRoot, { recursive: true });
788
+ const pythonExecutable = await ensurePythonCodegenEnvironment(functionsRoot, requirementsPath);
789
+ await runCommand(
790
+ pythonExecutable,
791
+ getPythonNativeGeneratorArgs(specPath, path.join(tempDir, "models.py")),
792
+ functionsRoot,
793
+ "datamodel-code-generator failed to generate Python backend schema assets."
794
+ );
795
+ }
796
+ } finally {
797
+ fs.rmSync(tempDir, { recursive: true, force: true });
798
+ }
799
+ }
744
800
 
745
- fs.rmSync(path.dirname(tempContractsPath), { recursive: true, force: true });
801
+ async function generateCSharpSchemaArtifacts(
802
+ models: ModelInfo[],
803
+ outputDir: string,
804
+ functionsRoot: string,
805
+ session: FileOperationSession,
806
+ sourceModel: string
807
+ ): Promise<void> {
808
+ ensureCSharpCodegenProjectFiles(functionsRoot, session, sourceModel);
746
809
 
747
810
  const optionPath = getCSharpSchemaOptionPath(outputDir);
748
- fs.mkdirSync(path.dirname(optionPath), { recursive: true });
749
- fs.writeFileSync(optionPath, generateLegacyCompatibleOptionSource(), "utf-8");
811
+ session.writeFile(optionPath, generateLegacyCompatibleOptionSource(), {
812
+ ownership: "managed",
813
+ generator: "native-schema",
814
+ sourceModel,
815
+ });
750
816
 
751
817
  for (const model of models) {
752
818
  const modelPath = getCSharpSchemaModelPath(outputDir, model.name);
753
- fs.mkdirSync(path.dirname(modelPath), { recursive: true });
754
- fs.writeFileSync(modelPath, generateLegacyCompatibleCSharpModelSource(model), "utf-8");
819
+ session.writeFile(modelPath, generateLegacyCompatibleCSharpModelSource(model), {
820
+ ownership: "managed",
821
+ generator: "native-schema",
822
+ sourceModel,
823
+ });
755
824
  }
756
825
  }
757
826
 
758
827
  async function generatePythonSchemaArtifacts(
759
828
  models: ModelInfo[],
760
- specPath: string,
761
829
  outputDir: string,
762
- functionsRoot: string
830
+ functionsRoot: string,
831
+ session: FileOperationSession,
832
+ sourceModel: string
763
833
  ): Promise<void> {
764
- const pythonExecutable = await ensurePythonCodegenEnvironment(functionsRoot);
765
- const tempModelsPath = path.join(outputDir, ".native-temp", "models.py");
766
- fs.mkdirSync(path.dirname(tempModelsPath), { recursive: true });
767
-
768
- await runCommand(
769
- pythonExecutable,
770
- getPythonNativeGeneratorArgs(specPath, tempModelsPath),
771
- functionsRoot,
772
- "datamodel-code-generator failed to generate Python backend schema assets."
773
- );
774
-
775
- fs.rmSync(path.dirname(tempModelsPath), { recursive: true, force: true });
834
+ ensurePythonCodegenProjectFiles(functionsRoot, session, sourceModel);
776
835
 
777
836
  const packageRoot = path.join(outputDir, "backend_models");
778
837
  const modelsRoot = path.join(packageRoot, "models");
779
- fs.mkdirSync(modelsRoot, { recursive: true });
780
- writeMergedPythonInitFile(path.join(packageRoot, "__init__.py"), buildGeneratedPythonPackageInitSource(models));
781
- writeMergedPythonInitFile(path.join(modelsRoot, "__init__.py"), buildGeneratedPythonModelsInitSource(models));
838
+ writeMergedPythonInitFile(
839
+ path.join(packageRoot, "__init__.py"),
840
+ buildGeneratedPythonPackageInitSource(models),
841
+ session,
842
+ sourceModel
843
+ );
844
+ writeMergedPythonInitFile(
845
+ path.join(modelsRoot, "__init__.py"),
846
+ buildGeneratedPythonModelsInitSource(models),
847
+ session,
848
+ sourceModel
849
+ );
782
850
 
783
851
  for (const model of models) {
784
852
  const modelPath = getPythonSchemaModelPath(outputDir, model.name);
785
- fs.mkdirSync(path.dirname(modelPath), { recursive: true });
786
- fs.writeFileSync(modelPath, generateLegacyCompatiblePythonModelSource(model), "utf-8");
853
+ session.writeFile(modelPath, generateLegacyCompatiblePythonModelSource(model), {
854
+ ownership: "managed",
855
+ generator: "native-schema",
856
+ sourceModel,
857
+ });
787
858
  }
788
859
  }
789
860
 
@@ -791,18 +862,27 @@ export async function generateLanguageSchemaArtifacts(
791
862
  models: ModelInfo[],
792
863
  rootModel: ModelInfo,
793
864
  functionsDir: string,
794
- backendLanguage: Exclude<BackendLanguage, "typescript">
865
+ backendLanguage: Exclude<BackendLanguage, "typescript">,
866
+ session: FileOperationSession = new FileOperationSession("commit")
795
867
  ): Promise<void> {
796
- console.log("\n🧬 Generating OpenAPI export and native schema assets...");
868
+ const planning = session.mode === "collect";
869
+ console.log(
870
+ planning
871
+ ? "\n\ud83e\uddec Planning OpenAPI export and native schema assets..."
872
+ : "\n\ud83e\uddec Generating OpenAPI export and native schema assets..."
873
+ );
797
874
 
798
875
  const projectRoot = process.cwd();
799
876
  const functionsRoot = path.join(projectRoot, functionsDir);
800
877
  const openApiDir = path.join(functionsRoot, "openapi");
801
- fs.mkdirSync(openApiDir, { recursive: true });
802
878
 
803
879
  const specPath = path.join(openApiDir, `${toKebabCase(rootModel.name)}.openapi.json`);
804
- fs.writeFileSync(specPath, generateOpenApiDocument(models, rootModel), "utf-8");
805
- console.log(`✅ Created: ${specPath}`);
880
+ session.writeFile(specPath, generateOpenApiDocument(models, rootModel), {
881
+ ownership: "managed",
882
+ generator: "native-schema",
883
+ sourceModel: rootModel.name,
884
+ });
885
+ console.log(planning ? `\ud83d\udccb Will create: ${specPath}` : `\u2705 Created: ${specPath}`);
806
886
 
807
887
  const outputDir = path.join(
808
888
  functionsRoot,
@@ -810,13 +890,15 @@ export async function generateLanguageSchemaArtifacts(
810
890
  backendLanguage === "csharp" ? "csharp-models" : "python-models"
811
891
  );
812
892
 
813
- fs.mkdirSync(outputDir, { recursive: true });
814
-
815
893
  if (backendLanguage === "csharp") {
816
- await generateCSharpSchemaArtifacts(models, specPath, outputDir, functionsRoot);
894
+ await generateCSharpSchemaArtifacts(models, outputDir, functionsRoot, session, rootModel.name);
817
895
  } else {
818
- await generatePythonSchemaArtifacts(models, specPath, outputDir, functionsRoot);
896
+ await generatePythonSchemaArtifacts(models, outputDir, functionsRoot, session, rootModel.name);
819
897
  }
820
898
 
821
- console.log(`✅ Generated ${backendLanguage} schema assets: ${outputDir}`);
899
+ console.log(
900
+ planning
901
+ ? `\ud83d\udccb Will generate ${backendLanguage} schema assets: ${outputDir}`
902
+ : `\u2705 Generated ${backendLanguage} schema assets: ${outputDir}`
903
+ );
822
904
  }