hyperloop 0.1.0__tar.gz → 0.2.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 (56) hide show
  1. {hyperloop-0.1.0 → hyperloop-0.2.0}/.github/workflows/release.yaml +5 -0
  2. {hyperloop-0.1.0 → hyperloop-0.2.0}/CHANGELOG.md +6 -0
  3. {hyperloop-0.1.0 → hyperloop-0.2.0}/PKG-INFO +16 -15
  4. {hyperloop-0.1.0 → hyperloop-0.2.0}/README.md +15 -14
  5. {hyperloop-0.1.0 → hyperloop-0.2.0}/pyproject.toml +1 -1
  6. {hyperloop-0.1.0 → hyperloop-0.2.0}/src/hyperloop/cli.py +16 -8
  7. {hyperloop-0.1.0 → hyperloop-0.2.0}/src/hyperloop/loop.py +42 -26
  8. {hyperloop-0.1.0 → hyperloop-0.2.0}/.github/workflows/ci.yaml +0 -0
  9. {hyperloop-0.1.0 → hyperloop-0.2.0}/.gitignore +0 -0
  10. {hyperloop-0.1.0 → hyperloop-0.2.0}/.pre-commit-config.yaml +0 -0
  11. {hyperloop-0.1.0 → hyperloop-0.2.0}/.python-version +0 -0
  12. {hyperloop-0.1.0 → hyperloop-0.2.0}/CLAUDE.md +0 -0
  13. {hyperloop-0.1.0 → hyperloop-0.2.0}/base/implementer.yaml +0 -0
  14. {hyperloop-0.1.0 → hyperloop-0.2.0}/base/pm.yaml +0 -0
  15. {hyperloop-0.1.0 → hyperloop-0.2.0}/base/process-improver.yaml +0 -0
  16. {hyperloop-0.1.0 → hyperloop-0.2.0}/base/process.yaml +0 -0
  17. {hyperloop-0.1.0 → hyperloop-0.2.0}/base/rebase-resolver.yaml +0 -0
  18. {hyperloop-0.1.0 → hyperloop-0.2.0}/base/verifier.yaml +0 -0
  19. {hyperloop-0.1.0 → hyperloop-0.2.0}/specs/spec.md +0 -0
  20. {hyperloop-0.1.0 → hyperloop-0.2.0}/src/hyperloop/__init__.py +0 -0
  21. {hyperloop-0.1.0 → hyperloop-0.2.0}/src/hyperloop/__main__.py +0 -0
  22. {hyperloop-0.1.0 → hyperloop-0.2.0}/src/hyperloop/adapters/__init__.py +0 -0
  23. {hyperloop-0.1.0 → hyperloop-0.2.0}/src/hyperloop/adapters/git_state.py +0 -0
  24. {hyperloop-0.1.0 → hyperloop-0.2.0}/src/hyperloop/adapters/local.py +0 -0
  25. {hyperloop-0.1.0 → hyperloop-0.2.0}/src/hyperloop/compose.py +0 -0
  26. {hyperloop-0.1.0 → hyperloop-0.2.0}/src/hyperloop/config.py +0 -0
  27. {hyperloop-0.1.0 → hyperloop-0.2.0}/src/hyperloop/domain/__init__.py +0 -0
  28. {hyperloop-0.1.0 → hyperloop-0.2.0}/src/hyperloop/domain/decide.py +0 -0
  29. {hyperloop-0.1.0 → hyperloop-0.2.0}/src/hyperloop/domain/deps.py +0 -0
  30. {hyperloop-0.1.0 → hyperloop-0.2.0}/src/hyperloop/domain/model.py +0 -0
  31. {hyperloop-0.1.0 → hyperloop-0.2.0}/src/hyperloop/domain/pipeline.py +0 -0
  32. {hyperloop-0.1.0 → hyperloop-0.2.0}/src/hyperloop/ports/__init__.py +0 -0
  33. {hyperloop-0.1.0 → hyperloop-0.2.0}/src/hyperloop/ports/pr.py +0 -0
  34. {hyperloop-0.1.0 → hyperloop-0.2.0}/src/hyperloop/ports/runtime.py +0 -0
  35. {hyperloop-0.1.0 → hyperloop-0.2.0}/src/hyperloop/ports/state.py +0 -0
  36. {hyperloop-0.1.0 → hyperloop-0.2.0}/src/hyperloop/pr.py +0 -0
  37. {hyperloop-0.1.0 → hyperloop-0.2.0}/tests/__init__.py +0 -0
  38. {hyperloop-0.1.0 → hyperloop-0.2.0}/tests/fakes/__init__.py +0 -0
  39. {hyperloop-0.1.0 → hyperloop-0.2.0}/tests/fakes/pr.py +0 -0
  40. {hyperloop-0.1.0 → hyperloop-0.2.0}/tests/fakes/runtime.py +0 -0
  41. {hyperloop-0.1.0 → hyperloop-0.2.0}/tests/fakes/state.py +0 -0
  42. {hyperloop-0.1.0 → hyperloop-0.2.0}/tests/test_cli.py +0 -0
  43. {hyperloop-0.1.0 → hyperloop-0.2.0}/tests/test_compose.py +0 -0
  44. {hyperloop-0.1.0 → hyperloop-0.2.0}/tests/test_config.py +0 -0
  45. {hyperloop-0.1.0 → hyperloop-0.2.0}/tests/test_decide.py +0 -0
  46. {hyperloop-0.1.0 → hyperloop-0.2.0}/tests/test_deps.py +0 -0
  47. {hyperloop-0.1.0 → hyperloop-0.2.0}/tests/test_fakes.py +0 -0
  48. {hyperloop-0.1.0 → hyperloop-0.2.0}/tests/test_git_state.py +0 -0
  49. {hyperloop-0.1.0 → hyperloop-0.2.0}/tests/test_local_runtime.py +0 -0
  50. {hyperloop-0.1.0 → hyperloop-0.2.0}/tests/test_loop.py +0 -0
  51. {hyperloop-0.1.0 → hyperloop-0.2.0}/tests/test_model.py +0 -0
  52. {hyperloop-0.1.0 → hyperloop-0.2.0}/tests/test_pipeline.py +0 -0
  53. {hyperloop-0.1.0 → hyperloop-0.2.0}/tests/test_pr.py +0 -0
  54. {hyperloop-0.1.0 → hyperloop-0.2.0}/tests/test_smoke.py +0 -0
  55. {hyperloop-0.1.0 → hyperloop-0.2.0}/tests/test_state_contract.py +0 -0
  56. {hyperloop-0.1.0 → hyperloop-0.2.0}/uv.lock +0 -0
