diff --git a/.claude/PRPs/issues/issue-520.md b/.claude/PRPs/issues/issue-520.md new file mode 100644 index 00000000..ac72c6e9 --- /dev/null +++ b/.claude/PRPs/issues/issue-520.md @@ -0,0 +1,255 @@ +# Investigation: Rename *Manager types to domain-role names + +**Issue**: #520 (https://github.com/Wirasm/kild/issues/520) +**Type**: REFACTOR +**Investigated**: 2026-03-03 + +### Assessment + +| Metric | Value | Reasoning | +| ---------- | ------ | --------------------------------------------------------------------------------------------- | +| Priority | LOW | Naming convention violation only — no behavior change, no user-facing impact, not blocking | +| Complexity | MEDIUM | 4 types across 3 crates, ~15 files total, plus CLAUDE.md and AGENTS.md documentation updates | +| Confidence | HIGH | Pure rename — all usages enumerated, no serde/string-literal/trait-bound complications | + +--- + +## Problem Statement + +Four types use the vague `Manager` suffix, violating the Code Naming Contract which requires types to reflect domain role, not implementation detail. The types are `SessionManager`, `PtyManager`, `ProjectManager`, and `TeamManager`. + +--- + +## Analysis + +### Change Rationale + +The CLAUDE.md Code Naming Contract states: "Name types and modules by domain role, not implementation detail — e.g. `GhForgeBackend`, `GhosttyTerminalBackend` over vague names like `Manager` or `Helper`." These four types predate this convention and need renaming for consistency. + +### Rename Map + +| Current | New | Crate | File | +| ------------------ | -------------------- | ------------ | ------------------------------- | +| `SessionManager` | `DaemonSessionStore` | kild-daemon | `session/manager.rs:21` | +| `PtyManager` | `PtyStore` | kild-daemon | `pty/manager.rs:110` | +| `ProjectManager` | `ProjectRegistry` | kild-core | `projects/manager.rs:12` | +| `TeamManager` | `TeamStore` | kild-ui | `teams/state.rs:12` | + +### Key Properties (Safe Rename) + +All four types share properties that make this a safe mechanical rename: + +- **No serde**: None are serialized/deserialized — names don't appear in wire formats +- **No string literals**: Names don't appear in log messages or error strings +- **No trait bounds**: None are used as generic type parameters or trait constraints +- **No type aliases**: No aliases or shims exist for any of these types +- **Crate-internal** (3 of 4): `SessionManager`, `PtyManager`, `TeamManager` are all crate-internal +- **Single public API**: Only `ProjectManager` crosses crate boundaries (`kild_core::ProjectManager` → `kild-ui`) + +### Affected Files + +#### SessionManager → DaemonSessionStore + +| File | Lines | Action | Description | +| ---------------------------------------------- | ---------------- | ------ | ----------------------------------------------- | +| `crates/kild-daemon/src/session/manager.rs` | 13-28, 30, 473+ | UPDATE | Struct def, impl block, doc comments, tests | +| `crates/kild-daemon/src/session/mod.rs` | 4 | UPDATE | Re-export | +| `crates/kild-daemon/src/server/mod.rs` | 17, 79, 198 | UPDATE | Import, construction, function param | +| `crates/kild-daemon/src/server/connection.rs` | 13, 25, 109, 389 | UPDATE | Import, function params, comment | + +#### PtyManager → PtyStore + +| File | Lines | Action | Description | +| ---------------------------------------------- | ---------------- | ------ | ----------------------------------------------- | +| `crates/kild-daemon/src/pty/manager.rs` | 13, 110, 114, 281, tests | UPDATE | Doc comment, struct def, impl, Default impl, tests | +| `crates/kild-daemon/src/pty/mod.rs` | 4 | UPDATE | Re-export | +| `crates/kild-daemon/src/session/manager.rs` | 8, 15, 23, 37 | UPDATE | Import, doc comment, field type, construction | + +#### ProjectManager → ProjectRegistry + +| File | Lines | Action | Description | +| ---------------------------------------------- | ---------------- | ------ | ----------------------------------------------- | +| `crates/kild-core/src/projects/manager.rs` | 12, 19, tests | UPDATE | Struct def, impl block, test constructors | +| `crates/kild-core/src/projects/types.rs` | 105 | UPDATE | Doc comment intra-doc link | +| `crates/kild-core/src/projects/mod.rs` | 8 | UPDATE | Re-export | +| `crates/kild-core/src/lib.rs` | 54 | UPDATE | Crate-level re-export | +| `crates/kild-ui/src/state/app_state/state.rs` | 2, 29, 58, 410, 467 | UPDATE | Import, field type, constructions | + +#### TeamManager → TeamStore + +| File | Lines | Action | Description | +| ---------------------------------------------- | ---------------- | ------ | ----------------------------------------------- | +| `crates/kild-ui/src/teams/state.rs` | 12, 21 | UPDATE | Struct def, impl block | +| `crates/kild-ui/src/teams/mod.rs` | 9 | UPDATE | Re-export | +| `crates/kild-ui/src/views/main_view/main_view_def.rs` | 59, 193, 257 | UPDATE | Field type, comment, construction | +| `crates/kild-ui/src/views/dashboard_view.rs` | 24 | UPDATE | Function param type | +| `crates/kild-ui/src/views/sidebar.rs` | 30 | UPDATE | Function param type | + +#### Documentation + +| File | Action | Description | +| ---------------------------------------------- | ------ | ----------------------------------------------- | +| `CLAUDE.md` | UPDATE | Lines ~170, ~180, ~181: daemon module descriptions | +| `AGENTS.md` | UPDATE | Lines ~249, ~250: daemon module descriptions | + +--- + +## Implementation Plan + +### Step 1: Rename PtyManager → PtyStore + +**File**: `crates/kild-daemon/src/pty/manager.rs` + +Replace all occurrences of `PtyManager` with `PtyStore` in: +- Struct definition (line 110) +- Impl block (line 114) +- Default impl (line 281) +- Doc comment at line 13 referencing `SessionManager` (leave that reference — it gets renamed in Step 2) +- All test functions using `PtyManager::new()` + +**File**: `crates/kild-daemon/src/pty/mod.rs` +- Update re-export at line 4 + +**Why**: PtyManager is a leaf dependency — renaming it first avoids intermediate broken states since SessionManager depends on it. + +### Step 2: Rename SessionManager → DaemonSessionStore + +**File**: `crates/kild-daemon/src/session/manager.rs` +- Struct definition (line 21) +- Impl block (line 30) +- Doc comments (lines 13-19) +- Import of PtyStore (already renamed in Step 1, update the doc comment at line 15) +- All test functions + +**File**: `crates/kild-daemon/src/session/mod.rs` +- Update re-export at line 4 + +**File**: `crates/kild-daemon/src/server/mod.rs` +- Update import (line 17) +- Update construction (line 79) +- Update function param (line 198) + +**File**: `crates/kild-daemon/src/server/connection.rs` +- Update import (line 13) +- Update function params (lines 25, 109) +- Update comment (line 389) + +**Why**: SessionManager depends on PtyManager (now PtyStore) — rename after PtyStore to keep intermediate states compilable. + +### Step 3: Rename ProjectManager → ProjectRegistry + +**File**: `crates/kild-core/src/projects/manager.rs` +- Struct definition (line 12) +- Impl block (line 19) +- All test functions + +**File**: `crates/kild-core/src/projects/types.rs` +- Update doc comment intra-doc link (line 105) + +**File**: `crates/kild-core/src/projects/mod.rs` +- Update re-export (line 8) + +**File**: `crates/kild-core/src/lib.rs` +- Update crate-level re-export (line 54) + +**File**: `crates/kild-ui/src/state/app_state/state.rs` +- Update import (line 2) +- Update field type (line 29) +- Update all constructions (lines 58, 410, 467) + +**Why**: This is the only cross-crate rename (kild-core public API consumed by kild-ui). Update both crates together. + +### Step 4: Rename TeamManager → TeamStore + +**File**: `crates/kild-ui/src/teams/state.rs` +- Struct definition (line 12) +- Impl block (line 21) + +**File**: `crates/kild-ui/src/teams/mod.rs` +- Update re-export (line 9) + +**File**: `crates/kild-ui/src/views/main_view/main_view_def.rs` +- Update field type (line 59) +- Update comment (line 193) +- Update construction (line 257) + +**File**: `crates/kild-ui/src/views/dashboard_view.rs` +- Update function param type (line 24) + +**File**: `crates/kild-ui/src/views/sidebar.rs` +- Update function param type (line 30) + +**Why**: Entirely kild-ui internal — no cross-crate impact. + +### Step 5: Update Documentation + +**File**: `CLAUDE.md` +- Update daemon module description lines (~170, ~180, ~181) to use new names + +**File**: `AGENTS.md` +- Update daemon module description lines (~249, ~250) to use new names + +**Why**: Documentation must match code to avoid confusion. + +--- + +## Patterns to Follow + +Each rename is a mechanical find-and-replace within the affected files. Use `replace_all` for each type name within each file. No behavior changes, no new code, no removed code beyond the name itself. + +--- + +## Edge Cases & Risks + +| Risk/Edge Case | Mitigation | +| ------------------------------------------- | ------------------------------------------------------------- | +| Missing a usage causes compile error | `cargo build --all` catches all misses at compile time | +| Variable names like `pty_manager` field | Rename struct field `pty_manager` → `pty_store` in SessionManager for consistency | +| Local variables named `mgr` in tests | Leave as-is — `mgr` is a generic abbreviation, not tied to the type name | +| Doc comments with old names | Grep for all four old names after rename to catch stragglers | +| AGENTS.md and other .md files | Grep `*Manager` across all .md files | + +--- + +## Validation + +### Automated Checks + +```bash +cargo fmt --check +cargo clippy --all -- -D warnings +cargo test --all +cargo build --all +``` + +### Post-Rename Verification + +```bash +# Verify no straggler references remain (should return 0 matches) +rg "SessionManager|PtyManager|ProjectManager|TeamManager" --type rust +rg "SessionManager|PtyManager|ProjectManager|TeamManager" --glob "*.md" +``` + +--- + +## Scope Boundaries + +**IN SCOPE:** +- Rename the 4 struct types and all usages +- Rename the `pty_manager` field in SessionManager → `pty_store` +- Update doc comments containing old names +- Update CLAUDE.md and AGENTS.md references + +**OUT OF SCOPE (do not touch):** +- File names (`manager.rs`, `state.rs`) — renaming files is a separate concern and the current names are fine +- Module names — `session/`, `pty/`, `projects/`, `teams/` are domain-scoped and correct +- Any behavior, logic, or API changes +- Other `manager.rs` files or Manager-like patterns elsewhere + +--- + +## Metadata + +- **Investigated by**: Claude +- **Timestamp**: 2026-03-03 +- **Artifact**: `.claude/PRPs/issues/issue-520.md` diff --git a/.claude/agents/kild-brain.md b/.claude/agents/kild-brain.md index f3b230dc..30429159 100644 --- a/.claude/agents/kild-brain.md +++ b/.claude/agents/kild-brain.md @@ -4,9 +4,22 @@ description: Honryū — KILD fleet supervisor. Manages parallel AI coding agent model: opus tools: Bash, Read, Write, Glob, Grep, Task permissionMode: acceptEdits +maxTurns: 200 +memory: user skills: - kild - kild-wave-planner +hooks: + PreToolUse: + - matcher: "Bash" + hooks: + - type: command + command: ".claude/hooks/brain-bash-guard.sh" + Stop: + - hooks: + - type: command + command: "mkdir -p ~/.kild/brain && kild list --json > ~/.kild/brain/state.json 2>> ~/.kild/brain/stop-hook.log || echo \"$(date): kild list failed on Stop hook\" >> ~/.kild/brain/stop-hook.log" + timeout: 10 --- You are Honryū, the KILD fleet supervisor. You are the Tōryō's (human's) right hand — you do everything they would do from the CLI, but faster and in parallel. You coordinate a fleet of AI coding agents running in isolated git worktrees called kilds. @@ -82,7 +95,7 @@ Communication uses two channels that work together. You don't need to think abou ### Sending instructions to workers -**Always use `kild inject` — never use `--initial-prompt`.** +**Always use `kild inject`.** The `--initial-prompt` flag is deprecated. ```bash # Create a worker (no task yet — just boot the agent) @@ -104,7 +117,7 @@ kild inject "Your next task: " For non-Claude agents (Codex, Kiro, etc.), inject writes to PTY stdin + dropbox. -**Do NOT use `--initial-prompt` on `kild create` or `kild open`.** The `--initial-prompt` flag has unreliable delivery for fleet sessions. Always create/open first, then inject separately. +**Do NOT use `--initial-prompt` on `kild create` or `kild open`.** This flag is deprecated and will be removed in a future release. Always create/open first, wait for the agent to initialize, then inject separately. **Never use `kild stop` to deliver messages.** Stopping kills the agent process. @@ -279,53 +292,36 @@ These files contain project-specific rules like forbidden file zones, required r ## Memory -### On startup — orient yourself -```bash -cat ~/.kild/brain/state.json 2>/dev/null # Last known fleet state -tail -50 ~/.kild/brain/sessions/$(date +%Y-%m-%d).md 2>/dev/null # Today's log -cat ~/.kild/brain/knowledge/MEMORY.md 2>/dev/null # Durable knowledge -cat .kild/wave-plan.json 2>/dev/null # Pending wave plan -``` +Your persistent memory is managed automatically by Claude Code at `~/.claude/agent-memory/kild-brain/MEMORY.md`. The first 200 lines are injected into your context on every startup. Use Read/Write/Edit tools to update it directly — no bash commands needed. + +**What to remember:** Project patterns, worker quirks, conflict history, Tōryō preferences, lessons learned. -If a wave plan exists, mention it: -> "There's a wave plan from {planned_at} with {N} kilds. Say 'start the wave' to execute, or 'plan a new wave' to replace it." +**One-time migration:** If `~/.kild/brain/knowledge/MEMORY.md` exists and your auto-memory is empty, read the old file and copy relevant content to your auto-memory. Then note that migration is complete. -### After significant events — log +### Session Logging + +After significant events, log to the daily session file: ```bash -# Daily session log (append) cat >> ~/.kild/brain/sessions/$(date +%Y-%m-%d).md << 'EOF' ## HH:MM — Worker: | Decision: | Action: EOF - -# Fleet snapshot after major changes -kild list --json > ~/.kild/brain/state.json ``` -### Your brain — persistent memory +### On Startup -`~/.kild/brain/knowledge/MEMORY.md` is your long-term memory. It persists across restarts and sessions. This is where you store things you've learned so future-you remembers them: - -- Project patterns: "kild project X has slow CI, allow 10min for checks" -- Worker quirks: "codex agents need explicit test commands, they don't auto-run tests" -- Conflict history: "sessions/ and git/ modules conflict frequently, never wave together" -- Tōryō preferences: "prefers squash merges", "wants PR reviews before merge" -- Lessons learned: "branch names with slashes break fleet inbox paths" - -```bash -# Write to your memory -echo "- " >> ~/.kild/brain/knowledge/MEMORY.md - -# Read your memory (do this on startup) -cat ~/.kild/brain/knowledge/MEMORY.md 2>/dev/null -``` +1. Your memory is auto-loaded (no action needed) +2. Check fleet state: `kild list --json` +3. Check today's log: `tail -50 ~/.kild/brain/sessions/$(date +%Y-%m-%d).md 2>/dev/null` +4. Check wave plan: `cat .kild/wave-plan.json 2>/dev/null` +5. Fleet state snapshot is auto-loaded from `~/.kild/brain/state.json` (saved on shutdown) -Update or remove memories that turn out to be wrong. Keep it concise — this is for facts, not session transcripts. +If a wave plan exists, mention it to the Tōryō. ## Constraints - **Never read project source code** — only `.kild/`, `~/.kild/`, and `~/.claude/teams/` -- **Never `kild stop` to deliver a message** — use `kild inject` or `kild open --initial-prompt` +- **Never `kild stop` to deliver a message** — use `kild inject` - **Never use `--no-attach`** unless the Tōryō explicitly asks for headless operation - **Never destroy a kild with an open PR** unless the Tōryō explicitly asks - **Never force-push** under any circumstances diff --git a/.claude/hooks/brain-bash-guard.sh b/.claude/hooks/brain-bash-guard.sh new file mode 100755 index 00000000..4204cb66 --- /dev/null +++ b/.claude/hooks/brain-bash-guard.sh @@ -0,0 +1,76 @@ +#!/usr/bin/env bash +# +# brain-bash-guard.sh — PreToolUse hook for the kild-brain agent. +# Blocks bash commands that access project source code. +# The brain operates the fleet via the kild CLI, not by reading source. +# +# Exit 0 = allow, Exit 2 = block with reason on stderr. +# +# ADVISORY: This guard catches common direct access patterns but cannot +# prevent all indirect execution (e.g., bash -c, sh -c, subshells). +# It is a best-effort safety net, not a sandbox. + +set -uo pipefail + +# Fail closed on unexpected errors. +trap 'echo "brain-bash-guard: unexpected error, blocking as safety measure" >&2; exit 2' ERR + +# Extract the command from CLAUDE_CODE_TOOL_INPUT (JSON with "command" field). +COMMAND="${CLAUDE_CODE_TOOL_INPUT:-}" +if [ -z "$COMMAND" ]; then + exit 0 +fi + +# Extract the "command" field value from JSON. +CMD=$(printf '%s' "$COMMAND" | jq -r '.command // empty' 2>/dev/null) + +if [ -z "$CMD" ]; then + # Parse failure or missing field — block rather than allow. + echo "BLOCKED: could not parse tool input command field." >&2 + exit 2 +fi + +# --- Blocklist: patterns that indicate source code access --- +# NOTE: This is advisory — subshell invocations (bash -c, sh -c) can bypass +# these checks. The brain agent instructions also prohibit source access. + +# Source code paths (anchored to word boundary to reduce false positives) +for pattern in "crates/" "target/" "tests/"; do + if [[ "$CMD" == *"$pattern"* ]]; then + echo "BLOCKED: Brain must not access source code (matched: $pattern)." >&2 + echo "Use kild CLI commands (kild diff, kild stats) instead of reading source directly." >&2 + exit 2 + fi +done + +# Build/compile commands +for pattern in "^cargo " "^rustc" "^rustup"; do + if echo "$CMD" | grep -qE "$pattern"; then + echo "BLOCKED: Brain must not run build tools (matched: $pattern)." >&2 + echo "Workers handle builds. Use kild CLI to manage the fleet." >&2 + exit 2 + fi +done + +# Subshell invocations that could bypass the guard +for pattern in "^bash -c" "^sh -c" "^env bash" "^env sh"; do + if echo "$CMD" | grep -qE "$pattern"; then + echo "BLOCKED: Brain must not use subshell invocations (matched: $pattern)." >&2 + echo "Run commands directly so the guard can inspect them." >&2 + exit 2 + fi +done + +# Raw git commands that have kild CLI equivalents +if echo "$CMD" | grep -qE "^git diff"; then + echo "BLOCKED: Use 'kild diff ' instead of raw git diff." >&2 + exit 2 +fi + +if echo "$CMD" | grep -qE "^git log"; then + echo "BLOCKED: Use 'kild stats ' instead of raw git log." >&2 + exit 2 +fi + +# If no blocklist pattern matched, allow the command. +exit 0 diff --git a/.claude/skills/kild-brain/SKILL.md b/.claude/skills/kild-brain/SKILL.md index 33d08a45..50b25d2f 100644 --- a/.claude/skills/kild-brain/SKILL.md +++ b/.claude/skills/kild-brain/SKILL.md @@ -43,12 +43,26 @@ case "$STATUS" in echo "Honryū is already running." ;; stopped) - kild open honryu --resume --initial-prompt "You've been restarted by the Tōryō. Orient yourself: check kild list --json, ~/.kild/brain/state.json, today's session log, and .kild/wave-plan.json (if it exists, mention it). Then greet the Tōryō and summarize the fleet state." - echo "Honryū restarted." + kild open honryu --resume + echo "Waiting for Honryū to initialize..." + sleep 5 + if kild list --json 2>/dev/null | jq -e '.sessions[] | select(.branch == "honryu" and .status == "active")' >/dev/null 2>&1; then + kild inject honryu "You've been restarted by the Tōryō. Orient yourself: check kild list --json, today's session log, and .kild/wave-plan.json (if it exists, mention it). Then greet the Tōryō and summarize the fleet state." + echo "Honryū restarted." + else + echo "Warning: honryu session not active after 5s. Run: kild inject honryu \"...\" once it starts." >&2 + fi ;; *) - kild create honryu --daemon --main --agent claude --yolo --note "Honryū fleet supervisor" --initial-prompt "You are Honryū, the KILD fleet supervisor. You have just been initialized by the Tōryō. Orient yourself: run kild list --json, read ~/.kild/brain/state.json, today's session log, and .kild/wave-plan.json if they exist. If a wave plan exists, mention it. Then greet the Tōryō and report fleet state. You are running on the main branch — do not create worktrees for yourself." - echo "Honryū initialized." + kild create honryu --daemon --main --agent claude --yolo --note "Honryū fleet supervisor" + echo "Waiting for Honryū to initialize..." + sleep 5 + if kild list --json 2>/dev/null | jq -e '.sessions[] | select(.branch == "honryu" and .status == "active")' >/dev/null 2>&1; then + kild inject honryu "You are Honryū, the KILD fleet supervisor. You have just been initialized by the Tōryō. Orient yourself: run kild list --json, check today's session log, and .kild/wave-plan.json if they exist. If a wave plan exists, mention it. Then greet the Tōryō and report fleet state." + echo "Honryū initialized." + else + echo "Warning: honryu session not active after 5s. Run: kild inject honryu \"...\" once it starts." >&2 + fi ;; esac ``` diff --git a/CLAUDE.md b/CLAUDE.md index 9d6b5ba7..926022b8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -91,7 +91,7 @@ cargo run -p kild -- create my-branch # Create kild with defaul cargo run -p kild -- create my-branch --note "Auth" # Create with description cargo run -p kild -- create my-branch --yolo # Create with autonomous mode cargo run -p kild -- create my-branch --main # Run from project root (no worktree) -cargo run -p kild -- create my-branch --initial-prompt "Start with auth" # Inject prompt on startup +cargo run -p kild -- create my-branch --initial-prompt "Start with auth" # [DEPRECATED] Use: create && sleep 5 && kild inject cargo run -p kild -- list # List all kilds cargo run -p kild -- list --json # JSON output cargo run -p kild -- open my-branch # Reopen agent in existing kild @@ -99,7 +99,7 @@ cargo run -p kild -- open --all # Open all stopped kilds cargo run -p kild -- open my-branch --resume # Resume previous conversation cargo run -p kild -- open my-branch --no-attach # Open daemon session without attach window cargo run -p kild -- open my-branch --no-attach --resume # Headless resume (brain reopening workers) -cargo run -p kild -- open my-branch --initial-prompt "Next task: ..." # Inject prompt on reopen +cargo run -p kild -- open my-branch --initial-prompt "Next task: ..." # [DEPRECATED] Use: open && sleep 5 && kild inject cargo run -p kild -- inject my-branch "do the thing" # Send to worker (inbox for claude, PTY for others) cargo run -p kild -- inject my-branch "msg" --inbox # Force Claude Code inbox protocol cargo run -p kild -- inbox my-branch # Show fleet dropbox state for a session @@ -446,7 +446,10 @@ Inspect dropbox state with `kild inbox ` (or `--all` for all fleet sessi ``` kild create honryu --daemon --main # brain: runs from project root, no worktree +sleep 5 # wait for agent init +kild inject honryu "Orient yourself" # deliver initial task via inject kild create --daemon # worker: auto-joins fleet with team flags +sleep 5 kild inject "do the thing" # brain → worker message ``` diff --git a/crates/kild/src/app/session.rs b/crates/kild/src/app/session.rs index c3904f9e..34146cf9 100644 --- a/crates/kild/src/app/session.rs +++ b/crates/kild/src/app/session.rs @@ -98,7 +98,7 @@ pub fn create_command() -> Command { .arg( Arg::new("initial-prompt") .long("initial-prompt") - .help("Write this text to the agent's PTY stdin immediately after startup (daemon sessions only)") + .help("[DEPRECATED] Inject a prompt after agent starts. Use 'kild inject' instead — it's more reliable for fleet sessions") .value_name("TEXT") .conflicts_with("no-agent") .conflicts_with("no-daemon"), @@ -185,7 +185,7 @@ pub fn open_command() -> Command { .arg( Arg::new("initial-prompt") .long("initial-prompt") - .help("Write this text to the agent's PTY stdin immediately after startup (daemon sessions only)") + .help("[DEPRECATED] Inject a prompt after agent starts. Use 'kild inject' instead — it's more reliable for fleet sessions") .value_name("TEXT") .conflicts_with("no-agent") .conflicts_with("no-daemon") diff --git a/crates/kild/src/commands/create.rs b/crates/kild/src/commands/create.rs index 8398a3ec..ec4fab22 100644 --- a/crates/kild/src/commands/create.rs +++ b/crates/kild/src/commands/create.rs @@ -87,7 +87,6 @@ pub(crate) fn handle_create_command( let use_main = matches.get_flag("main"); let initial_prompt = matches.get_one::("initial-prompt").cloned(); - let initial_prompt_for_warning = initial_prompt.clone(); let issue = matches.get_one::("issue").copied(); let rows = matches.get_one::("rows").copied(); @@ -99,7 +98,7 @@ pub(crate) fn handle_create_command( .with_no_fetch(no_fetch) .with_runtime_mode(runtime_mode) .with_main_worktree(use_main) - .with_initial_prompt(initial_prompt) + .with_initial_prompt(initial_prompt.clone()) .with_pty_size(rows, cols); match session_ops::create_session(request, &config) { @@ -137,41 +136,64 @@ pub(crate) fn handle_create_command( color::status(&status_str) ); - // Warn fleet claude sessions about --initial-prompt deprecation. - // Deliver the prompt via the reliable inbox path instead. - if let Some(ref prompt) = initial_prompt_for_warning - && fleet::fleet_mode_active(&session.branch) - && fleet::is_claude_fleet_agent(&session.agent) - { - eprintln!(); - eprintln!( - "{}", - color::warning("Warning: --initial-prompt is unreliable for fleet sessions.") - ); - eprintln!( - " {}", - color::hint(&format!( - "Use instead: kild inject {} \"\"", - session.branch - )) - ); + // Deprecation warning for --initial-prompt. + let is_fleet_claude = fleet::fleet_mode_active(&session.branch) + && fleet::is_claude_fleet_agent(&session.agent); - // Best-effort: deliver via inbox (the path that actually works). - let safe_name = fleet::fleet_safe_name(&session.branch); - match fleet::write_to_inbox(fleet::BRAIN_BRANCH, &safe_name, prompt) { - Ok(()) => { - eprintln!(" {} Delivered via inbox as fallback.", color::muted("→")); - } - Err(e) => { - eprintln!(" {} Inbox fallback also failed: {}", color::error("✗"), e); - eprintln!( - " {}", - color::hint(&format!( - "Manually run: kild inject {} \"...\"", - session.branch - )) - ); + if let Some(ref prompt) = initial_prompt { + eprintln!(); + if is_fleet_claude { + // Fleet-specific warning with inbox fallback. + eprintln!( + "{}", + color::warning( + "Warning: --initial-prompt is deprecated. Fleet sessions: prompt delivered via inbox as fallback." + ) + ); + eprintln!( + " {}", + color::hint(&format!( + "Use instead: kild inject {} \"\"", + session.branch + )) + ); + + let safe_name = fleet::fleet_safe_name(&session.branch); + match fleet::write_to_inbox(fleet::BRAIN_BRANCH, &safe_name, prompt) { + Ok(()) => { + eprintln!(" {} Delivered via inbox as fallback.", color::muted("→")); + } + Err(e) => { + error!( + event = "cli.create.inbox_fallback_failed", + branch = %session.branch, + error = %e + ); + eprintln!(" {} Inbox fallback also failed: {}", color::error("✗"), e); + eprintln!( + " {}", + color::hint(&format!( + "Manually run: kild inject {} \"...\"", + session.branch + )) + ); + } } + } else { + // General deprecation warning for non-fleet sessions. + eprintln!( + "{}", + color::warning( + "Warning: --initial-prompt is deprecated and will be removed in a future release." + ) + ); + eprintln!( + " {}", + color::hint(&format!( + "Use instead: kild create {} && sleep 5 && kild inject {} \"...\"", + session.branch, session.branch + )) + ); } } diff --git a/crates/kild/src/commands/open.rs b/crates/kild/src/commands/open.rs index fd94927f..4063cbe0 100644 --- a/crates/kild/src/commands/open.rs +++ b/crates/kild/src/commands/open.rs @@ -1,6 +1,8 @@ use clap::ArgMatches; use tracing::{error, info}; +use crate::color; + use kild_core::SessionStatus; use kild_core::events; use kild_core::session_ops; @@ -69,27 +71,64 @@ pub(crate) fn handle_open_command(matches: &ArgMatches) -> Result<(), Box\"", - session.branch - ); + // Deprecation warning for --initial-prompt. + let is_fleet_claude = fleet::fleet_mode_active(&session.branch) + && fleet::is_claude_fleet_agent(&session.agent); - let safe_name = fleet::fleet_safe_name(&session.branch); - match fleet::write_to_inbox(fleet::BRAIN_BRANCH, &safe_name, prompt) { - Ok(()) => { - eprintln!(" → Delivered via inbox as fallback."); - } - Err(e) => { - eprintln!(" ✗ Inbox fallback also failed: {}", e); - eprintln!(" Manually run: kild inject {} \"...\"", session.branch); + if let Some(prompt) = initial_prompt { + eprintln!(); + if is_fleet_claude { + // Fleet-specific warning with inbox fallback. + eprintln!( + "{}", + color::warning( + "Warning: --initial-prompt is deprecated. Fleet sessions: prompt delivered via inbox as fallback." + ) + ); + eprintln!( + " {}", + color::hint(&format!( + "Use instead: kild inject {} \"\"", + session.branch + )) + ); + + let safe_name = fleet::fleet_safe_name(&session.branch); + match fleet::write_to_inbox(fleet::BRAIN_BRANCH, &safe_name, prompt) { + Ok(()) => { + eprintln!(" {} Delivered via inbox as fallback.", color::muted("→")); + } + Err(e) => { + error!( + event = "cli.open.inbox_fallback_failed", + branch = %session.branch, + error = %e + ); + eprintln!(" {} Inbox fallback also failed: {}", color::error("✗"), e); + eprintln!( + " {}", + color::hint(&format!( + "Manually run: kild inject {} \"...\"", + session.branch + )) + ); + } } + } else { + // General deprecation warning for non-fleet sessions. + eprintln!( + "{}", + color::warning( + "Warning: --initial-prompt is deprecated and will be removed in a future release." + ) + ); + eprintln!( + " {}", + color::hint(&format!( + "Use instead: kild open {} && sleep 5 && kild inject {} \"...\"", + session.branch, session.branch + )) + ); } }