kingdom-cli 0.3.0__tar.gz → 0.3.2__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.3.0 → kingdom_cli-0.3.2}/PKG-INFO +1 -1
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/pyproject.toml +1 -1
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/cli/__init__.py +60 -1
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/cli/design.py +1 -0
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/cli/helpers.py +10 -5
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/cli/peasant.py +85 -35
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/cli/ticket.py +10 -6
- kingdom_cli-0.3.2/src/kingdom/skill/SKILL.md +257 -0
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/skill/references/council.md +1 -3
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/skill/references/peasants.md +10 -3
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/skill/references/tickets.md +9 -7
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/ticket.py +21 -3
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom_cli.egg-info/PKG-INFO +1 -1
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/tests/test_cli.py +95 -1
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/tests/test_cli_peasant.py +174 -25
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/tests/test_cli_ticket_lifecycle.py +7 -8
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/tests/test_skill.py +51 -1
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/tests/test_ticket.py +36 -8
- kingdom_cli-0.3.0/src/kingdom/skill/SKILL.md +0 -154
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/LICENSE +0 -0
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/README.md +0 -0
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/setup.cfg +0 -0
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/__init__.py +0 -0
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/__main__.py +0 -0
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/agent.py +0 -0
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/breakdown.py +0 -0
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/cli/config.py +0 -0
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/cli/council.py +0 -0
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/cli/display.py +0 -0
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/config.py +0 -0
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/council/__init__.py +0 -0
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/council/base.py +0 -0
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/council/bundle.py +0 -0
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/council/council.py +0 -0
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/council/worker.py +0 -0
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/design.py +0 -0
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/harness.py +0 -0
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/parsing.py +0 -0
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/session.py +0 -0
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/skill/__init__.py +0 -0
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/skill/references/__init__.py +0 -0
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/state.py +0 -0
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/synthesis.py +0 -0
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/thread.py +0 -0
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/tui/__init__.py +0 -0
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/tui/app.py +0 -0
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/tui/chat.tcss +0 -0
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/tui/clipboard.py +0 -0
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/tui/poll.py +0 -0
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/tui/terminal.py +0 -0
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/tui/widgets.py +0 -0
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/worker.py +0 -0
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/worktree.py +0 -0
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom_cli.egg-info/SOURCES.txt +0 -0
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom_cli.egg-info/dependency_links.txt +0 -0
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom_cli.egg-info/entry_points.txt +0 -0
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom_cli.egg-info/requires.txt +0 -0
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom_cli.egg-info/top_level.txt +0 -0
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/tests/test_agent.py +0 -0
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/tests/test_breakdown.py +0 -0
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/tests/test_cli_council.py +0 -0
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/tests/test_cli_design_breakdown.py +0 -0
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/tests/test_cli_status.py +0 -0
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/tests/test_cli_ticket_current.py +0 -0
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/tests/test_cli_ticket_deps.py +0 -0
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/tests/test_cli_ticket_links.py +0 -0
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/tests/test_cli_ticket_list.py +0 -0
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/tests/test_cli_ticket_show.py +0 -0
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/tests/test_cli_ticket_worklog.py +0 -0
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/tests/test_config.py +0 -0
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/tests/test_council.py +0 -0
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/tests/test_design.py +0 -0
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/tests/test_done.py +0 -0
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/tests/test_harness.py +0 -0
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/tests/test_init.py +0 -0
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/tests/test_parsing.py +0 -0
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/tests/test_session.py +0 -0
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/tests/test_state.py +0 -0
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/tests/test_synthesis.py +0 -0
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/tests/test_thread.py +0 -0
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/tests/test_tui.py +0 -0
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/tests/test_tui_clipboard.py +0 -0
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/tests/test_tui_integration.py +0 -0
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/tests/test_tui_poll.py +0 -0
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/tests/test_tui_reply.py +0 -0
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/tests/test_tui_terminal.py +0 -0
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/tests/test_tui_thinking_persistence.py +0 -0
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/tests/test_tui_widgets.py +0 -0
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/tests/test_worker.py +0 -0
- {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/tests/test_worktree.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: kingdom-cli
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
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
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "kingdom-cli"
|
|
3
|
-
version = "0.3.
|
|
3
|
+
version = "0.3.2"
|
|
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"
|
|
@@ -124,7 +124,9 @@ def start(
|
|
|
124
124
|
pass # Fall back to using base as-is
|
|
125
125
|
typer.echo("Auto-initializing .kd/ directory...")
|
|
126
126
|
ensure_base_layout(base)
|
|
127
|
-
|
|
127
|
+
|
|
128
|
+
# Always refresh bundled skill (skips symlinks for dev setups)
|
|
129
|
+
install_skill()
|
|
128
130
|
|
|
129
131
|
# Check for existing current run
|
|
130
132
|
current_path = state_root(base) / "current"
|
|
@@ -386,6 +388,63 @@ def status(
|
|
|
386
388
|
typer.echo(f" {assignee}: {t.id} [{t.status}] {t.title}")
|
|
387
389
|
|
|
388
390
|
|
|
391
|
+
@app.command(help="Upgrade the CLI and refresh skill files.")
|
|
392
|
+
def update() -> None:
|
|
393
|
+
"""Run ``uv tool upgrade kingdom-cli`` then refresh Claude skill files."""
|
|
394
|
+
console = Console()
|
|
395
|
+
|
|
396
|
+
# Step 1: uv tool upgrade kingdom-cli
|
|
397
|
+
typer.echo("Upgrading kingdom-cli...")
|
|
398
|
+
try:
|
|
399
|
+
result = subprocess.run(
|
|
400
|
+
["uv", "tool", "upgrade", "kingdom-cli"],
|
|
401
|
+
capture_output=True,
|
|
402
|
+
text=True,
|
|
403
|
+
timeout=120,
|
|
404
|
+
)
|
|
405
|
+
upgrade_ok = result.returncode == 0
|
|
406
|
+
upgrade_output = result.stdout.strip() or result.stderr.strip()
|
|
407
|
+
except FileNotFoundError:
|
|
408
|
+
upgrade_ok = False
|
|
409
|
+
upgrade_output = "uv not found — install it first (https://docs.astral.sh/uv/)"
|
|
410
|
+
except subprocess.TimeoutExpired:
|
|
411
|
+
upgrade_ok = False
|
|
412
|
+
upgrade_output = "uv tool upgrade timed out"
|
|
413
|
+
|
|
414
|
+
if upgrade_ok:
|
|
415
|
+
styled_echo(f" ✓ {upgrade_output or 'already up to date'}", fg=typer.colors.GREEN)
|
|
416
|
+
else:
|
|
417
|
+
styled_echo(f" ✗ {upgrade_output}", fg=typer.colors.RED)
|
|
418
|
+
|
|
419
|
+
# Step 2: refresh skill files
|
|
420
|
+
typer.echo("Refreshing skill files...")
|
|
421
|
+
skill_target = Path.home() / ".claude" / "skills" / "kingdom"
|
|
422
|
+
if skill_target.is_symlink():
|
|
423
|
+
styled_echo(" ○ Skipped (dev symlink)", fg=typer.colors.YELLOW)
|
|
424
|
+
skill_status = "skipped (dev symlink)"
|
|
425
|
+
else:
|
|
426
|
+
if install_skill():
|
|
427
|
+
styled_echo(" ✓ Skill files refreshed", fg=typer.colors.GREEN)
|
|
428
|
+
skill_status = "refreshed"
|
|
429
|
+
else:
|
|
430
|
+
styled_echo(" ✗ Skill refresh failed (see warning above)", fg=typer.colors.RED)
|
|
431
|
+
skill_status = "failed"
|
|
432
|
+
|
|
433
|
+
# Summary
|
|
434
|
+
typer.echo()
|
|
435
|
+
upgrade_summary = "upgraded" if upgrade_ok else "failed"
|
|
436
|
+
console.print(
|
|
437
|
+
Panel(
|
|
438
|
+
f"CLI: {upgrade_summary} | Skills: {skill_status}",
|
|
439
|
+
title="[bold]kd update[/bold]",
|
|
440
|
+
border_style="green" if upgrade_ok else "yellow",
|
|
441
|
+
)
|
|
442
|
+
)
|
|
443
|
+
|
|
444
|
+
if not upgrade_ok or skill_status == "failed":
|
|
445
|
+
raise typer.Exit(code=1)
|
|
446
|
+
|
|
447
|
+
|
|
389
448
|
@app.command(help="Check config and agent CLIs.")
|
|
390
449
|
def doctor(
|
|
391
450
|
output_json: Annotated[bool, typer.Option("--json", help="Output as JSON.")] = False,
|
|
@@ -78,6 +78,7 @@ def design_show() -> None:
|
|
|
78
78
|
|
|
79
79
|
|
|
80
80
|
@design_app.command("approve", help="Mark the design as approved.")
|
|
81
|
+
@design_app.command("accept", hidden=True)
|
|
81
82
|
def design_approve() -> None:
|
|
82
83
|
"""Set design_approved=true in state.json."""
|
|
83
84
|
base = require_project_root()
|
|
@@ -116,12 +116,14 @@ def ensure_feature_branch(feature: str) -> None:
|
|
|
116
116
|
typer.echo(f"Warning: current branch '{current}' does not match feature '{feature}'.")
|
|
117
117
|
|
|
118
118
|
|
|
119
|
-
def install_skill() ->
|
|
119
|
+
def install_skill() -> bool:
|
|
120
120
|
"""Install the bundled kingdom skill to ~/.claude/skills/kingdom/.
|
|
121
121
|
|
|
122
122
|
Copies SKILL.md and reference files from the package into the Claude
|
|
123
123
|
skills directory. Skips if the target is a symlink (dev setup).
|
|
124
124
|
Warns and continues on permission or filesystem errors.
|
|
125
|
+
|
|
126
|
+
Returns True on success (including symlink skip), False on error.
|
|
125
127
|
"""
|
|
126
128
|
from importlib.resources import as_file, files
|
|
127
129
|
|
|
@@ -130,7 +132,7 @@ def install_skill() -> None:
|
|
|
130
132
|
|
|
131
133
|
# Don't overwrite a dev symlink
|
|
132
134
|
if target.is_symlink():
|
|
133
|
-
return
|
|
135
|
+
return True
|
|
134
136
|
|
|
135
137
|
skill_pkg = files("kingdom.skill")
|
|
136
138
|
|
|
@@ -141,8 +143,11 @@ def install_skill() -> None:
|
|
|
141
143
|
refs_target = target / "references"
|
|
142
144
|
refs_target.mkdir(exist_ok=True)
|
|
143
145
|
refs_pkg = skill_pkg / "references"
|
|
144
|
-
for
|
|
145
|
-
|
|
146
|
-
(
|
|
146
|
+
for item in refs_pkg.iterdir():
|
|
147
|
+
if item.name.endswith(".md"):
|
|
148
|
+
with as_file(item) as src:
|
|
149
|
+
(refs_target / item.name).write_bytes(src.read_bytes())
|
|
147
150
|
except (OSError, RuntimeError) as exc:
|
|
148
151
|
typer.echo(f"Warning: could not install skill ({exc})")
|
|
152
|
+
return False
|
|
153
|
+
return True
|
|
@@ -481,46 +481,96 @@ def peasant_status(
|
|
|
481
481
|
console.print(f"[dim]{summary} — use --all to show[/dim]")
|
|
482
482
|
|
|
483
483
|
|
|
484
|
-
@peasant_app.command("
|
|
485
|
-
def
|
|
484
|
+
@peasant_app.command("show", help="Show structured peasant history.")
|
|
485
|
+
def peasant_show(
|
|
486
486
|
ticket_id: Annotated[str, typer.Argument(help="Ticket ID.")],
|
|
487
|
-
follow: Annotated[bool, typer.Option("--follow", "-f", help="Tail logs continuously.")] = False,
|
|
488
487
|
) -> None:
|
|
489
|
-
"""Show
|
|
490
|
-
|
|
488
|
+
"""Show worklog, agent activity, and commits for a peasant."""
|
|
489
|
+
from kingdom.harness import extract_worklog
|
|
490
|
+
from kingdom.session import get_agent_state
|
|
491
491
|
|
|
492
|
+
ctx = resolve_peasant_context(ticket_id)
|
|
492
493
|
session_name = peasant_session_name(ctx.full_ticket_id)
|
|
493
|
-
peasant_logs_dir = logs_root(ctx.base, ctx.feature) / session_name
|
|
494
|
-
stdout_log = peasant_logs_dir / "stdout.log"
|
|
495
|
-
stderr_log = peasant_logs_dir / "stderr.log"
|
|
496
|
-
|
|
497
|
-
if not peasant_logs_dir.exists():
|
|
498
|
-
print_error(f"No logs found for {ctx.full_ticket_id}. Has the peasant been started?")
|
|
499
|
-
raise typer.Exit(code=1)
|
|
500
|
-
|
|
501
|
-
if follow:
|
|
502
|
-
# Tail both stdout and stderr
|
|
503
|
-
with contextlib.suppress(KeyboardInterrupt):
|
|
504
|
-
files = [str(f) for f in [stdout_log, stderr_log] if f.exists()]
|
|
505
|
-
if files:
|
|
506
|
-
subprocess.run(["tail", "-f", *files])
|
|
507
|
-
else:
|
|
508
|
-
typer.echo("Log files are empty. The peasant may still be starting up.")
|
|
509
|
-
return
|
|
510
|
-
|
|
511
|
-
# Show both stdout and stderr
|
|
512
494
|
console = Console()
|
|
495
|
+
state = get_agent_state(ctx.base, ctx.feature, session_name)
|
|
496
|
+
|
|
497
|
+
# --- Worklog ---
|
|
498
|
+
worklog = extract_worklog(ctx.ticket_path)
|
|
499
|
+
if worklog:
|
|
500
|
+
console.print(Markdown(f"## Worklog\n\n{worklog}"))
|
|
501
|
+
else:
|
|
502
|
+
typer.echo("(no worklog entries)")
|
|
513
503
|
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
504
|
+
# --- Agent activity from agent-live.log ---
|
|
505
|
+
agent_live_log = logs_root(ctx.base, ctx.feature) / session_name / "agent-live.log"
|
|
506
|
+
if agent_live_log.exists() and agent_live_log.stat().st_size > 0:
|
|
507
|
+
# Resolve backend for NDJSON decoding
|
|
508
|
+
agent_backend = ""
|
|
509
|
+
try:
|
|
510
|
+
from kingdom.config import load_config
|
|
511
|
+
|
|
512
|
+
cfg = load_config(ctx.base)
|
|
513
|
+
agent_name = state.agent_backend or "claude"
|
|
514
|
+
agent_def = cfg.agents.get(agent_name)
|
|
515
|
+
if agent_def:
|
|
516
|
+
agent_backend = agent_def.backend
|
|
517
|
+
except Exception:
|
|
518
|
+
pass
|
|
519
|
+
|
|
520
|
+
raw_lines = agent_live_log.read_text(encoding="utf-8", errors="replace").splitlines()
|
|
521
|
+
|
|
522
|
+
# Reassemble NDJSON stream text (flush=True since we're reading after the fact)
|
|
523
|
+
stream_lines, _ = reassemble_stream_text(
|
|
524
|
+
"",
|
|
525
|
+
raw_lines,
|
|
526
|
+
agent_backend,
|
|
527
|
+
max_lines=500,
|
|
528
|
+
max_chars=300,
|
|
529
|
+
flush=True,
|
|
530
|
+
)
|
|
531
|
+
# Also pick up non-NDJSON readable lines
|
|
532
|
+
plain_lines = filter_agent_log_lines(raw_lines, max_lines=500, max_chars=300)
|
|
533
|
+
|
|
534
|
+
# Plain lines are non-JSON human-readable output; stream lines are
|
|
535
|
+
# reassembled NDJSON text fragments. We concatenate them (plain first)
|
|
536
|
+
# because they come from the same log file but are extracted by
|
|
537
|
+
# different parsers — ordering within each group is chronological.
|
|
538
|
+
activity = plain_lines + stream_lines
|
|
539
|
+
if activity:
|
|
540
|
+
console.print(Markdown("## Agent Activity"))
|
|
541
|
+
for line in activity:
|
|
542
|
+
console.print(f" {line}", markup=False)
|
|
543
|
+
else:
|
|
544
|
+
typer.echo("(no agent activity log)")
|
|
517
545
|
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
546
|
+
# --- Commits on the peasant's branch ---
|
|
547
|
+
branch_name = f"ticket/{ctx.full_ticket_id}"
|
|
548
|
+
if state.hand_mode:
|
|
549
|
+
start_sha = state.start_sha
|
|
550
|
+
log_spec = [f"{start_sha}..HEAD"] if start_sha else ["HEAD"]
|
|
551
|
+
else:
|
|
552
|
+
# ctx.feature may be normalized (slashes→dashes); resolve the
|
|
553
|
+
# original git branch name from state.json for a valid ref.
|
|
554
|
+
from kingdom.state import read_json
|
|
521
555
|
|
|
522
|
-
|
|
523
|
-
|
|
556
|
+
st = read_json(branch_root(ctx.base, ctx.feature) / "state.json")
|
|
557
|
+
git_ref = st.get("branch", ctx.feature)
|
|
558
|
+
log_spec = [f"{git_ref}..{branch_name}"]
|
|
559
|
+
try:
|
|
560
|
+
result = subprocess.run(
|
|
561
|
+
["git", "log", "--oneline", *log_spec],
|
|
562
|
+
capture_output=True,
|
|
563
|
+
text=True,
|
|
564
|
+
cwd=str(ctx.base),
|
|
565
|
+
timeout=10,
|
|
566
|
+
)
|
|
567
|
+
commits = result.stdout.strip()
|
|
568
|
+
if result.returncode == 0 and commits:
|
|
569
|
+
console.print(Markdown(f"## Commits\n\n```\n{commits}\n```"))
|
|
570
|
+
else:
|
|
571
|
+
typer.echo("(no commits)")
|
|
572
|
+
except (subprocess.TimeoutExpired, FileNotFoundError):
|
|
573
|
+
typer.echo("(could not read commits)")
|
|
524
574
|
|
|
525
575
|
|
|
526
576
|
FLAG_VERBS: dict[str, str] = {
|
|
@@ -532,7 +582,7 @@ FLAG_VERBS: dict[str, str] = {
|
|
|
532
582
|
}
|
|
533
583
|
|
|
534
584
|
|
|
535
|
-
def filter_agent_log_lines(lines: list[str], max_lines: int = 3, max_chars: int = 200
|
|
585
|
+
def filter_agent_log_lines(lines: list[str], max_lines: int = 3, max_chars: int = 200) -> list[str]:
|
|
536
586
|
"""Filter raw agent log lines to human-readable non-NDJSON content.
|
|
537
587
|
|
|
538
588
|
Strips ANSI escapes, skips JSON and metadata noise, and returns the last
|
|
@@ -823,8 +873,8 @@ def peasant_watch(
|
|
|
823
873
|
# Map status to action command and display label
|
|
824
874
|
status_actions = {
|
|
825
875
|
"needs_king_review": f"kd peasant review {ctx.full_ticket_id}",
|
|
826
|
-
"blocked": f"kd peasant
|
|
827
|
-
"failed": f"kd peasant
|
|
876
|
+
"blocked": f"kd peasant show {ctx.full_ticket_id}",
|
|
877
|
+
"failed": f"kd peasant show {ctx.full_ticket_id}",
|
|
828
878
|
"done": None,
|
|
829
879
|
"stopped": None,
|
|
830
880
|
}
|
|
@@ -342,7 +342,7 @@ def render_ticket_panel(
|
|
|
342
342
|
def ticket_create(
|
|
343
343
|
title: Annotated[str, typer.Argument(help="Ticket title.")],
|
|
344
344
|
description: Annotated[str | None, typer.Option("-d", "--description", help="Ticket description.")] = None,
|
|
345
|
-
priority: Annotated[
|
|
345
|
+
priority: Annotated[str, typer.Option("-p", "--priority", help="Priority (0-3 or p0-p3, 0 is highest).")] = "2",
|
|
346
346
|
ticket_type: Annotated[str, typer.Option("-t", "--type", help="Ticket type (task, bug, feature).")] = "task",
|
|
347
347
|
backlog: Annotated[bool, typer.Option("--backlog", help="Create in backlog instead of current branch.")] = False,
|
|
348
348
|
dep: Annotated[list[str] | None, typer.Option("--dep", help="Ticket ID(s) this depends on.")] = None,
|
|
@@ -355,10 +355,14 @@ def ticket_create(
|
|
|
355
355
|
|
|
356
356
|
base = require_project_root()
|
|
357
357
|
|
|
358
|
-
#
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
358
|
+
# Parse and validate priority (accepts 0-3, p0-p3, P0-P3)
|
|
359
|
+
from kingdom.ticket import clamp_priority
|
|
360
|
+
|
|
361
|
+
try:
|
|
362
|
+
priority_int = clamp_priority(priority)
|
|
363
|
+
except ValueError as e:
|
|
364
|
+
typer.echo(f"Error: {e}", err=True)
|
|
365
|
+
raise typer.Exit(code=1) from None
|
|
362
366
|
|
|
363
367
|
# Ensure base layout exists
|
|
364
368
|
ensure_base_layout(base)
|
|
@@ -402,7 +406,7 @@ def ticket_create(
|
|
|
402
406
|
links=[],
|
|
403
407
|
created=datetime.now(UTC),
|
|
404
408
|
type=ticket_type,
|
|
405
|
-
priority=
|
|
409
|
+
priority=priority_int,
|
|
406
410
|
title=title,
|
|
407
411
|
body=body,
|
|
408
412
|
parent=resolved_parent,
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: kingdom
|
|
3
|
+
description: >
|
|
4
|
+
Multi-agent design and development workflow using the kd CLI.
|
|
5
|
+
Manages design, breakdown, tickets, council consultation (multi-model
|
|
6
|
+
perspectives), and peasant workers. Use when starting a new feature
|
|
7
|
+
branch, breaking down work into tickets, consulting multiple AI models
|
|
8
|
+
for design decisions, or managing development workflow with kd commands.
|
|
9
|
+
Requires the kd CLI to be installed and on PATH.
|
|
10
|
+
compatibility: Requires Python 3.10+, kd CLI (uv tool install kingdom), git
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
You assist the developer (the "King") using the `kd` CLI. There are two common workflows — pick the one that fits.
|
|
14
|
+
|
|
15
|
+
All `kd` commands are safe to run. Use them proactively — don't wait to be told.
|
|
16
|
+
|
|
17
|
+
## Ticket-First Reflex
|
|
18
|
+
|
|
19
|
+
Every time the King says something, your first thought should be: **does this need a ticket?**
|
|
20
|
+
|
|
21
|
+
Bug report, feature idea, UX complaint, missing behavior, scope change — if there's work to be done, capture it in a ticket *immediately*. Don't start exploring, don't start coding, don't ask follow-up questions about the implementation. Get the ticket created first, then proceed. The ticket is how work gets tracked, prioritized, and not forgotten.
|
|
22
|
+
|
|
23
|
+
This applies even mid-conversation. If the King mentions a problem in passing while you're working on something else, create a backlog ticket on the spot (`kd tk create --backlog "..."`). If it's the main thing they're asking about, create it and start working it. Either way: ticket first, always.
|
|
24
|
+
|
|
25
|
+
The only exception is when you genuinely don't have enough information to write a meaningful title and description — then ask clarifying questions, but only the minimum needed to create the ticket.
|
|
26
|
+
|
|
27
|
+
## When to Reach for `kd`
|
|
28
|
+
|
|
29
|
+
Recognize these cues in conversation and translate them into the right `kd` action.
|
|
30
|
+
|
|
31
|
+
**King reports a bug with clear details.** Extract the actual vs expected behavior and open a ticket:
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
kd tk create -t bug -d "web app shows raw HTML, expected rendered markdown" "Markdown rendering broken in preview pane"
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Only include flags the King actually provided — don't invent a priority or tags if they weren't mentioned.
|
|
38
|
+
|
|
39
|
+
**King describes a problem vaguely.** "Hey, something's wrong with the backend" is not enough to create a ticket. Ask clarifying questions first:
|
|
40
|
+
|
|
41
|
+
- What's happening? (actual behavior)
|
|
42
|
+
- What should happen? (expected behavior)
|
|
43
|
+
- Where? (which page, endpoint, flow)
|
|
44
|
+
- Can you reproduce it?
|
|
45
|
+
|
|
46
|
+
Then structure the answers into a ticket:
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
kd tk create -t bug -d "Login endpoint returns 500 when email has a plus sign. Expected: normal login. Repro: try user+test@example.com" "Login fails for plus-sign emails"
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**King wants to track an idea for later.** Suggest a backlog ticket:
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
kd tk create --backlog "Add dark mode support"
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**King says work is done.** Close the ticket (after verifying acceptance criteria are met and tests pass):
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
kd tk close ab12
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**King is stuck on a design choice.** Suggest consulting the council:
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
kd council ask "Should we use WebSockets or SSE for real-time updates? We need low latency but also need to work behind corporate proxies."
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**King says "ask the council."** Decide whether to continue an existing thread or start a new one. If the conversation is a continuation of an active design discussion, default to the current thread — just run `kd council ask "follow-up question"`. If the topic has shifted materially or the old thread is resolved, start fresh with `kd council ask --new-thread "new topic"`. When ambiguous, check existing threads with `kd council list` or `kd council show <thread>`, or ask the King a brief clarifying question before running the command.
|
|
71
|
+
|
|
72
|
+
**King wants to note progress.** Log it against the active ticket:
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
kd tk log ab12 "Finished the API refactor, all endpoint tests passing"
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**King wants parallel execution.** Start a peasant worker:
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
kd peasant start ab12
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Workflow A: New Feature (design-first)
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
git checkout -b <branch>
|
|
88
|
+
kd start # init branch
|
|
89
|
+
kd design show # view/iterate on design doc
|
|
90
|
+
# iterate with council, co-author the design with the King
|
|
91
|
+
kd design approve
|
|
92
|
+
# create tickets from the design (via skill or manually with kd tk create)
|
|
93
|
+
# execute tickets (see below)
|
|
94
|
+
kd done # archive branch before merging PR
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
The design phase is collaborative: the King drives direction, you draft content, the council reviews. Use `kd council ask` to get multi-model feedback during design.
|
|
98
|
+
|
|
99
|
+
## Workflow B: Backlog Sprint (execution-first)
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
git checkout -b <branch>
|
|
103
|
+
kd start
|
|
104
|
+
kd tk pull <id> <id> ... # pull existing backlog tickets in
|
|
105
|
+
# execute tickets (see below)
|
|
106
|
+
kd done
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
No design phase — tickets are already scoped. If a ticket is ambiguous, escalate to the King or ask the council. Don't guess.
|
|
110
|
+
|
|
111
|
+
## Executing Tickets
|
|
112
|
+
|
|
113
|
+
After either workflow produces tickets, the King (or the coding agent they're working with, sometimes called the "Hand") chooses how to work through them:
|
|
114
|
+
|
|
115
|
+
**Option A: Work tickets directly.** The King and their coding agent work tickets one at a time, following the Working Tickets guidelines below.
|
|
116
|
+
|
|
117
|
+
**Option B: Spin up peasants.** `kd peasant start <id>` spawns a background worker in its own worktree. Each peasant works one ticket at a time — the council reviews automatically, the peasant iterates until approved. Spin up multiple peasants for parallel throughput. The King monitors with `kd peasant status` and `kd peasant watch <id>`. See [peasant reference](references/peasants.md) for details.
|
|
118
|
+
|
|
119
|
+
Use A when the King wants to be hands-on. Use B for throughput on well-scoped tickets. Both can be mixed — the King might work one tricky ticket directly while peasants handle the straightforward ones.
|
|
120
|
+
|
|
121
|
+
## Working Tickets
|
|
122
|
+
|
|
123
|
+
This applies regardless of execution mode.
|
|
124
|
+
|
|
125
|
+
- **One at a time per worker**: `kd tk start <id>` → do the work → `kd tk close <id>` → commit → next ticket.
|
|
126
|
+
- **Worklog**: append progress notes to the ticket's `## Worklog` section as you go. Log what you're doing, what you found, commands and results, decisions and why. The King reads these to stay informed — don't make them ask.
|
|
127
|
+
- **Acceptance criteria**: only close a ticket when all acceptance criteria are met and the full test suite is green.
|
|
128
|
+
- **Decisions**: ask the King or consult the council (`kd council ask "..."`) for difficult design decisions — don't guess. Never silently resolve ambiguity on architectural, product, or UX tradeoffs.
|
|
129
|
+
- **For bugs: test BEFORE you fix!** Write a test that fails in the current state, fix it, then verify.
|
|
130
|
+
- **Bugs from this branch**: immediately write a failing test that reproduces it, then fix.
|
|
131
|
+
- **Bugs from elsewhere**: if not blocking, create a backlog ticket (`kd tk create --backlog "..."`) and move on.
|
|
132
|
+
- **Commit often**: commit code changes as you go. Commit `.kd/` changes (ticket state, worklogs) alongside code.
|
|
133
|
+
- **One-off tests**: write a script or temp file to test an end-to-end flow with real data. Then consider if it can be an automated integration test.
|
|
134
|
+
|
|
135
|
+
## Automatic Worklog Updates
|
|
136
|
+
|
|
137
|
+
Proactively `kd tk log` whenever a durable state change occurs. The King should never have to say "update the worklog." Log against the active ticket without asking which ticket — you know what you're working on.
|
|
138
|
+
|
|
139
|
+
The threshold is **durable state change**, not every chat turn. If future-you or another agent would need this fact, log it now.
|
|
140
|
+
|
|
141
|
+
**Decision made** — King says "let's go with raw TypeScript over React":
|
|
142
|
+
|
|
143
|
+
```
|
|
144
|
+
kd tk log ab12 "Decision: raw TypeScript, not React — King's call
|
|
145
|
+
|
|
146
|
+
Rationale: we want full control over the build pipeline without
|
|
147
|
+
React's abstraction layer. This means we'll need to handle routing
|
|
148
|
+
and state management ourselves, but the bundle size stays minimal
|
|
149
|
+
and we avoid the React upgrade treadmill.
|
|
150
|
+
|
|
151
|
+
Affected tickets: may need to revisit ab34 (component library choice)"
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
**Root cause discovered** — you trace a bug to an unexpected place:
|
|
155
|
+
|
|
156
|
+
```
|
|
157
|
+
kd tk log ab12 "Root cause: stale cache in render_template()
|
|
158
|
+
|
|
159
|
+
The DB query was a red herring — render_template() caches the compiled
|
|
160
|
+
template and never invalidates when the schema changes. Found by tracing
|
|
161
|
+
the actual SQL output, which was correct. The fix is to add a cache key
|
|
162
|
+
that includes the schema version.
|
|
163
|
+
|
|
164
|
+
Affected files: src/kingdom/render.py, src/kingdom/cache.py"
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
**Scope change** — work expands or shifts mid-ticket:
|
|
168
|
+
|
|
169
|
+
```
|
|
170
|
+
kd tk log ab12 "Scope change: also need to update the migration script
|
|
171
|
+
|
|
172
|
+
The original ticket only covered the model changes, but the migration
|
|
173
|
+
script hardcodes the old column names. Without updating it, existing
|
|
174
|
+
installs will break on upgrade. Adding migration updates to this ticket
|
|
175
|
+
rather than splitting — it's the same logical change."
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
**Blocker cleared** — something that was stuck is now unblocked:
|
|
179
|
+
|
|
180
|
+
```
|
|
181
|
+
kd tk log ab12 "Unblocked: upstream API now returns correct schema
|
|
182
|
+
|
|
183
|
+
Tested against staging at 14:30 — the v2 endpoint returns the
|
|
184
|
+
'metadata' field we need. Removing the workaround shim and switching
|
|
185
|
+
to direct parsing. This also unblocks ticket cd56 (metadata display)."
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Rich, multi-line log entries are encouraged — a worklog entry is a place to dump everything you know in the moment, not a tweet.
|
|
189
|
+
|
|
190
|
+
## Workflow Reflexes
|
|
191
|
+
|
|
192
|
+
Decision patterns to get right:
|
|
193
|
+
|
|
194
|
+
- **Default to the active ticket.** If you're working a ticket, that's the target for `kd tk log`, `kd tk close`, and status updates. Don't ask "which ticket?" when context is obvious.
|
|
195
|
+
- **Move vs create.** "This work belongs somewhere else" → `kd tk move ab12 --to backlog`. "This is a separate problem I just noticed" → `kd tk create --backlog "..."`. Log is for new information about the current work; create/move is for separate work.
|
|
196
|
+
- **Council follow-through.** After `kd council ask`, summarize the key perspectives and disagreements for the King, log the decision that came out of it (`kd tk log`), and move on. Don't dump the raw council response and wait for the King to synthesize.
|
|
197
|
+
- **Close-out hygiene.** Before `kd tk close`: update the worklog with what changed and how it was verified, confirm tests pass. Closing is the last step after evidence is captured, not a declaration of intent.
|
|
198
|
+
|
|
199
|
+
## When `kd` Says No
|
|
200
|
+
|
|
201
|
+
When a command fails, diagnose before retrying. Never silently drop a failed operation.
|
|
202
|
+
|
|
203
|
+
- **`kd tk close` fails — deps not met.** Inspect with `kd tk deps tree <id>`, figure out what's blocking, work the blocker or tell the King.
|
|
204
|
+
- **No obvious active ticket.** Run `kd tk current` to check. Don't guess.
|
|
205
|
+
- **`kd council ask` times out or errors.** Run `kd council retry` to re-query failed members. Don't re-run the same ask from scratch.
|
|
206
|
+
- **`kd peasant start` fails — ticket is in_review or closed.** The ticket needs to be reopened or the review resolved before a peasant can work it. Check ticket status with `kd tk show <id>` and tell the King.
|
|
207
|
+
- **Peasant seems stuck.** Check `kd peasant status` and `kd peasant show <id>` before escalating to the King.
|
|
208
|
+
- **Council query sent — want to check on it.** Run `kd council show` to read the thread and see which members have responded. Don't re-run `kd council ask` with the same prompt.
|
|
209
|
+
|
|
210
|
+
## Council
|
|
211
|
+
|
|
212
|
+
- `kd council ask "prompt"` queries all members for independent perspectives.
|
|
213
|
+
- Use it proactively at decision points, not just when stuck.
|
|
214
|
+
- Present each member's perspective distinctly — don't flatten disagreements. Summarize the key perspectives and disagreements for the King, and preserve the tensions. Link or reference the thread for full detail.
|
|
215
|
+
- See [council reference](references/council.md) for threading, async flags, and session management.
|
|
216
|
+
|
|
217
|
+
## Command Quick-Ref
|
|
218
|
+
|
|
219
|
+
```bash
|
|
220
|
+
kd start / status / done # branch lifecycle
|
|
221
|
+
kd design show / design approve # design doc
|
|
222
|
+
kd tk list / show / list --ready # inspect tickets
|
|
223
|
+
kd tk start <id> / close <id> # work a ticket
|
|
224
|
+
kd tk current # show active ticket
|
|
225
|
+
kd tk pull <id>... # pull from backlog
|
|
226
|
+
kd tk log <id> "message" # append to worklog
|
|
227
|
+
kd tk deps add/remove/tree/cycle # manage dependencies
|
|
228
|
+
kd peasant status / watch <id> # monitor peasants
|
|
229
|
+
kd peasant review / accept / reject # review cycle
|
|
230
|
+
kd peasant msg / read # communicate with peasants
|
|
231
|
+
|
|
232
|
+
# ticket creation — use only the flags the King provides
|
|
233
|
+
kd tk create "title" # minimal
|
|
234
|
+
kd tk create -t bug -d "details" "title" # bug with description
|
|
235
|
+
kd tk create -t bug -p 2 -d "details" "title" # with priority (only if King specified it)
|
|
236
|
+
kd tk create --backlog "title" # backlog ticket
|
|
237
|
+
kd tk create --backlog --ac "criterion" "title" # backlog with acceptance criteria
|
|
238
|
+
|
|
239
|
+
# council — threading and targeting
|
|
240
|
+
kd council ask "prompt" # all members, current thread
|
|
241
|
+
kd council ask --to claude "prompt" # single member
|
|
242
|
+
kd council ask --new-thread "prompt" # start fresh thread
|
|
243
|
+
kd council list / show <thread> # inspect threads
|
|
244
|
+
kd council retry # re-query failed members
|
|
245
|
+
|
|
246
|
+
# peasants — launch modes
|
|
247
|
+
kd peasant start <id> # worktree (parallel)
|
|
248
|
+
kd peasant start <id> --hand # serial in cwd
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
Run `kd <command> --help` for flags and options not listed here.
|
|
252
|
+
|
|
253
|
+
## References
|
|
254
|
+
|
|
255
|
+
- [Council patterns and usage](references/council.md)
|
|
256
|
+
- [Ticket lifecycle and management](references/tickets.md)
|
|
257
|
+
- [Peasant workers and worktrees](references/peasants.md)
|
|
@@ -48,9 +48,7 @@ kd council watch <thread-id> # watch for incoming responses
|
|
|
48
48
|
|
|
49
49
|
## Reading Responses
|
|
50
50
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
If the King asks for a summary, you can highlight key points of agreement and disagreement, but always reference the original responses.
|
|
51
|
+
Summarize council responses for the King when they inform the active decision. Present the main agreements, disagreements, and any recommendation, then point to the thread for full context. Preserve the tensions — don't flatten dissent into false consensus.
|
|
54
52
|
|
|
55
53
|
## Async and Streaming
|
|
56
54
|
|
|
@@ -19,7 +19,8 @@ kd peasant start <ticket-id> --agent codex
|
|
|
19
19
|
|
|
20
20
|
```bash
|
|
21
21
|
kd peasant status # show all active peasants
|
|
22
|
-
kd peasant
|
|
22
|
+
kd peasant watch <id> # live-follow a peasant's progress
|
|
23
|
+
kd peasant show <id> # view structured peasant history
|
|
23
24
|
kd peasant stop <id> # stop a running peasant
|
|
24
25
|
```
|
|
25
26
|
|
|
@@ -44,6 +45,12 @@ kd peasant clean <id> # remove worktree when done
|
|
|
44
45
|
- **Hand mode** for sequential work that needs the full repo context
|
|
45
46
|
- **Multiple peasants** can run simultaneously on different tickets in worktree mode
|
|
46
47
|
|
|
47
|
-
##
|
|
48
|
+
## Reviewing Peasant Work
|
|
48
49
|
|
|
49
|
-
|
|
50
|
+
When a peasant finishes its ticket, review and accept or reject:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
kd peasant review <id> # review completed work (shows diff + worklog)
|
|
54
|
+
kd peasant accept <id> # accept work and close the ticket
|
|
55
|
+
kd peasant reject <id> # reject with feedback — peasant iterates
|
|
56
|
+
```
|