@@ -93,6 +93,11 @@ jobs:
93
93
  id-token: write
94
94
 
95
95
  steps:
96
+ - name: Checkout
97
+ uses: actions/checkout@v4
98
+ with:
99
+ fetch-depth: 0
100
+
96
101
  - name: Download dist artifacts
97
102
  uses: actions/download-artifact@v4
98
103
  with:
@@ -2,6 +2,12 @@
2
2
 
3
3
  <!-- version list -->
4
4
 
5
+ ## v0.2.0 (2026-04-15)
6
+
7
+
8
+ ## v0.1.1 (2026-04-15)
9
+
10
+
5
11
  ## v0.1.0 (2026-04-15)
6
12
 
7
13
  - Initial Release
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hyperloop
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: Orchestrator that walks tasks through composable process pipelines using AI agents
5
5
  Requires-Python: >=3.12
6
6
  Requires-Dist: pyyaml>=6.0.3
@@ -15,14 +15,14 @@ Description-Content-Type: text/markdown
15
15
 
16
16
  # hyperloop
17
17
 
18
- Walks tasks through composable process pipelines using AI agents. You write specs, it creates tasks, implements them, verifies the work, and merges PRs.
18
+ Walks tasks through composable process pipelines using AI agents. You write specs, it creates tasks, implements them, verifies the work, and merges the results.
19
19
 
20
20
  ## Prerequisites
21
21
 
22
22
  - Python 3.12+
23
23
  - [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code) (`claude` on PATH)
24
- - `gh` CLI (authenticated, for PR management)
25
24
  - `git`
25
+ - `gh` CLI (optional, for GitHub PR operations)
26
26
 
27
27
  ## Install
28
28
 
