Skip to content

Latest commit

 

History

History
256 lines (210 loc) · 17 KB

File metadata and controls

256 lines (210 loc) · 17 KB

Devflow Project Instructions

Devflow is a 5-layer AI dev environment orchestrator (bash CLI, zero build deps). It composes Hindsight, Worktrunk, Code Review, Skills/CLAUDE.md, and Langfuse into a unified workflow for AI coding agents (Claude Code, OpenCode).

Project Structure

bin/devflow              # CLI entry point — sources all lib/*.sh, routes subcommands
lib/                     # Core command implementations (bash)
  utils.sh               # Shared utilities: logging, VCS detection, merge detection
  init.sh                # devflow init — full 5-layer setup (idempotent)
  services.sh            # devflow up/down/status — Docker service orchestration
  check.sh               # devflow check — multi-CLI code review abstraction
  skills.sh              # devflow skills — list/install/remove/convert
  seed.sh                # devflow seed — seed Hindsight from project files
  worktree.sh            # devflow worktree — wrapper around worktrunk + agent launch
  done.sh                # devflow done/clean — cleanup after PR merge
  visualizations.sh      # devflow visualizations — diagram management
  watch.sh               # devflow watch — sensitive file watchdog
  release.sh             # devflow release/version-bump — release pipeline
  hooks/                 # Claude Code hook scripts (registered via init.sh step 5d)
    prompt-fetch-rebase.sh   # UserPromptSubmit — auto-fetch + rebase
    pending-reviews-notify.sh # UserPromptSubmit — notify about stale sensitive files
    post-pr-continue.sh      # PostToolUse — nudge agent after PR creation
    stop-finish-prompt.sh    # Stop — no-op stub (finish-feature moved to skill-level)
devflow-plugin/          # Claude Code plugin (marketplace-ready)
  commands/              # 20+ markdown command/skill files
  .claude-plugin/        # Plugin metadata (plugin.json, marketplace.json)
skills/                  # Categorized skill files (NOT auto-discovered — require explicit Read)
templates/               # Init templates (CLAUDE.md.tmpl, AGENTS.md.tmpl, etc.)
tests/                   # Bats test infrastructure
  unit/utils.bats        # Unit tests for lib/utils.sh
  helpers/               # common.bash, mocks.bash, assertions.bash
docker/                  # Docker Compose for Hindsight + Langfuse
visualizations/          # Architecture diagrams (Mermaid markdown)
tasks/                   # Task backlog (P0-P4 priority folders, done/)
docs/plans/              # Dated design docs and implementation plans

Development Workflow

make test                # Smoke tests (binary exists, version, help)
make test-unit           # Bats unit tests (tests/unit/)
make plugin-dev          # Symlink devflow-plugin/ → ~/.claude/commands/devflow/ (dev iteration)
make plugin-unlink       # Remove dev symlinks
make plugin-install      # Register marketplace + install (end users)
make install             # Install devflow binary to ~/.local/bin/

Coding Conventions

  • Language: Bash (all lib/ files). Portable, set -euo pipefail safe.
  • Function naming: devflow_<subcommand>() for CLI entry points, _helper() with underscore prefix for internal helpers.
  • Logging: Use log, info, warn, err, die from utils.sh. Status: ok, fail, skip. Section headers: section.
  • VCS detection: Use detect_vcs_provider() and get_vcs_pr_term() from utils.sh. Guard calls under pipefail: provider="$(detect_vcs_provider 2>/dev/null)" || provider="unknown".
  • Sections: Separate logical blocks in lib files with # ── Section Name ── banners.
  • Tests: Add bats tests in tests/unit/ for new lib/utils.sh functions. Use helpers from tests/helpers/.

Skill / Command Conventions

  • All command descriptions include a [devflow v0.1.0] version badge.
  • Reference Hindsight tools as "Hindsight retain tool" and "Hindsight recall tool" (not MCP tool names).
  • skills/ directory is NOT symlinked into ~/.claude/skills/ — wrappers require explicit Read instructions.

Single source of truth: skills/<name>/SKILL.md

