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..24fa9d2 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 name is derived from the filename: `commands/my-command.md` becomes `/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 diff --git a/skills/audit.md b/commands/audit.md similarity index 99% rename from skills/audit.md rename to commands/audit.md index 554c2f1..44c3977 100644 --- a/skills/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/skills/bugfix.md b/commands/bugfix.md similarity index 99% rename from skills/bugfix.md rename to commands/bugfix.md index d33a747..ca541f6 100644 --- a/skills/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/skills/decompose.md b/commands/decompose.md similarity index 99% rename from skills/decompose.md rename to commands/decompose.md index d87e9de..89e53be 100644 --- a/skills/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/skills/feature.md b/commands/feature.md similarity index 99% rename from skills/feature.md rename to commands/feature.md index 6a71053..a2bf79f 100644 --- a/skills/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/skills/pr-monitor.md b/commands/pr-monitor.md similarity index 99% rename from skills/pr-monitor.md rename to commands/pr-monitor.md index 5b7551a..7eca809 100644 --- a/skills/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/skills/pr-ready.md b/commands/pr-ready.md similarity index 99% rename from skills/pr-ready.md rename to commands/pr-ready.md index fff6391..1d14d91 100644 --- a/skills/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/skills/refactor.md b/commands/refactor.md similarity index 99% rename from skills/refactor.md rename to commands/refactor.md index ac3c449..b3d2515 100644 --- a/skills/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/skills/repo-map.md b/commands/repo-map.md similarity index 99% rename from skills/repo-map.md rename to commands/repo-map.md index 17b00e6..5e54211 100644 --- a/skills/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/skills/self-improve.md b/commands/self-improve.md similarity index 99% rename from skills/self-improve.md rename to commands/self-improve.md index df8e8f2..c61fbe1 100644 --- a/skills/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/skills/self-lint.md b/commands/self-lint.md similarity index 99% rename from skills/self-lint.md rename to commands/self-lint.md index 480dcf9..8265aca 100644 --- a/skills/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/skills/self-migrate.md b/commands/self-migrate.md similarity index 99% rename from skills/self-migrate.md rename to commands/self-migrate.md index a3c337d..74d2805 100644 --- a/skills/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/skills/self-perf.md b/commands/self-perf.md similarity index 99% rename from skills/self-perf.md rename to commands/self-perf.md index d930502..65e2475 100644 --- a/skills/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/skills/self-test.md b/commands/self-test.md similarity index 99% rename from skills/self-test.md rename to commands/self-test.md index 5be9481..3fa6479 100644 --- a/skills/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/skills/status.md b/commands/status.md similarity index 99% rename from skills/status.md rename to commands/status.md index 8852fe5..fb10adb 100644 --- a/skills/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/skills/tri-debug.md b/commands/tri-debug.md similarity index 99% rename from skills/tri-debug.md rename to commands/tri-debug.md index 9a2cae0..8e5c32a 100644 --- a/skills/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/skills/tri-dispatch.md b/commands/tri-dispatch.md similarity index 99% rename from skills/tri-dispatch.md rename to commands/tri-dispatch.md index 0162c92..8d066a9 100644 --- a/skills/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/skills/tri-review.md b/commands/tri-review.md similarity index 99% rename from skills/tri-review.md rename to commands/tri-review.md index 3252813..d1f5c7f 100644 --- a/skills/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/skills/tri-security.md b/commands/tri-security.md similarity index 99% rename from skills/tri-security.md rename to commands/tri-security.md index 0547a9d..852b16e 100644 --- a/skills/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/skills/tri-test-gen.md b/commands/tri-test-gen.md similarity index 99% rename from skills/tri-test-gen.md rename to commands/tri-test-gen.md index 998f374..2f40b90 100644 --- a/skills/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/skills/workflow.md b/commands/workflow.md similarity index 98% rename from skills/workflow.md rename to commands/workflow.md index dafa461..607d8ae 100644 --- a/skills/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: diff --git a/manifest.json b/manifest.json deleted file mode 100644 index 7068cc2..0000000 --- a/manifest.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "name": "devkit", - "version": "2.0.2", - "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" - ], - "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" - ] -} diff --git a/skills/changelog.md b/skills/changelog/SKILL.md similarity index 98% rename from skills/changelog.md rename to skills/changelog/SKILL.md index dee1f0c..552756e 100644 --- a/skills/changelog.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.md b/skills/clean-code/SKILL.md similarity index 98% rename from skills/clean-code.md rename to skills/clean-code/SKILL.md index 8d03462..585d5e1 100644 --- a/skills/clean-code.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.md b/skills/creating-workflows/SKILL.md similarity index 98% rename from skills/creating-workflows.md rename to skills/creating-workflows/SKILL.md index 1e24cf6..615a73d 100644 --- a/skills/creating-workflows.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.md b/skills/doc-gen/SKILL.md similarity index 98% rename from skills/doc-gen.md rename to skills/doc-gen/SKILL.md index 069543f..215f27b 100644 --- a/skills/doc-gen.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.md b/skills/dont-reinvent/SKILL.md similarity index 99% rename from skills/dont-reinvent.md rename to skills/dont-reinvent/SKILL.md index b5d3652..7407fc3 100644 --- a/skills/dont-reinvent.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.md b/skills/dry/SKILL.md similarity index 99% rename from skills/dry.md rename to skills/dry/SKILL.md index b7ddd83..326f4f5 100644 --- a/skills/dry.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.md b/skills/executing/SKILL.md similarity index 98% rename from skills/executing.md rename to skills/executing/SKILL.md index da21946..d46f30d 100644 --- a/skills/executing.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.md b/skills/gcli/SKILL.md similarity index 99% rename from skills/gcli.md rename to skills/gcli/SKILL.md index 852b48d..6d6986a 100644 --- a/skills/gcli.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.md b/skills/onboard/SKILL.md similarity index 99% rename from skills/onboard.md rename to skills/onboard/SKILL.md index 8a646ca..8bcb137 100644 --- a/skills/onboard.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.md b/skills/research/SKILL.md similarity index 99% rename from skills/research.md rename to skills/research/SKILL.md index 5907ee9..091ce30 100644 --- a/skills/research.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.md b/skills/scrape/SKILL.md similarity index 99% rename from skills/scrape.md rename to skills/scrape/SKILL.md index b6c32b3..4275fac 100644 --- a/skills/scrape.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.md b/skills/stuck/SKILL.md similarity index 99% rename from skills/stuck.md rename to skills/stuck/SKILL.md index c135805..cbfc0c9 100644 --- a/skills/stuck.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.md b/skills/test-gen/SKILL.md similarity index 98% rename from skills/test-gen.md rename to skills/test-gen/SKILL.md index 189c74a..6346879 100644 --- a/skills/test-gen.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.md b/skills/yagni/SKILL.md similarity index 99% rename from skills/yagni.md rename to skills/yagni/SKILL.md index fa67d3c..7b58db8 100644 --- a/skills/yagni.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. --- diff --git a/src/lib/db.go b/src/lib/db.go index 6e833f8..8468922 100644 --- a/src/lib/db.go +++ b/src/lib/db.go @@ -2,9 +2,11 @@ package lib import ( "database/sql" + "errors" "fmt" "os" "path/filepath" + "strings" "time" _ "modernc.org/sqlite" @@ -45,10 +47,34 @@ 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") + + 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/" { + return + } + } + + prefix := "" + if len(content) > 0 && content[len(content)-1] != '\n' { + prefix = "\n" + } + os.WriteFile(gitignorePath, append(content, []byte(prefix+".devkit/\n")...), 0o644) +} + 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..6860bd4 100644 --- a/src/lib/db_test.go +++ b/src/lib/db_test.go @@ -171,6 +171,148 @@ 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 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")