loopflow 0.2.2__tar.gz → 0.3.0__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.
Files changed (38) hide show
  1. {loopflow-0.2.2 → loopflow-0.3.0}/.gitignore +2 -2
  2. {loopflow-0.2.2 → loopflow-0.3.0}/PKG-INFO +29 -4
  3. {loopflow-0.2.2 → loopflow-0.3.0}/README.md +28 -3
  4. {loopflow-0.2.2 → loopflow-0.3.0}/pyproject.toml +11 -1
  5. loopflow-0.3.0/src/loopflow/LOOPFLOW_STYLE.md +130 -0
  6. loopflow-0.3.0/src/loopflow/__init__.py +1 -0
  7. {loopflow-0.2.2 → loopflow-0.3.0}/src/loopflow/cli/__init__.py +8 -3
  8. loopflow-0.3.0/src/loopflow/cli/maestro.py +81 -0
  9. {loopflow-0.2.2 → loopflow-0.3.0}/src/loopflow/cli/meta.py +68 -0
  10. {loopflow-0.2.2 → loopflow-0.3.0}/src/loopflow/cli/pr.py +92 -19
  11. {loopflow-0.2.2 → loopflow-0.3.0}/src/loopflow/cli/run.py +163 -43
  12. loopflow-0.3.0/src/loopflow/cli/status.py +55 -0
  13. loopflow-0.3.0/src/loopflow/cli/wt.py +457 -0
  14. {loopflow-0.2.2 → loopflow-0.3.0}/src/loopflow/config.py +2 -1
  15. loopflow-0.3.0/src/loopflow/config_template.yaml +36 -0
  16. {loopflow-0.2.2 → loopflow-0.3.0}/src/loopflow/context.py +36 -3
  17. {loopflow-0.2.2 → loopflow-0.3.0}/src/loopflow/files.py +37 -1
  18. loopflow-0.3.0/src/loopflow/git.py +594 -0
  19. {loopflow-0.2.2 → loopflow-0.3.0}/src/loopflow/launcher.py +99 -0
  20. loopflow-0.3.0/src/loopflow/maestro/__init__.py +16 -0
  21. loopflow-0.3.0/src/loopflow/maestro/adapters.py +38 -0
  22. loopflow-0.3.0/src/loopflow/maestro/client.py +75 -0
  23. loopflow-0.3.0/src/loopflow/maestro/daemon.py +19 -0
  24. loopflow-0.3.0/src/loopflow/maestro/notification.py +39 -0
  25. loopflow-0.3.0/src/loopflow/maestro/service.py +157 -0
  26. loopflow-0.3.0/src/loopflow/maestro/session.py +44 -0
  27. {loopflow-0.2.2 → loopflow-0.3.0}/src/loopflow/pipeline.py +8 -5
  28. loopflow-0.3.0/src/loopflow/prompts/design.md +22 -0
  29. loopflow-0.3.0/src/loopflow/prompts/implement.md +21 -0
  30. loopflow-0.3.0/src/loopflow/prompts/review.md +47 -0
  31. loopflow-0.2.2/src/loopflow/__init__.py +0 -1
  32. loopflow-0.2.2/src/loopflow/cli/wt.py +0 -143
  33. loopflow-0.2.2/src/loopflow/git.py +0 -334
  34. {loopflow-0.2.2 → loopflow-0.3.0}/src/loopflow/builtins/__init__.py +0 -0
  35. {loopflow-0.2.2 → loopflow-0.3.0}/src/loopflow/builtins/commit_message.txt +0 -0
  36. {loopflow-0.2.2 → loopflow-0.3.0}/src/loopflow/builtins/pr_message.txt +0 -0
  37. {loopflow-0.2.2 → loopflow-0.3.0}/src/loopflow/llm_http.py +0 -0
  38. {loopflow-0.2.2 → loopflow-0.3.0}/src/loopflow/tokens.py +0 -0
@@ -4,5 +4,5 @@ __pycache__/
4
4
  # Claude Code (personal state)
5
5
  .claude/settings.local.json
6
6
 
7
- # Worktrees (created by lf start)
8
- .lf/worktrees/
7
+ # Research/reference files
8
+ files/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: loopflow
3
- Version: 0.2.2
3
+ Version: 0.3.0
4
4
  Summary: Arrange LLMs to code in harmony
5
5
  Author: Jack
6
6
  License-Expression: MIT
