From ed437e5c6bc85f0544b3d92e21dca7b3539e2f40 Mon Sep 17 00:00:00 2001 From: Tym Rabchuk Date: Sat, 4 Apr 2026 16:28:37 -0400 Subject: [PATCH 01/11] Auto-add .devkit/ to .gitignore when initializing state directory When devkit creates .devkit/ in a project, it now ensures the directory is listed in .gitignore so runtime state (DB, sessions) is never accidentally committed. Creates .gitignore if absent, appends if present, and skips if already ignored. --- src/lib/db.go | 38 ++++++++++++++++++++ src/lib/db_test.go | 88 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 126 insertions(+) diff --git a/src/lib/db.go b/src/lib/db.go index 6e833f8..4b4e1b3 100644 --- a/src/lib/db.go +++ b/src/lib/db.go @@ -1,10 +1,12 @@ package lib import ( + "bufio" "database/sql" "fmt" "os" "path/filepath" + "strings" "time" _ "modernc.org/sqlite" @@ -45,10 +47,46 @@ type DB struct { path string } +// ensureGitignore adds ".devkit/" to the repo's .gitignore if not already present. +func ensureGitignore(devkitDir string) { + repoRoot := filepath.Dir(devkitDir) + gitignorePath := filepath.Join(repoRoot, ".gitignore") + + // Check if .devkit/ is already ignored + if f, err := os.Open(gitignorePath); err == nil { + scanner := bufio.NewScanner(f) + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + if line == ".devkit" || line == ".devkit/" { + f.Close() + return + } + } + f.Close() + } + + // Append .devkit/ to .gitignore (create if needed) + f, err := os.OpenFile(gitignorePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644) + if err != nil { + return // best-effort; don't fail the whole operation + } + defer f.Close() + + // If file exists and doesn't end with newline, add one first + if info, err := os.Stat(gitignorePath); err == nil && info.Size() > 0 { + content, err := os.ReadFile(gitignorePath) + if err == nil && len(content) > 0 && content[len(content)-1] != '\n' { + f.Write([]byte("\n")) + } + } + f.Write([]byte(".devkit/\n")) +} + func OpenDB(path string) (*DB, error) { if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil { return nil, fmt.Errorf("create db directory: %w", err) } + ensureGitignore(filepath.Dir(path)) conn, err := sql.Open("sqlite", path+"?_pragma=journal_mode(wal)&_pragma=busy_timeout(5000)") if err != nil { diff --git a/src/lib/db_test.go b/src/lib/db_test.go index 62f162b..7740442 100644 --- a/src/lib/db_test.go +++ b/src/lib/db_test.go @@ -171,6 +171,94 @@ func TestLastIteration(t *testing.T) { } } +func TestEnsureGitignore_CreatesNew(t *testing.T) { + dir := t.TempDir() + devkitDir := filepath.Join(dir, ".devkit") + os.MkdirAll(devkitDir, 0o700) + + ensureGitignore(devkitDir) + + content, err := os.ReadFile(filepath.Join(dir, ".gitignore")) + if err != nil { + t.Fatalf("read .gitignore: %v", err) + } + if string(content) != ".devkit/\n" { + t.Errorf("content = %q, want %q", string(content), ".devkit/\n") + } +} + +func TestEnsureGitignore_AppendsToExisting(t *testing.T) { + dir := t.TempDir() + devkitDir := filepath.Join(dir, ".devkit") + os.MkdirAll(devkitDir, 0o700) + os.WriteFile(filepath.Join(dir, ".gitignore"), []byte("node_modules/\n"), 0o644) + + ensureGitignore(devkitDir) + + content, err := os.ReadFile(filepath.Join(dir, ".gitignore")) + if err != nil { + t.Fatalf("read .gitignore: %v", err) + } + expected := "node_modules/\n.devkit/\n" + if string(content) != expected { + t.Errorf("content = %q, want %q", string(content), expected) + } +} + +func TestEnsureGitignore_NoTrailingNewline(t *testing.T) { + dir := t.TempDir() + devkitDir := filepath.Join(dir, ".devkit") + os.MkdirAll(devkitDir, 0o700) + os.WriteFile(filepath.Join(dir, ".gitignore"), []byte("node_modules/"), 0o644) + + ensureGitignore(devkitDir) + + content, err := os.ReadFile(filepath.Join(dir, ".gitignore")) + if err != nil { + t.Fatalf("read .gitignore: %v", err) + } + expected := "node_modules/\n.devkit/\n" + if string(content) != expected { + t.Errorf("content = %q, want %q", string(content), expected) + } +} + +func TestEnsureGitignore_AlreadyPresent(t *testing.T) { + dir := t.TempDir() + devkitDir := filepath.Join(dir, ".devkit") + os.MkdirAll(devkitDir, 0o700) + original := "node_modules/\n.devkit/\n" + os.WriteFile(filepath.Join(dir, ".gitignore"), []byte(original), 0o644) + + ensureGitignore(devkitDir) + + content, err := os.ReadFile(filepath.Join(dir, ".gitignore")) + if err != nil { + t.Fatalf("read .gitignore: %v", err) + } + if string(content) != original { + t.Errorf("content = %q, want %q (should not duplicate)", string(content), original) + } +} + +func TestEnsureGitignore_WithoutSlash(t *testing.T) { + dir := t.TempDir() + devkitDir := filepath.Join(dir, ".devkit") + os.MkdirAll(devkitDir, 0o700) + original := ".devkit\n" + os.WriteFile(filepath.Join(dir, ".gitignore"), []byte(original), 0o644) + + ensureGitignore(devkitDir) + + content, err := os.ReadFile(filepath.Join(dir, ".gitignore")) + if err != nil { + t.Fatalf("read .gitignore: %v", err) + } + if string(content) != original { + t.Errorf("content = %q, want %q (should recognize .devkit without slash)", string(content), original) + } +} + func TestDBDirectoryPermissions(t *testing.T) { dir := t.TempDir() dbDir := filepath.Join(dir, ".devkit") From da0ce26af2402f9e628e22acb5e52d02232bedbd Mon Sep 17 00:00:00 2001 From: Tym Rabchuk Date: Sat, 4 Apr 2026 16:45:01 -0400 Subject: [PATCH 02/11] Restore commands/ directory for slash command autocomplete Claude Code discovers commands from the commands/ directory, not the manifest array. Move 20 command files back from skills/ to commands/ and update manifest paths accordingly. --- {skills => commands}/audit.md | 0 {skills => commands}/bugfix.md | 0 {skills => commands}/decompose.md | 0 {skills => commands}/feature.md | 0 {skills => commands}/pr-monitor.md | 0 {skills => commands}/pr-ready.md | 0 {skills => commands}/refactor.md | 0 {skills => commands}/repo-map.md | 0 {skills => commands}/self-improve.md | 0 {skills => commands}/self-lint.md | 0 {skills => commands}/self-migrate.md | 0 {skills => commands}/self-perf.md | 0 {skills => commands}/self-test.md | 0 {skills => commands}/status.md | 0 {skills => commands}/tri-debug.md | 0 {skills => commands}/tri-dispatch.md | 0 {skills => commands}/tri-review.md | 0 {skills => commands}/tri-security.md | 0 {skills => commands}/tri-test-gen.md | 0 {skills => commands}/workflow.md | 0 manifest.json | 40 ++++++++++++++-------------- 21 files changed, 20 insertions(+), 20 deletions(-) rename {skills => commands}/audit.md (100%) rename {skills => commands}/bugfix.md (100%) rename {skills => commands}/decompose.md (100%) rename {skills => commands}/feature.md (100%) rename {skills => commands}/pr-monitor.md (100%) rename {skills => commands}/pr-ready.md (100%) rename {skills => commands}/refactor.md (100%) rename {skills => commands}/repo-map.md (100%) rename {skills => commands}/self-improve.md (100%) rename {skills => commands}/self-lint.md (100%) rename {skills => commands}/self-migrate.md (100%) rename {skills => commands}/self-perf.md (100%) rename {skills => commands}/self-test.md (100%) rename {skills => commands}/status.md (100%) rename {skills => commands}/tri-debug.md (100%) rename {skills => commands}/tri-dispatch.md (100%) rename {skills => commands}/tri-review.md (100%) rename {skills => commands}/tri-security.md (100%) rename {skills => commands}/tri-test-gen.md (100%) rename {skills => commands}/workflow.md (100%) diff --git a/skills/audit.md b/commands/audit.md similarity index 100% rename from skills/audit.md rename to commands/audit.md diff --git a/skills/bugfix.md b/commands/bugfix.md similarity index 100% rename from skills/bugfix.md rename to commands/bugfix.md diff --git a/skills/decompose.md b/commands/decompose.md similarity index 100% rename from skills/decompose.md rename to commands/decompose.md diff --git a/skills/feature.md b/commands/feature.md similarity index 100% rename from skills/feature.md rename to commands/feature.md diff --git a/skills/pr-monitor.md b/commands/pr-monitor.md similarity index 100% rename from skills/pr-monitor.md rename to commands/pr-monitor.md diff --git a/skills/pr-ready.md b/commands/pr-ready.md similarity index 100% rename from skills/pr-ready.md rename to commands/pr-ready.md diff --git a/skills/refactor.md b/commands/refactor.md similarity index 100% rename from skills/refactor.md rename to commands/refactor.md diff --git a/skills/repo-map.md b/commands/repo-map.md similarity index 100% rename from skills/repo-map.md rename to commands/repo-map.md diff --git a/skills/self-improve.md b/commands/self-improve.md similarity index 100% rename from skills/self-improve.md rename to commands/self-improve.md diff --git a/skills/self-lint.md b/commands/self-lint.md similarity index 100% rename from skills/self-lint.md rename to commands/self-lint.md diff --git a/skills/self-migrate.md b/commands/self-migrate.md similarity index 100% rename from skills/self-migrate.md rename to commands/self-migrate.md diff --git a/skills/self-perf.md b/commands/self-perf.md similarity index 100% rename from skills/self-perf.md rename to commands/self-perf.md diff --git a/skills/self-test.md b/commands/self-test.md similarity index 100% rename from skills/self-test.md rename to commands/self-test.md diff --git a/skills/status.md b/commands/status.md similarity index 100% rename from skills/status.md rename to commands/status.md diff --git a/skills/tri-debug.md b/commands/tri-debug.md similarity index 100% rename from skills/tri-debug.md rename to commands/tri-debug.md diff --git a/skills/tri-dispatch.md b/commands/tri-dispatch.md similarity index 100% rename from skills/tri-dispatch.md rename to commands/tri-dispatch.md diff --git a/skills/tri-review.md b/commands/tri-review.md similarity index 100% rename from skills/tri-review.md rename to commands/tri-review.md diff --git a/skills/tri-security.md b/commands/tri-security.md similarity index 100% rename from skills/tri-security.md rename to commands/tri-security.md diff --git a/skills/tri-test-gen.md b/commands/tri-test-gen.md similarity index 100% rename from skills/tri-test-gen.md rename to commands/tri-test-gen.md diff --git a/skills/workflow.md b/commands/workflow.md similarity index 100% rename from skills/workflow.md rename to commands/workflow.md diff --git a/manifest.json b/manifest.json index 4aa5ef3..742fe1e 100644 --- a/manifest.json +++ b/manifest.json @@ -3,26 +3,26 @@ "version": "2.0.1", "description": "Guardrails and consistency for Claude Code — deterministic workflows, metric-gated improvement loops, and multi-agent consensus", "commands": [ - "skills/tri-review.md", - "skills/tri-dispatch.md", - "skills/tri-debug.md", - "skills/tri-test-gen.md", - "skills/tri-security.md", - "skills/self-improve.md", - "skills/self-test.md", - "skills/self-lint.md", - "skills/self-perf.md", - "skills/self-migrate.md", - "skills/pr-ready.md", - "skills/pr-monitor.md", - "skills/workflow.md", - "skills/status.md", - "skills/bugfix.md", - "skills/feature.md", - "skills/refactor.md", - "skills/decompose.md", - "skills/audit.md", - "skills/repo-map.md" + "commands/tri-review.md", + "commands/tri-dispatch.md", + "commands/tri-debug.md", + "commands/tri-test-gen.md", + "commands/tri-security.md", + "commands/self-improve.md", + "commands/self-test.md", + "commands/self-lint.md", + "commands/self-perf.md", + "commands/self-migrate.md", + "commands/pr-ready.md", + "commands/pr-monitor.md", + "commands/workflow.md", + "commands/status.md", + "commands/bugfix.md", + "commands/feature.md", + "commands/refactor.md", + "commands/decompose.md", + "commands/audit.md", + "commands/repo-map.md" ], "skills": [ "skills/executing.md", From ca2abd051101ca5a41ca2bd6da1b46928b400285 Mon Sep 17 00:00:00 2001 From: Tym Rabchuk Date: Sat, 4 Apr 2026 16:50:41 -0400 Subject: [PATCH 03/11] =?UTF-8?q?Remove=20manifest.json=20=E2=80=94=20Clau?= =?UTF-8?q?de=20Code=20auto-discovers=20from=20directories?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The manifest is optional per the plugin spec. When present, it overrides directory scanning, which can interfere with auto-discovery. Since commands/, skills/, and agents/ directories are already correctly structured, let Claude Code discover them automatically. --- manifest.json | 51 --------------------------------------------------- 1 file changed, 51 deletions(-) delete mode 100644 manifest.json diff --git a/manifest.json b/manifest.json deleted file mode 100644 index 742fe1e..0000000 --- a/manifest.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "name": "devkit", - "version": "2.0.1", - "description": "Guardrails and consistency for Claude Code — deterministic workflows, metric-gated improvement loops, and multi-agent consensus", - "commands": [ - "commands/tri-review.md", - "commands/tri-dispatch.md", - "commands/tri-debug.md", - "commands/tri-test-gen.md", - "commands/tri-security.md", - "commands/self-improve.md", - "commands/self-test.md", - "commands/self-lint.md", - "commands/self-perf.md", - "commands/self-migrate.md", - "commands/pr-ready.md", - "commands/pr-monitor.md", - "commands/workflow.md", - "commands/status.md", - "commands/bugfix.md", - "commands/feature.md", - "commands/refactor.md", - "commands/decompose.md", - "commands/audit.md", - "commands/repo-map.md" - ], - "skills": [ - "skills/executing.md", - "skills/clean-code.md", - "skills/dry.md", - "skills/yagni.md", - "skills/creating-workflows.md", - "skills/stuck.md", - "skills/gcli.md", - "skills/dont-reinvent.md", - "skills/changelog.md", - "skills/onboard.md", - "skills/doc-gen.md", - "skills/test-gen.md", - "skills/scrape.md", - "skills/research.md" - ], - "agents": [ - "agents/reviewer.md", - "agents/researcher.md", - "agents/improver.md", - "agents/test-writer.md", - "agents/documenter.md", - "agents/security-auditor.md" - ] -} From 6f6444e43ce8a3244e873bb8a5232816fe78be92 Mon Sep 17 00:00:00 2001 From: Tym Rabchuk Date: Sat, 4 Apr 2026 16:51:21 -0400 Subject: [PATCH 04/11] Restructure skills to subdirectory/SKILL.md format Claude Code auto-discovers skills from skills/{name}/SKILL.md, not flat files. Move all 14 skill files into the expected structure. --- skills/{changelog.md => changelog/SKILL.md} | 0 skills/{clean-code.md => clean-code/SKILL.md} | 0 skills/{creating-workflows.md => creating-workflows/SKILL.md} | 0 skills/{doc-gen.md => doc-gen/SKILL.md} | 0 skills/{dont-reinvent.md => dont-reinvent/SKILL.md} | 0 skills/{dry.md => dry/SKILL.md} | 0 skills/{executing.md => executing/SKILL.md} | 0 skills/{gcli.md => gcli/SKILL.md} | 0 skills/{onboard.md => onboard/SKILL.md} | 0 skills/{research.md => research/SKILL.md} | 0 skills/{scrape.md => scrape/SKILL.md} | 0 skills/{stuck.md => stuck/SKILL.md} | 0 skills/{test-gen.md => test-gen/SKILL.md} | 0 skills/{yagni.md => yagni/SKILL.md} | 0 14 files changed, 0 insertions(+), 0 deletions(-) rename skills/{changelog.md => changelog/SKILL.md} (100%) rename skills/{clean-code.md => clean-code/SKILL.md} (100%) rename skills/{creating-workflows.md => creating-workflows/SKILL.md} (100%) rename skills/{doc-gen.md => doc-gen/SKILL.md} (100%) rename skills/{dont-reinvent.md => dont-reinvent/SKILL.md} (100%) rename skills/{dry.md => dry/SKILL.md} (100%) rename skills/{executing.md => executing/SKILL.md} (100%) rename skills/{gcli.md => gcli/SKILL.md} (100%) rename skills/{onboard.md => onboard/SKILL.md} (100%) rename skills/{research.md => research/SKILL.md} (100%) rename skills/{scrape.md => scrape/SKILL.md} (100%) rename skills/{stuck.md => stuck/SKILL.md} (100%) rename skills/{test-gen.md => test-gen/SKILL.md} (100%) rename skills/{yagni.md => yagni/SKILL.md} (100%) diff --git a/skills/changelog.md b/skills/changelog/SKILL.md similarity index 100% rename from skills/changelog.md rename to skills/changelog/SKILL.md diff --git a/skills/clean-code.md b/skills/clean-code/SKILL.md similarity index 100% rename from skills/clean-code.md rename to skills/clean-code/SKILL.md diff --git a/skills/creating-workflows.md b/skills/creating-workflows/SKILL.md similarity index 100% rename from skills/creating-workflows.md rename to skills/creating-workflows/SKILL.md diff --git a/skills/doc-gen.md b/skills/doc-gen/SKILL.md similarity index 100% rename from skills/doc-gen.md rename to skills/doc-gen/SKILL.md diff --git a/skills/dont-reinvent.md b/skills/dont-reinvent/SKILL.md similarity index 100% rename from skills/dont-reinvent.md rename to skills/dont-reinvent/SKILL.md diff --git a/skills/dry.md b/skills/dry/SKILL.md similarity index 100% rename from skills/dry.md rename to skills/dry/SKILL.md diff --git a/skills/executing.md b/skills/executing/SKILL.md similarity index 100% rename from skills/executing.md rename to skills/executing/SKILL.md diff --git a/skills/gcli.md b/skills/gcli/SKILL.md similarity index 100% rename from skills/gcli.md rename to skills/gcli/SKILL.md diff --git a/skills/onboard.md b/skills/onboard/SKILL.md similarity index 100% rename from skills/onboard.md rename to skills/onboard/SKILL.md diff --git a/skills/research.md b/skills/research/SKILL.md similarity index 100% rename from skills/research.md rename to skills/research/SKILL.md diff --git a/skills/scrape.md b/skills/scrape/SKILL.md similarity index 100% rename from skills/scrape.md rename to skills/scrape/SKILL.md diff --git a/skills/stuck.md b/skills/stuck/SKILL.md similarity index 100% rename from skills/stuck.md rename to skills/stuck/SKILL.md diff --git a/skills/test-gen.md b/skills/test-gen/SKILL.md similarity index 100% rename from skills/test-gen.md rename to skills/test-gen/SKILL.md diff --git a/skills/yagni.md b/skills/yagni/SKILL.md similarity index 100% rename from skills/yagni.md rename to skills/yagni/SKILL.md From d364f7b941d26ad38e533828add79aef392a6017 Mon Sep 17 00:00:00 2001 From: Tym Rabchuk Date: Sat, 4 Apr 2026 16:52:00 -0400 Subject: [PATCH 05/11] Fix skill frontmatter: use bare names, not devkit: prefix Claude Code constructs the full skill name as pluginName:directoryName automatically. The frontmatter name should match the directory name without the plugin prefix, matching the convention used by superpowers, context-mode, and all other working plugins. --- skills/changelog/SKILL.md | 2 +- skills/clean-code/SKILL.md | 2 +- skills/creating-workflows/SKILL.md | 2 +- skills/doc-gen/SKILL.md | 2 +- skills/dont-reinvent/SKILL.md | 2 +- skills/dry/SKILL.md | 2 +- skills/executing/SKILL.md | 2 +- skills/gcli/SKILL.md | 2 +- skills/onboard/SKILL.md | 2 +- skills/research/SKILL.md | 2 +- skills/scrape/SKILL.md | 2 +- skills/stuck/SKILL.md | 2 +- skills/test-gen/SKILL.md | 2 +- skills/yagni/SKILL.md | 2 +- 14 files changed, 14 insertions(+), 14 deletions(-) diff --git a/skills/changelog/SKILL.md b/skills/changelog/SKILL.md index dee1f0c..552756e 100644 --- a/skills/changelog/SKILL.md +++ b/skills/changelog/SKILL.md @@ -1,5 +1,5 @@ --- -name: devkit:changelog +name: changelog description: Generate a structured changelog from git history — use when asked to create a changelog, release notes, or summarize what changed between versions/tags/branches. --- diff --git a/skills/clean-code/SKILL.md b/skills/clean-code/SKILL.md index 8d03462..585d5e1 100644 --- a/skills/clean-code/SKILL.md +++ b/skills/clean-code/SKILL.md @@ -1,5 +1,5 @@ --- -name: devkit:clean-code +name: clean-code description: Clean code principles — meaningful names, small functions, single responsibility, stepdown rule, flat nesting. --- diff --git a/skills/creating-workflows/SKILL.md b/skills/creating-workflows/SKILL.md index 1e24cf6..615a73d 100644 --- a/skills/creating-workflows/SKILL.md +++ b/skills/creating-workflows/SKILL.md @@ -1,5 +1,5 @@ --- -name: devkit:creating-workflows +name: creating-workflows description: How to create devkit workflow YAML files — schema reference, step types, variable interpolation, and examples. --- diff --git a/skills/doc-gen/SKILL.md b/skills/doc-gen/SKILL.md index 069543f..215f27b 100644 --- a/skills/doc-gen/SKILL.md +++ b/skills/doc-gen/SKILL.md @@ -1,5 +1,5 @@ --- -name: devkit:doc-gen +name: doc-gen description: Generate documentation for code — use when asked to document a module, generate API docs, create a README for code, or write reference documentation. --- diff --git a/skills/dont-reinvent/SKILL.md b/skills/dont-reinvent/SKILL.md index b5d3652..7407fc3 100644 --- a/skills/dont-reinvent/SKILL.md +++ b/skills/dont-reinvent/SKILL.md @@ -1,5 +1,5 @@ --- -name: devkit:dont-reinvent +name: dont-reinvent description: Don't reinvent the wheel — use existing libraries, tools, and stdlib before building custom solutions. Every custom solution is maintenance burden. --- diff --git a/skills/dry/SKILL.md b/skills/dry/SKILL.md index b7ddd83..326f4f5 100644 --- a/skills/dry/SKILL.md +++ b/skills/dry/SKILL.md @@ -1,5 +1,5 @@ --- -name: devkit:dry +name: dry description: Don't Repeat Yourself — Rule of Three, when duplication is fine, extracting the right abstraction. --- diff --git a/skills/executing/SKILL.md b/skills/executing/SKILL.md index da21946..d46f30d 100644 --- a/skills/executing/SKILL.md +++ b/skills/executing/SKILL.md @@ -1,5 +1,5 @@ --- -name: devkit:executing +name: executing description: Execute implementation plans — work through steps methodically, verify each one, keep changes small and reviewable. --- diff --git a/skills/gcli/SKILL.md b/skills/gcli/SKILL.md index 852b48d..6d6986a 100644 --- a/skills/gcli/SKILL.md +++ b/skills/gcli/SKILL.md @@ -1,5 +1,5 @@ --- -name: devkit:gcli +name: gcli description: Google Workspace CLI (Gmail, Calendar, Drive) via gcli — use --for-ai flag for token-efficient structured output. --- diff --git a/skills/onboard/SKILL.md b/skills/onboard/SKILL.md index 8a646ca..8bcb137 100644 --- a/skills/onboard/SKILL.md +++ b/skills/onboard/SKILL.md @@ -1,5 +1,5 @@ --- -name: devkit:onboard +name: onboard description: Generate a codebase onboarding guide — use when asked to explain this codebase, help understand the architecture, give a tour of the repo, or onboard a new contributor. --- diff --git a/skills/research/SKILL.md b/skills/research/SKILL.md index 5907ee9..091ce30 100644 --- a/skills/research/SKILL.md +++ b/skills/research/SKILL.md @@ -1,5 +1,5 @@ --- -name: devkit:research +name: research description: Deep research workflow — use when asked to research a topic, do a deep dive, investigate options, compare approaches, or find the best solution to a technical question. --- diff --git a/skills/scrape/SKILL.md b/skills/scrape/SKILL.md index b6c32b3..4275fac 100644 --- a/skills/scrape/SKILL.md +++ b/skills/scrape/SKILL.md @@ -1,5 +1,5 @@ --- -name: devkit:scrape +name: scrape description: Scrape a URL to clean Markdown — use when asked to scrape, fetch, extract content from, or read a webpage and convert it to Markdown. Uses Jina Reader, Firecrawl, or WebFetch. --- diff --git a/skills/stuck/SKILL.md b/skills/stuck/SKILL.md index c135805..cbfc0c9 100644 --- a/skills/stuck/SKILL.md +++ b/skills/stuck/SKILL.md @@ -1,5 +1,5 @@ --- -name: devkit:stuck +name: stuck description: Detect when an agent is looping or failing repeatedly, and trigger structured recovery — backtrack, simplify, or escalate. --- diff --git a/skills/test-gen/SKILL.md b/skills/test-gen/SKILL.md index 189c74a..6346879 100644 --- a/skills/test-gen/SKILL.md +++ b/skills/test-gen/SKILL.md @@ -1,5 +1,5 @@ --- -name: devkit:test-gen +name: test-gen description: Generate tests for code — use when asked to write tests, create a test suite, add test coverage, or generate unit/integration tests for a file or module. --- diff --git a/skills/yagni/SKILL.md b/skills/yagni/SKILL.md index fa67d3c..7b58db8 100644 --- a/skills/yagni/SKILL.md +++ b/skills/yagni/SKILL.md @@ -1,5 +1,5 @@ --- -name: devkit:yagni +name: yagni description: You Aren't Gonna Need It — build only what's needed now, no speculative features or premature abstractions. --- From 5388c6060a2d00fe158b8300d168409d9551f908 Mon Sep 17 00:00:00 2001 From: Tym Rabchuk Date: Sat, 4 Apr 2026 16:53:18 -0400 Subject: [PATCH 06/11] Remove name field from command frontmatter Claude Code derives slash command names from pluginName:filename, not the frontmatter name field. Working plugins like hookify only use description in command frontmatter. Remove the redundant name field from all 20 commands. --- commands/audit.md | 1 - commands/bugfix.md | 1 - commands/decompose.md | 1 - commands/feature.md | 1 - commands/pr-monitor.md | 1 - commands/pr-ready.md | 1 - commands/refactor.md | 1 - commands/repo-map.md | 1 - commands/self-improve.md | 1 - commands/self-lint.md | 1 - commands/self-migrate.md | 1 - commands/self-perf.md | 1 - commands/self-test.md | 1 - commands/status.md | 1 - commands/tri-debug.md | 1 - commands/tri-dispatch.md | 1 - commands/tri-review.md | 1 - commands/tri-security.md | 1 - commands/tri-test-gen.md | 1 - commands/workflow.md | 2 -- 20 files changed, 21 deletions(-) diff --git a/commands/audit.md b/commands/audit.md index 554c2f1..44c3977 100644 --- a/commands/audit.md +++ b/commands/audit.md @@ -1,5 +1,4 @@ --- -name: devkit:audit description: Unified project health audit — dependencies, vulnerabilities, outdated packages, licenses, lint, and security in one report. --- diff --git a/commands/bugfix.md b/commands/bugfix.md index d33a747..ca541f6 100644 --- a/commands/bugfix.md +++ b/commands/bugfix.md @@ -1,5 +1,4 @@ --- -name: devkit:bugfix description: Full lifecycle bug fix — reproduce, diagnose, fix, regression test, verify. --- diff --git a/commands/decompose.md b/commands/decompose.md index d87e9de..89e53be 100644 --- a/commands/decompose.md +++ b/commands/decompose.md @@ -1,5 +1,4 @@ --- -name: devkit:decompose description: Decompose a high-level goal into a task DAG — break down, assign to agents, resolve dependencies, execute in order. --- diff --git a/commands/feature.md b/commands/feature.md index 6a71053..a2bf79f 100644 --- a/commands/feature.md +++ b/commands/feature.md @@ -1,5 +1,4 @@ --- -name: devkit:feature description: Full lifecycle feature development — brainstorm, plan, implement, test, lint, review. --- diff --git a/commands/pr-monitor.md b/commands/pr-monitor.md index 5b7551a..7eca809 100644 --- a/commands/pr-monitor.md +++ b/commands/pr-monitor.md @@ -1,5 +1,4 @@ --- -name: devkit:pr-monitor description: Post-PR review monitor — watches CI, fetches reviewer comments, iteratively resolves them, and pushes fixes. --- diff --git a/commands/pr-ready.md b/commands/pr-ready.md index fff6391..1d14d91 100644 --- a/commands/pr-ready.md +++ b/commands/pr-ready.md @@ -1,5 +1,4 @@ --- -name: devkit:pr-ready description: Full PR preparation pipeline — necessity check, DRY review, lint, test, security, changelog, and create PR. --- diff --git a/commands/refactor.md b/commands/refactor.md index ac3c449..b3d2515 100644 --- a/commands/refactor.md +++ b/commands/refactor.md @@ -1,5 +1,4 @@ --- -name: devkit:refactor description: Full lifecycle refactor — analyze code smells, plan transformations, restructure, verify nothing broke. --- diff --git a/commands/repo-map.md b/commands/repo-map.md index 17b00e6..5e54211 100644 --- a/commands/repo-map.md +++ b/commands/repo-map.md @@ -1,5 +1,4 @@ --- -name: devkit:repo-map description: Build an AST-based symbol index of the repository — exports, functions, classes, imports — cached for fast agent navigation. --- diff --git a/commands/self-improve.md b/commands/self-improve.md index df8e8f2..c61fbe1 100644 --- a/commands/self-improve.md +++ b/commands/self-improve.md @@ -1,5 +1,4 @@ --- -name: self:improve description: Self-recursive improvement loop — automated refactoring with test gate. Uses native improver agent in worktree isolation. Propose → measure → keep/discard → repeat. --- diff --git a/commands/self-lint.md b/commands/self-lint.md index 480dcf9..8265aca 100644 --- a/commands/self-lint.md +++ b/commands/self-lint.md @@ -1,5 +1,4 @@ --- -name: self:lint description: Self-improvement loop targeting lint and type errors. Iteratively fixes issues until zero remain or iterations exhausted. --- diff --git a/commands/self-migrate.md b/commands/self-migrate.md index a3c337d..74d2805 100644 --- a/commands/self-migrate.md +++ b/commands/self-migrate.md @@ -1,5 +1,4 @@ --- -name: self:migrate description: Self-improvement loop for incremental codebase migrations. Iteratively migrates code with tests as the safety gate. --- diff --git a/commands/self-perf.md b/commands/self-perf.md index d930502..65e2475 100644 --- a/commands/self-perf.md +++ b/commands/self-perf.md @@ -1,5 +1,4 @@ --- -name: self:perf description: Hypothesis-driven performance investigation — analyze, hypothesize, test one theory at a time, measure against baseline. --- diff --git a/commands/self-test.md b/commands/self-test.md index 5be9481..3fa6479 100644 --- a/commands/self-test.md +++ b/commands/self-test.md @@ -1,5 +1,4 @@ --- -name: self:test description: Self-improvement loop targeting test coverage. Iteratively generates and improves tests until a coverage target is met or iterations exhausted. --- diff --git a/commands/status.md b/commands/status.md index 8852fe5..fb10adb 100644 --- a/commands/status.md +++ b/commands/status.md @@ -1,5 +1,4 @@ --- -name: devkit:status description: Check devkit health — which external CLIs are installed, which agents are available, and which commands are ready to use. --- diff --git a/commands/tri-debug.md b/commands/tri-debug.md index 9a2cae0..8e5c32a 100644 --- a/commands/tri-debug.md +++ b/commands/tri-debug.md @@ -1,5 +1,4 @@ --- -name: tri:debug description: Multi-agent debugging — send a bug report to available agents (Claude + Codex + Gemini) via plugin or CLI, get independent root-cause hypotheses, and a consensus fix. --- diff --git a/commands/tri-dispatch.md b/commands/tri-dispatch.md index 0162c92..8d066a9 100644 --- a/commands/tri-dispatch.md +++ b/commands/tri-dispatch.md @@ -1,5 +1,4 @@ --- -name: tri:dispatch description: Dispatch a task to all three agents (Claude, Codex, Gemini) in parallel and compare results. Claude uses native background agent, others via plugin or CLI. --- diff --git a/commands/tri-review.md b/commands/tri-review.md index 3252813..d1f5c7f 100644 --- a/commands/tri-review.md +++ b/commands/tri-review.md @@ -1,5 +1,4 @@ --- -name: tri:review description: Triple-agent PR/code review. Claude runs as native background agent (token-efficient), Codex and Gemini via plugin or CLI. Consolidates findings. --- diff --git a/commands/tri-security.md b/commands/tri-security.md index 0547a9d..852b16e 100644 --- a/commands/tri-security.md +++ b/commands/tri-security.md @@ -1,5 +1,4 @@ --- -name: tri:security description: Multi-agent security audit — independent security reviews from available agents, consolidated with severity-ranked findings. --- diff --git a/commands/tri-test-gen.md b/commands/tri-test-gen.md index 998f374..2f40b90 100644 --- a/commands/tri-test-gen.md +++ b/commands/tri-test-gen.md @@ -1,5 +1,4 @@ --- -name: tri:test-gen description: Multi-agent test generation — each available agent generates tests independently, then merge for maximum coverage. --- diff --git a/commands/workflow.md b/commands/workflow.md index dafa461..607d8ae 100644 --- a/commands/workflow.md +++ b/commands/workflow.md @@ -1,5 +1,4 @@ --- -name: devkit:workflow description: Run a user-defined YAML workflow. Multi-step pipelines with loops, approval gates, and branching. --- @@ -19,7 +18,6 @@ Execute a YAML-defined workflow from the `workflows/` directory. Read `workflows/{name}.yml` and parse: ```yaml -name: workflow-name description: What this workflow does steps: From c288c3727b866ae80b21148c6420ccd64f9334c4 Mon Sep 17 00:00:00 2001 From: Tym Rabchuk Date: Sat, 4 Apr 2026 16:58:18 -0400 Subject: [PATCH 07/11] Simplify ensureGitignore to single read/write Replace three-open pattern (scan, append, re-read) with one ReadFile and one WriteFile. Fixes unchecked scanner.Err() bug and removes unused bufio import. --- src/lib/db.go | 35 ++++++++++------------------------- 1 file changed, 10 insertions(+), 25 deletions(-) diff --git a/src/lib/db.go b/src/lib/db.go index 4b4e1b3..77f26cb 100644 --- a/src/lib/db.go +++ b/src/lib/db.go @@ -1,7 +1,6 @@ package lib import ( - "bufio" "database/sql" "fmt" "os" @@ -52,34 +51,20 @@ func ensureGitignore(devkitDir string) { repoRoot := filepath.Dir(devkitDir) gitignorePath := filepath.Join(repoRoot, ".gitignore") - // Check if .devkit/ is already ignored - if f, err := os.Open(gitignorePath); err == nil { - scanner := bufio.NewScanner(f) - for scanner.Scan() { - line := strings.TrimSpace(scanner.Text()) - if line == ".devkit" || line == ".devkit/" { - f.Close() - return - } - } - f.Close() - } + content, _ := os.ReadFile(gitignorePath) // nil content if file doesn't exist - // Append .devkit/ to .gitignore (create if needed) - f, err := os.OpenFile(gitignorePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644) - if err != nil { - return // best-effort; don't fail the whole operation + for _, line := range strings.Split(string(content), "\n") { + if strings.TrimSpace(line) == ".devkit" || strings.TrimSpace(line) == ".devkit/" { + return + } } - defer f.Close() - // If file exists and doesn't end with newline, add one first - if info, err := os.Stat(gitignorePath); err == nil && info.Size() > 0 { - content, err := os.ReadFile(gitignorePath) - if err == nil && len(content) > 0 && content[len(content)-1] != '\n' { - f.Write([]byte("\n")) - } + prefix := "" + if len(content) > 0 && content[len(content)-1] != '\n' { + prefix = "\n" } - f.Write([]byte(".devkit/\n")) + // best-effort; don't fail the whole operation + os.WriteFile(gitignorePath, append(content, []byte(prefix+".devkit/\n")...), 0o644) } func OpenDB(path string) (*DB, error) { From 21a48e02f278af741d5d7343e8448aa09b5f59f6 Mon Sep 17 00:00:00 2001 From: Tym Rabchuk Date: Sat, 4 Apr 2026 16:59:27 -0400 Subject: [PATCH 08/11] Fix stale references to manifest.json and old paths - release.yml: read version solely from plugin.json, stop reading/writing manifest.json - CONTRIBUTING.md: update to commands/ and skills/*/SKILL.md structure, remove manifest.json references - README.md: update directory tree to show commands/, skills/*/SKILL.md, and .claude-plugin/ instead of manifest.json --- .github/workflows/release.yml | 29 ++++++++++++-------------- CONTRIBUTING.md | 22 ++++++++++---------- README.md | 38 +++++++++++++++++------------------ 3 files changed, 43 insertions(+), 46 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9af1de8..8cd1dae 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -21,30 +21,28 @@ jobs: - name: Determine version id: version run: | - MANIFEST_VERSION=$(jq -r '.version' manifest.json) PLUGIN_VERSION=$(jq -r '.version' .claude-plugin/plugin.json) LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null | sed 's/^v//' || echo "0.0.0") - echo "manifest=$MANIFEST_VERSION plugin=$PLUGIN_VERSION latest_tag=$LATEST_TAG" + echo "plugin=$PLUGIN_VERSION latest_tag=$LATEST_TAG" - # If manifest version is ahead of the latest tag, use it (manual bump) - if [ "$MANIFEST_VERSION" != "$LATEST_TAG" ] && [ "$MANIFEST_VERSION" != "$PLUGIN_VERSION" ] || [ "$MANIFEST_VERSION" != "$LATEST_TAG" ]; then - # Check if manifest version is actually newer - MANIFEST_MAJOR=$(echo "$MANIFEST_VERSION" | cut -d. -f1) - MANIFEST_MINOR=$(echo "$MANIFEST_VERSION" | cut -d. -f2) - MANIFEST_PATCH=$(echo "$MANIFEST_VERSION" | cut -d. -f3) + # If plugin.json version is ahead of the latest tag, use it (manual bump) + if [ "$PLUGIN_VERSION" != "$LATEST_TAG" ]; then + P_MAJOR=$(echo "$PLUGIN_VERSION" | cut -d. -f1) + P_MINOR=$(echo "$PLUGIN_VERSION" | cut -d. -f2) + P_PATCH=$(echo "$PLUGIN_VERSION" | cut -d. -f3) TAG_MAJOR=$(echo "$LATEST_TAG" | cut -d. -f1) TAG_MINOR=$(echo "$LATEST_TAG" | cut -d. -f2) TAG_PATCH=$(echo "$LATEST_TAG" | cut -d. -f3) - if [ "$MANIFEST_MAJOR" -gt "$TAG_MAJOR" ] 2>/dev/null || \ - ([ "$MANIFEST_MAJOR" -eq "$TAG_MAJOR" ] && [ "$MANIFEST_MINOR" -gt "$TAG_MINOR" ]) 2>/dev/null || \ - ([ "$MANIFEST_MAJOR" -eq "$TAG_MAJOR" ] && [ "$MANIFEST_MINOR" -eq "$TAG_MINOR" ] && [ "$MANIFEST_PATCH" -gt "$TAG_PATCH" ]) 2>/dev/null; then - echo "Manual version bump detected: $LATEST_TAG → $MANIFEST_VERSION" - echo "version=$MANIFEST_VERSION" >> "$GITHUB_OUTPUT" + if [ "$P_MAJOR" -gt "$TAG_MAJOR" ] 2>/dev/null || \ + ([ "$P_MAJOR" -eq "$TAG_MAJOR" ] && [ "$P_MINOR" -gt "$TAG_MINOR" ]) 2>/dev/null || \ + ([ "$P_MAJOR" -eq "$TAG_MAJOR" ] && [ "$P_MINOR" -eq "$TAG_MINOR" ] && [ "$P_PATCH" -gt "$TAG_PATCH" ]) 2>/dev/null; then + echo "Manual version bump detected: $LATEST_TAG → $PLUGIN_VERSION" + echo "version=$PLUGIN_VERSION" >> "$GITHUB_OUTPUT" echo "bumped=manual" >> "$GITHUB_OUTPUT" else - echo "Manifest version $MANIFEST_VERSION is not ahead of tag $LATEST_TAG — auto-bumping patch" + echo "plugin.json version $PLUGIN_VERSION is not ahead of tag $LATEST_TAG — auto-bumping patch" echo "bumped=auto" >> "$GITHUB_OUTPUT" fi else @@ -67,14 +65,13 @@ jobs: run: | VERSION="${{ steps.version.outputs.version }}" jq --arg v "$VERSION" '.version = $v' .claude-plugin/plugin.json > tmp.json && mv tmp.json .claude-plugin/plugin.json - jq --arg v "$VERSION" '.version = $v' manifest.json > tmp.json && mv tmp.json manifest.json - name: Commit version bump run: | VERSION="${{ steps.version.outputs.version }}" git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" - git add .claude-plugin/plugin.json manifest.json + git add .claude-plugin/plugin.json # Only commit if there are changes if git diff --cached --quiet; then echo "Version files already at $VERSION — no commit needed" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5475a3e..c8806f9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,39 +4,39 @@ Slash commands appear in tab-completion and run step-by-step workflows. -1. Create `skills/my-command.md` with YAML frontmatter: +1. Create `commands/my-command.md` with YAML frontmatter: ```markdown --- - name: devkit:my-command description: What this command does. --- # Command Title Step-by-step workflow with numbered steps. ``` -2. Add `"skills/my-command.md"` to the `"commands"` array in `manifest.json` -3. Include Budget & Early Exit section if the command loops -4. Include `[PARALLEL]` markers if steps run concurrently +2. Include Budget & Early Exit section if the command loops +3. Include `[PARALLEL]` markers if steps run concurrently + +The command will be auto-discovered as `/devkit:my-command`. ## Adding a Context-Activated Skill Skills activate automatically based on natural language — no slash command needed. -1. Create `skills/my-skill.md` with YAML frontmatter: +1. Create `skills/my-skill/SKILL.md` with YAML frontmatter: ```markdown --- - name: devkit:my-skill + name: my-skill description: Triggers on "natural language pattern". --- # Skill Title Guidelines or workflow (keep under 100 lines). ``` -2. Add `"skills/my-skill.md"` to the `"skills"` array in `manifest.json` + +The skill will be auto-discovered as `devkit:my-skill`. ## Adding an Agent -1. Create `agents/my-agent.md` with YAML frontmatter specifying model, tools, isolation, and maxTurns -2. Add `"agents/my-agent.md"` to `manifest.json` -3. Scope tools to only what the agent needs +1. Create `agents/my-agent.md` with YAML frontmatter specifying name, description, model, effort, maxTurns, and disallowedTools +2. Scope tools to only what the agent needs ## Adding a Workflow diff --git a/README.md b/README.md index cba4982..8dd6a5f 100644 --- a/README.md +++ b/README.md @@ -285,11 +285,11 @@ None yet — `presets/` is reserved for future use. ``` devkit/ -├── manifest.json # Plugin manifest +├── .claude-plugin/ +│ └── plugin.json # Plugin metadata (name, version, author) ├── ROADMAP.md # Implemented features and future plans ├── PREFERENCES.md # Agent behavior guidelines -├── skills/ # All skill files (commands + skills) -│ │ # — 20 slash commands (tab-completable) — +├── commands/ # 20 slash commands (tab-completable) │ ├── tri-*.md # Multi-agent dispatch (5) │ ├── self-*.md # Self-improvement loops (5) │ ├── pr-ready.md # PR preparation pipeline @@ -301,22 +301,22 @@ devkit/ │ ├── workflow.md # YAML workflow runner │ ├── audit.md # Project health audit │ ├── repo-map.md # AST-based symbol index -│ ├── status.md # Health check -│ │ # — 14 context-activated skills — -│ ├── executing.md # Principle: methodical execution -│ ├── clean-code.md # Principle: readability -│ ├── dry.md # Principle: don't repeat yourself -│ ├── yagni.md # Principle: no speculative features -│ ├── dont-reinvent.md # Principle: use existing solutions -│ ├── stuck.md # Principle: loop recovery -│ ├── creating-workflows.md # Tool: YAML workflow authoring -│ ├── gcli.md # Tool: Google Workspace CLI -│ ├── changelog.md # Auto: "generate a changelog" -│ ├── doc-gen.md # Auto: "document this module" -│ ├── test-gen.md # Auto: "write tests for X" -│ ├── onboard.md # Auto: "explain this codebase" -│ ├── research.md # Auto: "research X" -│ └── scrape.md # Auto: "scrape this URL" +│ └── status.md # Health check +├── skills/ # 14 context-activated skills +│ ├── executing/SKILL.md # Principle: methodical execution +│ ├── clean-code/SKILL.md # Principle: readability +│ ├── dry/SKILL.md # Principle: don't repeat yourself +│ ├── yagni/SKILL.md # Principle: no speculative features +│ ├── dont-reinvent/SKILL.md # Principle: use existing solutions +│ ├── stuck/SKILL.md # Principle: loop recovery +│ ├── creating-workflows/SKILL.md # Tool: YAML workflow authoring +│ ├── gcli/SKILL.md # Tool: Google Workspace CLI +│ ├── changelog/SKILL.md # Auto: "generate a changelog" +│ ├── doc-gen/SKILL.md # Auto: "document this module" +│ ├── test-gen/SKILL.md # Auto: "write tests for X" +│ ├── onboard/SKILL.md # Auto: "explain this codebase" +│ ├── research/SKILL.md # Auto: "research X" +│ └── scrape/SKILL.md # Auto: "scrape this URL" ├── agents/ # 6 agents │ ├── reviewer.md # Opus, worktree isolation │ ├── researcher.md # Sonnet, worktree isolation From 43794eecc49b7898d6eeba7322ce403648eba4a0 Mon Sep 17 00:00:00 2001 From: Tym Rabchuk Date: Sat, 4 Apr 2026 17:02:31 -0400 Subject: [PATCH 09/11] Guard ensureGitignore against non-ENOENT read errors Only proceed with empty content when .gitignore doesn't exist. If the file exists but can't be read (permission denied, I/O error), bail out rather than risk overwriting the user's .gitignore. --- src/lib/db.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/lib/db.go b/src/lib/db.go index 77f26cb..8468922 100644 --- a/src/lib/db.go +++ b/src/lib/db.go @@ -2,6 +2,7 @@ package lib import ( "database/sql" + "errors" "fmt" "os" "path/filepath" @@ -51,7 +52,10 @@ func ensureGitignore(devkitDir string) { repoRoot := filepath.Dir(devkitDir) gitignorePath := filepath.Join(repoRoot, ".gitignore") - content, _ := os.ReadFile(gitignorePath) // nil content if file doesn't exist + content, err := os.ReadFile(gitignorePath) + if err != nil && !errors.Is(err, os.ErrNotExist) { + return // don't risk overwriting a file we couldn't read + } for _, line := range strings.Split(string(content), "\n") { if strings.TrimSpace(line) == ".devkit" || strings.TrimSpace(line) == ".devkit/" { @@ -63,7 +67,6 @@ func ensureGitignore(devkitDir string) { if len(content) > 0 && content[len(content)-1] != '\n' { prefix = "\n" } - // best-effort; don't fail the whole operation os.WriteFile(gitignorePath, append(content, []byte(prefix+".devkit/\n")...), 0o644) } From 44f3a3128128ee1444916d37eaff1871f61b693d Mon Sep 17 00:00:00 2001 From: Tym Rabchuk Date: Sat, 4 Apr 2026 17:05:32 -0400 Subject: [PATCH 10/11] Add idempotency, whitespace, and mid-file tests for ensureGitignore Cover reviewer suggestions: triple-call idempotency, whitespace-padded entries, and .devkit/ appearing in the middle of the file. --- src/lib/db_test.go | 54 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/src/lib/db_test.go b/src/lib/db_test.go index 7740442..6860bd4 100644 --- a/src/lib/db_test.go +++ b/src/lib/db_test.go @@ -259,6 +259,60 @@ func TestEnsureGitignore_WithoutSlash(t *testing.T) { } } +func TestEnsureGitignore_Idempotent(t *testing.T) { + dir := t.TempDir() + devkitDir := filepath.Join(dir, ".devkit") + os.MkdirAll(devkitDir, 0o700) + + ensureGitignore(devkitDir) + ensureGitignore(devkitDir) + ensureGitignore(devkitDir) + + content, err := os.ReadFile(filepath.Join(dir, ".gitignore")) + if err != nil { + t.Fatalf("read .gitignore: %v", err) + } + if string(content) != ".devkit/\n" { + t.Errorf("content = %q, want single .devkit/ entry (no duplicates)", string(content)) + } +} + +func TestEnsureGitignore_WhitespacePadded(t *testing.T) { + dir := t.TempDir() + devkitDir := filepath.Join(dir, ".devkit") + os.MkdirAll(devkitDir, 0o700) + original := "node_modules/\n .devkit/ \n" + os.WriteFile(filepath.Join(dir, ".gitignore"), []byte(original), 0o644) + + ensureGitignore(devkitDir) + + content, err := os.ReadFile(filepath.Join(dir, ".gitignore")) + if err != nil { + t.Fatalf("read .gitignore: %v", err) + } + if string(content) != original { + t.Errorf("content = %q, want %q (should match whitespace-padded entry)", string(content), original) + } +} + +func TestEnsureGitignore_EntryMidFile(t *testing.T) { + dir := t.TempDir() + devkitDir := filepath.Join(dir, ".devkit") + os.MkdirAll(devkitDir, 0o700) + original := "node_modules/\n.devkit/\ndist/\n" + os.WriteFile(filepath.Join(dir, ".gitignore"), []byte(original), 0o644) + + ensureGitignore(devkitDir) + + content, err := os.ReadFile(filepath.Join(dir, ".gitignore")) + if err != nil { + t.Fatalf("read .gitignore: %v", err) + } + if string(content) != original { + t.Errorf("content = %q, want %q (should find entry in middle of file)", string(content), original) + } +} + func TestDBDirectoryPermissions(t *testing.T) { dir := t.TempDir() dbDir := filepath.Join(dir, ".devkit") From d6ccef38517adc16e897ac92eb134cd249954970 Mon Sep 17 00:00:00 2001 From: Tym Rabchuk Date: Sat, 4 Apr 2026 17:08:50 -0400 Subject: [PATCH 11/11] Clarify command name derivation in CONTRIBUTING.md Explain that the slash command name comes from the filename, not frontmatter, per tri-review suggestion. --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c8806f9..24fa9d2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -15,7 +15,7 @@ Slash commands appear in tab-completion and run step-by-step workflows. 2. Include Budget & Early Exit section if the command loops 3. Include `[PARALLEL]` markers if steps run concurrently -The command will be auto-discovered as `/devkit:my-command`. +The command name is derived from the filename: `commands/my-command.md` becomes `/devkit:my-command`. ## Adding a Context-Activated Skill