kingdom-cli 0.1.4__tar.gz → 0.2.1__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.
- {kingdom_cli-0.1.4/src/kingdom_cli.egg-info → kingdom_cli-0.2.1}/PKG-INFO +45 -23
- {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/README.md +44 -22
- {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/pyproject.toml +5 -1
- {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/src/kingdom/agent.py +92 -5
- {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/src/kingdom/breakdown.py +0 -2
- {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/src/kingdom/cli.py +1478 -334
- {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/src/kingdom/config.py +116 -30
- {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/src/kingdom/council/base.py +26 -10
- {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/src/kingdom/council/council.py +21 -27
- kingdom_cli-0.2.1/src/kingdom/harness.py +729 -0
- {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/src/kingdom/parsing.py +0 -22
- {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/src/kingdom/session.py +52 -68
- kingdom_cli-0.2.1/src/kingdom/skill/SKILL.md +160 -0
- kingdom_cli-0.2.1/src/kingdom/skill/__init__.py +0 -0
- kingdom_cli-0.2.1/src/kingdom/skill/references/__init__.py +0 -0
- kingdom_cli-0.2.1/src/kingdom/skill/references/council.md +69 -0
- kingdom_cli-0.2.1/src/kingdom/skill/references/peasants.md +53 -0
- kingdom_cli-0.2.1/src/kingdom/skill/references/tickets.md +72 -0
- {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/src/kingdom/state.py +100 -31
- {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/src/kingdom/synthesis.py +0 -11
- {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/src/kingdom/thread.py +26 -10
- {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/src/kingdom/ticket.py +48 -186
- {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/src/kingdom/tui/app.py +498 -188
- kingdom_cli-0.2.1/src/kingdom/tui/chat.tcss +116 -0
- {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/src/kingdom/tui/poll.py +23 -9
- kingdom_cli-0.2.1/src/kingdom/tui/terminal.py +43 -0
- {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/src/kingdom/tui/widgets.py +105 -84
- {kingdom_cli-0.1.4 → kingdom_cli-0.2.1/src/kingdom_cli.egg-info}/PKG-INFO +45 -23
- {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/src/kingdom_cli.egg-info/SOURCES.txt +9 -0
- {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/tests/test_agent.py +67 -0
- kingdom_cli-0.2.1/tests/test_cli.py +585 -0
- {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/tests/test_cli_council.py +145 -10
- {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/tests/test_cli_peasant.py +805 -94
- {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/tests/test_cli_status.py +27 -2
- {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/tests/test_cli_ticket.py +1101 -10
- {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/tests/test_cli_ticket_current.py +41 -2
- {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/tests/test_config.py +90 -25
- {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/tests/test_council.py +30 -0
- {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/tests/test_done.py +57 -0
- kingdom_cli-0.2.1/tests/test_harness.py +1396 -0
- {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/tests/test_init.py +201 -0
- {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/tests/test_session.py +81 -8
- {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/tests/test_state.py +174 -0
- {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/tests/test_thread.py +149 -0
- {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/tests/test_ticket.py +13 -13
- {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/tests/test_tui.py +1035 -75
- {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/tests/test_tui_clipboard.py +66 -1
- {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/tests/test_tui_integration.py +239 -22
- {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/tests/test_tui_poll.py +85 -14
- {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/tests/test_tui_reply.py +58 -16
- kingdom_cli-0.2.1/tests/test_tui_terminal.py +82 -0
- kingdom_cli-0.2.1/tests/test_tui_thinking_persistence.py +191 -0
- {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/tests/test_tui_widgets.py +109 -2
- kingdom_cli-0.1.4/src/kingdom/harness.py +0 -473
- kingdom_cli-0.1.4/tests/test_cli.py +0 -206
- kingdom_cli-0.1.4/tests/test_harness.py +0 -829
- kingdom_cli-0.1.4/tests/test_tui_thinking_persistence.py +0 -159
- {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/LICENSE +0 -0
- {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/setup.cfg +0 -0
- {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/src/kingdom/__init__.py +0 -0
- {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/src/kingdom/__main__.py +0 -0
- {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/src/kingdom/council/__init__.py +0 -0
- {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/src/kingdom/council/bundle.py +0 -0
- {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/src/kingdom/council/worker.py +0 -0
- {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/src/kingdom/design.py +0 -0
- {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/src/kingdom/tui/__init__.py +0 -0
- {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/src/kingdom/tui/clipboard.py +0 -0
- {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/src/kingdom_cli.egg-info/dependency_links.txt +0 -0
- {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/src/kingdom_cli.egg-info/entry_points.txt +0 -0
- {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/src/kingdom_cli.egg-info/requires.txt +0 -0
- {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/src/kingdom_cli.egg-info/top_level.txt +0 -0
- {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/tests/test_breakdown.py +0 -0
- {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/tests/test_cli_design_breakdown.py +0 -0
- {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/tests/test_cli_whoami.py +0 -0
- {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/tests/test_design.py +0 -0
- {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/tests/test_parsing.py +0 -0
- {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/tests/test_skill.py +0 -0
- {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/tests/test_synthesis.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: kingdom-cli
|
|
3
|
-
Version: 0.1
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: A markdown file-based CLI for software development with multi-agent council design, markdown tickets, and background worker loops.
|
|
5
5
|
Author: James Robinson-Bohnslav
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -48,49 +48,71 @@ Gastown minus the polecats.
|
|
|
48
48
|
uv tool install kingdom-cli
|
|
49
49
|
```
|
|
50
50
|
|
|
51
|
-
##
|
|
51
|
+
## Workflow Scales
|
|
52
|
+
|
|
53
|
+
`kd` scales down gracefully. You pick the parts of the workflow that fit the size of the work.
|
|
54
|
+
|
|
55
|
+
### Full workflow (new features)
|
|
52
56
|
|
|
53
|
-
|
|
57
|
+
Design with the council, break into tickets, dispatch peasants, review, and merge.
|
|
54
58
|
|
|
55
59
|
```bash
|
|
56
|
-
kd start
|
|
60
|
+
kd start # initialize branch session
|
|
61
|
+
kd chat --new # discuss design with the council TUI
|
|
62
|
+
kd design approve # lock in the design
|
|
63
|
+
kd breakdown # generate tickets from the design
|
|
64
|
+
kd peasant start <id> # dispatch parallel workers
|
|
65
|
+
kd done # archive and clean up
|
|
57
66
|
```
|
|
58
67
|
|
|
59
|
-
|
|
68
|
+
### Medium workflow (refactors, smaller features)
|
|
60
69
|
|
|
61
|
-
|
|
70
|
+
Pull tickets into a branch, work them directly or with peasants. No design phase needed.
|
|
62
71
|
|
|
63
72
|
```bash
|
|
64
|
-
kd
|
|
73
|
+
kd start
|
|
74
|
+
kd tk pull <id> <id> # pull backlog tickets onto this branch
|
|
75
|
+
kd tk start <id> # work tickets one at a time
|
|
76
|
+
kd tk close <id>
|
|
77
|
+
kd done
|
|
65
78
|
```
|
|
66
79
|
|
|
67
|
-
|
|
80
|
+
### Lightweight workflow (bug fixes)
|
|
81
|
+
|
|
82
|
+
Work a single ticket, close it, make a PR. Or batch several bug-fix tickets on one branch.
|
|
68
83
|
|
|
69
84
|
```bash
|
|
70
|
-
kd
|
|
85
|
+
kd start
|
|
86
|
+
kd tk create "fix: login redirect loop"
|
|
87
|
+
kd tk start <id>
|
|
88
|
+
# ... fix the bug ...
|
|
89
|
+
kd tk close <id>
|
|
90
|
+
kd done
|
|
71
91
|
```
|
|
72
92
|
|
|
73
|
-
|
|
93
|
+
Design docs, council sessions, and peasant workers are all optional. A branch with one ticket and no design doc is a perfectly valid `kd` workflow.
|
|
94
|
+
|
|
95
|
+
## Getting Started
|
|
74
96
|
|
|
75
97
|
```bash
|
|
76
|
-
kd
|
|
77
|
-
kd
|
|
98
|
+
kd init # one-time: create .kd/ directory
|
|
99
|
+
kd start # start a session on the current branch
|
|
78
100
|
```
|
|
79
101
|
|
|
80
|
-
|
|
81
|
-
- Serial: tell Claude Code or Codex to work tickets directly, or run `kd work <id>`.
|
|
82
|
-
- Parallel: dispatch peasants in worktrees.
|
|
102
|
+
Configure council agent CLIs in `.kd/config.json` (check effective config with `kd config show`).
|
|
83
103
|
|
|
84
|
-
|
|
85
|
-
kd peasant start <id>
|
|
86
|
-
kd peasant status
|
|
87
|
-
```
|
|
104
|
+
## Chat Modes
|
|
88
105
|
|
|
89
|
-
|
|
106
|
+
The council chat TUI (`kd chat`) supports four modes, configured via `council.chat.mode`:
|
|
90
107
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
108
|
+
| Mode | First turn | Auto-turns | Default |
|
|
109
|
+
|------|-----------|------------|---------|
|
|
110
|
+
| `natural` | parallel broadcast | shuffled round-robin | yes |
|
|
111
|
+
| `round_robin` | sequential fixed-order | sequential fixed-order | |
|
|
112
|
+
| `manual` | only @mentioned | only @mentioned | |
|
|
113
|
+
| `broadcast` | parallel to all | parallel to all | |
|
|
114
|
+
|
|
115
|
+
LLM-to-LLM @mentions in responses automatically bump the mentioned member to the front of the auto-turn queue.
|
|
94
116
|
|
|
95
117
|
## How It Works
|
|
96
118
|
|
|
@@ -21,49 +21,71 @@ Gastown minus the polecats.
|
|
|
21
21
|
uv tool install kingdom-cli
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
##
|
|
24
|
+
## Workflow Scales
|
|
25
|
+
|
|
26
|
+
`kd` scales down gracefully. You pick the parts of the workflow that fit the size of the work.
|
|
27
|
+
|
|
28
|
+
### Full workflow (new features)
|
|
25
29
|
|
|
26
|
-
|
|
30
|
+
Design with the council, break into tickets, dispatch peasants, review, and merge.
|
|
27
31
|
|
|
28
32
|
```bash
|
|
29
|
-
kd start
|
|
33
|
+
kd start # initialize branch session
|
|
34
|
+
kd chat --new # discuss design with the council TUI
|
|
35
|
+
kd design approve # lock in the design
|
|
36
|
+
kd breakdown # generate tickets from the design
|
|
37
|
+
kd peasant start <id> # dispatch parallel workers
|
|
38
|
+
kd done # archive and clean up
|
|
30
39
|
```
|
|
31
40
|
|
|
32
|
-
|
|
41
|
+
### Medium workflow (refactors, smaller features)
|
|
33
42
|
|
|
34
|
-
|
|
43
|
+
Pull tickets into a branch, work them directly or with peasants. No design phase needed.
|
|
35
44
|
|
|
36
45
|
```bash
|
|
37
|
-
kd
|
|
46
|
+
kd start
|
|
47
|
+
kd tk pull <id> <id> # pull backlog tickets onto this branch
|
|
48
|
+
kd tk start <id> # work tickets one at a time
|
|
49
|
+
kd tk close <id>
|
|
50
|
+
kd done
|
|
38
51
|
```
|
|
39
52
|
|
|
40
|
-
|
|
53
|
+
### Lightweight workflow (bug fixes)
|
|
54
|
+
|
|
55
|
+
Work a single ticket, close it, make a PR. Or batch several bug-fix tickets on one branch.
|
|
41
56
|
|
|
42
57
|
```bash
|
|
43
|
-
kd
|
|
58
|
+
kd start
|
|
59
|
+
kd tk create "fix: login redirect loop"
|
|
60
|
+
kd tk start <id>
|
|
61
|
+
# ... fix the bug ...
|
|
62
|
+
kd tk close <id>
|
|
63
|
+
kd done
|
|
44
64
|
```
|
|
45
65
|
|
|
46
|
-
|
|
66
|
+
Design docs, council sessions, and peasant workers are all optional. A branch with one ticket and no design doc is a perfectly valid `kd` workflow.
|
|
67
|
+
|
|
68
|
+
## Getting Started
|
|
47
69
|
|
|
48
70
|
```bash
|
|
49
|
-
kd
|
|
50
|
-
kd
|
|
71
|
+
kd init # one-time: create .kd/ directory
|
|
72
|
+
kd start # start a session on the current branch
|
|
51
73
|
```
|
|
52
74
|
|
|
53
|
-
|
|
54
|
-
- Serial: tell Claude Code or Codex to work tickets directly, or run `kd work <id>`.
|
|
55
|
-
- Parallel: dispatch peasants in worktrees.
|
|
75
|
+
Configure council agent CLIs in `.kd/config.json` (check effective config with `kd config show`).
|
|
56
76
|
|
|
57
|
-
|
|
58
|
-
kd peasant start <id>
|
|
59
|
-
kd peasant status
|
|
60
|
-
```
|
|
77
|
+
## Chat Modes
|
|
61
78
|
|
|
62
|
-
|
|
79
|
+
The council chat TUI (`kd chat`) supports four modes, configured via `council.chat.mode`:
|
|
63
80
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
81
|
+
| Mode | First turn | Auto-turns | Default |
|
|
82
|
+
|------|-----------|------------|---------|
|
|
83
|
+
| `natural` | parallel broadcast | shuffled round-robin | yes |
|
|
84
|
+
| `round_robin` | sequential fixed-order | sequential fixed-order | |
|
|
85
|
+
| `manual` | only @mentioned | only @mentioned | |
|
|
86
|
+
| `broadcast` | parallel to all | parallel to all | |
|
|
87
|
+
|
|
88
|
+
LLM-to-LLM @mentions in responses automatically bump the mentioned member to the front of the auto-turn queue.
|
|
67
89
|
|
|
68
90
|
## How It Works
|
|
69
91
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "kingdom-cli"
|
|
3
|
-
version = "0.1
|
|
3
|
+
version = "0.2.1"
|
|
4
4
|
description = "A markdown file-based CLI for software development with multi-agent council design, markdown tickets, and background worker loops."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.10"
|
|
@@ -40,6 +40,10 @@ build-backend = "setuptools.build_meta"
|
|
|
40
40
|
[tool.setuptools.packages.find]
|
|
41
41
|
where = ["src"]
|
|
42
42
|
|
|
43
|
+
[tool.setuptools.package-data]
|
|
44
|
+
"kingdom.skill" = ["*.md", "references/*.md"]
|
|
45
|
+
"kingdom.tui" = ["*.tcss"]
|
|
46
|
+
|
|
43
47
|
[dependency-groups]
|
|
44
48
|
dev = [
|
|
45
49
|
"pre-commit>=4.0.0",
|
|
@@ -11,8 +11,8 @@ from __future__ import annotations
|
|
|
11
11
|
import json
|
|
12
12
|
import logging
|
|
13
13
|
import shlex
|
|
14
|
+
from collections.abc import Callable
|
|
14
15
|
from dataclasses import dataclass, field
|
|
15
|
-
from typing import Any
|
|
16
16
|
|
|
17
17
|
from kingdom.config import AgentDef
|
|
18
18
|
|
|
@@ -301,7 +301,9 @@ def parse_cursor_response(stdout: str, stderr: str, code: int) -> tuple[str, str
|
|
|
301
301
|
return "", session_id, raw
|
|
302
302
|
|
|
303
303
|
|
|
304
|
-
|
|
304
|
+
ResponseParser = Callable[[str, str, int], tuple[str, str | None, str]]
|
|
305
|
+
|
|
306
|
+
RESPONSE_PARSERS: dict[str, ResponseParser] = {
|
|
305
307
|
"claude_code": parse_claude_response,
|
|
306
308
|
"codex": parse_codex_response,
|
|
307
309
|
"cursor": parse_cursor_response,
|
|
@@ -424,7 +426,9 @@ def build_cursor_command(
|
|
|
424
426
|
return cmd
|
|
425
427
|
|
|
426
428
|
|
|
427
|
-
|
|
429
|
+
CommandBuilder = Callable[[AgentConfig, str, str | None, bool, bool], list[str]]
|
|
430
|
+
|
|
431
|
+
COMMAND_BUILDERS: dict[str, CommandBuilder] = {
|
|
428
432
|
"claude_code": build_claude_command,
|
|
429
433
|
"codex": build_codex_command,
|
|
430
434
|
"cursor": build_cursor_command,
|
|
@@ -561,7 +565,9 @@ def extract_cursor_stream_text(line: str) -> str | None:
|
|
|
561
565
|
return None
|
|
562
566
|
|
|
563
567
|
|
|
564
|
-
|
|
568
|
+
StreamExtractor = Callable[[str], str | None]
|
|
569
|
+
|
|
570
|
+
STREAM_TEXT_EXTRACTORS: dict[str, StreamExtractor] = {
|
|
565
571
|
"claude_code": extract_claude_stream_text,
|
|
566
572
|
"codex": extract_codex_stream_text,
|
|
567
573
|
"cursor": extract_cursor_stream_text,
|
|
@@ -624,7 +630,7 @@ def extract_codex_stream_thinking(line: str) -> str | None:
|
|
|
624
630
|
return None
|
|
625
631
|
|
|
626
632
|
|
|
627
|
-
STREAM_THINKING_EXTRACTORS: dict[str,
|
|
633
|
+
STREAM_THINKING_EXTRACTORS: dict[str, StreamExtractor] = {
|
|
628
634
|
"codex": extract_codex_stream_thinking,
|
|
629
635
|
"cursor": extract_cursor_stream_thinking,
|
|
630
636
|
}
|
|
@@ -640,3 +646,84 @@ def extract_stream_thinking(line: str, backend: str) -> str | None:
|
|
|
640
646
|
if extractor is None:
|
|
641
647
|
return None
|
|
642
648
|
return extractor(line)
|
|
649
|
+
|
|
650
|
+
|
|
651
|
+
# ---------------------------------------------------------------------------
|
|
652
|
+
# Stream tool-use extractors — extract tool invocation names from NDJSON
|
|
653
|
+
# ---------------------------------------------------------------------------
|
|
654
|
+
|
|
655
|
+
|
|
656
|
+
def extract_claude_stream_tool_use(line: str) -> str | None:
|
|
657
|
+
"""Extract tool name from a Claude Code stream-json NDJSON line.
|
|
658
|
+
|
|
659
|
+
Claude Code emits ``content_block_start`` with a ``tool_use`` block
|
|
660
|
+
containing the tool name (e.g. "Read", "Bash", "Write").
|
|
661
|
+
"""
|
|
662
|
+
try:
|
|
663
|
+
event = json.loads(line)
|
|
664
|
+
except json.JSONDecodeError:
|
|
665
|
+
return None
|
|
666
|
+
if not isinstance(event, dict):
|
|
667
|
+
return None
|
|
668
|
+
if event.get("type") == "stream_event":
|
|
669
|
+
event = event.get("event", {})
|
|
670
|
+
if event.get("type") == "content_block_start":
|
|
671
|
+
block = event.get("content_block", {})
|
|
672
|
+
if block.get("type") == "tool_use":
|
|
673
|
+
return block.get("name")
|
|
674
|
+
return None
|
|
675
|
+
|
|
676
|
+
|
|
677
|
+
def extract_cursor_stream_tool_use(line: str) -> str | None:
|
|
678
|
+
"""Extract tool name from a Cursor stream-json NDJSON line.
|
|
679
|
+
|
|
680
|
+
Cursor emits ``{"type": "tool_call", "subtype": "started", ...}``
|
|
681
|
+
when a tool invocation begins. The ``name`` field may or may not
|
|
682
|
+
be present depending on the Cursor version.
|
|
683
|
+
"""
|
|
684
|
+
try:
|
|
685
|
+
event = json.loads(line)
|
|
686
|
+
except json.JSONDecodeError:
|
|
687
|
+
return None
|
|
688
|
+
if not isinstance(event, dict):
|
|
689
|
+
return None
|
|
690
|
+
if event.get("type") == "tool_call" and event.get("subtype") == "started":
|
|
691
|
+
return event.get("name") or "tool"
|
|
692
|
+
return None
|
|
693
|
+
|
|
694
|
+
|
|
695
|
+
def extract_codex_stream_tool_use(line: str) -> str | None:
|
|
696
|
+
"""Extract tool name from a Codex NDJSON line.
|
|
697
|
+
|
|
698
|
+
Codex emits ``item.completed`` events for tool calls with
|
|
699
|
+
``item.type == "function_call"``.
|
|
700
|
+
"""
|
|
701
|
+
try:
|
|
702
|
+
event = json.loads(line)
|
|
703
|
+
except json.JSONDecodeError:
|
|
704
|
+
return None
|
|
705
|
+
if not isinstance(event, dict):
|
|
706
|
+
return None
|
|
707
|
+
if event.get("type") == "item.completed":
|
|
708
|
+
item = event.get("item", {})
|
|
709
|
+
if item.get("type") == "function_call":
|
|
710
|
+
return item.get("name", "tool")
|
|
711
|
+
return None
|
|
712
|
+
|
|
713
|
+
|
|
714
|
+
STREAM_TOOL_USE_EXTRACTORS: dict[str, StreamExtractor] = {
|
|
715
|
+
"claude_code": extract_claude_stream_tool_use,
|
|
716
|
+
"codex": extract_codex_stream_tool_use,
|
|
717
|
+
"cursor": extract_cursor_stream_tool_use,
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
|
|
721
|
+
def extract_stream_tool_use(line: str, backend: str) -> str | None:
|
|
722
|
+
"""Extract tool name from a single NDJSON line for the given backend.
|
|
723
|
+
|
|
724
|
+
Returns the tool name string, or None for non-tool-use events.
|
|
725
|
+
"""
|
|
726
|
+
extractor = STREAM_TOOL_USE_EXTRACTORS.get(backend)
|
|
727
|
+
if extractor is None:
|
|
728
|
+
return None
|
|
729
|
+
return extractor(line)
|
|
@@ -7,7 +7,6 @@ from pathlib import Path
|
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
def build_breakdown_template(feature: str) -> str:
|
|
10
|
-
"""Return a minimal `breakdown.md` template for a feature."""
|
|
11
10
|
return (
|
|
12
11
|
f"# Breakdown: {feature}\n\n"
|
|
13
12
|
"## Design Summary\n"
|
|
@@ -27,7 +26,6 @@ def build_breakdown_template(feature: str) -> str:
|
|
|
27
26
|
|
|
28
27
|
|
|
29
28
|
def read_breakdown(breakdown_path: Path) -> str:
|
|
30
|
-
"""Read the breakdown file text (empty string if missing)."""
|
|
31
29
|
if not breakdown_path.exists():
|
|
32
30
|
return ""
|
|
33
31
|
return breakdown_path.read_text(encoding="utf-8")
|