loopflow 0.2.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,8 @@
1
+ # Python
2
+ __pycache__/
3
+
4
+ # Claude Code (personal state)
5
+ .claude/settings.local.json
6
+
7
+ # Worktrees (created by lf start)
8
+ .lf/worktrees/
@@ -0,0 +1,143 @@
1
+ Metadata-Version: 2.4
2
+ Name: loopflow
3
+ Version: 0.2.2
4
+ Summary: Arrange LLMs to code in harmony
5
+ Author: Jack
6
+ License-Expression: MIT
7
+ Keywords: ai,claude,cli,coding,llm
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Environment :: Console
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Topic :: Software Development
17
+ Requires-Python: >=3.10
18
+ Requires-Dist: pathspec>=0.11.0
19
+ Requires-Dist: pydantic-ai-slim[anthropic]>=1.0.0
20
+ Requires-Dist: pydantic>=2.12.5
21
+ Requires-Dist: pyyaml>=6.0
22
+ Requires-Dist: tiktoken>=0.7.0
23
+ Requires-Dist: typer>=0.9.0
24
+ Provides-Extra: dev
25
+ Requires-Dist: pytest>=7.0.0; extra == 'dev'
26
+ Description-Content-Type: text/markdown
27
+
28
+ # Loopflow
29
+
30
+ Run LLM coding tasks from reusable prompt files.
31
+
32
+ macOS only. Currently uses Claude Code; other backends planned.
33
+
34
+ ## Install
35
+
36
+ ```bash
37
+ pip install loopflow
38
+ lf meta install # installs Claude Code via npm
39
+ ```
40
+
41
+ ## Why Worktrees?
42
+
43
+ Loopflow is designed for running background agents while you work on something else. That means isolated branches - you can't have an agent committing to the branch you're actively editing.
44
+
45
+ The workflow: create a worktree, run tasks there, merge when ready. You can have multiple features in flight at once.
46
+
47
+ ## Quick Start
48
+
49
+ ```bash
50
+ lf wt create my-feature # branch + worktree, opens IDEs
51
+ cd .lf/worktrees/my-feature
52
+
53
+ lf design # interactive: figure out what to build
54
+ lf ship # batch: implement, review, test, commit, open PR
55
+ ```
56
+
57
+ `lf design` runs `.lf/design.lf`. `lf ship` runs the `ship` pipeline from `.lf/config.yaml`.
58
+
59
+ ## Tasks
60
+
61
+ Tasks are prompt files in `.lf/`. Here's an example:
62
+
63
+ ```markdown
64
+ # .lf/review.lf
65
+
66
+ Review the diff on the current branch against `main` and fix any issues found.
67
+
68
+ The deliverable is the fixes themselves, not a written review.
69
+
70
+ ## What to look for
71
+
72
+ - Style guide violations (read STYLE.md)
73
+ - Bugs, logic errors, edge cases
74
+ - Unnecessary complexity
75
+ - Missing tests
76
+ ```
77
+
78
+ Run tasks by name:
79
+
80
+ ```bash
81
+ lf review # run .lf/review.lf
82
+ lf review -x src/utils.py # add context files
83
+ lf : "fix the typo" # inline prompt, no task file
84
+ ```
85
+
86
+ All `.md` files at repo root (README, STYLE, etc.) are included as context automatically.
87
+
88
+ ## Pipelines
89
+
90
+ Chain tasks in `.lf/config.yaml`:
91
+
92
+ ```yaml
93
+ pipelines:
94
+ ship:
95
+ tasks: [implement, review, test, commit]
96
+ pr: true # open PR when done
97
+ ```
98
+
99
+ ```bash
100
+ lf ship # runs each task, auto-commits between steps
101
+ ```
102
+
103
+ ## Worktrees
104
+
105
+ ```bash
106
+ lf wt create auth # .lf/worktrees/auth/, opens Warp + Cursor
107
+ lf wt list # show all worktrees
108
+ lf wt clean # remove merged branches
109
+ ```
110
+
111
+ ## Configuration
112
+
113
+ ```yaml
114
+ # .lf/config.yaml
115
+ push: true # auto-push after commits
116
+ pr: false # open PR after pipelines
117
+
118
+ ide:
119
+ warp: true
120
+ cursor: true
121
+ ```
122
+
123
+ ## Options
124
+
125
+ | Option | Description |
126
+ |--------|-------------|
127
+ | `-p, --print` | Batch mode (non-interactive) |
128
+ | `-x, --context` | Add context files |
129
+ | `-w, --worktree` | Create worktree and run task there |
130
+ | `-c, --copy` | Copy prompt to clipboard, show token breakdown |
131
+
132
+ ## Commands
133
+
134
+ | Command | Description |
135
+ |---------|-------------|
136
+ | `lf <task>` | Run a task from `.lf/` |
137
+ | `lf <pipeline>` | Run a pipeline |
138
+ | `lf : "prompt"` | Inline prompt |
139
+ | `lf wt create/list/clean` | Worktree management |
140
+ | `lf pr create` | Open GitHub PR |
141
+ | `lf pr land [-a]` | Squash-merge to main |
142
+ | `lf meta install` | Install Claude Code |
143
+ | `lf meta doctor` | Check dependencies |
@@ -0,0 +1,116 @@
1
+ # Loopflow
2
+
3
+ Run LLM coding tasks from reusable prompt files.
4
+
5
+ macOS only. Currently uses Claude Code; other backends planned.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ pip install loopflow
11
+ lf meta install # installs Claude Code via npm
12
+ ```
13
+
14
+ ## Why Worktrees?
15
+
16
+ Loopflow is designed for running background agents while you work on something else. That means isolated branches - you can't have an agent committing to the branch you're actively editing.
17
+
18
+ The workflow: create a worktree, run tasks there, merge when ready. You can have multiple features in flight at once.
19
+
20
+ ## Quick Start
21
+
22
+ ```bash
23
+ lf wt create my-feature # branch + worktree, opens IDEs
24
+ cd .lf/worktrees/my-feature
25
+
26
+ lf design # interactive: figure out what to build
27
+ lf ship # batch: implement, review, test, commit, open PR
28
+ ```
29
+
30
+ `lf design` runs `.lf/design.lf`. `lf ship` runs the `ship` pipeline from `.lf/config.yaml`.
31
+
32
+ ## Tasks
33
+
34
+ Tasks are prompt files in `.lf/`. Here's an example:
35
+
36
+ ```markdown
37
+ # .lf/review.lf
38
+
39
+ Review the diff on the current branch against `main` and fix any issues found.
40
+
41
+ The deliverable is the fixes themselves, not a written review.
42
+
43
+ ## What to look for
44
+
45
+ - Style guide violations (read STYLE.md)
46
+ - Bugs, logic errors, edge cases
47
+ - Unnecessary complexity
48
+ - Missing tests
49
+ ```
50
+
51
+ Run tasks by name:
52
+
53
+ ```bash
54
+ lf review # run .lf/review.lf
55
+ lf review -x src/utils.py # add context files
56
+ lf : "fix the typo" # inline prompt, no task file
57
+ ```
58
+
59
+ All `.md` files at repo root (README, STYLE, etc.) are included as context automatically.
60
+
61
+ ## Pipelines
62
+
63
+ Chain tasks in `.lf/config.yaml`:
64
+
65
+ ```yaml
66
+ pipelines:
67
+ ship:
68
+ tasks: [implement, review, test, commit]
69
+ pr: true # open PR when done
70
+ ```
71
+
72
+ ```bash
73
+ lf ship # runs each task, auto-commits between steps
74
+ ```
75
+
76
+ ## Worktrees
77
+
78
+ ```bash
79
+ lf wt create auth # .lf/worktrees/auth/, opens Warp + Cursor
80
+ lf wt list # show all worktrees
81
+ lf wt clean # remove merged branches
82
+ ```
83
+
84
+ ## Configuration
85
+
86
+ ```yaml
87
+ # .lf/config.yaml
88
+ push: true # auto-push after commits
89
+ pr: false # open PR after pipelines
90
+
91
+ ide:
92
+ warp: true
93
+ cursor: true
94
+ ```
95
+
96
+ ## Options
97
+
98
+ | Option | Description |
99
+ |--------|-------------|
100
+ | `-p, --print` | Batch mode (non-interactive) |
101
+ | `-x, --context` | Add context files |
102
+ | `-w, --worktree` | Create worktree and run task there |
103
+ | `-c, --copy` | Copy prompt to clipboard, show token breakdown |
104
+
105
+ ## Commands
106
+
107
+ | Command | Description |
108
+ |---------|-------------|
109
+ | `lf <task>` | Run a task from `.lf/` |
110
+ | `lf <pipeline>` | Run a pipeline |
111
+ | `lf : "prompt"` | Inline prompt |
112
+ | `lf wt create/list/clean` | Worktree management |
113
+ | `lf pr create` | Open GitHub PR |
114
+ | `lf pr land [-a]` | Squash-merge to main |
115
+ | `lf meta install` | Install Claude Code |
116
+ | `lf meta doctor` | Check dependencies |
@@ -0,0 +1,49 @@
1
+ [project]
2
+ name = "loopflow"
3
+ dynamic = ["version"]
4
+ description = "Arrange LLMs to code in harmony"
5
+ readme = "README.md"
6
+ requires-python = ">=3.10"
7
+ license = "MIT"
8
+ authors = [{ name = "Jack" }]
9
+ keywords = ["llm", "claude", "ai", "coding", "cli"]
10
+ classifiers = [
11
+ "Development Status :: 3 - Alpha",
12
+ "Environment :: Console",
13
+ "Intended Audience :: Developers",
14
+ "License :: OSI Approved :: MIT License",
15
+ "Programming Language :: Python :: 3",
16
+ "Programming Language :: Python :: 3.10",
17
+ "Programming Language :: Python :: 3.11",
18
+ "Programming Language :: Python :: 3.12",
19
+ "Topic :: Software Development",
20
+ ]
21
+ dependencies = [
22
+ "typer>=0.9.0",
23
+ "pathspec>=0.11.0",
24
+ "pyyaml>=6.0",
25
+ "pydantic>=2.12.5",
26
+ "pydantic-ai-slim[anthropic]>=1.0.0",
27
+ "tiktoken>=0.7.0",
28
+ ]
29
+
30
+ [project.optional-dependencies]
31
+ dev = [
32
+ "pytest>=7.0.0",
33
+ ]
34
+
35
+ [project.scripts]
36
+ lf = "loopflow.cli:main"
37
+
38
+ [build-system]
39
+ requires = ["hatchling"]
40
+ build-backend = "hatchling.build"
41
+
42
+ [tool.hatch.build.targets.wheel]
43
+ packages = ["src/loopflow"]
44
+
45
+ [tool.hatch.build.targets.sdist]
46
+ include = ["src/loopflow"]
47
+
48
+ [tool.hatch.version]
49
+ path = "src/loopflow/__init__.py"
@@ -0,0 +1 @@
1
+ __version__ = "0.2.2"
@@ -0,0 +1,15 @@
1
+ """Builtin prompts for loopflow commands.
2
+
3
+ These are prompts used by loopflow's own commands (lf pr create, etc.),
4
+ not user-defined tasks in .lf/.
5
+ """
6
+
7
+ from pathlib import Path
8
+
9
+ _BUILTINS_DIR = Path(__file__).parent
10
+
11
+
12
+ def get_builtin_prompt(name: str) -> str:
13
+ """Get a builtin prompt by name. Raises FileNotFoundError if not found."""
14
+ prompt_file = _BUILTINS_DIR / f"{name}.txt"
15
+ return prompt_file.read_text()
@@ -0,0 +1,24 @@
1
+ Generate a commit message for the staged changes.
2
+
3
+ Review the diff and write a concise commit message.
4
+
5
+ ## Output format
6
+
7
+ Return a structured response with:
8
+ - **title**: lowercase, with optional area prefix (e.g. `llm_http: add structured output`)
9
+ - **body**: brief explanation if needed, otherwise empty string
10
+
11
+ ## Title style
12
+
13
+ Titles are lowercase and concise. Use an area prefix when changes are focused on a specific module or feature area.
14
+
15
+ Examples:
16
+ - `llm_http: add structured output for pr messages`
17
+ - `pr workflow: add -a flag to commit and push`
18
+ - `fix typo in readme`
19
+
20
+ ## Body style
21
+
22
+ Keep it brief—one sentence or a few bullets if the change needs explanation. Empty string is fine for self-explanatory changes.
23
+
24
+ Most commits don't need a body. Only add one if the "why" isn't obvious from the title.
@@ -0,0 +1,46 @@
1
+ Generate a PR title and body for the changes on this branch.
2
+
3
+ Review the diff against main and summarize what changed and why.
4
+
5
+ ## Output format
6
+
7
+ Return a structured response with:
8
+ - **title**: lowercase, with optional area prefix (e.g. `llm_http: add structured output`)
9
+ - **body**: markdown with headers, code blocks for commands, and bullet lists
10
+
11
+ ## Title style
12
+
13
+ Titles are lowercase and concise. Use an area prefix when changes are focused on a specific module or feature area. The area can be new or existing.
14
+
15
+ Examples:
16
+ - `llm_http: add structured output for pr messages`
17
+ - `pr workflow: add -a flag to commit and push`
18
+ - `fix worktree cleanup on branch delete`
19
+
20
+ ## Body style
21
+
22
+ Use markdown headers to organize the body. Open with a "Usage" or "Try it" section showing commands in code blocks. Then explain what changed.
23
+
24
+ Structure:
25
+ 1. **Usage section** (header + code block) - how to try it, run it, or see it in action
26
+ 2. **Summary** - one paragraph explaining what this PR does and why
27
+ 3. **Changes** (optional) - bullet list of notable changes if helpful
28
+
29
+ Keep it medium length. Stay high-level; don't enumerate every file.
30
+
31
+ Example body:
32
+ ```
33
+ ## Usage
34
+
35
+ \`\`\`bash
36
+ lf pr create -a
37
+ lf pr update -a
38
+ \`\`\`
39
+
40
+ PR create and update now generate title/body via Claude API instead of reading from .lf/COMMIT. The -a flag adds, commits, and pushes before creating/updating.
41
+
42
+ ## Changes
43
+
44
+ - New llm_http module for structured LLM responses
45
+ - Builtin prompts stored in package, separate from user .lf/ tasks
46
+ ```
@@ -0,0 +1,74 @@
1
+ """Loopflow CLI: Arrange LLMs to code in harmony."""
2
+
3
+ import sys
4
+
5
+ import typer
6
+
7
+ from loopflow.config import ConfigError, load_config
8
+ from loopflow.context import find_worktree_root, gather_task
9
+
10
+ app = typer.Typer(
11
+ name="lf",
12
+ help="Arrange LLMs to code in harmony.",
13
+ no_args_is_help=True,
14
+ )
15
+
16
+ # Import and register subcommands
17
+ from loopflow.cli import run as run_module
18
+ from loopflow.cli import wt, pr, meta
19
+
20
+ app.add_typer(wt.app, name="wt")
21
+ app.add_typer(pr.app, name="pr")
22
+ app.add_typer(meta.app, name="meta")
23
+
24
+ # Register top-level commands
25
+ app.command()(run_module.run)
26
+ app.command()(run_module.inline)
27
+ app.command(name="pipeline")(run_module.pipeline)
28
+
29
+
30
+ def main():
31
+ """Entry point that supports 'lf <task>' and 'lf <pipeline>' shorthand."""
32
+ known_commands = {"run", "pipeline", "inline", "wt", "pr", "meta", "--help", "-h"}
33
+
34
+ try:
35
+ if len(sys.argv) > 1:
36
+ first_arg = sys.argv[1]
37
+
38
+ # Inline prompt: lf : "prompt"
39
+ if first_arg == ":":
40
+ sys.argv.pop(1)
41
+ sys.argv.insert(1, "inline")
42
+ elif first_arg not in known_commands:
43
+ name = sys.argv[1]
44
+ repo_root = find_worktree_root()
45
+ config = load_config(repo_root) if repo_root else None
46
+
47
+ has_pipeline = config and name in config.pipelines
48
+ has_task = repo_root and gather_task(repo_root, name) is not None
49
+
50
+ if has_pipeline and has_task:
51
+ typer.echo(
52
+ f"Error: '{name}' exists as both a pipeline and a task. "
53
+ "Remove one to resolve the conflict.",
54
+ err=True,
55
+ )
56
+ raise SystemExit(1)
57
+
58
+ if has_pipeline:
59
+ sys.argv.insert(1, "pipeline")
60
+ elif has_task:
61
+ sys.argv.insert(1, "run")
62
+ else:
63
+ typer.echo(f"Error: No task or pipeline named '{name}'", err=True)
64
+ typer.echo(f"Create .lf/{name}.lf or add '{name}' to pipelines in .lf/config.yaml", err=True)
65
+ raise SystemExit(1)
66
+
67
+ app()
68
+ except ConfigError as e:
69
+ typer.echo(f"Error: {e}", err=True)
70
+ raise SystemExit(1)
71
+
72
+
73
+ if __name__ == "__main__":
74
+ main()
@@ -0,0 +1,158 @@
1
+ """Setup and diagnostics commands."""
2
+
3
+ import platform
4
+ import shutil
5
+ import subprocess
6
+
7
+ import typer
8
+
9
+ from loopflow.config import load_config
10
+ from loopflow.context import find_worktree_root
11
+ from loopflow.launcher import check_claude_available
12
+
13
+ app = typer.Typer(help="Setup and diagnostics.")
14
+
15
+
16
+ def _install_node() -> bool:
17
+ """Attempt to install Node.js via Homebrew on macOS."""
18
+ if platform.system() != "Darwin":
19
+ return False
20
+
21
+ if not shutil.which("brew"):
22
+ typer.echo("Homebrew not found. Install from https://brew.sh", err=True)
23
+ return False
24
+
25
+ typer.echo("Installing Node.js via Homebrew...")
26
+ result = subprocess.run(["brew", "install", "node"], capture_output=True)
27
+ return result.returncode == 0
28
+
29
+
30
+ def _install_cask(name: str) -> bool:
31
+ """Install a Homebrew cask. Returns success."""
32
+ result = subprocess.run(
33
+ ["brew", "install", "--cask", name],
34
+ capture_output=True,
35
+ )
36
+ return result.returncode == 0
37
+
38
+
39
+ @app.command()
40
+ def version():
41
+ """Show loopflow version."""
42
+ from loopflow import __version__
43
+
44
+ typer.echo(f"loopflow {__version__}")
45
+
46
+
47
+ @app.command()
48
+ def install():
49
+ """Install loopflow dependencies based on config. macOS only."""
50
+ if platform.system() != "Darwin":
51
+ typer.echo("Error: lf install only supports macOS", err=True)
52
+ typer.echo("Install dependencies manually.", err=True)
53
+ raise typer.Exit(1)
54
+
55
+ if not shutil.which("brew"):
56
+ typer.echo("Error: Homebrew not found. Install from https://brew.sh", err=True)
57
+ raise typer.Exit(1)
58
+
59
+ # Load config to check what's needed
60
+ repo_root = find_worktree_root()
61
+ config = load_config(repo_root) if repo_root else None
62
+ ide = config.ide if config else None
63
+
64
+ # Node.js (required for Claude Code)
65
+ if not shutil.which("npm"):
66
+ typer.echo("Installing Node.js...")
67
+ if _install_node() and shutil.which("npm"):
68
+ typer.echo("✓ Node.js installed")
69
+ else:
70
+ typer.echo("✗ Could not install Node.js", err=True)
71
+ raise typer.Exit(1)
72
+ else:
73
+ typer.echo("✓ Node.js")
74
+
75
+ # Claude Code (always required)
76
+ if check_claude_available():
77
+ typer.echo("✓ Claude Code")
78
+ else:
79
+ typer.echo("Installing Claude Code...")
80
+ result = subprocess.run(
81
+ ["npm", "install", "-g", "@anthropic-ai/claude-code"],
82
+ capture_output=True,
83
+ text=True,
84
+ )
85
+ if result.returncode == 0:
86
+ typer.echo("✓ Claude Code installed")
87
+ else:
88
+ typer.echo(f"✗ Could not install Claude Code: {result.stderr}", err=True)
89
+ raise typer.Exit(1)
90
+
91
+ # Warp (if enabled in config, default true)
92
+ if not ide or ide.warp:
93
+ if shutil.which("warp"):
94
+ typer.echo("✓ Warp")
95
+ else:
96
+ typer.echo("Installing Warp...")
97
+ if _install_cask("warp"):
98
+ typer.echo("✓ Warp installed")
99
+ else:
100
+ typer.echo("✗ Could not install Warp", err=True)
101
+
102
+ # Cursor (if enabled in config, default true)
103
+ if not ide or ide.cursor:
104
+ if shutil.which("cursor"):
105
+ typer.echo("✓ Cursor")
106
+ else:
107
+ typer.echo("Installing Cursor...")
108
+ if _install_cask("cursor"):
109
+ typer.echo("✓ Cursor installed")
110
+ else:
111
+ typer.echo("✗ Could not install Cursor", err=True)
112
+
113
+
114
+ @app.command()
115
+ def doctor():
116
+ """Check loopflow dependencies based on config."""
117
+ all_ok = True
118
+
119
+ # Load config to check what's needed
120
+ repo_root = find_worktree_root()
121
+ config = load_config(repo_root) if repo_root else None
122
+ ide = config.ide if config else None
123
+
124
+ # Required
125
+ if shutil.which("npm"):
126
+ typer.echo("✓ npm")
127
+ else:
128
+ typer.echo("✗ npm - Install Node.js: https://nodejs.org")
129
+ all_ok = False
130
+
131
+ if check_claude_available():
132
+ typer.echo("✓ claude")
133
+ else:
134
+ typer.echo("✗ claude - Run: lf meta install")
135
+ all_ok = False
136
+
137
+ # IDE tools (based on config)
138
+ if not ide or ide.warp:
139
+ if shutil.which("warp"):
140
+ typer.echo("✓ warp")
141
+ else:
142
+ typer.echo("✗ warp - Run: lf meta install")
143
+ all_ok = False
144
+
145
+ if not ide or ide.cursor:
146
+ if shutil.which("cursor"):
147
+ typer.echo("✓ cursor")
148
+ else:
149
+ typer.echo("✗ cursor - Run: lf meta install")
150
+ all_ok = False
151
+
152
+ # Optional: gh for PR creation
153
+ if shutil.which("gh"):
154
+ typer.echo("✓ gh (optional)")
155
+ else:
156
+ typer.echo("- gh (optional): brew install gh")
157
+
158
+ raise typer.Exit(0 if all_ok else 1)