@@ -67,23 +67,23 @@ Implement JWT-based authentication for the API.
67
67
  - JWTs expire after 24 hours
68
68
  ```
69
69
 
70
- 3. Run:
70
+ 3. Run against the local repo:
71
71
 
72
72
  ```bash
73
- # From source:
74
- uv run hyperloop run --repo owner/repo --branch main
73
+ # From inside the repo
74
+ uv run hyperloop run
75
75
 
76
- # Or if installed:
77
- hyperloop run --repo owner/repo --branch main
78
- ```
76
+ # Or point to a repo elsewhere
77
+ uv run hyperloop run --path ~/code/my-project
79
78
 
80
- 4. See what it would do without executing:
79
+ # With GitHub PR support (draft PRs, lgtm gates, squash-merge)
80
+ uv run hyperloop run --repo owner/repo
81
81
 
82
- ```bash
83
- hyperloop run --repo owner/repo --dry-run
82
+ # Dry run (show config, don't execute)
83
+ uv run hyperloop run --dry-run
84
84
  ```
85
85
 
86
- The orchestrator reads your specs, has the PM create tasks in `specs/tasks/`, then walks each task through the default pipeline: implement, verify, merge.
86
+ When `--repo` is not set, completed work is merged locally into the base branch via `git merge`. When `--repo` is set, the orchestrator creates draft PRs, polls for `lgtm` labels on gates, and squash-merges via GitHub.
87
87
 
88
88
  ## Configuration
89
89
 
@@ -101,13 +101,14 @@ merge:
101
101
  strategy: squash
102
102
  ```
103
103
 
104
- Then just run from the repo directory:
104
+ Then run from the repo directory (or use `--path`):
105
105
 
106
106
  ```bash
107
107
  hyperloop run
108
+ hyperloop run --path ~/code/my-project
108
109
  ```
109
110
 
110
- The repo is inferred from your git remote. All settings have sensible defaults.
111
+ All settings have sensible defaults. `--repo` is only needed for GitHub PR operations.
111
112
 
112
113
  ## Customizing Agent Behavior
113
114
 
@@ -1,13 +1,13 @@
1
1
  # hyperloop
2
2
 
3
- Walks tasks through composable process pipelines using AI agents. You write specs, it creates tasks, implements them, verifies the work, and merges PRs.
3
+ Walks tasks through composable process pipelines using AI agents. You write specs, it creates tasks, implements them, verifies the work, and merges the results.
4
4
 
5
5
  ## Prerequisites
6
6
 
7
7
  - Python 3.12+
8
8
  - [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code) (`claude` on PATH)
9
- - `gh` CLI (authenticated, for PR management)
10
9
  - `git`
10
+ - `gh` CLI (optional, for GitHub PR operations)
11
11
 
12
12
  ## Install
13
13
 
@@ -52,23 +52,23 @@ Implement JWT-based authentication for the API.
52
52
  - JWTs expire after 24 hours
53
53
  ```
54
54
 
55
- 3. Run:
55
+ 3. Run against the local repo:
56
56
 
57
57
  ```bash
58
- # From source:
59
- uv run hyperloop run --repo owner/repo --branch main
58
+ # From inside the repo
59
+ uv run hyperloop run
60
60
 
61
- # Or if installed:
62
- hyperloop run --repo owner/repo --branch main
63
- ```
61
+ # Or point to a repo elsewhere
62
+ uv run hyperloop run --path ~/code/my-project
64
63
 
65
- 4. See what it would do without executing:
64
+ # With GitHub PR support (draft PRs, lgtm gates, squash-merge)
65
+ uv run hyperloop run --repo owner/repo
66
66
 
67
- ```bash
68
- hyperloop run --repo owner/repo --dry-run
67
+ # Dry run (show config, don't execute)
68
+ uv run hyperloop run --dry-run
69
69
  ```
70
70
 
71
- The orchestrator reads your specs, has the PM create tasks in `specs/tasks/`, then walks each task through the default pipeline: implement, verify, merge.
71
+ When `--repo` is not set, completed work is merged locally into the base branch via `git merge`. When `--repo` is set, the orchestrator creates draft PRs, polls for `lgtm` labels on gates, and squash-merges via GitHub.
72
72
 
73
73
  ## Configuration
74
74
 
@@ -86,13 +86,14 @@ merge:
86
86
  strategy: squash
87
87
  ```
