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).
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
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/- Language: Bash (all lib/ files). Portable,
set -euo pipefailsafe. - Function naming:
devflow_<subcommand>()for CLI entry points,_helper()with underscore prefix for internal helpers. - Logging: Use
log,info,warn,err,diefrom utils.sh. Status:ok,fail,skip. Section headers:section. - VCS detection: Use
detect_vcs_provider()andget_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 fromtests/helpers/.
- All command descriptions include a
[devflow v0.1.0]version badge. - Reference Hindsight tools as "Hindsight
retaintool" and "Hindsightrecalltool" (not MCP tool names). skills/directory is NOT symlinked into~/.claude/skills/— wrappers require explicit Read instructions.
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-rootskills/. Both are fed from the single source via generation, so they can never drift. - Edit skills only under repo-root
skills/, then runmake skills-sync. The command form'sdescriptionis[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 intomake 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 intoskills/<name>/SKILL.mdand regenerates, instead of lettingskills-syncsilently overwrite it./devflow:create-prruns it before opening a PR when working inside this repo.
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 viaeval/lib/langfuse-push.sh. v2 has no OTLP receiver; full Claude-Code-OTel trace ingestion (all real skill activations) needs Langfuse v3 — tracked intasks/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; insteaddevflow watch setupwires it into the local.git/hooks/post-mergefor the devflow repo, so it refreshes on pull to main.make mirroris reachability-guarded: a clean no-op when Langfuse is down.
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 2is ONLY valid for Stop hooks — never for UserPromptSubmit (causes infinite blocking).- Stop hooks use
stop_hook_activeJSON 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.
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-featuresets up context, recalls memories, runs scope-check, and starts brainstorming./devflow:spec-featurewrites the spec document./devflow:writing-planswrites the implementation plan (devflow wrapper around the upstream writing-plans skill)./devflow:lock-testswrites the full failing-test inventory and gates on user approval before implementation begins./devflow:executing-plansdrives per-task red-green-refactor (devflow wrapper that forces the post-implementation handoff to/devflow:finish-feature)./devflow:finish-featureruns 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.
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 setupinstalls 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
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 releasechore(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 releaseshows 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.shruns underset -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 underset -e(right after "All version files updated", beforemake 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-tagsdoes NOT push lightweight tags, which leavesgh release createfailing 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).
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
maketarget automatically - State tracked in
~/.devflow/.last-installed-sha
Git only allows one worktree per branch. To support multiple concurrent worktrees:
- Never check out
main/masteras a tracked branch in any worktree. Use detached HEAD instead. devflow worktreeauto-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.
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.