diff --git a/src/cli/init.ts b/src/cli/init.ts index dfaaa25..2a075dd 100644 --- a/src/cli/init.ts +++ b/src/cli/init.ts @@ -44,6 +44,46 @@ async function createPreflightConfig(): Promise { } } +/** Inject preflight rules into CLAUDE.md */ +async function injectClaudeRules(mode: "strict" | "relaxed"): Promise { + const claudePath = join(process.cwd(), "CLAUDE.md"); + const rulesDir = join(process.cwd(), ".claude"); + const rulesPath = join(rulesDir, "preflight-rules.md"); + + try { + // Get the template + const currentFile = fileURLToPath(import.meta.url); + const srcDir = dirname(dirname(currentFile)); + const templatesDir = join(srcDir, "templates"); + const templateFile = join(templatesDir, `claude-rules-${mode}.md`); + const rules = await readFile(templateFile, "utf-8"); + + // Write rules to .claude/preflight-rules.md + await mkdir(rulesDir, { recursive: true }); + await writeFile(rulesPath, rules); + console.log(`✅ Created .claude/preflight-rules.md (${mode} mode)`); + + // Check if CLAUDE.md exists and already imports the rules + const importLine = `@.claude/preflight-rules.md`; + let claudeContent = ""; + try { + claudeContent = await readFile(claudePath, "utf-8"); + } catch { /* file doesn't exist yet */ } + + if (claudeContent.includes(importLine)) { + console.log(" CLAUDE.md already imports preflight rules\n"); + return; + } + + // Append import to CLAUDE.md + const separator = claudeContent.length > 0 ? "\n\n" : ""; + await writeFile(claudePath, claudeContent + separator + importLine + "\n"); + console.log(" Added import to CLAUDE.md\n"); + } catch (error) { + console.error(`❌ Failed to inject CLAUDE.md rules: ${error}\n`); + } +} + interface McpConfig { mcpServers: Record { await createPreflightConfig(); } + // Ask about CLAUDE.md rules + console.log("Preflight can inject rules into CLAUDE.md so Claude Code automatically"); + console.log("uses preflight tools at the right moments.\n"); + + const rulesChoice = await ask("Add preflight rules to CLAUDE.md? [strict/relaxed/N] (default: N): "); + const rulesMode = rulesChoice.trim().toLowerCase(); + if (rulesMode === "strict" || rulesMode === "relaxed") { + await injectClaudeRules(rulesMode); + } + const env: Record = { PROMPT_DISCIPLINE_PROFILE: profile, }; diff --git a/src/templates/claude-rules-relaxed.md b/src/templates/claude-rules-relaxed.md new file mode 100644 index 0000000..f4414d1 --- /dev/null +++ b/src/templates/claude-rules-relaxed.md @@ -0,0 +1,9 @@ +# Preflight Rules (Relaxed Mode) + +## Recommended Practices + +- Call `preflight_check` before large or complex tasks spanning multiple files. +- Use `clarify_intent` when a prompt seems vague or underspecified. +- Use `verify_completion` before wrapping up significant tasks. +- Call `checkpoint` periodically during long sessions to save progress. +- Use `what_changed` to review modifications before committing. diff --git a/src/templates/claude-rules-strict.md b/src/templates/claude-rules-strict.md new file mode 100644 index 0000000..96f5fff --- /dev/null +++ b/src/templates/claude-rules-strict.md @@ -0,0 +1,25 @@ +# Preflight Rules (Strict Mode) + +## Before Starting Work + +- **ALWAYS** call `preflight_check` before any multi-file task or task involving more than 3 tool calls. +- **ALWAYS** call `clarify_intent` when a prompt is ambiguous or could be interpreted multiple ways. +- **ALWAYS** call `scope_work` before tasks that touch more than 2 files. + +## During Work + +- Call `checkpoint` every 100 tool calls or when completing a logical milestone. +- Call `session_health` if you notice degraded performance or context confusion. +- Call `search_history` before re-implementing something that may already exist in the session. + +## Before Completing Work + +- **ALWAYS** call `verify_completion` before declaring a task done. +- **ALWAYS** call `prompt_score` on your own summary to ensure clarity. +- Call `generate_scorecard` at the end of significant work sessions. + +## Error Handling + +- If a task fails, call `log_correction` to record the pattern. +- Call `what_changed` to review your modifications before committing. +- Use `token_audit` if the session feels expensive or bloated. diff --git a/tests/cli/claude-rules.test.ts b/tests/cli/claude-rules.test.ts new file mode 100644 index 0000000..6e19ae5 --- /dev/null +++ b/tests/cli/claude-rules.test.ts @@ -0,0 +1,39 @@ +import { describe, it, expect } from "vitest"; +import { readFile } from "node:fs/promises"; +import { join, dirname } from "node:path"; +import { fileURLToPath } from "node:url"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); +const templatesDir = join(__dirname, "../../src/templates"); + +describe("CLAUDE.md rule templates", () => { + it("strict template exists and contains required rules", async () => { + const content = await readFile(join(templatesDir, "claude-rules-strict.md"), "utf-8"); + expect(content).toContain("preflight_check"); + expect(content).toContain("verify_completion"); + expect(content).toContain("clarify_intent"); + expect(content).toContain("checkpoint"); + expect(content).toContain("Strict Mode"); + }); + + it("relaxed template exists and is shorter than strict", async () => { + const strict = await readFile(join(templatesDir, "claude-rules-strict.md"), "utf-8"); + const relaxed = await readFile(join(templatesDir, "claude-rules-relaxed.md"), "utf-8"); + expect(relaxed.length).toBeLessThan(strict.length); + expect(relaxed).toContain("preflight_check"); + expect(relaxed).toContain("Relaxed Mode"); + }); + + it("strict template has ALWAYS keywords for critical rules", async () => { + const content = await readFile(join(templatesDir, "claude-rules-strict.md"), "utf-8"); + const alwaysCount = (content.match(/ALWAYS/g) || []).length; + expect(alwaysCount).toBeGreaterThanOrEqual(4); + }); + + it("relaxed template uses softer language", async () => { + const content = await readFile(join(templatesDir, "claude-rules-relaxed.md"), "utf-8"); + expect(content).not.toContain("**ALWAYS**"); + expect(content).toContain("Recommended"); + }); +});