diff --git a/package.json b/package.json index 802ab22..59be2cc 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "upstream/skills/", "agents/", "commands/", + "principles/", "docs/agents/" ], "scripts": { diff --git a/principles/karpathy-primary.md b/principles/karpathy-primary.md new file mode 100644 index 0000000..4727a23 --- /dev/null +++ b/principles/karpathy-primary.md @@ -0,0 +1,41 @@ +# Andrej Karpathy's Coding Principles + +## Principle 1: Think Before Coding + +Before writing a single line of code, think through the problem thoroughly. Understand the requirements, design the approach, and consider edge cases. Most coding time should be spent thinking, not typing. A clear mental model prevents rework and produces cleaner solutions. + +Ask yourself: +- What exactly am I trying to accomplish? +- What are the constraints and edge cases? +- What is the simplest approach that could work? +- How will I verify correctness? + +## Principle 2: Simplicity First + +Always reach for the simplest solution first. Simple code is easier to understand, debug, test, and extend. Resist the urge to build elaborate abstractions or optimize prematurely. Complexity should be earned — only introduce it when the simple solution demonstrably falls short. + +Guidelines: +- Write code a junior engineer can understand +- Avoid premature abstraction and optimization +- Delete code whenever possible — less code is better code +- Favor boring, proven patterns over clever, novel ones + +## Principle 3: Surgical Changes + +Make the smallest possible change to achieve the goal. Each change should do exactly one thing, and do it well. Do not refactor unrelated code, fix unrelated bugs, or add "while I'm here" improvements. Precise, minimal changes reduce risk and make review straightforward. + +Guidelines: +- One logical change per commit/PR +- Don't mix refactoring with feature work +- Leave the codebase cleaner than you found it — but only in the area you're touching +- If you see something broken that's out of scope, file an issue, don't fix it inline + +## Principle 4: Goal-Driven Execution + +Stay relentlessly focused on the goal. Do not chase shiny objects, explore interesting tangents, or get sidetracked by adjacent improvements. Every action should trace back to the acceptance criteria. If it's not required to meet the goal, it's a distraction. + +Guidelines: +- Before every action, ask: "Does this directly advance the goal?" +- Track progress against acceptance criteria, not against interesting side quests +- Timebox exploration — if you need to research, set a limit and return to the goal +- Ship the minimum viable implementation, then iterate diff --git a/principles/karpathy.md b/principles/karpathy.md new file mode 100644 index 0000000..0d8b703 --- /dev/null +++ b/principles/karpathy.md @@ -0,0 +1,61 @@ +# Andrej Karpathy's Coding Principles + +## Principle 1: Think Before Coding + +Before writing a single line of code, think through the problem thoroughly. Understand the requirements, design the approach, and consider edge cases. Most coding time should be spent thinking, not typing. A clear mental model prevents rework and produces cleaner solutions. + +Ask yourself: +- What exactly am I trying to accomplish? +- What are the constraints and edge cases? +- What is the simplest approach that could work? +- How will I verify correctness? + +## Principle 1 (Reviewer Variant): Think Before Judging + +Before forming judgments about code quality, take time to understand the full context. Consider the constraints the implementer was working under, the trade-offs they had to make, and the requirements they were given. A hasty judgment misses nuance; a considered judgment improves the codebase. + +Ask yourself: +- What was the implementer trying to accomplish? +- What constraints and trade-offs shaped this implementation? +- Is the issue a real problem or a matter of style preference? +- What context might I be missing? + +## Principle 1 (Argus Variant): Think Before Analyzing + +Before analyzing any content, take time to observe thoroughly. Let the full picture form before drawing conclusions. Surface-level analysis misses patterns; deep observation reveals insights that matter. + +Ask yourself: +- What am I actually looking at? What is the full scope? +- What patterns emerge across the whole, not just the parts? +- What is the context surrounding this content? +- What details might be significant that are easy to overlook? + +## Principle 2: Simplicity First + +Always reach for the simplest solution first. Simple code is easier to understand, debug, test, and extend. Resist the urge to build elaborate abstractions or optimize prematurely. Complexity should be earned — only introduce it when the simple solution demonstrably falls short. + +Guidelines: +- Write code a junior engineer can understand +- Avoid premature abstraction and optimization +- Delete code whenever possible — less code is better code +- Favor boring, proven patterns over clever, novel ones + +## Principle 3: Surgical Changes + +Make the smallest possible change to achieve the goal. Each change should do exactly one thing, and do it well. Do not refactor unrelated code, fix unrelated bugs, or add "while I'm here" improvements. Precise, minimal changes reduce risk and make review straightforward. + +Guidelines: +- One logical change per commit/PR +- Don't mix refactoring with feature work +- Leave the codebase cleaner than you found it — but only in the area you're touching +- If you see something broken that's out of scope, file an issue, don't fix it inline + +## Principle 4: Goal-Driven Execution + +Stay relentlessly focused on the goal. Do not chase shiny objects, explore interesting tangents, or get sidetracked by adjacent improvements. Every action should trace back to the acceptance criteria. If it's not required to meet the goal, it's a distraction. + +Guidelines: +- Before every action, ask: "Does this directly advance the goal?" +- Track progress against acceptance criteria, not against interesting side quests +- Timebox exploration — if you need to research, set a limit and return to the goal +- Ship the minimum viable implementation, then iterate diff --git a/src/index.test.ts b/src/index.test.ts new file mode 100644 index 0000000..04d028d --- /dev/null +++ b/src/index.test.ts @@ -0,0 +1,106 @@ +import { beforeAll, describe, expect, test } from "bun:test"; +import type { Config } from "@opencode-ai/plugin"; +import { OpenCodeToolbox } from "./index"; + +/** + * RED phase: Test that principles are prepended to agent prompts based on agent mapping. + * This test will FAIL until the injection code is implemented in src/index.ts. + */ +describe("Karpathy Principles Injection", () => { + let result: { config?: (cfg: Config) => Promise } | undefined; + + beforeAll(async () => { + result = await OpenCodeToolbox({ directory: "." } as any); + }); + + test("implementer gets all four principles with Think Before Coding", async () => { + const cfg = { agent: {}, skills: { paths: [] } } as unknown as Config & Record; + await result?.config?.(cfg as Config); + + const prompt = (cfg as Record).agent as Record; + const implPrompt = prompt.implementer?.prompt ?? ""; + + expect(implPrompt).toContain("Think Before Coding"); + expect(implPrompt).toContain("Simplicity First"); + expect(implPrompt).toContain("Surgical Changes"); + expect(implPrompt).toContain("Goal-Driven Execution"); + }); + + test("general gets all four principles with Think Before Coding", async () => { + const cfg = { agent: { general: {} }, skills: { paths: [] } } as unknown as Config & Record; + await result?.config?.(cfg as Config); + + const prompt = (cfg as Record).agent as Record; + const genPrompt = prompt.general?.prompt ?? ""; + + expect(genPrompt).toContain("Think Before Coding"); + expect(genPrompt).toContain("Simplicity First"); + expect(genPrompt).toContain("Surgical Changes"); + expect(genPrompt).toContain("Goal-Driven Execution"); + }); + + test("reviewer gets principles 1(variant),2,4 — Think Before Judging, not Think Before Coding", async () => { + const cfg = { agent: { reviewer: {} }, skills: { paths: [] } } as unknown as Config & Record; + await result?.config?.(cfg as Config); + + const prompt = (cfg as Record).agent as Record; + const revPrompt = prompt.reviewer?.prompt ?? ""; + + // Should have the judging variant + expect(revPrompt).toContain("Think Before Judging"); + // Should have principles 2 and 4 + expect(revPrompt).toContain("Simplicity First"); + expect(revPrompt).toContain("Goal-Driven Execution"); + // Should NOT have principle 3 (Surgical Changes) + expect(revPrompt).not.toContain("Surgical Changes"); + // Should NOT have the coder variant + expect(revPrompt).not.toContain("Think Before Coding"); + }); + + test("argus gets principles 1(variant),2,4 — Think Before Analyzing", async () => { + const cfg = { agent: { argus: {} }, skills: { paths: [] } } as unknown as Config & Record; + await result?.config?.(cfg as Config); + + const prompt = (cfg as Record).agent as Record; + const argusPrompt = prompt.argus?.prompt ?? ""; + + // Should have the analyzing variant + expect(argusPrompt).toContain("Think Before Analyzing"); + // Should have principles 2 and 4 + expect(argusPrompt).toContain("Simplicity First"); + expect(argusPrompt).toContain("Goal-Driven Execution"); + // Should NOT have principle 3 + expect(argusPrompt).not.toContain("Surgical Changes"); + // Should NOT have the coder or judging variants + expect(argusPrompt).not.toContain("Think Before Coding"); + expect(argusPrompt).not.toContain("Think Before Judging"); + }); + + test("explore gets no principles", async () => { + const cfg = { agent: { explore: {} }, skills: { paths: [] } } as unknown as Config & Record; + await result?.config?.(cfg as Config); + + const prompt = (cfg as Record).agent as Record; + const explorePrompt = prompt.explore?.prompt ?? ""; + + // explore should NOT get any principles + expect(explorePrompt).not.toContain("Think Before Coding"); + expect(explorePrompt).not.toContain("Think Before Judging"); + expect(explorePrompt).not.toContain("Think Before Analyzing"); + expect(explorePrompt).not.toContain("Simplicity First"); + expect(explorePrompt).not.toContain("Surgical Changes"); + expect(explorePrompt).not.toContain("Goal-Driven Execution"); + }); + + test("principles are prepended (appear at start of agent prompt)", async () => { + const cfg = { agent: { implementer: {} }, skills: { paths: [] } } as unknown as Config & Record; + await result?.config?.(cfg as Config); + + const prompt = (cfg as Record).agent as Record; + const implPrompt = prompt.implementer?.prompt ?? ""; + + // Principles header should be at the very beginning of the prompt + const headerIndex = implPrompt.indexOf("# Andrej Karpathy's Coding Principles"); + expect(headerIndex).toBe(0); + }); +}); diff --git a/src/index.ts b/src/index.ts index 3dfb646..201fa84 100644 --- a/src/index.ts +++ b/src/index.ts @@ -22,6 +22,70 @@ interface CommandConfig { [key: string]: unknown; } +// ── Karpathy Principles ─────────────────────────────────────────── + +interface PrincipleSections { + v1Coding: string; + v1Judging: string; + v1Analyzing: string; + v2: string; + v3: string; + v4: string; +} + +function parsePrinciples(content: string): PrincipleSections { + const sections: Record = {}; + // Split by "## Principle" headers; skip the intro before the first header + const parts = content.split(/(?=^## Principle)/m); + for (const part of parts) { + const headerMatch = part.match(/^## Principle\s+(\d).*?\n/); + if (!headerMatch) continue; + const num = headerMatch[1]; + const body = part.slice(headerMatch[0].length).trim(); + + if (num === "1") { + if (part.includes("Reviewer Variant")) { + sections.v1Judging = body; + } else if (part.includes("Argus Variant")) { + sections.v1Analyzing = body; + } else { + sections.v1Coding = body; + } + } else { + sections[`v${num}`] = body; + } + } + return sections as unknown as PrincipleSections; +} + +const AGENT_PRINCIPLE_MAP: Record = { + implementer: ["v1Coding", "v2", "v3", "v4"], + general: ["v1Coding", "v2", "v3", "v4"], + reviewer: ["v1Judging", "v2", "v4"], + argus: ["v1Analyzing", "v2", "v4"], +}; + +const HEADER_TEMPLATES: Record = { + v1Coding: "## Principle 1: Think Before Coding", + v1Judging: "## Principle 1: Think Before Judging", + v1Analyzing: "## Principle 1: Think Before Analyzing", + v2: "## Principle 2: Simplicity First", + v3: "## Principle 3: Surgical Changes", + v4: "## Principle 4: Goal-Driven Execution", +}; + +function buildPrinciplesBlock(sections: PrincipleSections, agentName: string): string { + const keys = AGENT_PRINCIPLE_MAP[agentName]; + if (!keys || keys.length === 0) return ""; + + const blocks = keys.map((key) => { + const header = HEADER_TEMPLATES[key]; + const body = sections[key] ?? ""; + return `${header}\n\n${body}`; + }); + return `# Andrej Karpathy's Coding Principles\n\n${blocks.join("\n\n")}\n\n---\n\n`; +} + function readMarkdownConfigs(dirPath: string): Record { const result: Record = {}; if (!fs.existsSync(dirPath)) return result; @@ -100,6 +164,15 @@ export const OpenCodeToolbox: Plugin = async ({ directory: _directory }) => { }; const upstreamCommandConfigs = buildCommandConfigs(upstreamCommandsRaw); + // ── Karpathy principles ─────────────────────────────────────── + const principlesPath = path.resolve(__dirname, "principles", "karpathy.md"); + const primaryPrinciplesPath = path.resolve(__dirname, "principles", "karpathy-primary.md"); + let principleSections: PrincipleSections | null = null; + if (fs.existsSync(principlesPath)) { + const rawPrinciples = fs.readFileSync(principlesPath, "utf8"); + principleSections = parsePrinciples(rawPrinciples); + } + return { config: async (config) => { const cfg = config as DynamicConfig; @@ -118,6 +191,23 @@ export const OpenCodeToolbox: Plugin = async ({ directory: _directory }) => { cfg.agent = { ...(cfg.agent ?? {}), ...agentConfigs }; cfg.command = { ...upstreamCommandConfigs, ...commandConfigs, ...(cfg.command ?? {}) }; + + // Prepend Karpathy principles to agent prompts based on agent mapping + if (principleSections) { + for (const [agentName, agentCfg] of Object.entries(cfg.agent)) { + if (!agentCfg) continue; + const block = buildPrinciplesBlock(principleSections, agentName); + if (block) { + agentCfg.prompt = block + (agentCfg.prompt ?? ""); + } + } + } + + // Primary agent: inject full Karpathy principles via instructions + cfg.instructions = cfg.instructions || []; + if (!cfg.instructions.includes(primaryPrinciplesPath)) { + cfg.instructions.push(primaryPrinciplesPath); + } }, }; };