Skip to content

Commit 794e0c2

Browse files
feat(tests): add markdown output
Statistics: 15 files changed, 492 insertions, 28 deletions Summary: - Dirs: src=6, tests=5, .=2, docs=2 - Exts: .py=11, .md=2, .yaml=1, .lock=1 - A/M/D: 0/15/0 - Symbols: write_serve_endpoint_file, render_tools_detect_text, bind_serve_server, serve_endpoint_path, read_serve_endpoint Modified files: - README.md (+7/-4) - docs/ai-tool-registry-2026.yaml (+3/-3) - docs/cli-examples.md (+17/-0) - src/koru/agents.py (+13/-0) - src/koru/cli.py (+68/-9) - src/koru/context.py (+2/-2) - src/koru/serve.py (+102/-2) - src/koru/tasks.py (+31/-5) - src/koru/tools.py (+78/-0) - tests/test_agents.py (+25/-0) - tests/test_e2e.py (+22/-0) - tests/test_serve.py (+73/-1) - tests/test_tasks.py (+26/-0) - tests/test_tools.py (+24/-1) - uv.lock (+1/-1) Implementation notes (heuristics): - Type inferred from file paths + diff keywords + add/delete ratio - Scope prefers 'goal' when goal/* is touched; otherwise based on top-level dirs - For <=6 files: generate short per-file notes from added lines (defs/classes/click options/headings) - A/M/D derived from git name-status; per-file +X/-X from git numstat
1 parent ac8b0b0 commit 794e0c2

18 files changed

Lines changed: 511 additions & 30 deletions

CHANGELOG.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
6060
`quality:regix``gate:regix`, `quality:redup*``gate:redup`,
6161
`quality:sumr:*``gate:sumr`.
6262

63+
## [0.1.38] - 2026-05-12
64+
65+
### Docs
66+
- Update README.md
67+
- Update docs/ai-tool-registry-2026.yaml
68+
- Update docs/cli-examples.md
69+
70+
### Test
71+
- Update tests/test_agents.py
72+
- Update tests/test_e2e.py
73+
- Update tests/test_serve.py
74+
- Update tests/test_tasks.py
75+
- Update tests/test_tools.py
76+
77+
### Other
78+
- Update uv.lock
79+
6380
## [0.1.37] - 2026-05-12
6481

6582
### Docs

README.md

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@
44

55
## AI Cost Tracking
66

7-
![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.1.37-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
8-
![AI Cost](https://img.shields.io/badge/AI%20Cost-$3.19-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-20.9h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
7+
![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.1.38-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
8+
![AI Cost](https://img.shields.io/badge/AI%20Cost-$3.13-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-21.0h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
99

10-
- 🤖 **LLM usage:** $3.1882 (60 commits)
11-
- 👤 **Human dev:** ~$2093 (20.9h @ $100/h, 30min dedup)
10+
- 🤖 **LLM usage:** $3.1281 (61 commits)
11+
- 👤 **Human dev:** ~$2103 (21.0h @ $100/h, 30min dedup)
1212

1313
Generated on 2026-05-12 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
1414

@@ -166,6 +166,9 @@ koru autopilot status # is the daemon up? plugins connected?
166166
koru tools detect
167167
koru tools detect --format json
168168

169+
# phase-2 tool adapter scaffold ticket
170+
koru task "Prepare Gemini adapter" --project . --tool gemini-cli
171+
169172
# non-invasive smoke test (daemon routing + IDE autodetect, no real key injection)
170173
scripts/autopilot-ide-autodetect-smoke.sh --require-running-ide
171174
scripts/autopilot-ide-autodetect-smoke.sh --check-ide jetbrains --check-ide windsurf

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.1.37
1+
0.1.38

docs/ai-tool-registry-2026.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,14 +88,14 @@ tools:
8888
- id: gemini-cli
8989
name: Gemini CLI
9090
category: cli_agent
91-
lane: adapter
91+
lane: native
9292
stability: beta
9393
detect:
9494
commands: ["gemini"]
9595
markers: []
9696
env: ["GEMINI_API_KEY"]
97-
invoke: "planfile shell ticket with `gemini` command"
98-
notes: "Adapter lane pending native integration."
97+
invoke: "koru agent --agent gemini-cli --launch"
98+
notes: "Native agent lane (phase-3 promotion); adapter tickets still supported."
9999

100100
- id: cline
101101
name: Cline

docs/cli-examples.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,23 @@ task loop:test
9494
task loop:lint
9595
```
9696

97+
### Create task tickets (including tool-adapter scaffold)
98+
99+
```bash
100+
# standard NL ticket
101+
koru task "Fix login form validation" --project . --priority high
102+
103+
# phase-2 adapter scaffold from tool registry
104+
koru task "Prepare Gemini adapter" --project . --tool gemini-cli
105+
106+
# override inferred executor hint for the scaffold
107+
koru task "Prepare n8n webhook adapter" --project . --tool n8n --tool-kind api
108+
109+
# use custom registry file
110+
koru task "Prepare custom tool adapter" --project . --tool my-tool \
111+
--tool-registry /path/to/ai-tool-registry-2026.yaml
112+
```
113+
97114
### Real-world example (semcod org)
98115

99116
```bash

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "koru"
7-
version = "0.1.37"
7+
version = "0.1.38"
88
description = "Closed-loop automation across semcod/* repositories."
99
readme = "README.md"
1010
requires-python = ">=3.12"

src/koru/agents.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ def detect_agent_options(project: Path) -> list[AgentOption]:
5353
claude_cmd = _which("claude")
5454
aider_cmd = _which("aider")
5555
codex_cmd = _which("codex")
56+
gemini_cmd = _which("gemini")
5657
openrouter_ready = bool(os.getenv("OPENROUTER_API_KEY"))
5758
antigravity_ready = bool(os.getenv("ANTIGRAVITY_AGENT"))
5859

@@ -89,6 +90,18 @@ def detect_agent_options(project: Path) -> list[AgentOption]:
8990
command=codex_cmd,
9091
reason="Codex CLI detected in PATH." if codex_cmd else "Codex CLI is not in PATH.",
9192
),
93+
AgentOption(
94+
id="gemini-cli",
95+
label="Gemini CLI",
96+
available=bool(gemini_cmd),
97+
launchable=bool(gemini_cmd),
98+
command=gemini_cmd,
99+
reason=(
100+
"Gemini CLI detected in PATH."
101+
if gemini_cmd
102+
else "Gemini CLI is not in PATH."
103+
),
104+
),
92105
AgentOption(
93106
id="cursor",
94107
label="Cursor",

src/koru/cli.py

Lines changed: 68 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import argparse
66
import asyncio
77
import json
8+
import os
89
import shlex
910
import sys
1011
from collections.abc import Callable
@@ -34,8 +35,18 @@
3435
from .run_log import open_run_log_eagerly
3536
from .scan import ScanResult, run_scan
3637
from .serve import DEFAULT_HOST, DEFAULT_PORT, ServeConfig, serve
38+
39+
40+
def _env_truthy(name: str) -> bool:
41+
return os.environ.get(name, "").strip().lower() in ("1", "true", "yes", "on")
3742
from .tasks import create_nl_task
38-
from .tools import detect_tools, load_tool_registry, render_tools_detect_text
43+
from .tools import (
44+
build_tool_task_scaffold,
45+
detect_tools,
46+
find_tool_entry,
47+
load_tool_registry,
48+
render_tools_detect_text,
49+
)
3950
from .watch import watch_planfile_events
4051

4152

@@ -295,6 +306,29 @@ def _build_task_parser() -> argparse.ArgumentParser:
295306
parser.add_argument("--sprint", default="current", help="Target planfile sprint.")
296307
parser.add_argument("--queue-name", default=None, help="Execution queue for the new ticket.")
297308
parser.add_argument("--priority", default="normal", help="Ticket priority.")
309+
parser.add_argument(
310+
"--tool",
311+
dest="tool_id",
312+
default=None,
313+
help=(
314+
"Build a tool-adapter scaffold ticket for this tool id "
315+
"(from docs/ai-tool-registry-2026.yaml)."
316+
),
317+
)
318+
parser.add_argument(
319+
"--tool-kind",
320+
dest="tool_kind",
321+
choices=("human", "shell", "api", "llm"),
322+
default=None,
323+
help="Override scaffolded executor hint for --tool.",
324+
)
325+
parser.add_argument(
326+
"--tool-registry",
327+
dest="tool_registry",
328+
type=Path,
329+
default=None,
330+
help="Override tool registry path for --tool lookup.",
331+
)
298332
return parser
299333

300334

@@ -323,6 +357,14 @@ def _build_serve_parser() -> argparse.ArgumentParser:
323357
default=DEFAULT_PORT,
324358
help=f"TCP port to listen on (default {DEFAULT_PORT}).",
325359
)
360+
parser.add_argument(
361+
"--auto-port",
362+
action="store_true",
363+
help=(
364+
"If the port is busy, try the next ports (then an ephemeral port). "
365+
"Also on when KORU_SERVE_AUTO_PORT is 1/true/yes."
366+
),
367+
)
326368
open_group = parser.add_mutually_exclusive_group()
327369
open_group.add_argument(
328370
"--open",
@@ -876,20 +918,44 @@ def _build_agent_parser() -> argparse.ArgumentParser:
876918

877919
def _task_main(argv: list[str]) -> int:
878920
args = _build_task_parser().parse_args(argv)
921+
scaffold: dict[str, Any] | None = None
922+
if args.tool_id:
923+
registry, registry_path = load_tool_registry(args.tool_registry)
924+
if not registry:
925+
print(
926+
"koru task: tool registry is empty or missing. "
927+
"Use --tool-registry PATH or ensure docs/ai-tool-registry-2026.yaml exists."
928+
)
929+
return 2
930+
tool = find_tool_entry(registry, args.tool_id)
931+
if tool is None:
932+
known = ", ".join(sorted(str(t.get("id")) for t in registry if t.get("id")))
933+
print(f"koru task: unknown --tool '{args.tool_id}'. Known ids: {known}")
934+
return 2
935+
scaffold = build_tool_task_scaffold(tool, adapter_kind=args.tool_kind)
936+
if registry_path is not None:
937+
scaffold.setdefault("source_context", {})
938+
if isinstance(scaffold.get("source_context"), dict):
939+
scaffold["source_context"]["registry"] = str(registry_path)
940+
879941
try:
880942
created = create_nl_task(
881943
args.project,
882944
" ".join(args.text),
883945
sprint=args.sprint,
884946
queue_name=args.queue_name,
885947
priority=args.priority,
948+
scaffold=scaffold,
886949
)
887950
except ValueError as exc:
888951
print(f"koru task: {exc}")
889952
return 2
890953
print(f"koru task: ✓ created {created.ticket_id} in {created.path}")
891954
print(f" name: {created.name}")
892955
print(f" queue: {args.queue_name or 'default'}")
956+
if args.tool_id:
957+
print(f" tool: {args.tool_id}")
958+
print(" note: scaffold ticket created — fill concrete executor inputs before queue run")
893959
print("Next: run `koru` to get the LLM prompt, or `koru --queue` to execute one task.")
894960
emit_management_event(
895961
tool="koru.task",
@@ -915,14 +981,7 @@ def _serve_main(argv: list[str]) -> int:
915981
port=args.port,
916982
open_browser=args.open_browser,
917983
queue_name=args.queue_name,
918-
)
919-
emit_management_event(
920-
tool="koru.serve",
921-
action="started",
922-
status="running",
923-
message=f"http://{config.host}:{config.port}/",
924-
queue=config.queue_name,
925-
details={"project": str(config.project), "open_browser": config.open_browser},
984+
auto_port=bool(args.auto_port) or _env_truthy("KORU_SERVE_AUTO_PORT"),
926985
)
927986
exit_code = serve(config)
928987
emit_management_event(

src/koru/context.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -778,9 +778,9 @@ def _render_ai_tool_support_2026() -> list[str]:
778778
"3. **manual lane** — no stable automation API yet; operator uses it directly.",
779779
"",
780780
"Current native lane includes: `windsurf`, `vscode`, `cursor`, `jetbrains`, `zed`, "
781-
"`claude-code`, `aider`, `codex` and OpenRouter-compatible `llm` tickets.",
781+
"`claude-code`, `aider`, `codex`, `gemini-cli` and OpenRouter-compatible `llm` tickets.",
782782
"",
783-
"For tools not listed as native (e.g. Gemini CLI, Cline, OpenCode, Qwen Code, "
783+
"For tools not listed as native (e.g. Cline, OpenCode, Qwen Code, "
784784
"Copilot/Tabnine plugins, app builders), use adapter lane first; promote to native "
785785
"only when reliability is proven.",
786786
"",

0 commit comments

Comments
 (0)