8000 GitHub - dioptx/weft: Deterministic workflow tracking for Claude Code — smart skills, template management, dashboard auto-refresh · GitHub
[go: up one dir, main page]

Skip to content
dioptxPublic

About

Deterministic workflow tracking for Claude Code — smart skills, template management, dashboard auto-refresh

Topics

Resources

Contributing

Stars

30 stars

Watchers

1 watching

Forks

Repository files navigation

weft

Deterministic workflow tracking for Claude Code — smart skills, template management, dashboard auto-refresh.

weft pitch — agentic chain of skills with bounded loops + guards

Weft turns ad-hoc agent sessions into auditable, event-sourced workflows. Define a template (plan → implement → verify, or an 11-step ticket-to-PR cycle), and weft enforces the order, blocks tools that violate the current step's guards, and refuses to let you Stop with steps still pending.

Why this exists

Multi-step engineering work needs structure that survives compaction, retries, and the agent forgetting what step it's on. Weft solves three problems:

  • Skills fire reliably — /wf-start, /wf-step, /wf-status read state from disk, not memory.
  • Templates apply consistently — workflows live in JSON, version-controlled with your project.
  • Hooks enforce the contract — PreToolUse, PreCompact, and Stop block out-of-order actions or premature exits.

State is an append-only event log under .claude/weft/, so every workflow is fully reconstructible.

Why not just use TODOs or Claude's task list?

Built-in TODOs and ad-hoc checklists live in the agent's context window. They're advisory — the model can ignore them, lose them across compaction, or skip steps it judges unnecessary. They have no enforcement, no replay, no per-step guards. Weft is the opposite tradeoff: a small contract written to disk, enforced by hooks, with an event log you can audit after the fact. Use TODOs when "the model probably remembers" is good enough. Use weft when it isn't.

Requirements

  • Python 3.10+ (stdlib only — no runtime dependencies)
  • Claude Code with plugin support
  • macOS or Linux (the dashboard uses curses; Windows is untested)

Quick Start

Weft ships as a Claude Code plugin. Install via the marketplace:

/plugin marketplace add dioptx/weft
/plugin install weft@dioptx-weft

Or, for development / hacking on weft itself:

git clone https://github.com/dioptx/weft.git
claude --plugin-dir ./weft

The plugin auto-registers four hooks (SessionStart, PreToolUse, PreCompact, Stop) and 11 skills via .claude-plugin/plugin.json and hooks/hooks.json. Restart Claude Code and the /wf-* commands become available.

First workflow

/wf-start generic        # plan → implement → verify
/wf-status               # show current state
/wf-step complete        # advance to the next step
/wf-analyze              # per-template timing + recurring friction across runs

Run /wf-start with no args for the guided template picker.

weft walkthrough — start, step, complete, audit

See docs/demo.md for an annotated end-to-end walkthrough, and docs/demos/ for the asciicasts and recording scripts behind every GIF in this README.

Compose your own workflow

Templates are JSON. You can author one inline and pipe it to weft save-template — no editor required, no scaffolder. The template lands in .claude/weft/templates/ and is immediately available to weft start.

weft compose — heredoc → save-template → preview → start

cat > flow.json << 'EOF'
{
  "name": "site-audit",
  "steps": [
    {"name": "crawl",  "skill": "/perplexity"},
    {"name": "review", "skill": "/staff-review"},
    {"name": "fix",    "skill": "/fix-polish"},
    {"name": "verify", "loop_back_to": "review", "max_iterations": 3,
     "exit_condition": "no high-severity findings"},
    {"name": "push",
     "guards": [{"command_pattern": "\\bgit\\b[^|;&\\n]*\\bpush\\b",
                 "message": "git push may run only during push, after verification"}]}
  ]
}
EOF
weft save-template < flow.json
weft start site-audit

For interactive composition based on what's in your current conversation, use /wf-compose.

How guards work

Each denial also appends a JSONL event to ~/.local/state/agentry/events.jsonl. Set AGENTRY_EVENTS_PATH to override that path. The event contains event: "guard", tool, input (the refused command or file path), and rule (the matched regex). build_id and task come from the JSON object in AGENT_P_TAGS; missing or invalid tags produce null values. A stream write failure warns but still denies the call.

A guard means this command may run only on its owner step. During a running workflow, the engine skips the current step's guards and checks every other step's guards. A match there blocks the call. Put each command's guard on one step: putting the same guard on two steps blocks the command on every step, because another owner's guard always matches.

Guards are regular expressions in command_pattern (or plain strings). For Bash they match the command, for Edit and Write the file path, and for MCP tools the tool name first, then commands found in their inputs; other tools match by name only. Write patterns that cover flags between command words: \bgit\b[^|;&\n]*\bpush\b matches both git push and git -C work push. JSON requires doubled backslashes, as in the example above. These are regex checks over tool calls, not a shell parser. Existing per-run approvals can allow exact Bash commands or individual MCP input candidates, not the tool name.