@@ -29,7 +29,7 @@ Description-Content-Type: text/markdown
29
29
 
30
30
  Run LLM coding tasks from reusable prompt files.
31
31
 
32
- macOS only. Currently uses Claude Code; other backends planned.
32
+ macOS only. Supports Claude Code and OpenAI Codex via configuration.
33
33
 
34
34
  ## Install
35
35
 
@@ -48,7 +48,7 @@ The workflow: create a worktree, run tasks there, merge when ready. You can have
48
48
 
49
49
  ```bash
50
50
  lf wt create my-feature # branch + worktree, opens IDEs
51
- cd .lf/worktrees/my-feature
51
+ cd ../loopflow.my-feature # worktrees are sibling directories
52
52
 
53
53
  lf design # interactive: figure out what to build
54
54
  lf ship # batch: implement, review, test, commit, open PR
@@ -103,15 +103,33 @@ lf ship # runs each task, auto-commits between steps
103
103
  ## Worktrees
104
104
 
105
105
  ```bash
106
- lf wt create auth # .lf/worktrees/auth/, opens Warp + Cursor
106
+ lf wt create auth # ../loopflow.auth/, opens Warp + Cursor
107
107
  lf wt list # show all worktrees
108
108
  lf wt clean # remove merged branches
109
109
  ```
110
110
 
111
+ ## Session Tracking
112
+
113
+ Track running tasks across multiple terminals with the maestro daemon:
114
+
115
+ ```bash
116
+ lf maestro start # start tracking daemon
117
+ lf status # show running sessions
118
+
119
+ # In another terminal
120
+ lf implement -p # batch task registers automatically
121
+
122
+ # Check from anywhere
123
+ lf status # see all running sessions
124
+ ```
125
+
126
+ When maestro is running, tasks automatically register, and you'll get macOS notifications when batch tasks complete.
127
+
111
128
  ## Configuration
112
129
 
113
130
  ```yaml
114
131
  # .lf/config.yaml
132
+ model: claude # Model to use (claude or codex)
115
133
  push: true # auto-push after commits
116
134
  pr: false # open PR after pipelines
117
135
 
@@ -128,6 +146,8 @@ ide:
128
146
  | `-x, --context` | Add context files |
129
147
  | `-w, --worktree` | Create worktree and run task there |
130
148
  | `-c, --copy` | Copy prompt to clipboard, show token breakdown |
149
+ | `-m, --model` | Choose model (claude, codex) |
150
+ | `--parallel` | Run with multiple models in parallel |
131
151
 
132
152
  ## Commands
133
153
 
@@ -137,7 +157,12 @@ ide:
137
157
  | `lf <pipeline>` | Run a pipeline |
138
158
  | `lf : "prompt"` | Inline prompt |
139
159
  | `lf wt create/list/clean` | Worktree management |
160
+ | `lf wt compare <a> <b>` | Compare two worktree implementations |
140
161
  | `lf pr create` | Open GitHub PR |
141
162
  | `lf pr land [-a]` | Squash-merge to main |
163
+ | `lf meta init` | Initialize repo with prompts and config |
142
164
  | `lf meta install` | Install Claude Code |
143
165
  | `lf meta doctor` | Check dependencies |
166
+ | `lf maestro start` | Start session tracking daemon |
167
+ | `lf maestro stop` | Stop session tracking daemon |
168
+ | `lf status` | Show running sessions |
@@ -2,7 +2,7 @@
2
2
 
3
3
  Run LLM coding tasks from reusable prompt files.
4
4
 
5
- macOS only. Currently uses Claude Code; other backends planned.
5
+ macOS only. Supports Claude Code and OpenAI Codex via configuration.
6
6
 
7
7
  ## Install
8
8
 
@@ -21,7 +21,7 @@ The workflow: create a worktree, run tasks there, merge when ready. You can have
21
21
 
22
22
  ```bash
23
23
  lf wt create my-feature # branch + worktree, opens IDEs
24
- cd .lf/worktrees/my-feature
24
+ cd ../loopflow.my-feature # worktrees are sibling directories
25
25
 
26
26
  lf design # interactive: figure out what to build
27
27
  lf ship # batch: implement, review, test, commit, open PR
@@ -76,15 +76,33 @@ lf ship # runs each task, auto-commits between steps
76
76
  ## Worktrees
77
77
 
78
78
  ```bash
