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.
Files changed (78) hide show
  1. {kingdom_cli-0.1.4/src/kingdom_cli.egg-info → kingdom_cli-0.2.1}/PKG-INFO +45 -23
  2. {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/README.md +44 -22
  3. {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/pyproject.toml +5 -1
  4. {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/src/kingdom/agent.py +92 -5
  5. {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/src/kingdom/breakdown.py +0 -2
  6. {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/src/kingdom/cli.py +1478 -334
  7. {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/src/kingdom/config.py +116 -30
  8. {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/src/kingdom/council/base.py +26 -10
  9. {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/src/kingdom/council/council.py +21 -27
  10. kingdom_cli-0.2.1/src/kingdom/harness.py +729 -0
  11. {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/src/kingdom/parsing.py +0 -22
  12. {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/src/kingdom/session.py +52 -68
  13. kingdom_cli-0.2.1/src/kingdom/skill/SKILL.md +160 -0
  14. kingdom_cli-0.2.1/src/kingdom/skill/__init__.py +0 -0
  15. kingdom_cli-0.2.1/src/kingdom/skill/references/__init__.py +0 -0
  16. kingdom_cli-0.2.1/src/kingdom/skill/references/council.md +69 -0
  17. kingdom_cli-0.2.1/src/kingdom/skill/references/peasants.md +53 -0
  18. kingdom_cli-0.2.1/src/kingdom/skill/references/tickets.md +72 -0
  19. {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/src/kingdom/state.py +100 -31
  20. {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/src/kingdom/synthesis.py +0 -11
  21. {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/src/kingdom/thread.py +26 -10
  22. {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/src/kingdom/ticket.py +48 -186
  23. {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/src/kingdom/tui/app.py +498 -188
  24. kingdom_cli-0.2.1/src/kingdom/tui/chat.tcss +116 -0
  25. {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/src/kingdom/tui/poll.py +23 -9
  26. kingdom_cli-0.2.1/src/kingdom/tui/terminal.py +43 -0
  27. {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/src/kingdom/tui/widgets.py +105 -84
  28. {kingdom_cli-0.1.4 → kingdom_cli-0.2.1/src/kingdom_cli.egg-info}/PKG-INFO +45 -23
  29. {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/src/kingdom_cli.egg-info/SOURCES.txt +9 -0
  30. {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/tests/test_agent.py +67 -0
  31. kingdom_cli-0.2.1/tests/test_cli.py +585 -0
  32. {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/tests/test_cli_council.py +145 -10
  33. {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/tests/test_cli_peasant.py +805 -94
  34. {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/tests/test_cli_status.py +27 -2
  35. {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/tests/test_cli_ticket.py +1101 -10
  36. {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/tests/test_cli_ticket_current.py +41 -2
  37. {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/tests/test_config.py +90 -25
  38. {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/tests/test_council.py +30 -0
  39. {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/tests/test_done.py +57 -0
  40. kingdom_cli-0.2.1/tests/test_harness.py +1396 -0
  41. {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/tests/test_init.py +201 -0
  42. {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/tests/test_session.py +81 -8
  43. {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/tests/test_state.py +174 -0
  44. {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/tests/test_thread.py +149 -0
  45. {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/tests/test_ticket.py +13 -13
  46. {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/tests/test_tui.py +1035 -75
  47. {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/tests/test_tui_clipboard.py +66 -1
  48. {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/tests/test_tui_integration.py +239 -22
  49. {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/tests/test_tui_poll.py +85 -14
  50. {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/tests/test_tui_reply.py +58 -16
  51. kingdom_cli-0.2.1/tests/test_tui_terminal.py +82 -0
  52. kingdom_cli-0.2.1/tests/test_tui_thinking_persistence.py +191 -0
  53. {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/tests/test_tui_widgets.py +109 -2
  54. kingdom_cli-0.1.4/src/kingdom/harness.py +0 -473
  55. kingdom_cli-0.1.4/tests/test_cli.py +0 -206
  56. kingdom_cli-0.1.4/tests/test_harness.py +0 -829
  57. kingdom_cli-0.1.4/tests/test_tui_thinking_persistence.py +0 -159
  58. {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/LICENSE +0 -0
  59. {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/setup.cfg +0 -0
  60. {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/src/kingdom/__init__.py +0 -0
  61. {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/src/kingdom/__main__.py +0 -0
  62. {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/src/kingdom/council/__init__.py +0 -0
  63. {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/src/kingdom/council/bundle.py +0 -0
  64. {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/src/kingdom/council/worker.py +0 -0
  65. {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/src/kingdom/design.py +0 -0
  66. {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/src/kingdom/tui/__init__.py +0 -0
  67. {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/src/kingdom/tui/clipboard.py +0 -0
  68. {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/src/kingdom_cli.egg-info/dependency_links.txt +0 -0
  69. {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/src/kingdom_cli.egg-info/entry_points.txt +0 -0
  70. {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/src/kingdom_cli.egg-info/requires.txt +0 -0
  71. {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/src/kingdom_cli.egg-info/top_level.txt +0 -0
  72. {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/tests/test_breakdown.py +0 -0
  73. {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/tests/test_cli_design_breakdown.py +0 -0
  74. {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/tests/test_cli_whoami.py +0 -0
  75. {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/tests/test_design.py +0 -0
  76. {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/tests/test_parsing.py +0 -0
  77. {kingdom_cli-0.1.4 → kingdom_cli-0.2.1}/tests/test_skill.py +0 -0
  78. {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.4
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
- ## Getting Started
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
- 1. Initialize the project on your current branch:
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
- 2. Configure council agent CLIs in `.kd/config.json` (check effective config with `kd config show`).
68
+ ### Medium workflow (refactors, smaller features)
60
69
 
61
- 3. Design with the council in the TUI:
70
+ Pull tickets into a branch, work them directly or with peasants. No design phase needed.
62
71
 
63
72
  ```bash
64
- kd chat --new
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
- 4. Break your design into markdown tickets:
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 breakdown
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
- 5. Review and refine tickets:
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 ticket list
77
- kd ticket show <id>
98
+ kd init # one-time: create .kd/ directory
99
+ kd start # start a session on the current branch
78
100
  ```
79
101
 
80
- 6. Execute tickets:
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
- ```bash
85
- kd peasant start <id>
86
- kd peasant status
87
- ```
104
+ ## Chat Modes
88
105
 
89
- 7. Close out the session:
106
+ The council chat TUI (`kd chat`) supports four modes, configured via `council.chat.mode`:
90
107
 
91
- ```bash
92
- kd done
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
- ## Getting Started
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
- 1. Initialize the project on your current branch:
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
- 2. Configure council agent CLIs in `.kd/config.json` (check effective config with `kd config show`).
41
+ ### Medium workflow (refactors, smaller features)
33
42
 
34
- 3. Design with the council in the TUI:
43
+ Pull tickets into a branch, work them directly or with peasants. No design phase needed.
35
44
 
36
45
  ```bash
37
- kd chat --new
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
- 4. Break your design into markdown tickets:
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 breakdown
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
- 5. Review and refine tickets:
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 ticket list
50
- kd ticket show <id>
71
+ kd init # one-time: create .kd/ directory
72
+ kd start # start a session on the current branch
51
73
  ```
52
74
 
53
- 6. Execute tickets:
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
- ```bash
58
- kd peasant start <id>
59
- kd peasant status
60
- ```
77
+ ## Chat Modes
61
78
 
62
- 7. Close out the session:
79
+ The council chat TUI (`kd chat`) supports four modes, configured via `council.chat.mode`:
63
80
 
64
- ```bash
65
- kd done
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.4"
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
- RESPONSE_PARSERS: dict[str, Any] = {
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
- COMMAND_BUILDERS: dict[str, Any] = {
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
- STREAM_TEXT_EXTRACTORS: dict[str, Any] = {
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, Any] = {
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")