MCP command keys are command, cmd, cmdline, commandline, argv, script, code, shell, bash, sh, zsh, powershell, pwsh, program, executable, bin, stdin, stdin_text, and keys ending in command, cmd or script. Command-list keys are commands, cmds, scripts, and keys ending in those words. Keys are lowercased with underscores, hyphens, dots and spaces removed: bashCommand counts, commandId does not. Every string nested under these keys is checked; script bodies are checked as one string regardless of language.

A command/program string or list is also joined with sibling args, argv, arguments, subcommand or params: {"cmd":"git","argv":["push","origin"]} and {"command":"git","subcommand":"push"} are checked as joined commands. Each sibling argument group and all groups in document order form candidates. Lists in command context flatten nested lists and stringify scalars using JSON spelling (null, true, numbers), so {"argv":["git",["push"],"origin"]} cannot hide a push. A command-list key's direct list still contains separate commands and is never joined across its entries. Objects inside such a list are left out of the joined argv; their strings are still checked. Object-valued argument groups or elements joined to a program fail closed rather than guessing server coercion.

String values that parse as JSON are decoded and scanned repeatedly, including quoted/double-encoded JSON, with at most 8 successful decodes per branch. A ninth decode blocks when an MCP input guard applies, even if the decoded data is benign. Split-command joining checks both raw and decoded forms, retaining existing raw candidates. Failed parses, including Python's integer digit limit, are ignored without skipping other fields.

To avoid treating prose and file data as commands, keys ending in transcript or postscript do not start command context. Outside command context, content, text, body and data next to a string path or file_path are file data and are not walked or JSON-decoded. Explicit mcp_fields can override these exclusions. Ordinary prose remains data; {"command":"search","query":"git push tutorial"} and {"args":["--title","git","push"],"tool":"note"} remain allowed because query is not an argument key and args alone has no program. Literal argument text can still match a command regex: {"program":"note","args":["--title","git","push"]} blocks under a push guard. The structural depth limit applies only when a nonempty command candidate is found; deep data-only documents within the JSON-decoding limit stay allowed.

On a dict guard, set "mcp_input": false to check only MCP tool names (Bash checks are unchanged), or use "mcp_fields" to add command keys for that guard only. Input checking is on by default; only boolean false disables it. Extra fields use the same key normalisation; non-list values and non-string entries are ignored, and mcp_fields is ignored when input checking is off. For example:

{"command_pattern": "\\bgit\\b[^|;&\\n]*\\bpush\\b", "message": "git push may run only during ship", "mcp_fields": ["input", "query"]}

When a guard applies, checks fail closed on the shared 1 s extraction/matching budget (Bash included), MCP command candidates nested deeper than 32 levels, or MCP extraction/matching errors; invalid regexes still warn and skip. Deep MCP hook JSON uses an iterative decoder rather than crashing at Python's recursion limit. Reporting failures cannot cancel a denial. The budget starts at the guard loop, not at shell startup or hook-envelope parsing, and is unavailable on platforms or threads without usable signal timers. MCP events include subject: "tool_name" for name matches (the name remains the command), "input" for input matches, or the failure reason. Out of scope: shell obfuscation, anchored patterns missing later lines in multi-line bodies, regex limitations in language-specific syntax (such as quoted argv separators), MCP file-path guards, commands inside Agent prompts, and unknown command fields unless added with mcp_fields. Claude Code's hook timeout/exit-1 handling, visibility of exit-2 reasons with merged stderr, and context-mode's real schema or hook rewriting order are not verified here.

Step descriptions and exit conditions guide the agent; the state machine does not execute checks or collect human approval automatically. Claude Code hooks enforce the guards when registered; running the CLI alone exercises the state machine without installing tool enforcement in another agent.

Extend with your own skills

Weft consumes Claude Code skills (SKILL.md files under .claude/skills/) as workflow steps. Drop a new skill in, reference it from a template via skill: /<name>, and weft picks it up.

weft extend — author SKILL.md, reference it from a template

mkdir -p .claude/skills/site-audit
cat > .claude/skills/site-audit/SKILL.md << 'EOF'
---
name: site-audit
description: Crawl + audit a target site with lighthouse
allowed-tools: [Bash, Read]
---
# Site Audit
Run lighthouse against $URL, report findings.
EOF

cat <<'JSON' | weft save-template
{
  "name": "audit-cycle",
  "steps": [
    {"name": "audit", "skill": "/site-audit"},
    {"name": "fix",   "skill": "/fix-polish"}
  ]
}
JSON