79
- lf wt create auth # .lf/worktrees/auth/, opens Warp + Cursor
79
+ lf wt create auth # ../loopflow.auth/, opens Warp + Cursor
80
80
  lf wt list # show all worktrees
81
81
  lf wt clean # remove merged branches
82
82
  ```
83
83
 
84
+ ## Session Tracking
85
+
86
+ Track running tasks across multiple terminals with the maestro daemon:
87
+
88
+ ```bash
89
+ lf maestro start # start tracking daemon
90
+ lf status # show running sessions
91
+
92
+ # In another terminal
93
+ lf implement -p # batch task registers automatically
94
+
95
+ # Check from anywhere
96
+ lf status # see all running sessions
97
+ ```
98
+
99
+ When maestro is running, tasks automatically register, and you'll get macOS notifications when batch tasks complete.
100
+
84
101
  ## Configuration
85
102
 
86
103
  ```yaml
87
104
  # .lf/config.yaml
105
+ model: claude # Model to use (claude or codex)
88
106
  push: true # auto-push after commits
89
107
  pr: false # open PR after pipelines
90
108
 
@@ -101,6 +119,8 @@ ide:
101
119
  | `-x, --context` | Add context files |
102
120
  | `-w, --worktree` | Create worktree and run task there |
103
121
  | `-c, --copy` | Copy prompt to clipboard, show token breakdown |
122
+ | `-m, --model` | Choose model (claude, codex) |
123
+ | `--parallel` | Run with multiple models in parallel |
104
124
 
105
125
  ## Commands
106
126
 
@@ -110,7 +130,12 @@ ide:
110
130
  | `lf <pipeline>` | Run a pipeline |
111
131
  | `lf : "prompt"` | Inline prompt |
112
132
  | `lf wt create/list/clean` | Worktree management |
133
+ | `lf wt compare <a> <b>` | Compare two worktree implementations |
113
134
  | `lf pr create` | Open GitHub PR |
114
135
  | `lf pr land [-a]` | Squash-merge to main |
136
+ | `lf meta init` | Initialize repo with prompts and config |
115
137
  | `lf meta install` | Install Claude Code |
116
138
  | `lf meta doctor` | Check dependencies |
139
+ | `lf maestro start` | Start session tracking daemon |
140
+ | `lf maestro stop` | Stop session tracking daemon |
141
+ | `lf status` | Show running sessions |
@@ -41,9 +41,19 @@ build-backend = "hatchling.build"
41
41
 
42
42
  [tool.hatch.build.targets.wheel]
43
43
  packages = ["src/loopflow"]
44
+ include = [
45
+ "src/loopflow/prompts/*.md",
46
+ "src/loopflow/LOOPFLOW_STYLE.md",
47
+ "src/loopflow/config_template.yaml",
48
+ ]
44
49
 
45
50
  [tool.hatch.build.targets.sdist]
46
- include = ["src/loopflow"]
51
+ include = [
52
+ "src/loopflow",
53
+ "src/loopflow/prompts/*.md",
54
+ "src/loopflow/LOOPFLOW_STYLE.md",
55
+ "src/loopflow/config_template.yaml",
56
+ ]
47
57
 
48
58
  [tool.hatch.version]
49
59
  path = "src/loopflow/__init__.py"
