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.
Files changed (90) hide show
  1. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/PKG-INFO +1 -1
  2. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/pyproject.toml +1 -1
  3. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/cli/__init__.py +60 -1
  4. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/cli/design.py +1 -0
  5. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/cli/helpers.py +10 -5
  6. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/cli/peasant.py +85 -35
  7. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/cli/ticket.py +10 -6
  8. kingdom_cli-0.3.2/src/kingdom/skill/SKILL.md +257 -0
  9. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/skill/references/council.md +1 -3
  10. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/skill/references/peasants.md +10 -3
  11. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/skill/references/tickets.md +9 -7
  12. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/ticket.py +21 -3
  13. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom_cli.egg-info/PKG-INFO +1 -1
  14. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/tests/test_cli.py +95 -1
  15. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/tests/test_cli_peasant.py +174 -25
  16. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/tests/test_cli_ticket_lifecycle.py +7 -8
  17. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/tests/test_skill.py +51 -1
  18. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/tests/test_ticket.py +36 -8
  19. kingdom_cli-0.3.0/src/kingdom/skill/SKILL.md +0 -154
  20. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/LICENSE +0 -0
  21. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/README.md +0 -0
  22. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/setup.cfg +0 -0
  23. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/__init__.py +0 -0
  24. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/__main__.py +0 -0
  25. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/agent.py +0 -0
  26. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/breakdown.py +0 -0
  27. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/cli/config.py +0 -0
  28. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/cli/council.py +0 -0
  29. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/cli/display.py +0 -0
  30. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/config.py +0 -0
  31. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/council/__init__.py +0 -0
  32. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/council/base.py +0 -0
  33. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/council/bundle.py +0 -0
  34. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/council/council.py +0 -0
  35. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/council/worker.py +0 -0
  36. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/design.py +0 -0
  37. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/harness.py +0 -0
  38. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/parsing.py +0 -0
  39. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/session.py +0 -0
  40. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/skill/__init__.py +0 -0
  41. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/skill/references/__init__.py +0 -0
  42. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/state.py +0 -0
  43. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/synthesis.py +0 -0
  44. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/thread.py +0 -0
  45. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/tui/__init__.py +0 -0
  46. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/tui/app.py +0 -0
  47. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/tui/chat.tcss +0 -0
  48. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/tui/clipboard.py +0 -0
  49. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/tui/poll.py +0 -0
  50. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/tui/terminal.py +0 -0
  51. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/tui/widgets.py +0 -0
  52. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/worker.py +0 -0
  53. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom/worktree.py +0 -0
  54. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom_cli.egg-info/SOURCES.txt +0 -0
  55. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom_cli.egg-info/dependency_links.txt +0 -0
  56. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom_cli.egg-info/entry_points.txt +0 -0
  57. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom_cli.egg-info/requires.txt +0 -0
  58. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/src/kingdom_cli.egg-info/top_level.txt +0 -0
  59. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/tests/test_agent.py +0 -0
  60. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/tests/test_breakdown.py +0 -0
  61. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/tests/test_cli_council.py +0 -0
  62. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/tests/test_cli_design_breakdown.py +0 -0
  63. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/tests/test_cli_status.py +0 -0
  64. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/tests/test_cli_ticket_current.py +0 -0
  65. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/tests/test_cli_ticket_deps.py +0 -0
  66. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/tests/test_cli_ticket_links.py +0 -0
  67. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/tests/test_cli_ticket_list.py +0 -0
  68. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/tests/test_cli_ticket_show.py +0 -0
  69. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/tests/test_cli_ticket_worklog.py +0 -0
  70. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/tests/test_config.py +0 -0
  71. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/tests/test_council.py +0 -0
  72. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/tests/test_design.py +0 -0
  73. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/tests/test_done.py +0 -0
  74. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/tests/test_harness.py +0 -0
  75. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/tests/test_init.py +0 -0
  76. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/tests/test_parsing.py +0 -0
  77. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/tests/test_session.py +0 -0
  78. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/tests/test_state.py +0 -0
  79. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/tests/test_synthesis.py +0 -0
  80. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/tests/test_thread.py +0 -0
  81. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/tests/test_tui.py +0 -0
  82. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/tests/test_tui_clipboard.py +0 -0
  83. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/tests/test_tui_integration.py +0 -0
  84. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/tests/test_tui_poll.py +0 -0
  85. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/tests/test_tui_reply.py +0 -0
  86. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/tests/test_tui_terminal.py +0 -0
  87. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/tests/test_tui_thinking_persistence.py +0 -0
  88. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/tests/test_tui_widgets.py +0 -0
  89. {kingdom_cli-0.3.0 → kingdom_cli-0.3.2}/tests/test_worker.py +0 -0
  90. {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.0
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.0"
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
- install_skill()
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() -> None:
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 name in ("council.md", "peasants.md", "tickets.md"):
145
- with as_file(refs_pkg / name) as src:
146
- (refs_target / name).write_bytes(src.read_bytes())
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("logs", help="Show peasant logs.")
485
- def peasant_logs(
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 stdout/stderr logs for a peasant."""
490
- ctx = resolve_peasant_context(ticket_id)
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
- if stdout_log.exists() and stdout_log.stat().st_size > 0:
515
- content = stdout_log.read_text(encoding="utf-8")
516
- console.print(Markdown(f"## stdout\n\n```\n{content}\n```"))
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
- if stderr_log.exists() and stderr_log.stat().st_size > 0:
519
- content = stderr_log.read_text(encoding="utf-8")
520
- console.print(Markdown(f"## stderr\n\n```\n{content}\n```"))
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
- if not (stdout_log.exists() or stderr_log.exists()):
523
- typer.echo("Log files are empty. The peasant may still be starting up.")
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, backend: str = "") -> list[str]:
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 logs {ctx.full_ticket_id}",
827
- "failed": f"kd peasant logs {ctx.full_ticket_id}",
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[int, typer.Option("-p", "--priority", help="Priority (0-3, 0 is highest).")] = 2,
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
- # Validate priority range (0-3)
359
- if priority < 0 or priority > 3:
360
- sys.stderr.write(f"Warning: Priority {priority} outside valid range (0-3), clamping.\n")
361
- priority = max(0, min(3, priority))
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=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
- **Do not synthesize or summarize council responses for the King.** Point them to the thread and let them read directly. The council provides perspectives — the King decides.
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 logs <id> # view peasant output
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
- ## Autonomous Work
48
+ ## Reviewing Peasant Work
48
49
 
49
- The worker agent loop is invoked internally by `kd peasant start` via `python -m kingdom.worker`.
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
+ ```