Features

  • Event-sourced state machine — append-only JSON log; rebuild state with /wf-rebuild
  • Template system — six bundled workflows (listed below); add your own under templates/ (project), ~/.weft/templates/ (user-global), or WEFT_USER_TEMPLATES_DIR
  • Smart skills — 12 slash commands (wf-start, wf-step, wf-status, wf-resume, wf-run-step, wf-abort, wf-preview, wf-rebuild, wf-analyze, wf-template, wf-compose, ev-query)
  • Guard engine — commands, file paths, MCP tool names and commands inside MCP inputs restricted to their owner step via the PreToolUse hook
  • Plan checker — weft check-plan plan.json validates claim coverage, milestone dates, dependencies and completion criteria with no tracker access
  • Stop gate — refuses session exit while steps are incomplete (configurable via on_fail: block|retry|skip)
  • Compaction-safe — PreCompact hook writes a context.md projection so the next session resumes mid-workflow
  • Live dashboard — auto-refreshing TUI for monitoring active workflows
  • Template management — /wf-template for creating and editing templates in-session
Bundled template Steps Purpose
generic 3 Plan, implement and verify
feature-workflow 11 Develop a feature from context through review and push
codex-review 5 Combine two code reviews and fix findings
land-pr 3 Prepare, post review and preview evidence, then merge
plan-from-interview 8 Extract claims, reconcile, draft, approve, publish, verify and record a planning retrospective
plan-retro 2 Gather ticket, PR and check results after build-out, then write a human-confirmed retrospective

See the planning contract for plan.json, optional whiteboard annotations and capacity evidence, existing issue reuse, and retry-safe publication. Planning ends at tickets and never starts a build. The final planning step writes planning/<slug>/retro.md; plan-retro later adds the build-out section with evidence and human confirmation.

Auditable by design

Every workflow transition is an append-only event in .claude/weft/events.jsonl. The state.json snapshot is just a projection — delete it and weft will rebuild it from events alone.

weft audit — query events, raw jsonl, rebuild from log

weft query                       # human-readable timeline
tail -3 .claude/weft/events.jsonl  # raw structured events
rm .claude/weft/state.json       # nuke the snapshot
weft rebuild                     # reconstruct from events alone

Use weft query --type wf.step_changed, --last 50, or --workflow <id> to filter.

Interactive TUI

For watching multiple workflows at once, scripts/wf.py is a curses-based TUI over the same state. Three views: list (every running workflow + PR/CI status), detail (per-workflow step + recent events), templates (discovery).

python3 $WEFT_ROOT/scripts/wf.py                 # interactive TUI
python3 $WEFT_ROOT/scripts/wf.py --list          # plain-text table (auto when not TTY)
python3 $WEFT_ROOT/scripts/wf.py --json | jq     # machine-readable
python3 $WEFT_ROOT/scripts/wf.py --templates     # template catalog

Keys in the list view: j/k navigate, enter open detail, t templates, r refresh, q quit. Detail view: esc/h back, r refresh, q quit. Read-only — never mutates weft state.

The TUI scans the current directory by default; pass a path to scan elsewhere. It picks up both legacy single-workflow layouts (<project>/.claude/weft/) and per-ticket layouts (<project>/.claude/weft/workflows/<id>/.claude/weft/).

For scripting without curses, scripts/wf-monitor.py produces the same aligned-table output and supports --json, --templates, and an explicit project path argument.

Suggested shell alias:

alias wf='python3 ~/.claude/plugins/cache/local/weft/0.4.2/scripts/wf.py'

Repository Layout

.claude-plugin/   plugin.json — Claude Code plugin manifest
core/             Python event store, state machine, projections, CLI, dashboard
hooks/            SessionStart / PreToolUse / PreCompact / Stop bash hooks + hooks.json
skills/           11 SKILL.md files for the /wf-* and /ev-* slash commands
scripts/          wf.py (curses TUI) + wf-monitor.py (plain-text + JSON viewer)
templates/        Bundled workflow definitions (JSON)
tests/            pytest suite covering event store, state machine, hooks, CLI, behaviors

The Python core is dependency-free (stdlib only). The dashboard uses curses. Tests use pytest.

Custom templates

Weft discovers templates from three locations, in order of precedence:

Tier Path Use for
Project-local <repo>/.claude/weft/templates/*.json Templates specific to one project, version-controlled with it
User-global ~/.weft/templates/*.json (override with WEFT_USER_TEMPLATES_DIR) Personal templates available across every project
Bundled templates/*.json inside the plugin Defaults shipped with weft

Same-named template higher in the table overrides lower tiers, so you can shadow a bundled template with a project-specific variant.

Version

v0.4.2 — current. See CHANGELOG.md for release history.

Contributing

See CONTRIBUTING.md for how to run tests, add templates, and propose changes.

License

MIT © dioptx

About

Deterministic workflow tracking for Claude Code — smart skills, template management, dashboard auto-refresh

Topics

Resources

Contributing

Stars

30 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

0