loopflow 0.6.2__tar.gz → 0.6.4__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 (73) hide show
  1. {loopflow-0.6.2 → loopflow-0.6.4}/.gitignore +4 -0
  2. {loopflow-0.6.2 → loopflow-0.6.4}/PKG-INFO +8 -7
  3. {loopflow-0.6.2 → loopflow-0.6.4}/README.md +6 -6
  4. {loopflow-0.6.2 → loopflow-0.6.4}/pyproject.toml +3 -0
  5. {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/LOOPFLOW.md +4 -2
  6. loopflow-0.6.4/src/loopflow/__init__.py +1 -0
  7. loopflow-0.6.4/src/loopflow/builtins/summarize.txt +14 -0
  8. loopflow-0.6.4/src/loopflow/cli/__init__.py +222 -0
  9. {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/cli/run.py +114 -40
  10. {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/config.py +21 -0
  11. {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/context.py +233 -30
  12. {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/files.py +54 -19
  13. {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/launcher.py +17 -0
  14. {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/lfd/__init__.py +7 -5
  15. {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/lfd/agents.py +127 -7
  16. {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/lfd/collector.py +13 -8
  17. {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/lfd/models.py +17 -6
  18. loopflow-0.6.4/src/loopflow/lfd/naming.py +134 -0
  19. {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/lfd/pipelines.py +78 -82
  20. {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/lfd/runner.py +224 -19
  21. {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/lfd/server.py +5 -1
  22. {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/lfops.py +522 -104
  23. loopflow-0.6.4/src/loopflow/lfwork.py +258 -0
  24. loopflow-0.6.4/src/loopflow/pipeline.py +630 -0
  25. {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/publish.py +92 -0
  26. loopflow-0.6.4/src/loopflow/summarize.py +399 -0
  27. loopflow-0.6.4/src/loopflow/templates/commands/expand.md +27 -0
  28. loopflow-0.6.4/src/loopflow/templates/commands/explore.md +27 -0
  29. loopflow-0.6.4/src/loopflow/templates/commands/rebase.md +61 -0
  30. loopflow-0.6.4/src/loopflow/templates/commands/reduce.md +27 -0
  31. loopflow-0.6.4/src/loopflow/templates/config.yaml +32 -0
  32. loopflow-0.6.4/src/loopflow/work/__init__.py +0 -0
  33. loopflow-0.6.4/src/loopflow/work/asana_backend.py +205 -0
  34. loopflow-0.6.4/src/loopflow/work/backend.py +29 -0
  35. loopflow-0.6.4/src/loopflow/work/file_backend.py +177 -0
  36. loopflow-0.6.4/src/loopflow/work/models.py +37 -0
  37. loopflow-0.6.2/src/loopflow/__init__.py +0 -1
  38. loopflow-0.6.2/src/loopflow/cli/__init__.py +0 -90
  39. loopflow-0.6.2/src/loopflow/lfd/naming.py +0 -59
  40. loopflow-0.6.2/src/loopflow/pipeline.py +0 -143
  41. loopflow-0.6.2/src/loopflow/templates/config.yaml +0 -49
  42. {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/builtins/__init__.py +0 -0
  43. {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/builtins/commit_message.txt +0 -0
  44. {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/builtins/pr_message.txt +0 -0
  45. {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/builtins/release_notes.txt +0 -0
  46. {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/design.py +0 -0
  47. {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/frontmatter.py +0 -0
  48. {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/git.py +0 -0
  49. {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/init_check.py +0 -0
  50. {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/lfd/README.md +0 -0
  51. {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/lfd/__main__.py +0 -0
  52. {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/lfd/agent_runner.py +0 -0
  53. {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/lfd/client.py +0 -0
  54. {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/lfd/cron.py +0 -0
  55. {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/lfd/db.py +0 -0
  56. {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/lfd/launchd.py +0 -0
  57. {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/lfd/process.py +0 -0
  58. {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/lfd/protocol.py +0 -0
  59. {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/lfd/triggers.py +0 -0
  60. {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/lfwt.py +0 -0
  61. {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/llm_http.py +0 -0
  62. {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/logging.py +0 -0
  63. {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/prompts/CHECKPOINT_MESSAGE.md +0 -0
  64. {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/prompts/COMMIT_MESSAGE.md +0 -0
  65. {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/templates/commands/debug.md +0 -0
  66. {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/templates/commands/design.md +0 -0
  67. {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/templates/commands/implement.md +0 -0
  68. {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/templates/commands/iterate.md +0 -0
  69. {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/templates/commands/polish.md +0 -0
  70. {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/templates/commands/review.md +0 -0
  71. {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/tokens.py +0 -0
  72. {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/voices.py +0 -0
  73. {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/worktrees.py +0 -0
@@ -7,6 +7,10 @@ __pycache__/
7
7
  xcuserdata/
8
8
  .swiftpm/xcode/
9
9
  Maestro/Maestro.xcodeproj/
10
+ Maestro/dist/
10
11
 
11
12
  # Claude Code (personal state)
12
13
  .claude/settings.local.json
14
+
15
+ # Loopflow (generated cache)
16
+ .lf/summaries/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: loopflow
3
- Version: 0.6.2
3
+ Version: 0.6.4
4
4
  Summary: Run LLM coding agents from reusable prompt files
5
5
  Project-URL: Homepage, https://loopflowstudio.github.io/loopflow/
6
6
  Project-URL: Repository, https://github.com/loopflowstudio/loopflow
@@ -22,6 +22,7 @@ Classifier: Programming Language :: Python :: 3.12
22
22
  Classifier: Topic :: Software Development
23
23
  Classifier: Typing :: Typed
24
24
  Requires-Python: >=3.10
25
+ Requires-Dist: asana>=5.0.0
25
26
  Requires-Dist: pathspec>=0.11.0
26
27
  Requires-Dist: pydantic-ai-slim[anthropic]>=1.0.0
27
28
  Requires-Dist: pydantic>=2.12.5
@@ -37,7 +38,7 @@ Description-Content-Type: text/markdown
37
38
  Run LLM coding agents from reusable prompt files.
38
39
 
39
40
  ```bash
40
- lf review # run .lf/review.lf
41
+ lf review # run .claude/commands/review.md
41
42
  lf ship # pipeline: implement → review → test → commit → PR
42
43
  ```
43
44
 
@@ -68,14 +69,14 @@ lf design # interactive: figure out what to build
68
69
  lf ship # batch: implement, review, test, commit, open PR
69
70
  ```
70
71
 
71
- `lf design` runs `.lf/design.lf`. `lf ship` runs the `ship` pipeline from `.lf/config.yaml`.
72
+ `lf design` runs `.claude/commands/design.md`. `lf ship` runs the `ship` pipeline from `.lf/config.yaml`.
72
73
 
73
74
  ## Tasks
74
75
 
75
- Tasks are prompt files in `.lf/`. Here's an example:
76
+ Tasks are markdown files in `.claude/commands/` or `.lf/`. Here's an example:
76
77
 
77
78
  ```markdown
78
- # .lf/review.lf
79
+ # .claude/commands/review.md
79
80
 
80
81
  Review the diff on the current branch against `main` and fix any issues found.
81
82
 
@@ -92,7 +93,7 @@ The deliverable is the fixes themselves, not a written review.
92
93
  Run tasks by name:
93
94
 
94
95
  ```bash
95
- lf review # run .lf/review.lf
96
+ lf review # run .claude/commands/review.md
96
97
  lf review -x src/utils.py # add context files
97
98
  lf : "fix the typo" # inline prompt, no task file
98
99
  ```
@@ -234,7 +235,7 @@ Priority: CLI > frontmatter > config > none.
234
235
 
235
236
  | Command | Description |
236
237
  |---------|-------------|
237
- | `lf <task>` | Run a task from `.lf/` |
238
+ | `lf <task>` | Run a task from `.claude/commands/` or `.lf/` |
238
239
  | `lf <pipeline>` | Run a pipeline |
239
240
  | `lf : "prompt"` | Inline prompt |
240
241
  | `wt <subcommand>` | Worktree management (worktrunk) |
@@ -3,7 +3,7 @@
3
3
  Run LLM coding agents from reusable prompt files.
4
4
 
5
5
  ```bash
6
- lf review # run .lf/review.lf
6
+ lf review # run .claude/commands/review.md
7
7
  lf ship # pipeline: implement → review → test → commit → PR
8
8
  ```
9
9
 
@@ -34,14 +34,14 @@ lf design # interactive: figure out what to build
34
34
  lf ship # batch: implement, review, test, commit, open PR
35
35
  ```
36
36
 
37
- `lf design` runs `.lf/design.lf`. `lf ship` runs the `ship` pipeline from `.lf/config.yaml`.
37
+ `lf design` runs `.claude/commands/design.md`. `lf ship` runs the `ship` pipeline from `.lf/config.yaml`.
38
38
 
39
39
  ## Tasks
40
40
 
41
- Tasks are prompt files in `.lf/`. Here's an example:
41
+ Tasks are markdown files in `.claude/commands/` or `.lf/`. Here's an example:
42
42
 
43
43
  ```markdown
44
- # .lf/review.lf
44
+ # .claude/commands/review.md
45
45
 
46
46
  Review the diff on the current branch against `main` and fix any issues found.
47
47
 
@@ -58,7 +58,7 @@ The deliverable is the fixes themselves, not a written review.
58
58
  Run tasks by name:
59
59
 
60
60
  ```bash
61
- lf review # run .lf/review.lf
61
+ lf review # run .claude/commands/review.md
62
62
  lf review -x src/utils.py # add context files
63
63
  lf : "fix the typo" # inline prompt, no task file
64
64
  ```
@@ -200,7 +200,7 @@ Priority: CLI > frontmatter > config > none.
200
200
 
201
201
  | Command | Description |
202
202
  |---------|-------------|
203
- | `lf <task>` | Run a task from `.lf/` |
203
+ | `lf <task>` | Run a task from `.claude/commands/` or `.lf/` |
204
204
  | `lf <pipeline>` | Run a pipeline |
205
205
  | `lf : "prompt"` | Inline prompt |
206
206
  | `wt <subcommand>` | Worktree management (worktrunk) |
@@ -31,6 +31,7 @@ dependencies = [
31
31
  "pydantic>=2.12.5",
32
32
  "pydantic-ai-slim[anthropic]>=1.0.0",
33
33
  "tiktoken>=0.7.0",
34
+ "asana>=5.0.0",
34
35
  ]
35
36
 
36
37
  [project.urls]
@@ -50,6 +51,7 @@ lf = "loopflow.cli:main"
50
51
  lfd = "loopflow.lfd:main"
51
52
  lfwt = "loopflow.lfwt:main"
52
53
  lfops = "loopflow.lfops:main"
54
+ lfwork = "loopflow.lfwork:main"
53
55
 
54
56
  [build-system]
55
57
  requires = ["hatchling"]
@@ -79,4 +81,5 @@ path = "src/loopflow/__init__.py"
79
81
  [dependency-groups]
80
82
  dev = [
81
83
  "pytest>=9.0.2",
84
+ "boto3>=1.35.0",
82
85
  ]
@@ -20,7 +20,7 @@ Prompts receive context assembled by loopflow. What's included is configurable v
20
20
 
21
21
  - Root-level `.md` files (README, STYLE, etc.)
22
22
  - Current diff against main
23
- - The task prompt from `.lf/<task>.lf` or `.claude/commands/<task>.md`
23
+ - The task prompt from `.claude/commands/<task>.md` or `.lf/<task>.md`
24
24
  - Additional files via `-x` flag or `context:` in config
25
25
 
26
26
  ---
@@ -36,9 +36,11 @@ In interactive mode, commit at natural breakpoints. Don't leave the branch in a
36
36
  ## File Structure
37
37
 
38
38
  ```
39
+ .claude/commands/
40
+ <task>.md # prompt files (Claude Code compatible)
41
+
39
42
  .lf/
40
43
  config.yaml # repo configuration
41
- <task>.lf # prompt files
42
44
 
43
45
  .design/
44
46
  <branch>.md # design doc for current branch
@@ -0,0 +1 @@
1
+ __version__ = "0.6.4"
@@ -0,0 +1,14 @@
1
+ Summarize this codebase for LLM context. Target: {token_budget} tokens.
2
+
3
+ Prioritize:
4
+ 1. **Data structures** - Core types with field annotations
5
+ 2. **Public APIs** - Function signatures with one-line descriptions
6
+ 3. **Key patterns** - How the codebase is organized
7
+ 4. **Direct quotes** - Preserve exact names, paths, commands
8
+
9
+ Format as dense markdown. No fluff. Code blocks for types/signatures.
10
+ Omit implementation details unless they're critical to understanding.
11
+
12
+ <source>
13
+ {content}
14
+ </source>
@@ -0,0 +1,222 @@
1
+ """Loopflow CLI: Arrange LLMs to code in harmony."""
2
+
3
+ import sys
4
+ from pathlib import Path
5
+ from typing import Optional
6
+
7
+ import typer
8
+ import yaml
9
+
10
+ from loopflow.config import ConfigError, load_config
11
+ from loopflow.context import find_worktree_root, gather_task, list_all_tasks, _get_builtin_task
12
+ from loopflow.init_check import check_init_status
13
+ from loopflow.lfd.pipelines import load_pipeline
14
+
15
+ app = typer.Typer(
16
+ name="lf",
17
+ help="Arrange LLMs to code in harmony.",
18
+ no_args_is_help=False,
19
+ )
20
+
21
+ # Import and register subcommands
22
+ from loopflow.cli import run as run_module
23
+
24
+ # Register top-level commands
25
+ app.command(context_settings={"allow_extra_args": True, "allow_interspersed_args": True})(run_module.run)
26
+ app.command()(run_module.inline)
27
+ app.command(name="pipeline")(run_module.pipeline)
28
+ app.command()(run_module.cp)
29
+
30
+
31
+ def _get_task_source(repo_root: Path | None, name: str) -> str:
32
+ """Return source location: .claude, .lf, or builtin."""
33
+ if repo_root:
34
+ if (repo_root / ".claude" / "commands" / f"{name}.md").exists():
35
+ return ".claude"
36
+ lf_dir = repo_root / ".lf"
37
+ if lf_dir.exists():
38
+ for p in lf_dir.iterdir():
39
+ if p.is_file() and (p.stem == name or p.name == name):
40
+ return ".lf"
41
+ return "builtin"
42
+
43
+
44
+ def _parse_frontmatter(content: str) -> dict:
45
+ """Extract frontmatter fields from task content."""
46
+ if not content.startswith("---"):
47
+ return {}
48
+ try:
49
+ _, fm, _ = content.split("---", 2)
50
+ return yaml.safe_load(fm) or {}
51
+ except Exception:
52
+ return {}
53
+
54
+
55
+ def _get_task_info(repo_root: Path | None, name: str) -> dict:
56
+ """Get task metadata for display."""
57
+ info = {"name": name, "source": _get_task_source(repo_root, name)}
58
+
59
+ # Find the actual file to read frontmatter
60
+ content = None
61
+ if repo_root:
62
+ # Check .claude/commands first
63
+ claude_path = repo_root / ".claude" / "commands" / f"{name}.md"
64
+ if claude_path.exists():
65
+ content = claude_path.read_text()
66
+ else:
67
+ # Check .lf
68
+ lf_dir = repo_root / ".lf"
69
+ if lf_dir.exists():
70
+ for p in lf_dir.iterdir():
71
+ if p.is_file() and (p.stem == name or p.name == name):
72
+ content = p.read_text()
73
+ break
74
+
75
+ # Fall back to builtin
76
+ if content is None:
77
+ builtin = _get_builtin_task(name)
78
+ if builtin:
79
+ content = builtin.read_text()
80
+
81
+ if content:
82
+ fm = _parse_frontmatter(content)
83
+ info.update(fm)
84
+
85
+ return info
86
+
87
+
88
+ def _list_tasks() -> None:
89
+ """List available tasks and pipelines."""
90
+ repo_root = find_worktree_root()
91
+ config = load_config(repo_root) if repo_root else None
92
+
93
+ user_tasks, builtin_only = list_all_tasks(repo_root)
94
+ pipelines = list(config.pipelines.keys()) if config else []
95
+
96
+ # Show pipelines
97
+ if pipelines:
98
+ typer.echo("Pipelines (defined in .lf/config.yaml):")
99
+ for name in sorted(pipelines):
100
+ p = config.pipelines[name]
101
+ tasks_str = " → ".join(p.tasks) if p.tasks else ""
102
+ typer.echo(f" {name:<16} {tasks_str}")
103
+ typer.echo()
104
+
105
+ # Gather task info
106
+ all_tasks = []
107
+ for name in user_tasks:
108
+ info = _get_task_info(repo_root, name)
109
+ info["builtin"] = False
110
+ all_tasks.append(info)
111
+ for name in builtin_only:
112
+ info = _get_task_info(repo_root, name)
113
+ info["builtin"] = True
114
+ all_tasks.append(info)
115
+
116
+ if not all_tasks:
117
+ typer.echo("No tasks found.")
118
+ typer.echo("Run: lfops init")
119
+ return
120
+
121
+ def format_task(t: dict, show_source: bool = False) -> str:
122
+ """Format a task for display."""
123
+ parts = [f" {t['name']:<14}"]
124
+ if show_source:
125
+ parts.append(f" {t['source']:<8}")
126
+
127
+ meta = []
128
+ if t.get("interactive"):
129
+ meta.append("i")
130
+ if t.get("requires"):
131
+ meta.append(f"← {t['requires']}")
132
+ if t.get("produces"):
133
+ meta.append(f"→ {t['produces']}")
134
+
135
+ if meta:
136
+ parts.append(f" {' '.join(meta)}")
137
+ return "".join(parts)
138
+
139
+ # Display custom tasks
140
+ custom = [t for t in all_tasks if not t["builtin"]]
141
+ if custom:
142
+ typer.echo("Tasks (repo-specific, override built-ins):")
143
+ for t in custom:
144
+ typer.echo(format_task(t, show_source=True))
145
+ typer.echo()
146
+
147
+ # Display builtins
148
+ builtins = [t for t in all_tasks if t["builtin"]]
149
+ if builtins:
150
+ typer.echo("Built-in (bundled defaults, work in any repo):")
151
+ for t in builtins:
152
+ typer.echo(format_task(t, show_source=False))
153
+
154
+
155
+ def main():
156
+ """Entry point that supports 'lf <task>' and 'lf <pipeline>' shorthand."""
157
+ known_commands = {
158
+ "run",
159
+ "pipeline",
160
+ "inline",
161
+ "cp",
162
+ "--help",
163
+ "-h",
164
+ }
165
+
166
+ try:
167
+ # Handle 'lf' with no arguments: list available tasks
168
+ if len(sys.argv) == 1:
169
+ _list_tasks()
170
+ raise SystemExit(0)
171
+
172
+ if len(sys.argv) > 1:
173
+ first_arg = sys.argv[1]
174
+
175
+ # Inline prompt: lf : "prompt"
176
+ if first_arg == ":":
177
+ sys.argv.pop(1)
178
+ sys.argv.insert(1, "inline")
179
+ elif first_arg not in known_commands:
180
+ # Handle colon suffix: "lf implement: add logout" -> "lf implement add logout"
181
+ if first_arg.endswith(":"):
182
+ sys.argv[1] = first_arg[:-1]
183
+ name = sys.argv[1]
184
+ repo_root = find_worktree_root()
185
+ config = load_config(repo_root) if repo_root else None
186
+
187
+ # Check for pipeline in config.yaml or .lf/pipelines/
188
+ has_config_pipeline = config and name in config.pipelines
189
+ has_file_pipeline = repo_root and load_pipeline(name, repo_root) is not None
190
+ has_pipeline = has_config_pipeline or has_file_pipeline
191
+
192
+ # gather_task now includes builtins
193
+ has_task = gather_task(repo_root, name) is not None if repo_root else False
194
+ # Also check builtin even without repo_root
195
+ if not has_task and not repo_root:
196
+ has_task = _get_builtin_task(name) is not None
197
+
198
+ if has_pipeline and has_task:
199
+ typer.echo(f"Error: '{name}' exists as both a pipeline and a task", err=True)
200
+ typer.echo(f" Pipeline: defined in .lf/config.yaml", err=True)
201
+ typer.echo(f" Task: .claude/commands/{name}.md or .lf/{name}.*", err=True)
202
+ typer.echo(f"Remove one to resolve the conflict.", err=True)
203
+ raise SystemExit(1)
204
+
205
+ if has_pipeline:
206
+ sys.argv.insert(1, "pipeline")
207
+ elif has_task:
208
+ sys.argv.insert(1, "run")
209
+ else:
210
+ # Task not found
211
+ typer.echo(f"No task or pipeline named '{name}'", err=True)
212
+ typer.echo(f"Run 'lf' to see available tasks.", err=True)
213
+ raise SystemExit(1)
214
+
215
+ app()
216
+ except ConfigError as e:
217
+ typer.echo(f"Error: {e}", err=True)
218
+ raise SystemExit(1)
219
+
220
+
221
+ if __name__ == "__main__":
222
+ main()