diff --git a/.specify/specs/001-vibe-git-strategy/plan.md b/.specify/specs/001-vibe-git-strategy/plan.md
new file mode 100644
index 0000000..1fced2b
--- /dev/null
+++ b/.specify/specs/001-vibe-git-strategy/plan.md
@@ -0,0 +1,64 @@
+# Implementation Plan: Git Strategy Configuration for Vibe Workflow
+
+## Tech Stack & Libraries
+
+- **Language**: TypeScript (ESM)
+- **Frameworks**: Commander.js (CLI), Inquirer.js (prompts)
+- **Storage**: JSON (settings.json template via Handlebars)
+
+## Project Structure Impact
+
+```
+src/
+├── types.ts # Add gitStrategy field to ProjectConfig
+├── prompts/
+│ └── project.ts # Add conditional prompt for git strategy
+├── generators/
+│ └── claude-code/
+│ └── index.ts # Pass gitStrategy to template
+└── templates/
+ └── claude-code/
+ └── settings.json.hbs # Conditionally include git.strategy
+```
+
+## Implementation Strategy
+
+### Phase 1: Type Definitions
+- Add `gitStrategy: "pr-required" | "direct-push"` to `ProjectConfig` interface in `src/types.ts`
+- Default value: `"pr-required"` (conservative, safe default)
+
+### Phase 2: Prompts
+- Modify `src/prompts/project.ts` to add conditional prompt after `workflowMode` selection
+- Condition: only ask if `workflowMode === "vibe"`
+- Question: "Which git strategy will you use?"
+- Options:
+ - "PR required before merge (safer)" → `"pr-required"`
+ - "Push directly to master (faster)" → `"no-pr"`
+- Default: `"pr-required"`
+
+### Phase 3: Generator & Template
+- Update `src/generators/claude-code/index.ts` to pass `gitStrategy` to the settings.json template
+- Update `src/templates/claude-code/settings.json.hbs` to include the `git.strategy` field based on the value
+
+## Dependencies & Sequencing
+
+1. **T001** - Update types (no dependencies)
+2. **T002** - Add prompt logic (depends on T001)
+3. **T003** - Update generator and template (depends on T001, T002)
+
+## Testing Strategy
+
+- Unit tests in existing test files (`src/__tests__/new-command.test.ts`)
+- Verify prompt appears only when `workflowMode === "vibe"`
+- Verify `ProjectConfig.gitStrategy` is set correctly
+- Verify settings.json contains correct `git.strategy` value
+- E2E: Generate a project with Vibe + PR workflow, verify settings.json
+
+## Parallel Opportunities
+
+None — tasks are sequential due to dependencies on type definitions.
+
+## MVP Scope
+
+- All three tasks (types, prompts, template) are minimal and required for the feature
+- No optional enhancements in Phase 1
diff --git a/.specify/specs/001-vibe-git-strategy/spec.md b/.specify/specs/001-vibe-git-strategy/spec.md
new file mode 100644
index 0000000..09e7539
--- /dev/null
+++ b/.specify/specs/001-vibe-git-strategy/spec.md
@@ -0,0 +1,33 @@
+# Feature Specification: Git Strategy Configuration for Vibe Workflow
+
+## Overview
+
+When users select "Vibe" workflow mode during ForgeKit project setup, add an additional prompt to determine their git strategy preference. This configuration should be written to the generated project's `settings.json` to guide Claude Code behavior.
+
+## User Stories
+
+### US1: Prompt for Git Strategy (P1)
+As a user setting up a ForgeKit project with Vibe workflow, I want to be asked whether I'll use a PR-based workflow or push directly to master, so that my project's Claude Code configuration reflects my development process.
+
+**Acceptance Criteria:**
+- When user selects "Vibe" in the workflow selection prompt, a follow-up question appears
+- Question offers two clear choices: "PR workflow" or "Direct push to master"
+- User can select one of these options
+- Selection is stored in the config and passed to the claude-code generator
+
+### US2: Update settings.json with Git Strategy (P1)
+As a user with a configured git strategy preference, I want the generated `settings.json` to include the `git.strategy` setting, so that Claude Code enforces the appropriate workflow.
+
+**Acceptance Criteria:**
+- If "PR workflow" is selected: `settings.json` contains `"git.strategy": "pr-required"`
+- If "Direct push to master" is selected: `settings.json` contains `"git.strategy": "no-pr"`
+- The setting is placed in the correct location in settings.json
+- The setting is present in every generated project regardless of other options
+
+## Implementation Notes
+
+- The prompt should appear only when `workflowMode === "vibe"`
+- The question should be asked during the main project configuration wizard (in `prompts/project.ts`)
+- The answer should be stored in `ProjectConfig.gitStrategy` (new field)
+- The claude-code generator should pass this value to the settings.json template
+- Backward compatibility: Projects without this setting should default to "pr-required"
diff --git a/.specify/specs/001-vibe-git-strategy/tasks.md b/.specify/specs/001-vibe-git-strategy/tasks.md
new file mode 100644
index 0000000..fdce2ae
--- /dev/null
+++ b/.specify/specs/001-vibe-git-strategy/tasks.md
@@ -0,0 +1,111 @@
+# Tasks: Git Strategy Configuration for Vibe Workflow
+
+## Implementation Summary
+
+Add `gitStrategy` configuration prompt to ForgeKit's Vibe workflow mode, allowing users to choose between PR-required or direct-push workflows. The selection updates the generated project's `settings.json`.
+
+**Total tasks**: 3
+**Estimated effort**: ~30 mins
+**MVP scope**: All 3 tasks
+
+---
+
+## Phase 1: Setup (Foundation)
+
+*No setup tasks needed — changes are localized to existing files.*
+
+---
+
+## Phase 2: Implementation
+
+### T001 — Add gitStrategy type to ProjectConfig
+- [x] T001 Add `gitStrategy: "pr-required" | "no-pr"` field to `ProjectConfig` interface in `src/types.ts`
+ - Default value when creating ProjectConfig instances: `"pr-required"`
+ - Rationale: Conservative default ensures safety (PR workflow) if unset
+
+**Test criteria**: TypeScript compilation passes, no type errors
+
+---
+
+## Phase 3: User Story 1 — Vibe Git Strategy Prompt
+
+### T002 — Add conditional git strategy prompt in project wizard
+- [x] T002 [US1] Add git strategy question to `src/prompts/project.ts` after `workflowMode` selection
+ - Condition: Only ask if `workflowMode === "vibe"`
+ - Question text: "Which git strategy will you use?"
+ - Options:
+ - "PR required before merge (safer)" → sets `gitStrategy = "pr-required"`
+ - "Push directly to master (faster)" → sets `gitStrategy = "no-pr"`
+ - Default: `"pr-required"`
+ - Store result in returned `ProjectConfig`
+
+**Test criteria**:
+- Prompt appears only when Vibe workflow selected
+- Both options work correctly
+- Default is "pr-required"
+- ProjectConfig.gitStrategy is set accurately
+
+### T003 — Update claude-code generator and settings.json template
+- [x] T003 [US1] Update `src/generators/claude-code/index.ts` to pass `gitStrategy` from `ProjectConfig` to the settings.json template
+- [x] T003b [US1] Update `src/templates/claude-code/settings.json.hbs` to include:
+ ```json
+ "git": {
+ "strategy": "{{gitStrategy}}"
+ }
+ ```
+ - Ensure the `git` object exists (may need to merge with existing config)
+ - Template should render `"pr-required"` or `"direct-push"` based on the config value
+
+**Test criteria**:
+- Generated project's `settings.json` contains `"git.strategy": "pr-required"` when user selects PR mode
+- Generated project's `settings.json` contains `"git.strategy": "no-pr"` when user selects direct-push mode
+- All other settings remain intact
+
+---
+
+## Testing Strategy
+
+### Unit Tests
+Update `src/__tests__/new-command.test.ts`:
+- Test that `gitStrategy` defaults to `"pr-required"` when not explicitly set
+- Test that prompt is skipped when `workflowMode !== "vibe"`
+- Test that prompt appears and sets correct value when `workflowMode === "vibe"`
+
+### E2E Test (optional, use existing e2e framework)
+- Run `forgekit new test-pr-mode --vibe --git-strategy=pr-required` (if flag support added)
+- Verify generated `settings.json` contains correct `git.strategy`
+- Run `forgekit new test-no-pr-mode --vibe --git-strategy=no-pr`
+- Verify generated `settings.json` contains correct `git.strategy`
+
+---
+
+## Implementation Notes
+
+### Dependencies
+- T001 must complete before T002 and T003 (types needed for prompt config)
+- T002 and T003 are sequential (prompt result needs to reach template)
+
+### Constitution Alignment
+- **Principle 3** (ProjectConfig is source of truth): gitStrategy flows through ProjectConfig only
+- **Principle 2** (Templates contain zero logic): Settings.json template receives flat value, no conditionals
+- **Principle 1** (Each generator owns one layer): claude-code generator handles passing config to template
+
+### File Impact Summary
+- `src/types.ts` — 1 line addition (gitStrategy field)
+- `src/prompts/project.ts` — ~15 lines (conditional prompt block)
+- `src/generators/claude-code/index.ts` — 1 line (pass gitStrategy)
+- `src/templates/claude-code/settings.json.hbs` — 2 lines (git.strategy object)
+
+### Backward Compatibility
+- Existing projects without `gitStrategy` in `ProjectConfig` will use default `"pr-required"`
+- No breaking changes to CLI flags or existing workflows
+
+---
+
+## Completion Criteria
+
+- [ ] All three tasks marked `[x]`
+- [ ] TypeScript compiles with no errors
+- [ ] Tests pass (unit + optional e2e)
+- [ ] Code follows constitution principles
+- [ ] PR reviewed and merged
diff --git a/.specify/specs/001-vibe-git-strategy/workflow-state.json b/.specify/specs/001-vibe-git-strategy/workflow-state.json
new file mode 100644
index 0000000..d31268c
--- /dev/null
+++ b/.specify/specs/001-vibe-git-strategy/workflow-state.json
@@ -0,0 +1,8 @@
+{
+ "routing": "fast-track",
+ "phase": "fast-track",
+ "branch": null,
+ "completed_steps": ["tasks"],
+ "skills_used": ["sk:tasks"],
+ "last_updated": "2026-04-17T15:50:00Z"
+}
diff --git a/src/__tests__/add-command.test.ts b/src/__tests__/add-command.test.ts
index 95f0236..ad3b60e 100644
--- a/src/__tests__/add-command.test.ts
+++ b/src/__tests__/add-command.test.ts
@@ -81,6 +81,39 @@ describe("forgekit add — integration", () => {
).toBe(true);
});
+ it("adds svelte frontend to a spring-boot-only project", async () => {
+ await generateBase({ backendType: "spring-boot" });
+
+ const existingResult = await detectProject(projectDir);
+ const updatedConfig: ProjectConfig = {
+ ...existingResult.config,
+ frontend: "svelte",
+ auth: false,
+ };
+
+ const addTmpDir = await fs.mkdtemp(
+ path.join(os.tmpdir(), "forgekit-add-svelte-gen-"),
+ );
+ try {
+ const { generateFrontend } =
+ await import("../generators/frontend/index.js");
+ await generateFrontend(addTmpDir, updatedConfig, BASE_VERSIONS);
+ await fs.copy(addTmpDir, projectDir, { overwrite: false });
+ } finally {
+ await fs.remove(addTmpDir);
+ }
+
+ expect(
+ await fs.pathExists(path.join(projectDir, "frontend", "package.json")),
+ ).toBe(true);
+ expect(
+ await fs.pathExists(path.join(projectDir, "frontend", "src", "App.svelte")),
+ ).toBe(true);
+ expect(
+ await fs.pathExists(path.join(projectDir, "backend", "pom.xml")),
+ ).toBe(true);
+ });
+
it("detects conflict when adding spring-boot to project with existing backend", async () => {
await generateBase({ backendType: "fastapi" });
diff --git a/src/__tests__/detect-project.test.ts b/src/__tests__/detect-project.test.ts
index 2467bdd..f29546d 100644
--- a/src/__tests__/detect-project.test.ts
+++ b/src/__tests__/detect-project.test.ts
@@ -52,6 +52,18 @@ describe("detectProject", () => {
expect(result.config.frontend).toBe("react-vite");
});
+ it("detects svelte frontend from App.svelte", async () => {
+ await fs.ensureDir(path.join(tmpDir, "frontend", "src"));
+ await fs.writeFile(
+ path.join(tmpDir, "frontend", "src", "App.svelte"),
+ "",
+ );
+
+ const result = await detectProject(tmpDir);
+ expect(result.source).toBe("filesystem");
+ expect(result.config.frontend).toBe("svelte");
+ });
+
it("detects multiple layers from sentinel files", async () => {
await fs.writeFile(path.join(tmpDir, "docker-compose.yml"), "version: 3");
await fs.ensureDir(path.join(tmpDir, ".github", "workflows"));
diff --git a/src/__tests__/e2e.test.ts b/src/__tests__/e2e.test.ts
index 1ab846f..70d65c2 100644
--- a/src/__tests__/e2e.test.ts
+++ b/src/__tests__/e2e.test.ts
@@ -380,4 +380,34 @@ describe("ForgeKit e2e — generation pipeline", () => {
);
expect(dockerCompose).toContain("postgres:");
}, 15_000);
+
+ it("S11: Svelte only — generates key files and package.json contains svelte", async () => {
+ const projectDir = await run(
+ baseConfig({
+ frontend: "svelte",
+ uiFramework: "tailwind",
+ }),
+ );
+
+ const frontendDir = path.join(projectDir, "frontend");
+ const srcDir = path.join(frontendDir, "src");
+
+ expect(await fs.pathExists(path.join(frontendDir, "package.json"))).toBe(
+ true,
+ );
+ expect(await fs.pathExists(path.join(frontendDir, "vite.config.ts"))).toBe(
+ true,
+ );
+ expect(await fs.pathExists(path.join(frontendDir, "svelte.config.js"))).toBe(
+ true,
+ );
+ expect(await fs.pathExists(path.join(srcDir, "App.svelte"))).toBe(true);
+ expect(await fs.pathExists(path.join(srcDir, "main.ts"))).toBe(true);
+ expect(
+ await fs.pathExists(path.join(srcDir, "components", "Layout.svelte")),
+ ).toBe(true);
+
+ const pkg = await fs.readJson(path.join(frontendDir, "package.json"));
+ expect(pkg.dependencies["svelte"]).toBeDefined();
+ }, 15_000);
});
diff --git a/src/__tests__/fixtures.ts b/src/__tests__/fixtures.ts
index 49294d8..2df51e4 100644
--- a/src/__tests__/fixtures.ts
+++ b/src/__tests__/fixtures.ts
@@ -25,6 +25,7 @@ export function makeBaseConfig(
claudeCode: false,
speckit: false,
workflowMode: "none",
+ gitStrategy: "pr-required",
speckitPreset: null,
gitInit: false,
...overrides,
@@ -53,6 +54,8 @@ export const BASE_VERSIONS: ResolvedVersions = {
reactRouter: "7.5.0",
vite: "6.3.0",
axiosReact: "1.8.0",
+ svelte: "5.38.7",
+ vitePluginSvelte: "6.2.0",
next: "15.3.0",
nextAuth: "5.0.0",
prismaClient: "6.6.0",
diff --git a/src/__tests__/versions.test.ts b/src/__tests__/versions.test.ts
index 720a0ed..d51e4c9 100644
--- a/src/__tests__/versions.test.ts
+++ b/src/__tests__/versions.test.ts
@@ -98,4 +98,14 @@ describe("resolveVersions", () => {
);
});
});
+
+ it("resolves Svelte frontend versions with fallback data", async () => {
+ vi.stubGlobal("fetch", () => Promise.resolve({ ok: false } as Response));
+
+ const result = await resolveVersions({ backendType: null, frontend: "svelte" });
+
+ expect(result.svelte).toBe(FALLBACK_VERSIONS.svelte);
+ expect(result.vitePluginSvelte).toBe(FALLBACK_VERSIONS.vitePluginSvelte);
+ expect(result.tailwind).toBe(FALLBACK_VERSIONS.tailwind);
+ });
});
diff --git a/src/commands/add.ts b/src/commands/add.ts
index 7fa3a95..d97054f 100644
--- a/src/commands/add.ts
+++ b/src/commands/add.ts
@@ -62,6 +62,11 @@ const LAYER_CONFIG_MAP: Record = {
configValue: "vue",
conflictGroup: "frontend",
},
+ svelte: {
+ configField: "frontend",
+ configValue: "svelte",
+ conflictGroup: "frontend",
+ },
docker: { configField: "docker", configValue: true, conflictGroup: null },
ci: { configField: "ci", configValue: true, conflictGroup: null },
"claude-code": {
@@ -123,6 +128,7 @@ async function runLayerGenerator(
case "angular":
case "react":
case "vue":
+ case "svelte":
await generateFrontend(projectDir, config, versions);
break;
case "docker":
@@ -157,7 +163,10 @@ async function regenerateDependentLayers(
layer === "laravel" ||
layer === "nextjs";
const isFrontendLayer =
- layer === "angular" || layer === "react" || layer === "vue";
+ layer === "angular" ||
+ layer === "react" ||
+ layer === "vue" ||
+ layer === "svelte";
if (!isBackendLayer && !isFrontendLayer) return;
@@ -218,6 +227,9 @@ Layers:
react Frontend React (Vite + Tailwind CSS)
Options : --auth
+ svelte Frontend Svelte (Vite + Tailwind CSS)
+ Options : --auth
+
docker Docker Compose (PostgreSQL + pgAdmin)
ci GitHub Actions CI (lint + test + build)
claude-code Config Claude Code (.claude/, CLAUDE.md, skills, hooks)
@@ -287,6 +299,7 @@ Exemples:
claudeCode: false,
speckit: false,
workflowMode: "none",
+ gitStrategy: "pr-required",
speckitPreset: null,
gitInit: false,
};
diff --git a/src/commands/new.ts b/src/commands/new.ts
index 822e17d..97f8076 100644
--- a/src/commands/new.ts
+++ b/src/commands/new.ts
@@ -103,6 +103,14 @@ export async function generateProject(
`\r ✔ Frontend Vue.js ${versions.vue} (Vite) généré `,
),
);
+ } else if (config.frontend === "svelte") {
+ process.stdout.write(chalk.yellow(" ⏳ Frontend Svelte (Vite)..."));
+ await generateFrontend(projectDir, config, versions);
+ console.log(
+ chalk.green(
+ `\r ✔ Frontend Svelte ${versions.svelte} (Vite) généré `,
+ ),
+ );
}
if (config.docker) {
@@ -172,6 +180,7 @@ export const newCommand = new Command("new")
.option("--frontend", "Inclure le frontend Angular")
.option("--no-frontend", "Exclure le frontend Angular")
.option("--react", "Inclure le frontend React (Vite + Tailwind)")
+ .option("--svelte", "Inclure le frontend Svelte (Vite + Tailwind)")
.option("--angular", "Inclure le frontend Angular (standalone, OnPush)")
.option("--auth", "Inclure l'authentification")
.option("--flyway", "Inclure Flyway (migrations SQL)")
@@ -206,6 +215,7 @@ Backends:
Frontends:
--angular Angular (standalone, OnPush)
--react React (Vite + Tailwind CSS)
+ --svelte Svelte (Vite + Tailwind CSS)
Infrastructure:
--docker Docker Compose (PostgreSQL + pgAdmin) [défaut: oui si backend]
@@ -218,6 +228,7 @@ Exemples:
$ forgekit new my-app
$ forgekit new my-api --spring-boot --no-frontend --group com.acme
$ forgekit new my-app --laravel --react --auth --openapi
+ $ forgekit new my-app --fastapi --svelte --auth
$ forgekit new my-app --fastapi --angular --ui tailwind --ngrx
$ forgekit new my-app --spring-boot --angular --no-flyway --no-docker`,
)
@@ -238,6 +249,7 @@ Exemples:
if (options.laravel) defaults.backendType = "laravel" as BackendType;
if (options.nestjs) defaults.backendType = "nestjs" as BackendType;
if (options.react) defaults.frontend = "react-vite" as FrontendType;
+ else if (options.svelte) defaults.frontend = "svelte" as FrontendType;
else if (options.angular || options.frontend === true)
defaults.frontend = "angular" as FrontendType;
else if (options.frontend === false) defaults.frontend = null;
@@ -322,6 +334,10 @@ Exemples:
console.log(
chalk.cyan(" cd frontend && npm install && npm run dev"),
);
+ if (config.frontend === "svelte")
+ console.log(
+ chalk.cyan(" cd frontend && npm install && npm run dev"),
+ );
console.log("");
} catch (error) {
console.log(
diff --git a/src/generators/claude-code/__tests__/claude-code.test.ts b/src/generators/claude-code/__tests__/claude-code.test.ts
index b4e95bc..79a26ec 100644
--- a/src/generators/claude-code/__tests__/claude-code.test.ts
+++ b/src/generators/claude-code/__tests__/claude-code.test.ts
@@ -474,7 +474,11 @@ describe("ClaudeCodeGenerator", () => {
});
it("vibe mode → settings.json git.strategy=no-pr and CLAUDE.md uses commit skill", async () => {
- const config = { ...baseConfig, workflowMode: "vibe" as const };
+ const config = {
+ ...baseConfig,
+ workflowMode: "vibe" as const,
+ gitStrategy: "no-pr" as const,
+ };
await generateClaudeCode(tmpDir, config, baseVersions);
const settings = await fs.readJson(
path.join(tmpDir, ".claude", "settings.json"),
diff --git a/src/generators/claude-code/index.ts b/src/generators/claude-code/index.ts
index c942e77..b0298bb 100644
--- a/src/generators/claude-code/index.ts
+++ b/src/generators/claude-code/index.ts
@@ -114,6 +114,8 @@ class ClaudeCodeGenerator extends BaseGenerator {
const hasFrontend = this.config.frontend !== null;
const angular = this.config.frontend === "angular";
const reactVite = this.config.frontend === "react-vite";
+ const vue = this.config.frontend === "vue";
+ const svelte = this.config.frontend === "svelte";
const parentHooks = await this.readParentHooks();
@@ -128,6 +130,8 @@ class ClaudeCodeGenerator extends BaseGenerator {
hasFrontend,
angular,
reactVite,
+ vue,
+ svelte,
frontend: hasFrontend,
docker: this.config.docker,
flyway: this.config.flyway,
@@ -138,9 +142,8 @@ class ClaudeCodeGenerator extends BaseGenerator {
claudeDir: ".claude",
workflowSpeckit: this.config.workflowMode === "speckit",
workflowVibe: this.config.workflowMode === "vibe",
- gitStrategy:
- this.config.workflowMode === "vibe" ? "no-pr" : "pr-required",
- gitStrategyNoPr: this.config.workflowMode === "vibe",
+ gitStrategy: this.config.gitStrategy,
+ gitStrategyNoPr: this.config.gitStrategy === "no-pr",
hasParentSessionStart: parentHooks.has("SessionStart"),
hasParentPreCompact: parentHooks.has("PreCompact"),
...this.resolveSpeckitData(),
@@ -405,6 +408,16 @@ class ClaudeCodeGenerator extends BaseGenerator {
);
}
+ if (this.config.frontend === "svelte") {
+ commands.push(
+ "Bash(npm run dev)",
+ "Bash(npm run build)",
+ "Bash(npm run lint)",
+ "Bash(npm install)",
+ "Bash(npm run)",
+ );
+ }
+
if (this.config.docker) {
commands.push(
"Bash(docker compose up)",
diff --git a/src/generators/frontend/__tests__/index.test.ts b/src/generators/frontend/__tests__/index.test.ts
index 8e9191a..c720bae 100644
--- a/src/generators/frontend/__tests__/index.test.ts
+++ b/src/generators/frontend/__tests__/index.test.ts
@@ -32,6 +32,17 @@ describe("generateFrontend router", () => {
).toBe(true);
});
+ it("generates Svelte files when frontend is svelte", async () => {
+ const config = { ...baseConfig, frontend: "svelte" as const };
+ await generateFrontend(tmpDir, config, baseVersions);
+ expect(
+ await fs.pathExists(path.join(tmpDir, "frontend", "vite.config.ts")),
+ ).toBe(true);
+ expect(
+ await fs.pathExists(path.join(tmpDir, "frontend", "src", "App.svelte")),
+ ).toBe(true);
+ });
+
it("generates Angular files when frontend is angular (US1 regression)", async () => {
const config = { ...baseConfig, frontend: "angular" as const };
await generateFrontend(tmpDir, config, baseVersions);
diff --git a/src/generators/root/index.ts b/src/generators/root/index.ts
index 7444014..fcf0f0a 100644
--- a/src/generators/root/index.ts
+++ b/src/generators/root/index.ts
@@ -20,6 +20,8 @@ class RootGenerator extends BaseGenerator {
const hasFrontend = this.config.frontend !== null;
const angular = this.config.frontend === "angular";
const reactVite = this.config.frontend === "react-vite";
+ const vue = this.config.frontend === "vue";
+ const svelte = this.config.frontend === "svelte";
const data = {
name: this.config.name,
@@ -31,6 +33,8 @@ class RootGenerator extends BaseGenerator {
hasFrontend,
angular,
reactVite,
+ vue,
+ svelte,
docker: this.config.docker,
versions: this.versions,
};
diff --git a/src/prompts/add.ts b/src/prompts/add.ts
index 015485f..1cdd963 100644
--- a/src/prompts/add.ts
+++ b/src/prompts/add.ts
@@ -29,6 +29,9 @@ export async function promptAddLayerConfig(
if (layer === "vue") {
return promptVue(defaults);
}
+ if (layer === "svelte") {
+ return promptSvelte(defaults);
+ }
if (layer === "prettier") {
if (existingConfig.frontend === null) {
throw new Error(
@@ -217,3 +220,9 @@ async function promptVue(
): Promise> {
return promptAuth(defaults);
}
+
+async function promptSvelte(
+ defaults: Partial,
+): Promise> {
+ return promptAuth(defaults);
+}
diff --git a/src/prompts/project.ts b/src/prompts/project.ts
index 69224c1..2a7e3d9 100644
--- a/src/prompts/project.ts
+++ b/src/prompts/project.ts
@@ -10,6 +10,7 @@ import type {
BackendType,
FrontendType,
WorkflowMode,
+ GitStrategy,
SpeckitPreset,
} from "../types.js";
@@ -61,6 +62,7 @@ export async function promptProjectConfig(
{ name: "Angular (standalone, OnPush)", value: "angular" },
{ name: "React (Vite + Tailwind)", value: "react-vite" },
{ name: "Vue.js (Vite + Tailwind)", value: "vue" },
+ { name: "Svelte (Vite + Tailwind)", value: "svelte" },
{ name: "Aucun", value: null },
],
default: "angular",
@@ -231,6 +233,8 @@ export async function promptProjectConfig(
uiFramework = "tailwind";
} else if (frontend === "vue") {
uiFramework = "tailwind";
+ } else if (frontend === "svelte") {
+ uiFramework = "tailwind";
}
// ── Section 5: Infrastructure ─────────────────────────────────────────────
@@ -321,6 +325,24 @@ export async function promptProjectConfig(
});
}
+ let gitStrategy: GitStrategy = defaults.gitStrategy ?? "pr-required";
+ if (workflowMode === "vibe" && defaults.gitStrategy === undefined) {
+ gitStrategy = await select({
+ message: "Stratégie git",
+ choices: [
+ {
+ name: "PR obligatoire (plus sûr)",
+ value: "pr-required",
+ },
+ {
+ name: "Push direct sur master (plus rapide)",
+ value: "no-pr",
+ },
+ ],
+ default: "pr-required",
+ });
+ }
+
let speckitPreset: SpeckitPreset | null = defaults.speckitPreset ?? null;
if (workflowMode === "speckit" && defaults.speckitPreset === undefined) {
speckitPreset = await select({
@@ -364,6 +386,7 @@ export async function promptProjectConfig(
docker,
speckit,
workflowMode,
+ gitStrategy,
speckitPreset,
ci,
claudeCode,
diff --git a/src/templates/claude-code/CLAUDE.md.hbs b/src/templates/claude-code/CLAUDE.md.hbs
index eab36fa..247346b 100644
--- a/src/templates/claude-code/CLAUDE.md.hbs
+++ b/src/templates/claude-code/CLAUDE.md.hbs
@@ -64,3 +64,31 @@ npm run build # Production build
npm run lint # TypeScript check
```
{{/if}}
+{{#if vue}}
+## Frontend — Vue {{versions.vue}} (Vite)
+
+- **Architecture:** `src/router/`, `src/stores/`, `src/components/`, `src/composables/`
+- **Conventions:** Composition API, typed stores, Tailwind-first styling
+
+### Commands
+```bash
+cd frontend
+npm run dev # Start dev server (port 4200)
+npm run build # Production build
+npm run lint # TypeScript check
+```
+{{/if}}
+{{#if svelte}}
+## Frontend — Svelte {{versions.svelte}} (Vite)
+
+- **Architecture:** `src/components/`, `src/stores/`, `src/lib/`
+- **Conventions:** Small components, typed stores, Tailwind-first styling
+
+### Commands
+```bash
+cd frontend
+npm run dev # Start dev server (port 4200)
+npm run build # Production build
+npm run lint # Svelte/TypeScript check
+```
+{{/if}}
diff --git a/src/templates/claude-code/rules/frontend.md.hbs b/src/templates/claude-code/rules/frontend.md.hbs
index b895c7f..85a58f1 100644
--- a/src/templates/claude-code/rules/frontend.md.hbs
+++ b/src/templates/claude-code/rules/frontend.md.hbs
@@ -20,3 +20,18 @@ paths: ["**/frontend/**"]
- **CSS:** Tailwind CSS v4
- **Commands:** `cd frontend` → `npm install` | `npm run dev` | `npm run build` | `npm run lint`
{{/if}}
+{{#if vue}}
+## Frontend — Vue {{versions.vue}} (Vite + Tailwind)
+
+- **Vue {{versions.vue}}** — Composition API, single-file components
+- **State:** Pinia
+- **CSS:** Tailwind CSS v4
+- **Commands:** `cd frontend` → `npm install` | `npm run dev` | `npm run build` | `npm run lint`
+{{/if}}
+{{#if svelte}}
+## Frontend — Svelte {{versions.svelte}} (Vite + Tailwind)
+
+- **Svelte {{versions.svelte}}** — component-first, minimal client-side setup
+- **CSS:** Tailwind CSS v4
+- **Commands:** `cd frontend` → `npm install` | `npm run dev` | `npm run build` | `npm run lint`
+{{/if}}
diff --git a/src/templates/root/README.md.hbs b/src/templates/root/README.md.hbs
index a797462..95880c3 100644
--- a/src/templates/root/README.md.hbs
+++ b/src/templates/root/README.md.hbs
@@ -16,6 +16,12 @@
{{#if reactVite}}
- **Frontend:** React {{versions.react}} / Vite / Tailwind CSS
{{/if}}
+{{#if vue}}
+- **Frontend:** Vue {{versions.vue}} / Vite / Tailwind CSS
+{{/if}}
+{{#if svelte}}
+- **Frontend:** Svelte {{versions.svelte}} / Vite / Tailwind CSS
+{{/if}}
{{#if docker}}
- **Infra:** Docker Compose (PostgreSQL 17 + pgAdmin)
{{/if}}
@@ -49,6 +55,14 @@ cd frontend && npm install && ng serve
# Démarrer le frontend (port 4200)
cd frontend && npm install && npm run dev
{{/if}}
+{{#if vue}}
+# Démarrer le frontend (port 4200)
+cd frontend && npm install && npm run dev
+{{/if}}
+{{#if svelte}}
+# Démarrer le frontend (port 4200)
+cd frontend && npm install && npm run dev
+{{/if}}
```
---
diff --git a/src/types.ts b/src/types.ts
index ac25650..fd0ee7b 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -1,6 +1,7 @@
export type UIFramework = "primeng" | "tailwind" | "none";
export type PrimeNGPreset = "Aura" | "Lara" | "Nora";
export type WorkflowMode = "speckit" | "vibe" | "none";
+export type GitStrategy = "pr-required" | "no-pr";
export type SpeckitPreset = "rigorous" | "balanced" | "fast" | "bare-metal";
export type BackendType =
| "spring-boot"
@@ -9,7 +10,7 @@ export type BackendType =
| "nextjs"
| "nestjs"
| null;
-export type FrontendType = "angular" | "react-vite" | "vue" | null;
+export type FrontendType = "angular" | "react-vite" | "vue" | "svelte" | null;
export interface ProjectConfig {
name: string;
@@ -36,6 +37,7 @@ export interface ProjectConfig {
claudeCode: boolean;
speckit: boolean;
workflowMode: WorkflowMode;
+ gitStrategy: GitStrategy;
speckitPreset: SpeckitPreset | null;
gitInit: boolean;
}
diff --git a/src/utils/detect-project.ts b/src/utils/detect-project.ts
index bb2c124..848cd88 100644
--- a/src/utils/detect-project.ts
+++ b/src/utils/detect-project.ts
@@ -48,6 +48,12 @@ const SENTINEL_MAP: Array<{
c.frontend = "vue";
},
},
+ {
+ files: ["frontend/src/App.svelte", "frontend/svelte.config.js"],
+ apply: (c) => {
+ c.frontend = "svelte";
+ },
+ },
{
files: ["frontend/vite.config.ts"],
apply: (c) => {
@@ -108,6 +114,7 @@ function defaultConfig(projectDir: string): ProjectConfig {
claudeCode: false,
speckit: false,
workflowMode: "none",
+ gitStrategy: "pr-required",
speckitPreset: null,
gitInit: false,
};
diff --git a/src/versions.ts b/src/versions.ts
index f6aebe5..b1b1519 100644
--- a/src/versions.ts
+++ b/src/versions.ts
@@ -26,6 +26,8 @@ export interface ResolvedVersions {
reactRouter: string;
vite: string;
axiosReact: string;
+ svelte: string;
+ vitePluginSvelte: string;
// Backend — Next.js
next: string;
nextAuth: string;
@@ -69,6 +71,8 @@ export const FALLBACK_VERSIONS: ResolvedVersions = {
reactRouter: "7.5.0", // renovate: datasource=npm depName=react-router
vite: "7.0.0", // renovate: datasource=npm depName=vite
axiosReact: "1.8.0", // renovate: datasource=npm depName=axios
+ svelte: "5.38.7", // renovate: datasource=npm depName=svelte
+ vitePluginSvelte: "6.2.0", // renovate: datasource=npm depName=@sveltejs/vite-plugin-svelte
next: "15.3.0", // renovate: datasource=npm depName=next
nextAuth: "5.0.0-beta.31", // renovate: datasource=npm depName=next-auth
prismaClient: "6.6.0", // renovate: datasource=npm depName=prisma
@@ -260,6 +264,17 @@ export async function resolveVersions(opts: {
);
}
+ if (opts.frontend === "svelte") {
+ tasks.push(
+ fetchNpmVersion("svelte").then(set("svelte")),
+ fetchNpmVersion("@sveltejs/vite-plugin-svelte").then(
+ set("vitePluginSvelte"),
+ ),
+ fetchNpmVersion("tailwindcss").then(set("tailwind")),
+ fetchNpmVersion("vite").then(set("vite")),
+ );
+ }
+
if (opts.frontend === "react-vite") {
tasks.push(
fetchNpmVersion("react").then(set("react")),