Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 44 additions & 67 deletions src/generators/claude-code/__tests__/claude-code.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,6 @@ describe("ClaudeCodeGenerator", () => {

it("generates hooks directory with scripts", async () => {
await generateClaudeCode(tmpDir, baseConfig, baseVersions);
expect(
await fs.pathExists(path.join(tmpDir, ".claude", "hooks", "pre-bash.sh")),
).toBe(true);
expect(
await fs.pathExists(
path.join(tmpDir, ".claude", "hooks", "session-start.sh"),
Expand All @@ -69,7 +66,7 @@ describe("ClaudeCodeGenerator", () => {
it("makes hook scripts executable", async () => {
await generateClaudeCode(tmpDir, baseConfig, baseVersions);
const stat = await fs.stat(
path.join(tmpDir, ".claude", "hooks", "pre-bash.sh"),
path.join(tmpDir, ".claude", "hooks", "session-start.sh"),
);
expect(stat.mode & 0o100).toBeTruthy();
});
Expand Down Expand Up @@ -102,16 +99,13 @@ describe("ClaudeCodeGenerator", () => {
).toBe(true);
});

it("settings.json contains hooks configuration", async () => {
it("settings.json contains a hooks object", async () => {
await generateClaudeCode(tmpDir, baseConfig, baseVersions);
const settings = await fs.readJson(
path.join(tmpDir, ".claude", "settings.json"),
);
expect(settings.hooks).toBeDefined();
// PreToolUse is always present (project-specific pre-bash guards)
expect(settings.hooks.PreToolUse).toBeDefined();
// In test environment (no parent ~/.claude/settings.json), SessionStart and PreCompact should be included
expect(Object.keys(settings.hooks).length).toBeGreaterThanOrEqual(1);
expect(typeof settings.hooks).toBe("object");
});

it("generates angular skill when frontend is enabled", async () => {
Expand Down Expand Up @@ -371,98 +365,85 @@ describe("ClaudeCodeGenerator", () => {
expect(content).not.toContain("## Workflow Mode: vibe");
});

it("CLAUDE.md contains ## Claude Settings block when workflowMode is speckit", async () => {
it("CLAUDE.md never contains a Claude Settings table (settings.json is the SSOT)", async () => {
for (const mode of ["speckit", "vibe", "none"] as const) {
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "forgekit-test-"));
await generateClaudeCode(
dir,
{
...baseConfig,
workflowMode: mode,
speckitPreset: mode === "speckit" ? ("balanced" as const) : null,
},
baseVersions,
);
const content = await fs.readFile(path.join(dir, "CLAUDE.md"), "utf-8");
expect(content).not.toContain("## Claude Settings");
expect(content).not.toContain("`speckit.tests`");
expect(content).not.toContain("`git.strategy`");
await fs.remove(dir);
}
});

it("settings.json reflects balanced preset values in speckit mode", async () => {
const config = {
...baseConfig,
workflowMode: "speckit" as const,
speckitPreset: "balanced" as const,
};
await generateClaudeCode(tmpDir, config, baseVersions);
const content = await fs.readFile(path.join(tmpDir, "CLAUDE.md"), "utf-8");
expect(content).toContain("## Claude Settings");
expect(content).toContain("| `speckit.tests` | `settings.json` | `true` |");
expect(content).toContain("| `speckit.tdd` | `settings.json` | `false` |");
expect(content).toContain(
"| `speckit.code-review` | `settings.json` | `true` |",
);
expect(content).toContain(
"| `speckit.verification` | `settings.json` | `minimal` |",
);
expect(content).toContain(
"| `speckit.plan-detail` | `settings.json` | `medium` |",
);

const settingsContent = await fs.readFile(
const settings = await fs.readJson(
path.join(tmpDir, ".claude", "settings.json"),
"utf-8",
);
const settings = JSON.parse(settingsContent);
expect(settings.git.strategy).toBe("pr-required");
expect(settings.speckit.tests).toBe(true);
expect(settings.speckit.tdd).toBe(false);
expect(settings.speckit["code-review"]).toBe(true);
expect(settings.speckit.verification).toBe("minimal");
expect(settings.speckit["plan-detail"]).toBe("medium");
});

it("CLAUDE.md Speckit Config — rigorous preset", async () => {
it("settings.json reflects rigorous preset values", async () => {
const config = {
...baseConfig,
workflowMode: "speckit" as const,
speckitPreset: "rigorous" as const,
};
await generateClaudeCode(tmpDir, config, baseVersions);
const content = await fs.readFile(path.join(tmpDir, "CLAUDE.md"), "utf-8");
expect(content).toContain("| `speckit.tdd` | `settings.json` | `true` |");
expect(content).toContain(
"| `speckit.verification` | `settings.json` | `full` |",
);
expect(content).toContain(
"| `speckit.plan-detail` | `settings.json` | `high` |",
const settings = await fs.readJson(
path.join(tmpDir, ".claude", "settings.json"),
);
expect(settings.speckit.tdd).toBe(true);
expect(settings.speckit.verification).toBe("full");
expect(settings.speckit["plan-detail"]).toBe("high");
});

it("CLAUDE.md Speckit Config — fast preset (skip-clarify: true, no fast-mode line)", async () => {
it("settings.json reflects fast preset values", async () => {
const config = {
...baseConfig,
workflowMode: "speckit" as const,
speckitPreset: "fast" as const,
};
await generateClaudeCode(tmpDir, config, baseVersions);
const content = await fs.readFile(path.join(tmpDir, "CLAUDE.md"), "utf-8");
expect(content).toContain(
"| `speckit.skip-clarify` | `settings.json` | `true` |",
);
expect(content).toContain(
"| `speckit.code-review` | `settings.json` | `false` |",
const settings = await fs.readJson(
path.join(tmpDir, ".claude", "settings.json"),
);
expect(settings.speckit["skip-clarify"]).toBe(true);
expect(settings.speckit["code-review"]).toBe(false);
});

it("CLAUDE.md Speckit Config — bare-metal preset", async () => {
it("settings.json reflects bare-metal preset values", async () => {
const config = {
...baseConfig,
workflowMode: "speckit" as const,
speckitPreset: "bare-metal" as const,
};
await generateClaudeCode(tmpDir, config, baseVersions);
const content = await fs.readFile(path.join(tmpDir, "CLAUDE.md"), "utf-8");
expect(content).toContain(
"| `speckit.tests` | `settings.json` | `false` |",
);
expect(content).toContain(
"| `speckit.verification` | `settings.json` | `skip` |",
const settings = await fs.readJson(
path.join(tmpDir, ".claude", "settings.json"),
);
});

it("CLAUDE.md does not contain ## Claude Settings when workflowMode is vibe", async () => {
const config = { ...baseConfig, workflowMode: "vibe" as const };
await generateClaudeCode(tmpDir, config, baseVersions);
const content = await fs.readFile(path.join(tmpDir, "CLAUDE.md"), "utf-8");
expect(content).not.toContain("`speckit.tests`");
});

it("CLAUDE.md does not contain ## Claude Settings when workflowMode is none", async () => {
const config = { ...baseConfig, workflowMode: "none" as const };
await generateClaudeCode(tmpDir, config, baseVersions);
const content = await fs.readFile(path.join(tmpDir, "CLAUDE.md"), "utf-8");
expect(content).not.toContain("`speckit.tests`");
expect(settings.speckit.tests).toBe(false);
expect(settings.speckit.verification).toBe("skip");
});

it("CLAUDE.md contains '## Workflow Mode: vibe' when workflowMode is vibe", async () => {
Expand All @@ -488,7 +469,6 @@ describe("ClaudeCodeGenerator", () => {
expect(content).toContain("Merge direct sur `master`");
expect(content).toContain("`commit-commands:commit`");
expect(content).not.toContain("`commit-commands:commit-push-pr`");
expect(content).toContain("| `git.strategy` | `settings.json` | `no-pr` |");
});

it("speckit mode → settings.json git.strategy=pr-required and CLAUDE.md uses commit-push-pr skill", async () => {
Expand All @@ -505,9 +485,6 @@ describe("ClaudeCodeGenerator", () => {
const content = await fs.readFile(path.join(tmpDir, "CLAUDE.md"), "utf-8");
expect(content).toContain("PR obligatoire");
expect(content).toContain("`commit-commands:commit-push-pr`");
expect(content).toContain(
"| `git.strategy` | `settings.json` | `pr-required` |",
);
});

it("none mode → settings.json git.strategy=pr-required (default)", async () => {
Expand Down
6 changes: 0 additions & 6 deletions src/generators/claude-code/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,12 +171,6 @@ class ClaudeCodeGenerator extends BaseGenerator {
data,
),
// Hook scripts (templated, then chmod)
renderAndWrite(
"claude-code/hooks/pre-bash.sh.hbs",
path.join(hooksDir, "pre-bash.sh"),
data,
{ mode: 0o755 },
),
renderAndWrite(
"claude-code/hooks/session-start.sh.hbs",
path.join(hooksDir, "session-start.sh"),
Expand Down
20 changes: 0 additions & 20 deletions src/templates/claude-code/CLAUDE.md.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -19,26 +19,6 @@
- Commit skill: `commit-commands:commit-push-pr`
{{/if}}

## Claude Settings

| Clé | Fichier | Valeur | Description |
|-----|---------|--------|-------------|
| `git.strategy` | `settings.json` | `{{gitStrategy}}` | Stratégie git — lue par `vibe.workflow` step 7 |
{{#if workflowSpeckit}}
| `speckit.tests` | `settings.json` | `{{speckitTests}}` | Génération de tests |
| `speckit.tdd` | `settings.json` | `{{speckitTdd}}` | Mode RED→GREEN |
| `speckit.test-types` | `settings.json` | `{{speckitTestTypes}}` | Types de tests |
| `speckit.code-review` | `settings.json` | `{{speckitCodeReview}}` | Code review automatique |
| `speckit.security-review` | `settings.json` | `{{speckitSecurityReview}}` | Security review |
| `speckit.verification` | `settings.json` | `{{speckitVerification}}` | Niveau de vérification |
| `speckit.plan-detail` | `settings.json` | `{{speckitPlanDetail}}` | Détail du plan |
| `speckit.skip-clarify` | `settings.json` | `{{speckitSkipClarify}}` | Sauter sk.clarify |
| `speckit.fast-mode` | `settings.json` | `{{speckitFastMode}}` | Fast mode |
| `speckit.subagents` | `settings.json` | `true` | Utiliser subagent-driven-development |

> `settings.local.json` overrides `settings.json` pour tous ces champs.
{{/if}}

## Architecture Constitution

Read `.specify/memory/constitution.md` before any architectural decision. It is auto-loaded at session start.
Expand Down
4 changes: 2 additions & 2 deletions src/templates/claude-code/hookify/block-dangerous-rm.local.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ pattern: rm\s+(-rf|-fr)
action: block
---

⚠️ **Commande rm -rf détectée**
⚠️ **`rm -rf` detected**

Cette commande peut supprimer des fichiers de façon irréversible. Vérifie bien le chemin avant de continuer.
This command can irreversibly delete files. Double-check the path before proceeding.
4 changes: 2 additions & 2 deletions src/templates/claude-code/hookify/block-force-push.local.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ pattern: git\s+push\s+.*--force(?!-with-lease)|git\s+push\s+.*\s-f\b
action: block
---

🚫 **git push --force bloqué**
🚫 **`git push --force` blocked**

Le force push est interdit sur ce projet. Utilise `--force-with-lease` si tu sais ce que tu fais, ou ouvre une PR à la place.
Force push is not allowed on this project. Use `--force-with-lease` if you know what you're doing, or open a PR instead.
4 changes: 2 additions & 2 deletions src/templates/claude-code/hookify/block-no-verify.local.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ pattern: --no-verify
action: block
---

🚫 **--no-verify bloqué**
🚫 **`--no-verify` blocked**

Le bypass des hooks de commit est interdit. Les tests et le lint doivent passer avant tout commit.
Bypassing commit hooks is not allowed. Tests and lint must pass before any commit.
10 changes: 5 additions & 5 deletions src/templates/claude-code/hookify/stop-verify-tests.local.md.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,16 @@ pattern: .*
action: warn
---

⚠️ **Avant de terminer — TDD check**
⚠️ **Before wrapping up — TDD check**

As-tu :
- [ ] Lancé les tests et vérifié qu'ils passent ?
Did you:
- [ ] Run the tests and confirm they pass?
{{#if fastapi}} - `cd backend && .venv/bin/pytest tests/ -v`
{{/if}}{{#if springBoot}} - `cd backend && ./mvnw test`
{{/if}}{{#if frontend}} - `cd frontend && npm test -- --watch=false`
{{/if}}- [ ] Lancé le lint ?
{{/if}}- [ ] Run the linter?
{{#if fastapi}} - `cd backend && .venv/bin/ruff check .`
{{/if}}{{#if springBoot}} - `cd backend && ./mvnw checkstyle:check` (if configured)
{{/if}}{{#if frontend}} - `cd frontend && npm run lint`
{{/if}}
Iron law : aucun commit sans tests verts.
Iron law: no commit without green tests.
4 changes: 2 additions & 2 deletions src/templates/claude-code/hookify/warn-console-log.local.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
---
name: warn-console-log
enabled: true
enabled: false
event: file
pattern: console\.log\(
action: warn
---

⚠️ **console.log detected**
⚠️ **`console.log` detected**

You're about to add a `console.log` statement. Make sure this is intentional and not a debug leftover.
4 changes: 2 additions & 2 deletions src/templates/claude-code/hookify/warn-env-edit.local.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ pattern: (^|/)\.env(\.|$)
action: warn
---

⚠️ **Fichier .env détecté**
⚠️ **`.env` file detected**

Tu es sur le point de modifier un fichier d'environnement. Assure-toi de ne pas committer de secrets — utilise `.env.example` pour les valeurs de référence.
You're about to modify an environment file. Do not commit secrets — use `.env.example` for reference values.
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ pattern: git\s+commit
action: warn
---

⚠️ **Commit détecté — tests lancés ?**
⚠️ **Commit detected — tests run?**

Avant de committer, confirme :
{{#if fastapi}}- [ ] `cd backend && .venv/bin/pytest tests/ -v` → vert
{{/if}}{{#if springBoot}}- [ ] `cd backend && ./mvnw test` → vert
{{/if}}{{#if frontend}}- [ ] `cd frontend && npm test -- --watch=false` → vert
{{/if}}{{#if fastapi}}- [ ] `cd backend && .venv/bin/ruff check .` → propre
{{/if}}{{#if frontend}}- [ ] `cd frontend && npm run lint` → propre
Before committing, confirm:
{{#if fastapi}}- [ ] `cd backend && .venv/bin/pytest tests/ -v` → green
{{/if}}{{#if springBoot}}- [ ] `cd backend && ./mvnw test` → green
{{/if}}{{#if frontend}}- [ ] `cd frontend && npm test -- --watch=false` → green
{{/if}}{{#if fastapi}}- [ ] `cd backend && .venv/bin/ruff check .` → clean
{{/if}}{{#if frontend}}- [ ] `cd frontend && npm run lint` → clean
{{/if}}
Iron law : aucun commit sans tests verts.
Iron law: no commit without green tests.
6 changes: 3 additions & 3 deletions src/templates/claude-code/hookify/warn-todo-fixme.local.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
---
name: warn-todo-fixme
enabled: true
enabled: false
event: file
pattern: TODO|FIXME
action: warn
---

⚠️ **TODO / FIXME détecté**
⚠️ **TODO / FIXME detected**

Tu ajoutes un marqueur TODO ou FIXME. Assure-toi qu'il ne sera pas oublié avant de committer.
You're adding a TODO or FIXME marker. Make sure it won't be forgotten before committing.
38 changes: 0 additions & 38 deletions src/templates/claude-code/hooks/pre-bash.sh.hbs

This file was deleted.

Loading
Loading