88
88
 
89
- Then just run from the repo directory:
89
+ Then run from the repo directory (or use `--path`):
90
90
 
91
91
  ```bash
92
92
  hyperloop run
93
+ hyperloop run --path ~/code/my-project
93
94
  ```
94
95
 
95
- The repo is inferred from your git remote. All settings have sensible defaults.
96
+ All settings have sensible defaults. `--repo` is only needed for GitHub PR operations.
96
97
 
97
98
  ## Customizing Agent Behavior
98
99
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "hyperloop"
3
- version = "0.1.0"
3
+ version = "0.2.0"
4
4
  description = "Orchestrator that walks tasks through composable process pipelines using AI agents"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -52,6 +52,10 @@ def _config_table(cfg: Config) -> Table:
52
52
 
53
53
  @app.command()
54
54
  def run(
55
+ path: Path = typer.Option(
56
+ Path.cwd(),
57
+ help="Path to the target repo. Default: current directory.",
58
+ ),
55
59
  repo: str | None = typer.Option(
56
60
  None,
57
61
  help="GitHub repo (owner/repo). Inferred from git remote if not set.",
@@ -64,7 +68,7 @@ def run(
64
68
  None,
65
69
  "--config",
66
70
  "-c",
67
- help="Config file path. Default: .hyperloop.yaml",
71
+ help="Config file path. Default: .hyperloop.yaml in the target repo.",
68
72
  ),
69
73
  max_workers: int | None = typer.Option(
70
74
  None,
@@ -78,7 +82,8 @@ def run(
78
82
  ) -> None:
79
83
  """Run the orchestrator loop."""
80
84
  # 1. Load config (file + CLI overrides)
81
- config_path = config_file or Path(".hyperloop.yaml")
85
+ repo_path = path.resolve()
86
+ config_path = config_file or (repo_path / ".hyperloop.yaml")
82
87
  try:
83
88
  cfg = load_config(
84
89
  config_path,
@@ -99,7 +104,9 @@ def run(
99
104
  style="blue",
100
105
  )
101
106
  )
102
- console.print(_config_table(cfg))
107
+ table = _config_table(cfg)
108
+ table.add_row("path", str(repo_path))
109
+ console.print(table)
103
110
  console.print()
104
111
 
105
112
  # 3. Dry run — show config and exit
@@ -107,13 +114,15 @@ def run(
107
114
  console.print("[bold yellow]Dry run[/bold yellow] -- exiting without executing.")
108
115
  return
109
116
 
110
- # 4. Validate repo is set (required for actual run)
117
+ # 4. Validate repo path exists and is a git repo
118
+ if not (repo_path / ".git").exists():
119
+ console.print(f"[bold red]Error:[/bold red] {repo_path} is not a git repository.")
120
+ raise typer.Exit(code=1)
121
+
111
122
  if cfg.repo is None:
112
123
  console.print(
113
- "[bold red]Error:[/bold red] No repo specified. "
114
- "Use --repo owner/repo or set target.repo in .hyperloop.yaml"
124
+ "[dim]No --repo set. PR operations (draft, merge, gate) will be skipped.[/dim]"
115
125
  )
116
- raise typer.Exit(code=1)
117
126
 
118
127
  # 5. Construct runtime and state store, run loop
119
128
  from hyperloop.adapters.git_state import GitStateStore
@@ -136,7 +145,6 @@ def run(
136
145
  ),
137
146
  )
138
147
 
139
- repo_path = Path.cwd()
140
148
  state = GitStateStore(repo_path, specs_dir=cfg.specs_dir)
141
149
  runtime = LocalRuntime(repo_path=str(repo_path))
142
150
 
@@ -340,45 +340,61 @@ class Orchestrator:
340
340
  self._state.clear_findings(task.id)
341
341
 
342
342
  def _merge_ready_prs(self) -> None:
343
- """Merge PRs for tasks at the merge-pr action step.
343
+ """Merge branches for tasks at the merge-pr action step.
344
344
 
345
- Rebases branch first; on conflict transitions to NEEDS_REBASE.
345
+ With a PRManager: rebase + squash-merge the PR.
346
+ Without a PRManager: local git merge of worker branch into base branch.
347
+ On conflict, transitions to NEEDS_REBASE.
346
348
  """
347
- if self._pr_manager is None:
348
- logger.debug("merge: no PRManager — skipping merge")
349
- return
350
-
351
349
  all_tasks = self._state.get_world().tasks
352
350
  for task in all_tasks.values():
353
351
  if task.status != TaskStatus.IN_PROGRESS:
354
352
  continue
355
353
  if task.phase != Phase("merge-pr"):
356
354
  continue
357
- if task.pr is None:
358
- continue
359
355
 
360
356
  branch = task.branch or f"worker/{task.id}"
361
357
 
362
- # Step 1: Rebase onto base branch
363
- if not self._pr_manager.rebase_branch(branch, "main"):
364
- logger.warning("Rebase conflict for task %s, marking NEEDS_REBASE", task.id)
365
- self._state.transition_task(
366
- task.id, TaskStatus.NEEDS_REBASE, phase=Phase("merge-pr")
367
- )
368
- continue
358
+ if self._pr_manager is not None and task.pr is not None:
359
+ self._merge_via_pr(task.id, task.pr, task.spec_ref, branch)
360
+ else:
361
+ self._merge_local(task.id, branch)
369
362
 
370
- # Step 2: Squash-merge the PR
371
- if not self._pr_manager.merge(task.pr, task.id, task.spec_ref):
372
- logger.warning("Merge conflict for task %s, marking NEEDS_REBASE", task.id)
373
- self._state.transition_task(
374
- task.id, TaskStatus.NEEDS_REBASE, phase=Phase("merge-pr")
375
- )
376
- continue
363
+ def _merge_via_pr(self, task_id: str, pr_url: str, spec_ref: str, branch: str) -> None:
364
+ """Merge via GitHub PR: rebase, then squash-merge."""
365
+ assert self._pr_manager is not None
377
366
 
378
- # Merge succeeded — mark task complete
379
- self._state.transition_task(task.id, TaskStatus.COMPLETE, phase=None)
380
- self._state.clear_findings(task.id)
381
- logger.info("Merged PR for task %s", task.id)
367
+ if not self._pr_manager.rebase_branch(branch, "main"):
368
+ logger.warning("Rebase conflict for task %s, marking NEEDS_REBASE", task_id)
369
+ self._state.transition_task(task_id, TaskStatus.NEEDS_REBASE, phase=Phase("merge-pr"))
370
+ return
371
+
372
+ if not self._pr_manager.merge(pr_url, task_id, spec_ref):
373
+ logger.warning("Merge conflict for task %s, marking NEEDS_REBASE", task_id)
374
+ self._state.transition_task(task_id, TaskStatus.NEEDS_REBASE, phase=Phase("merge-pr"))
375
+ return
376
+
377
+ self._state.transition_task(task_id, TaskStatus.COMPLETE, phase=None)
378
+ self._state.clear_findings(task_id)
379
+ logger.info("Merged PR for task %s", task_id)
380
+
381
+ def _merge_local(self, task_id: str, branch: str) -> None:
382
+ """Merge worker branch into base branch locally (no PR)."""
383
+ import subprocess
384
+
385
+ try:
386
+ subprocess.run(
387
+ ["git", "merge", branch, "--no-edit", "-m", f"merge: {task_id}"],
388
+ check=True,
389
+ capture_output=True,
390
+ )
391
+ self._state.transition_task(task_id, TaskStatus.COMPLETE, phase=None)
392
+ self._state.clear_findings(task_id)
393
+ logger.info("Local merge of %s into base branch", task_id)
394
+ except subprocess.CalledProcessError:
395
+ logger.warning("Local merge conflict for task %s, marking NEEDS_REBASE", task_id)
396
+ subprocess.run(["git", "merge", "--abort"], capture_output=True, check=False)
397
+ self._state.transition_task(task_id, TaskStatus.NEEDS_REBASE, phase=Phase("merge-pr"))
382
398
 
383
399
  # -----------------------------------------------------------------------
384
400
  # World building
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes