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.
- {loopflow-0.6.2 → loopflow-0.6.4}/.gitignore +4 -0
- {loopflow-0.6.2 → loopflow-0.6.4}/PKG-INFO +8 -7
- {loopflow-0.6.2 → loopflow-0.6.4}/README.md +6 -6
- {loopflow-0.6.2 → loopflow-0.6.4}/pyproject.toml +3 -0
- {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/LOOPFLOW.md +4 -2
- loopflow-0.6.4/src/loopflow/__init__.py +1 -0
- loopflow-0.6.4/src/loopflow/builtins/summarize.txt +14 -0
- loopflow-0.6.4/src/loopflow/cli/__init__.py +222 -0
- {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/cli/run.py +114 -40
- {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/config.py +21 -0
- {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/context.py +233 -30
- {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/files.py +54 -19
- {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/launcher.py +17 -0
- {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/lfd/__init__.py +7 -5
- {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/lfd/agents.py +127 -7
- {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/lfd/collector.py +13 -8
- {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/lfd/models.py +17 -6
- loopflow-0.6.4/src/loopflow/lfd/naming.py +134 -0
- {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/lfd/pipelines.py +78 -82
- {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/lfd/runner.py +224 -19
- {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/lfd/server.py +5 -1
- {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/lfops.py +522 -104
- loopflow-0.6.4/src/loopflow/lfwork.py +258 -0
- loopflow-0.6.4/src/loopflow/pipeline.py +630 -0
- {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/publish.py +92 -0
- loopflow-0.6.4/src/loopflow/summarize.py +399 -0
- loopflow-0.6.4/src/loopflow/templates/commands/expand.md +27 -0
- loopflow-0.6.4/src/loopflow/templates/commands/explore.md +27 -0
- loopflow-0.6.4/src/loopflow/templates/commands/rebase.md +61 -0
- loopflow-0.6.4/src/loopflow/templates/commands/reduce.md +27 -0
- loopflow-0.6.4/src/loopflow/templates/config.yaml +32 -0
- loopflow-0.6.4/src/loopflow/work/__init__.py +0 -0
- loopflow-0.6.4/src/loopflow/work/asana_backend.py +205 -0
- loopflow-0.6.4/src/loopflow/work/backend.py +29 -0
- loopflow-0.6.4/src/loopflow/work/file_backend.py +177 -0
- loopflow-0.6.4/src/loopflow/work/models.py +37 -0
- loopflow-0.6.2/src/loopflow/__init__.py +0 -1
- loopflow-0.6.2/src/loopflow/cli/__init__.py +0 -90
- loopflow-0.6.2/src/loopflow/lfd/naming.py +0 -59
- loopflow-0.6.2/src/loopflow/pipeline.py +0 -143
- loopflow-0.6.2/src/loopflow/templates/config.yaml +0 -49
- {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/builtins/__init__.py +0 -0
- {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/builtins/commit_message.txt +0 -0
- {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/builtins/pr_message.txt +0 -0
- {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/builtins/release_notes.txt +0 -0
- {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/design.py +0 -0
- {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/frontmatter.py +0 -0
- {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/git.py +0 -0
- {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/init_check.py +0 -0
- {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/lfd/README.md +0 -0
- {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/lfd/__main__.py +0 -0
- {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/lfd/agent_runner.py +0 -0
- {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/lfd/client.py +0 -0
- {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/lfd/cron.py +0 -0
- {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/lfd/db.py +0 -0
- {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/lfd/launchd.py +0 -0
- {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/lfd/process.py +0 -0
- {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/lfd/protocol.py +0 -0
- {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/lfd/triggers.py +0 -0
- {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/lfwt.py +0 -0
- {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/llm_http.py +0 -0
- {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/logging.py +0 -0
- {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/prompts/CHECKPOINT_MESSAGE.md +0 -0
- {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/prompts/COMMIT_MESSAGE.md +0 -0
- {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/templates/commands/debug.md +0 -0
- {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/templates/commands/design.md +0 -0
- {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/templates/commands/implement.md +0 -0
- {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/templates/commands/iterate.md +0 -0
- {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/templates/commands/polish.md +0 -0
- {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/templates/commands/review.md +0 -0
- {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/tokens.py +0 -0
- {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/voices.py +0 -0
- {loopflow-0.6.2 → loopflow-0.6.4}/src/loopflow/worktrees.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: loopflow
|
|
3
|
-
Version: 0.6.
|
|
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 .
|
|
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 `.
|
|
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
|
|
76
|
+
Tasks are markdown files in `.claude/commands/` or `.lf/`. Here's an example:
|
|
76
77
|
|
|
77
78
|
```markdown
|
|
78
|
-
# .
|
|
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 .
|
|
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 .
|
|
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 `.
|
|
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
|
|
41
|
+
Tasks are markdown files in `.claude/commands/` or `.lf/`. Here's an example:
|
|
42
42
|
|
|
43
43
|
```markdown
|
|
44
|
-
# .
|
|
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 .
|
|
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 `.
|
|
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()
|