Repo-root skills/<name>/SKILL.md (+ its bundle files) is the ONE authored copy of each skill. Everything else is GENERATED — never hand-edit it:

skills/<name>/SKILL.md          # SOURCE (authored). Also: registry.json, eval configs (dev only)
  │  scripts/build-skills.sh  (make skills-sync)
  ├─► devflow-plugin/skills/<name>/     # byte copy, minus dev-only determinism.promptfooconfig.yaml
  ├─► devflow-plugin/commands/<name>.md # frontmatter -> command form ([VERSION] + description), body verbatim
  └─► make flows ─► devflow-plugin/flows/**  # 4th copy, generated by scripts/build-flows.sh
  • The plugin loads devflow-plugin/{commands,skills}; the CLI (make install) copies repo-root skills/. Both are fed from the single source via generation, so they can never drift.
  • Edit skills only under repo-root skills/, then run make skills-sync. The command form's description is [VERSION] + the SKILL.md description (version from plugin.json); its body is the SKILL.md body verbatim. Dev-only eval files (determinism.promptfooconfig.yaml) stay at the repo root and are excluded from the shipped plugin.
  • make skills-check (wired into make test) fails if the generated copies drift from source.
  • Pre-PR rescue guard: make skills-guard (scripts/skills-guard.sh). If a skill was edited in the WRONG (generated) tree, it folds that edit back into skills/<name>/SKILL.md and regenerates, instead of letting skills-sync silently overwrite it. /devflow:create-pr runs it before opening a PR when working inside this repo.

Skill Determinism & Observability

eval/ is the skill-quality harness (see eval/OBSERVABILITY.md):

  • promptfoo bench (eval/promptfooconfig.yaml + eval/lib/run-skill.sh): runs a skill headlessly (claude --print, CLAUDECODE="") and asserts on its stdout — deterministic asserts + LLM-judge + side-by-side version compare. This is a curated test bench, not live capture (no sampling); it sees only the fixtures you write.
  • Langfuse (docker, :3100, restart: unless-stopped): optional score history via eval/lib/langfuse-push.sh. v2 has no OTLP receiver; full Claude-Code-OTel trace ingestion (all real skill activations) needs Langfuse v3 — tracked in tasks/P3/SPIKE-telemetry-observability.md.

When authoring or changing a skill, prefer deterministic logic over AI judgment for computable work, on two axes: offload an AI decide/parse/score step to bash/regex/jq (assert with bats), and constrain any free-form result block to a pinned format (assert with promptfoo is-json/regex/no-fences on stdout). Leave genuinely-creative steps model-driven. The determinize-skill (proof-of-skill) automates this audit.

Each converted skill carries a skills/<name>/determinism.promptfooconfig.yaml (dev-only, not shipped in the plugin). Run the gate with make determinism (all skills) or make determinism SKILL=<name> (one). It needs API keys (the skill-spawning configs) and a reachable Langfuse (trace-review), so it is deliberately NOT part of make test / CI. promptfoo runs each config's exec: provider from that config's own directory, and only accepts a provider file that resolves locally to that dir, so every skill has a local eval-run.sh wrapper (the claude-spawning ones exec ../../eval/lib/run-skill.sh; trace-review runs its own engine).

Gate fidelity — it tests your WORKING TREE (not the installed plugin): eval/lib/run-skill.sh stages the local skill into a throwaway project dir (.claude/commands/<name>.md + .claude/skills/<name>/) and invokes it there as a PROJECT command /<name> (no devflow: prefix, so it can't collide with an installed /devflow:<name>). So editing skills/<name>/SKILL.md + make skills-sync is enough to gate a pending change — no reinstall/link. (Two dead ends, both verified: claude --print /devflow:<name> loads the INSTALLED skill, and --append-system-prompt conflicts with any installed command so the model improvises. The staged-project-command path avoids both.)

Model: the claude-spawning gate defaults to sonnet — haiku is too weak to adhere to a pinned output shape and flaps the asserts (verified). Override per run with SKILL_EVAL_MODEL. The deterministic-engine skill (trace-review) ignores the model entirely (it runs bin/devflow, not an LLM), so its gate is a hard gate; the AI-judgment gates are as reliable as sonnet's format adherence — assert robustly-present surfaces (final answer block, required tokens, forbidden patterns), not compressible intermediate prose.

Side-effect skills — DEVFLOW_EVAL test-mode + throwaway-fixture isolation. A gate executes the skill, so skills with irreversible side effects (create-pr opens a PR; finish-feature / new-feature push/commit/spawn; lock-tests / write-spike / verify-first write files) can't be run against the real repo. Their eval-run.sh sets DEVFLOW_EVAL=1; run-skill.sh then (a) REFUSES to run a listed side-effect skill without it (footgun guard — a stray make determinism can never push/write), and (b) runs it inside a THROWAWAY git fixture (feature branch 1 commit ahead of a local main, sample plan.md/spec.md/doc.md, no remote, no gh auth, deleted after) so it has real context but any push / PR-create / file write is contained. Each side-effect skill also honors DEVFLOW_EVAL itself (emit the final artifact to stdout, skip the irreversible step) — the fixture is the belt to that suspenders. Add a new side-effect skill → add it to SIDE_EFFECT_SKILLS in run-skill.sh and give its SKILL.md an eval-mode guard. Read-only skills (resolve-repo, review, review-document) need none and run from the repo root. The gate sets PROMPTFOO_DISABLE_DB=true so a running promptfoo view server can't lock the results DB.

Langfuse skill mirror (make mirror / bin/devflow skills mirror): pushes each skills/<name>/SKILL.md to Langfuse prompt-management as skill/<name> for versioned history

  • diff (a MIRROR only — Claude Code never reads skills back from Langfuse). It is local-only (targets localhost:3100) so it can never run in GitHub CI; instead devflow watch setup wires it into the local .git/hooks/post-merge for the devflow repo, so it refreshes on pull to main. make mirror is reachability-guarded: a clean no-op when Langfuse is down.

Hooks Architecture

Hook scripts live in lib/hooks/, registered in ~/.claude/settings.json via devflow init (step 5d). Protocol: stdin receives JSON payload, exit codes control behavior (0=allow, 2=block+re-activate for Stop hooks).

  • exit 2 is ONLY valid for Stop hooks — never for UserPromptSubmit (causes infinite blocking).
  • Stop hooks use stop_hook_active JSON field to prevent infinite re-activation loops.
  • Stop hook is a no-op. The finish-feature prompt was removed from the stop hook because it fires on ALL agent stops (including subagents, reviews, etc.). Finish-feature transition is now handled at the skill level in new-feature.md.

Feature Lifecycle

The expected feature lifecycle within a single session is:

/devflow:brainstorming → /devflow:spec-feature → /devflow:writing-plans → /devflow:lock-tests → /devflow:executing-plans → /devflow:finish-feature
  • /devflow:new-feature sets up context, recalls memories, runs scope-check, and starts brainstorming.
  • /devflow:spec-feature writes the spec document.
  • /devflow:writing-plans writes the implementation plan (devflow wrapper around the upstream writing-plans skill).
  • /devflow:lock-tests writes the full failing-test inventory and gates on user approval before implementation begins.
  • /devflow:executing-plans drives per-task red-green-refactor (devflow wrapper that forces the post-implementation handoff to /devflow:finish-feature).
  • /devflow:finish-feature runs verification, creates the PR/MR, retains learnings.

Convention: all callers always go through /devflow:* wrappers — never reach past them to the upstream skill directly. The wrappers are the canonical surface devflow callers see; the upstream skills are an implementation detail of the wrappers.

Each phase ends by invoking /devflow:phase-handoff, which writes a frozen-state file at .devflow/state/<branch>/<phase>.md (with worktree-relative artefact paths), gates on a one-click AskUserQuestion, then spawns a new Claude Desktop session via mcp__ccd_session__spawn_task. The spawned session's title is deterministic: [<TICKET>] [MR#<N>] <Phase> (e.g. [MES-4282] [MR#29] Implementation); the MR# slot is omitted when no MR/PR exists yet for the branch. Its initial prompt hands it ABSOLUTE paths to the frozen-state file + spec + plan + test inventory as the only authoritative inputs — the prior-phase context does not bleed into implementation because the spawned session has zero conversational memory of the prior phase.

On feature branches, always complete work with /devflow:finish-feature before ending the session.

Sensitive File Watchdog

devflow watch monitors files that must stay in sync with source code changes.

  • Config: .devflow/sensitive-files.conf (pipe-delimited, bash-native format)
  • Background: devflow watch setup installs a 5-min cron + git post-merge hook
  • In-session: finish-feature checks sensitive files before PR creation
  • Mechanical checks (version strings) auto-fixable; semantic checks (docs) need AI review

Release Process

Releases are automated via GitHub Actions on push to main (.github/workflows/release.yml).

  • Conventional commits determine the version bump:
    • feat: → minor, fix: → patch, feat!: / BREAKING CHANGE: → major
    • [skip release] in the HEAD commit message skips the release
    • chore(release): commits from the bot are auto-filtered (no re-trigger)
  • Version files are updated by scripts/bump-version.sh (Makefile, utils.sh, plugin.json, marketplace.json, all command badges)
  • GitHub Release created with tarball and install instructions
  • Homebrew formula updated with new SHA/URL in the same commit
  • Preview locally: devflow release shows what the next release would be
  • Manual bump: devflow version-bump <version> updates all version files locally

Release pipeline gotchas (fixed in v0.2.0 — do not reintroduce):

  • bump-version.sh runs under set -o pipefail. check_version_consistency (lib/watch.sh) greps each command file for its ^description: [X.Y.Z] badge; that grep pipeline MUST stay guarded with || true — a command with no matching badge makes the grep return 1, and unguarded that would abort the whole release silently under set -e (right after "All version files updated", before make release). Every generated command carries the badge today, but the guard is the safety net if one ever does not.
  • release.yml must create an annotated tag (git tag -a "v$X" -m ...). git push --follow-tags does NOT push lightweight tags, which leaves gh release create failing with "tag exists locally but has not been pushed".
  • To release an already-merged commit that used [skip release]: gh workflow run release.yml -f bump_override=<minor|patch|major> (the dispatch override bypasses the skip / "none" detection).

Auto-Reinstall

When devflow watch setup is run in the devflow source repo, it offers an auto-reinstall opt-in:

  • Uses SHA-based staleness detection (catches all changes, not just version bumps)
  • Detects install mode: symlink (make link), copy (make install), or Homebrew (warn only)
  • On new commits to main, runs the appropriate make target automatically
  • State tracked in ~/.devflow/.last-installed-sha

Worktree Convention

Git only allows one worktree per branch. To support multiple concurrent worktrees:

  • Never check out main/master as a tracked branch in any worktree. Use detached HEAD instead.
  • devflow worktree auto-detaches any worktree that has main locked before creating a new one.
  • Feature worktrees always create NEW branches from main (git worktree add <path> -b <branch> main).
  • When done, remove worktrees with devflow done <branch> — don't leave them lingering on main.
  • This matches the pattern used by OpenCode/Superpowers, Agent-deck, and Claude Code's worktree isolation.

Skill Interaction Rules

MANDATORY: Use AskUserQuestion for all user choices

When any skill or command needs to present choices, confirmations, or selections to the user, you MUST use the AskUserQuestion tool instead of printing a text question and waiting for input. This applies to:

  • Yes/No confirmations (e.g., "Proceed with these changes?")
  • Multiple choice selections (e.g., "Which group?", "Which template?")
  • Approval gates (e.g., "Proceed with these changes?")

Why: Text-based questions create a poor UX — the user sees a wall of text and has to type a free-form response. AskUserQuestion provides clickable options, is faster to answer, and prevents misinterpretation.

Exception: Open-ended questions where the user needs to provide free text (e.g., "Describe the feature") should still use normal text output, since AskUserQuestion is designed for structured choices.