@@ -0,0 +1,130 @@
1
+ # Style Guide
2
+
3
+ This is the governing document for this codebase. Humans and LLMs alike are expected to follow it.
4
+
5
+ ## Quick Reference
6
+
7
+ - Prefix private functions with `_`
8
+ - Return `None` for "not found"; raise exceptions for "shouldn't happen"
9
+ - No `Args:`/`Returns:` docstrings—if types are clear, skip the docstring
10
+ - Mock side effects, but don't test mock wiring
11
+
12
+ # Goals
13
+
14
+ ## Clarity
15
+
16
+ Design around data structures and public APIs. Aim for a 1:1 mapping between real-world concepts and their representation in code.
17
+
18
+ Write code that demonstrates its own correctness. If a feature exists, write a test that proves it works. Assume you won't finish everything you start—make it easy to see what's done and what's broken.
19
+
20
+ ## Simplicity
21
+
22
+ Every line of code must earn its place. Readable code is not terse code; don't sacrifice clarity for brevity. But recognize that lines can be net-negative:
23
+
24
+ * Unused code
25
+ * Comments that restate the obvious
26
+ * Checks for impossible conditions
27
+
28
+ Start with minimal data structures and APIs. If the core is right, trimming excess at the edges is straightforward.
29
+
30
+ # Code Organization
31
+
32
+ Consistency with existing code matters more than any specific rule.
33
+
34
+ Keep information in one place. Version numbers, configuration, documentation—each piece of information should have a single source of truth. Don't duplicate versions in multiple files. If something needs to appear in multiple places, generate it or reference the source.
35
+
36
+ Put imports at the top of the file, not inline.
37
+
38
+ Keep one implementation. Avoid `v2_`, `_old`, `_new`, `_backup` prefixes and suffixes—look up old versions in git. If you're tempted to keep both old and new code around, delete the old version and commit. You can always get it back from git if needed.
39
+
40
+ Don't maintain backwards compatibility unless explicitly required. If a config format or API changes, migrate everything to the new format—don't write code that handles both old and new. Backwards compatibility is for production databases and published APIs with external users, not internal config files.
41
+
42
+ ## Naming
43
+
44
+ Use verb-first names for action functions: `find_user()`, `load_config()`, `create_session()`.
45
+
46
+ Prefix private functions with underscore: `_validate()`, `_parse_line()`.
47
+
48
+ Name things after what they are, not what they're for: `Document`, `Session`, `Target`—not `DocumentHelper`, `SessionManager`, `OutputHandler`.
49
+
50
+ ## Error Handling
51
+
52
+ Errors are for users; exceptions are for programmers.
53
+
54
+ Return errors when the caller should handle them—invalid input, missing files, failed requests. Raise exceptions for bugs: violated invariants, impossible states, programming mistakes.
55
+
56
+ ```python
57
+ # Error: caller decides what to do
58
+ def find_config(path: Path) -> Optional[Config]:
59
+ if not path.exists():
60
+ return None
61
+ return load(path)
62
+
63
+ # Exception: this shouldn't happen
64
+ def get_target(name: str) -> Target:
65
+ if name not in TARGETS:
66
+ raise ValueError(f"Unknown target: {name}")
67
+ return TARGETS[name]
68
+ ```
69
+
70
+ When in doubt: if you'd write an `assert`, raise an exception instead—it's easier for callers to catch.
71
+
72
+ # Documentation
73
+
74
+ The best documentation is simple code. Descriptive names, type hints, and clear APIs often suffice.
75
+
76
+ The worst documentation is wrong documentation. If it can drift from the code, it will. Update docs when you change code—or delete them.
77
+
78
+ Put documentation next to code. A few paragraphs at the top of a key file beats a separate doc that nobody maintains.
79
+
80
+ Skip obvious docstrings. If the function name and types tell the whole story, don't repeat it in prose.
81
+
82
+ # Testing
83
+
84
+ Test user behavior, not implementation details. A good test proves that something users care about actually works. Most tests don't meet that bar. Delete them.
85
+
86
+ Aim for a mix:
87
+ - **Smoke tests**: Does the system run without crashing?
88
+ - **Edge case tests**: What happens at boundaries?
89
+ - **Value tests**: Does this feature do what users expect?
90
+
91
+ ## When to Mock
92
+
93
+ Mock to isolate your code from things that shouldn't be part of unit tests:
94
+ - **External systems**: Network calls, databases, file systems (when testing logic, not I/O)
95
+ - **Side effects**: Sending emails, writing logs, spawning processes
96
+ - **Slow operations**: Anything that would make tests take seconds instead of milliseconds
97
+
98
+ Don't mock to verify internal wiring. If a test's assertions are just "did we call the mock with the right args?"—that's testing implementation, not behavior. The test will break when you refactor, even if the feature still works.
99
+
100
+ ```python
101
+ # Bad: testing that we called the mock correctly
102
+ def test_send_notification():
103
+ with patch("app.email.send") as mock_send:
104
+ notify_user(user)
105
+ mock_send.assert_called_once_with(user.email, ANY)
106
+
107
+ # Good: mock the side effect, test the behavior
108
+ def test_notify_user_returns_success():
109
+ with patch("app.email.send"): # prevent actual email
110
+ result = notify_user(user)
111
+ assert result.success
112
+
113
+ # Better: if possible, test without mocking
114
+ def test_notification_message_format():
115
+ msg = build_notification(user)
116
+ assert user.name in msg.body
117
+ ```
118
+
119
+ If a test requires elaborate mock setup, it's usually a sign that either:
120
+ 1. The code under test does too much (refactor it)
121
+ 2. You're testing implementation rather than behavior (test something else)
122
+ 3. This should be an integration test, not a unit test (move it)
123
+
124
+ # Git
125
+
126
+ Commit messages are documentation. Explain what changed and why, not line-by-line what you did.
127
+
128
+ Keep messages short—one sentence to one paragraph.
129
+
130
+ Do not add AI attribution footers like "Generated with Claude Code" or "Co-Authored-By: Claude" to commits. The git history should read the same whether written by a human or AI.
@@ -0,0 +1 @@
1
+ __version__ = "0.3.0"
@@ -15,21 +15,23 @@ app = typer.Typer(
15
15
 
16
16
  # Import and register subcommands
17
17
  from loopflow.cli import run as run_module
18
- from loopflow.cli import wt, pr, meta
18
+ from loopflow.cli import wt, pr, meta, maestro, status
19
19
 
20
20
  app.add_typer(wt.app, name="wt")
21
21
  app.add_typer(pr.app, name="pr")
22
22
  app.add_typer(meta.app, name="meta")
23
+ app.add_typer(maestro.app, name="maestro")
23
24
 
24
25
  # Register top-level commands
25
- app.command()(run_module.run)
26
+ app.command(context_settings={"allow_extra_args": True, "allow_interspersed_args": True})(run_module.run)
26
27
  app.command()(run_module.inline)
27
28
  app.command(name="pipeline")(run_module.pipeline)
29
+ app.command()(status.status)
28
30
 
29
31
 
30
32
  def main():
31
33
  """Entry point that supports 'lf <task>' and 'lf <pipeline>' shorthand."""
32
- known_commands = {"run", "pipeline", "inline", "wt", "pr", "meta", "--help", "-h"}
34
+ known_commands = {"run", "pipeline", "inline", "wt", "pr", "meta", "maestro", "status", "--help", "-h"}
33
35
 
34
36
  try:
35
37
  if len(sys.argv) > 1:
@@ -40,6 +42,9 @@ def main():
40
42
  sys.argv.pop(1)
41
43
  sys.argv.insert(1, "inline")
42
44
  elif first_arg not in known_commands:
45
+ # Handle colon suffix: "lf implement: add logout" -> "lf implement add logout"
46
+ if first_arg.endswith(":"):
47
+ sys.argv[1] = first_arg[:-1]
43
48
  name = sys.argv[1]
44
49
  repo_root = find_worktree_root()
45
50
  config = load_config(repo_root) if repo_root else None
@@ -0,0 +1,81 @@
1
+ """Maestro daemon management commands."""
2
+
3
+ import os
4
+ import signal
5
+ import subprocess
6
+ import sys
7
+ from pathlib import Path
8
+
9
+ import typer
10
+
11
+ app = typer.Typer(help="Maestro daemon management.")
12
+
13
+
14
+ def _get_paths() -> tuple[Path, Path, Path]:
15
+ """Get maestro file paths."""
16
+ lf_dir = Path.home() / ".lf"
17
+ return (
18
+ lf_dir / "maestro.sock",
19
+ lf_dir / "maestro.json",
20
+ lf_dir / "maestro.pid",
21
+ )
22
+
23
+
24
+ def _is_running(pid_path: Path) -> bool:
25
+ """Check if maestro is running."""
26
+ if not pid_path.exists():
27
+ return False
28
+
29
+ try:
30
+ pid = int(pid_path.read_text().strip())
31
+ os.kill(pid, 0) # Signal 0 checks if process exists
32
+ return True
33
+ except (OSError, ValueError):
34
+ # Process doesn't exist or invalid PID
35
+ return False
36
+
37
+
38
+ @app.command()
39
+ def start():
40
+ """Start the maestro daemon."""
41
+ socket_path, state_path, pid_path = _get_paths()
42
+
43
+ if _is_running(pid_path):
44
+ typer.echo("Maestro already running")
45
+ raise typer.Exit(0)
46
+
47
+ # Start daemon as background process
48
+ process = subprocess.Popen(
49
+ [sys.executable, "-m", "loopflow.maestro.daemon"],
50
+ stdout=subprocess.DEVNULL,
51
+ stderr=subprocess.DEVNULL,
52
+ start_new_session=True,
53
+ )
54
+
55
+ # Save PID
56
+ pid_path.parent.mkdir(parents=True, exist_ok=True)
57
+ pid_path.write_text(str(process.pid))
58
+
59
+ typer.echo(f"Maestro listening on {socket_path}")
60
+
61
+
62
+ @app.command()
63
+ def stop():
64
+ """Stop the maestro daemon."""
65
+ socket_path, state_path, pid_path = _get_paths()
66
+
67
+ if not _is_running(pid_path):
68
+ typer.echo("Maestro not running")
69
+ raise typer.Exit(0)
70
+
71
+ pid = int(pid_path.read_text().strip())
72
+
73
+ try:
74
+ os.kill(pid, signal.SIGTERM)
75
+ pid_path.unlink()
76
+ if socket_path.exists():
77
+ socket_path.unlink()
78
+ typer.echo("Maestro stopped")
79
+ except OSError:
80
+ typer.echo("Failed to stop maestro", err=True)
81
+ raise typer.Exit(1)
@@ -3,6 +3,7 @@
3
3
  import platform
4
4
  import shutil
5
5
  import subprocess
6
+ from pathlib import Path
6
7
 
7
8
  import typer
8
9
 
@@ -36,6 +37,73 @@ def _install_cask(name: str) -> bool:
36
37
  return result.returncode == 0
37
38
 
38
39
 
40
+ @app.command()
41
+ def init(
42
+ prompts_only: bool = typer.Option(False, "--prompts", help="Only install prompts"),
43
+ style_only: bool = typer.Option(False, "--style", help="Only install style guide"),
44
+ ):
45
+ """Initialize a repository with loopflow prompts, style guide, and config."""
46
+ repo_root = find_worktree_root()
47
+ if not repo_root:
48
+ typer.echo("Error: Not in a git repository", err=True)
49
+ raise typer.Exit(1)
50
+
51
+ # Determine what to install
52
+ install_prompts = prompts_only or not style_only
53
+ install_style = style_only or not prompts_only
54
+ install_config = not prompts_only and not style_only
55
+
56
+ # Get bundled assets path
57
+ bundled_dir = Path(__file__).parent.parent
58
+ prompts_dir = bundled_dir / "prompts"
59
+ style_template = bundled_dir / "LOOPFLOW_STYLE.md"
60
+ config_template = bundled_dir / "config_template.yaml"
61
+
62
+ # Install prompts to .claude/commands/ (accessible via raw claude too)
63
+ if install_prompts:
64
+ commands_dir = repo_root / ".claude" / "commands"
65
+ commands_dir.mkdir(parents=True, exist_ok=True)
66
+
67
+ for prompt_name in ["review.md", "implement.md", "design.md"]:
68
+ src = prompts_dir / prompt_name
69
+ dst = commands_dir / prompt_name
70
+
71
+ if dst.exists():
72
+ typer.echo(f"- .claude/commands/{prompt_name} (already exists)")
73
+ else:
74
+ shutil.copy(src, dst)
75
+ typer.echo(f"✓ Created .claude/commands/{prompt_name}")
76
+
77
+ # Install style guide to .lf/ (only included in lf sessions, not raw claude/codex)
78
+ if install_style:
79
+ lf_dir = repo_root / ".lf"
80
+ lf_dir.mkdir(exist_ok=True)
81
+ style_dst = lf_dir / "LOOPFLOW_STYLE.md"
82
+ if style_dst.exists():
83
+ typer.echo("- .lf/LOOPFLOW_STYLE.md (already exists)")
84
+ else:
85
+ shutil.copy(style_template, style_dst)
86
+ typer.echo("✓ Created .lf/LOOPFLOW_STYLE.md")
87
+
88
+ # Install config
89
+ if install_config:
90
+ config_dir = repo_root / ".lf"
91
+ config_dir.mkdir(exist_ok=True)
92
+ config_dst = config_dir / "config.yaml"
93
+
94
+ if config_dst.exists():
95
+ typer.echo("- .lf/config.yaml (already exists)")
96
+ else:
97
+ shutil.copy(config_template, config_dst)
98
+ typer.echo("✓ Created .lf/config.yaml")
99
+
100
+ if install_prompts and not prompts_only:
101
+ typer.echo("\nYou can now use:")
102
+ typer.echo(" lf review # or: claude /review")
103
+ typer.echo(" lf implement")
104
+ typer.echo(" lf design")
105
+
106
+
39
107
  @app.command()
40
108
  def version():
41
109
  """Show loopflow version."""