| ISSUE-001 |
Task |
Done |
Normal |
Task 1.1 — Package skeleton + config module |
2026-07-14 |
2026-07-14 |
| ISSUE-002 |
Task |
Done |
Normal |
Task 1.2 — Mute store |
2026-07-14 |
2026-07-14 |
| ISSUE-003 |
Task |
Done |
Normal |
Task 1.3 — Executor core (subprocess, exit codes, side channel) |
2026-07-14 |
2026-07-14 |
| ISSUE-004 |
Task |
Done |
Normal |
Task 1.4 — Executor PTY tees (stderr always, stdout opt-in) |
2026-07-14 |
2026-07-14 |
| ISSUE-005 |
Task |
Done |
Normal |
Task 1.5 — Trigger engine |
2026-07-14 |
2026-07-14 |
| ISSUE-006 |
Task |
Done |
Normal |
Task 2.1 — Streaming chat client |
2026-07-14 |
2026-07-14 |
| ISSUE-007 |
Task |
Done |
Normal |
Task 2.2 — Session & context building |
2026-07-14 |
2026-07-14 |
| ISSUE-008 |
Task |
Done |
Normal |
Task 2.3 — Agent loop with confirmation gate |
2026-07-14 |
2026-07-14 |
| ISSUE-009 |
Task |
Done |
Normal |
Task 3.1 — REPL dispatch |
2026-07-14 |
2026-07-14 |
| ISSUE-010 |
Task |
Done |
Normal |
Task 3.2 — Entry point & terminal wiring |
2026-07-14 |
2026-07-14 |
| ISSUE-011 |
Task |
Done |
Normal |
Task 3.3 — End-to-end smoke + README |
2026-07-14 |
2026-07-14 |
| ISSUE-012 |
Task |
Done |
High |
Task 3.0 — Agent loop hardening (tool-turn liveness) |
2026-07-14 |
2026-07-14 |
| ISSUE-013 |
Task |
Done |
High |
Task 3.4 — REPL streaming passthrough (v1 review finding) |
2026-07-14 |
2026-07-14 |
| ISSUE-014 |
Issue |
Done |
Normal |
out() conflates lines and stream chunks — repl hardcodes sys.stdout + a print identity check; redesign sink at v2 |
2026-07-14 |
2026-07-15 |
| ISSUE-015 |
Issue |
Done |
High |
REPL blocking on synchronous command execution (e.g. 'yes') |
2026-07-14 |
2026-07-14 |
| ISSUE-016 |
Issue |
Done |
High |
TUI programs (vi, mc, etc.) do not display correctly (best effort — mechanism fixed, visual rendering not contract-tested) |
2026-07-14 |
2026-07-14 |
| ISSUE-017 |
Issue |
Done |
Low |
Redundant LLM explanation on 'command not found' (exit 127) |
2026-07-14 |
2026-07-15 |
| ISSUE-018 |
Issue |
Done |
Low |
LLM output missing trailing newline causes prompt discontinuity |
2026-07-14 |
2026-07-15 |
| ISSUE-019 |
Issue |
Done |
Low |
Enhanced shell prompt and terminal colorization |
2026-07-14 |
2026-07-15 |
| ISSUE-020 |
Issue |
Done |
Low |
Missing 'thinking' indicator during LLM queries |
2026-07-14 |
2026-07-15 |
| ISSUE-021 |
Task |
Done |
High |
Task 4.1 — Job control handoff (tcsetpgrp, Ctrl+Z termination, TUI passthrough) |
2026-07-14 |
2026-07-14 |
| ISSUE-022 |
Issue |
Done |
Low |
Configurable prompt — bash $PS1-style convention (Tier 1: \u \h \w \W + named color tokens) |
2026-07-15 |
2026-07-16 |
| ISSUE-023 |
Issue |
Done |
Low |
Confirmation gate — distinct colors per line (command / description / prompt) |
2026-07-15 |
2026-07-15 |
| ISSUE-024 |
Issue |
Done |
Normal |
TerminalSink.write_line() doesn't call ensure_newline() — mid-consult write_line (e.g. confirmation gate) runs onto an open streamed line |
2026-07-15 |
2026-07-15 |
| ISSUE-025 |
Issue |
Open |
Low |
Reuse prompt.py's color-token vocabulary for other UI surfaces (confirmation gate, thinking indicator, sink colors) — design note only, not specced |
2026-07-16 |
— |
| ISSUE-026 |
Issue |
Done |
Normal |
First-run setup friction — no config.toml is ever written, empty api_key silently sends an unauthenticated request instead of a friendly setup prompt |
2026-07-16 |
2026-07-17 |
| ISSUE-027 |
Issue |
Done |
Low |
Sasha looks like a thin bash wrapper — no boot-time hint about the ?? LLM trigger or which profile is active |
2026-07-16 |
2026-07-16 |
| ISSUE-028 |
Issue |
Done |
Normal |
No TAB completion — bash-style command/argument completion via the real bash-completion ecosystem |
2026-07-16 |
2026-07-16 |
| ISSUE-029 |
Issue |
Done |
High |
Path completion broken after cd and for any path containing '/' — completer ignores Executor.cwd, readline's default delims split words on '/' |
2026-07-16 |
2026-07-16 |
| ISSUE-030 |
Issue |
Done |
Normal |
Personality file — load ~/.config/sasha/PERSONALITY.md into the LLM system prompt, self-installing a neutral default when absent |
2026-07-16 |
2026-07-16 |
| ISSUE-031 |
Issue |
Done |
High |
Stale command notes/stderr silently re-surface into every subsequent LLM turn — Session.notes/last_stderr are never consumed after being shown once |
2026-07-16 |
2026-07-16 |
| ISSUE-032 |
Issue |
Open |
Normal |
Possible second, distinct repetition mechanism in agent.py's tool-calling loop (file writes etc.) — not covered by ISSUE-031's fix; needs a reproducible transcript — design note only, not specced |
2026-07-16 |
— |
| ISSUE-033 |
Issue |
Done |
Normal |
Multi-tool registry & per-tool permissions (v2) — replace hardcoded run_command with a tool registry, add write_file/read_file/list_dir/search_files, generalize the repetition guard to (tool_name, key) as a plausible mitigation for ISSUE-032 |
2026-07-17 |
2026-07-17 |
| ISSUE-034 |
Issue |
Done |
Normal |
Project memory (v2) — git-repo-scoped long-term memory (semantic.jsonl + episodic/ logs under .sasha/memory/), automatic encoding on git commit, deliberate encoding via a new remember() tool, place-shift detection on cwd change |
2026-07-17 |
2026-07-17 |
| ISSUE-035 |
Issue |
Open |
Low |
remember() can't explicitly target general (cross-project) memory while inside a git repo — always writes to whichever place is current. Deferred by design (Salvador's call, during general-memory brainstorm) — revisit whether the LLM can infer project-vs-general scope from a note's own content, rather than adding an explicit scope parameter or asking the user — design note only, not specced |
2026-07-17 |
— |
| ISSUE-036 |
Issue |
Done |
Normal |
General (cross-project) memory (v2) — ~/.config/sasha/memory/, always layered with project memory, plus a new crash-signal (SIGSEGV/SIGABRT/SIGBUS/SIGILL/SIGFPE) automatic encoding trigger |
2026-07-17 |
2026-07-18 |
| ISSUE-037 |
Issue |
Open |
Low |
General memory (~/.config/sasha/memory/) self-installs silently on first write — every other self-installing file in this project (config.toml, PERSONALITY.md, project memory's .sasha/memory/) prints a one-time "created X" notice; general memory currently doesn't. Found during Phase 18 review — design note only, not specced |
2026-07-18 |
— |
| ISSUE-038 |
Task |
Done |
Normal |
Memory bootstrap (v1) — a repo entering Sasha for the first time with real git/doc history gets an instant mechanical replay of recent commits plus a background LLM summary (root docs + history) into semantic memory, surfaced via a blinking prompt star instead of blocking the shell |
2026-07-18 |
2026-07-18 |
| ISSUE-039 |
Issue |
Open |
Low |
Bootstrap (kind=bootstrap) entries have no refresh path once written — stage 2 never re-attempts after a first success, so if a project's mission/vision genuinely shifts the only way to update its bootstrap facts is sasha forget, which nukes all memory (every commit/remember entry too), not just the stale bootstrap bullets. A narrower "resummarize" mechanism was deliberately out of scope for v1 (§2 of the memory-bootstrap design). Found studying the organically-generated .sasha/memory/ contents from Phase 19's own dispatch — design note only, not specced |
2026-07-18 |
— |
| ISSUE-040 |
Task |
Done |
High |
Pager/TUI programs invoked from inside sasha (e.g. less, git log) don't receive arrow keys or q — only Ctrl+Z escapes. ROOT CAUSE CONFIRMED (2026-07-20) via strace + /proc/<pid>/fd inspection: readlink -f /proc/<less-pid>/fd/{0,1,2,3} showed fd 0/1 → the real terminal (e.g. /dev/pts/4) but fd 2 AND fd 3 → a different device (e.g. /dev/pts/3). less follows the standard pager convention of using fd 2 (stderr) for terminal control (raw-mode negotiation + keyboard reads), specifically so paged output can be redirected while keyboard control still works — plain bash never triggers this bug because it gives stderr no identity other than the real terminal. executor.py breaks that assumption: it unconditionally wraps stderr in its own fresh synthetic PTY (pty.openpty() for err_master/err_slave, every command, so stderr text can be captured for LLM context/memory) — a real, valid tty (isatty(2) true), but nothing ever feeds real keystrokes into it. less sets raw mode there successfully and blocks reading it forever, correctly but uselessly, while the real terminal (fd 0/1) never gets touched and stays in cooked/echo mode. Ctrl+Z still works because that's driven by the real terminal's foreground process group, unrelated to which fd less reads. Task 20.1 (SIGTTIN-ignore + stop-cause classification, Fixed: ISSUE-040, 2026-07-20) was a reasonable job-control hardening in its own right but did NOT address this mechanism — confirmed insufficient by direct reproduction after it shipped; reopening. A follow-up design (docs/superpowers/specs/2026-07-20-issue-040-stderr-pty-keystroke-relay-design.md) proposed having sasha's relay loop read fd 0 and broadcast real keystrokes into whichever capture PTY (err_master always, out_master if capturing stdout) the child has put into raw mode — approved and partially implemented, but BLOCKED by a hard OS-level constraint discovered during implementation (2026-07-20): once sasha hands the terminal's foreground status to the child via tcsetpgrp (required for Ctrl+C/Ctrl+Z to reach the child correctly — Task 4.1's mechanism), sasha's own process becomes background relative to fd 0, and POSIX/Linux flatly does not allow a background process to read its controlling terminal — confirmed empirically: os.read(0, ...) from sasha's own (background) process fails with EIO every time once SIGTTIN is ignored (Task 20.1's own fix), or would otherwise stop the reading process. Sasha CAN briefly reclaim the terminal (tcsetpgrp back to itself) to mirror termios settings and hand it back — that part works — but it cannot durably read fd 0 while the child holds the foreground, which the relay design requires. The two requirements (child must be foreground for correct signal delivery; sasha must read fd 0 to source keystrokes) are in direct, unavoidable conflict as designed. Candidate way forward, not yet evaluated: have something INSIDE the child's own foreground process group do the relay instead of sasha — e.g. pass err_master's fd through to the wrapped bash script (same mechanism already used for SASHA_STATE_FD) and have a helper launched within that script (inheriting foreground-group membership) read fd 0 and write into it, since a foreground reader is not restricted the way sasha's own backgrounded process is. Untested; adds a background helper process per interactive command and needs a reliably binary-safe byte relay (bash's own read builtin is not well-suited to arbitrary bytes) — real added complexity not yet weighed. Scratch contract-test file (uncommitted, moved 2026-07-20 to gitignored quarantine/manual-issue-040-stderr-relay-scratch/test_task_21_1_stderr_keystroke_relay.py — it was blocking scripts/verify.sh's clean-tree gate for Task 21.1, an unrelated task that has since taken the real "Task 21.1" number; this scratch file's internal task-number references are stale/superseded) already encodes the two ACs (fd2-raw-mode-receives-real-keystroke; fd0-direct-reader-unaffected) that any real fix still needs to satisfy — AC2 (fd0-direct-reader) already passes against the untouched baseline; AC1 is what's blocked. UPDATE (2026-07-21): the "candidate way forward" (a relay helper sharing the target's foreground process group, instead of Sasha's own now-background process) was verified viable with an isolated minimal repro before any design was written — a foreground-group helper CAN read fd 0 successfully where Sasha's own process gets EIO, confirmed 3/3 runs, full relay chain (helper reads fd 0 → writes into the capture PTY's master side → target's raw-mode read on the slave side returns the real byte) working end to end. New design: docs/superpowers/specs/2026-07-21-issue-040-foreground-relay-helper-design.md. RESOLVED (2026-07-21) by Task 24.1: src/sasha/_kb_relay_helper.py (new) relays real keystrokes from fd 0 into whichever capture PTY a child has raw-moded, running as a helper that shares the target's foreground process group (spawned via bash & from the wrapped script, err_master/out_master passed through with FD_CLOEXEC cleared) so it isn't subject to the background-read restriction that blocked the second design. Dispatched clean, verified byte-for-byte match with the fix sketch, gate passed (564/564), and re-confirmed directly against the real shipped code with an actual less invocation (real arrow-key/q keystrokes through a pty — less now exits cleanly on q, the exact behavior that was broken). Two real implementation gotchas were caught during verification and are now documented inline in the helper: termios.error isn't an OSError subclass (same recurring Python gotcha as the abandoned second design), and bash silently redirects a backgrounded job's stdin to /dev/null unless given an explicit <&0 |
2026-07-19 |
2026-07-21 |
| ISSUE-041 |
Task |
Done |
Normal |
Safe read-only command auto-approval — extend run_command's requires_confirmation to a per-call classifier (hardcoded verb allowlist + config-editable sensitive-path denylist) so genuinely read-only shell scanning (ls, wc, find, head, etc.) skips the y/n/e gate the way read_file/list_dir/search_files already do, without reinventing those commands as bespoke tools. Discovered from real transcript friction (repeated confirmation prompts for pwd, ls, head, wc calls); investigation showed most of that friction was actually the LLM not using already-free auto-approved tools (cwd is already in context; read_file already handles missing files and previews large files), leaving line-counting as the one genuine capability gap — but Salvador explicitly wants run_command itself to be able to skip confirmation for read-only invocations rather than adding more bespoke tools. Design: docs/superpowers/specs/2026-07-20-issue-041-safe-command-auto-approval-design.md |
2026-07-20 |
2026-07-20 |
| ISSUE-042 |
Task |
Done |
Normal |
Tool-call logging — a global, append-only JSONL log (~/.local/state/sasha/tool_log.jsonl) of every tool call the LLM proposes (executed, declined, edited, or replied-to), full verbatim call+result capture except results are withheld for run_command calls touching the ISSUE-041 sensitive-path denylist, bounded via background compaction (strips oldest results first, [tool_log].max_size_mb in config.toml) plus a manual sasha compact-log command. Requested by Salvador to study his own tool-usage patterns later. Design: docs/superpowers/specs/2026-07-21-tool-call-logging-design.md |
2026-07-21 |
2026-07-21 |
| ISSUE-043 |
Task |
Done |
High |
Sensitive-path denylist doesn't match a bare directory — ls ~/.ssh (no trailing filename) currently auto-approves (requires_confirmation returns False) despite directly listing a denylisted directory, because fnmatch-style patterns like ~/.ssh/** require a literal / plus content after .ssh, which a bare directory path doesn't have. Confirmed directly: requires_confirmation({"command": "ls ~/.ssh"}, executor) returns False with the default denylist. Discovered writing Task 22.1's touches_sensitive_path tests (the same matching logic, extracted verbatim from Task 21.1's classifier) — this is a pre-existing gap in the already-shipped ISSUE-041 classifier, not something introduced by the extraction. Candidate fix, not yet designed: also match when the normalized path equals the pattern with its trailing /** stripped (i.e. treat dir/** as matching dir itself too). Logged separately rather than fixed in passing, since fixing it would change requires_confirmation's already-tested behavior and Task 22.1 must preserve that behavior byte-for-byte. Fixed in Task 23.1: touches_sensitive_path now also matches when the normalized path equals a /**-suffixed pattern with the /** stripped |
2026-07-21 |
2026-07-21 |
| ISSUE-044 |
Task |
Done |
Normal |
Raw-typed command activity awareness — extend tool_log.log_call (ISSUE-042) to a new call site in repl.py's raw-command path (verdict="direct"), reusing its existing schema/compaction/sensitive-path-withholding wholesale, so commands typed directly at the prompt (not just LLM-invoked run_command calls) get a durable, compacted audit trail. Separately, enrich Session.notes (ISSUE-031's recency window) with per-entry stderr_text, removing the now-redundant Session.last_stderr, shrinking the cap from 10 to 5. Requested by Salvador after noticing sasha only really pays attention to failed commands; project scan found Session.notes/last_stderr already partially covers this (line+exit-code, last 10, cleared after each ??) — narrower gap than first assumed. Design: docs/superpowers/specs/2026-07-21-issue-044-raw-command-activity-design.md. Shipped in Task 25.1: byte-for-byte match with the verified fix sketch, gate passed (570/570) |
2026-07-21 |
2026-07-21 |
| ISSUE-045 |
Task |
Done |
Normal |
Cursor-based activity indicator — replaces the Phase 19 prompt busy-star (removed outright: only ever wired to the memory-bootstrap thread, never worked for the synchronous LLM-thinking/tool-call phases it was meant to signal). New ActivityIndicator class drives cursor visibility (\e[?25l/\e[?25h) + OSC 12 color from a background thread, hooked into agent.py's run_consult (new optional on_phase callback, default no-op) for thinking/tool-call phases and repl.py's raw-command path (ISSUE-044). Configurable via new [activity_indicator] config.toml section (enabled, interval_s, per-phase colors). Discovered via Salvador noticing the busy-star didn't blink as expected; validated end-to-end by hand first with throwaway scratch scripts before any design was written (his terminal doesn't honor DECSCUSR blink-vs-steady, but cursor-visibility toggling and OSC 12 color both work reliably). Design: docs/superpowers/specs/2026-07-21-issue-045-cursor-activity-indicator-design.md. Shipped in two tasks: 26.1 (the new indicator, wired into agent.py/repl.py/config.py) and 26.2 (old busy-star mechanism removed outright — \S token, _busy_star(), _background_tasks machinery all deleted). Both byte-for-byte matches with their verified fix sketches, gates passed |
2026-07-21 |
2026-07-21 |
| ISSUE-046 |
Task |
Done |
Low |
Bold TerminalSink colors — _SASHA_COLOR/_LLM_COLOR/_PROMPT_COLOR/_COMMAND_COLOR (src/sasha/repl.py) gain the bold SGR prefix (1;) for better visual weight against plain shell output; _DIM stays non-bold (bold+faint is a contradictory SGR combination). Purely a constant-value change — all five colors are already referenced symbolically everywhere they're used, so no contract-test amendments to existing files were needed (one new contract test file pins the exact new values). Requested by Salvador. Design: docs/superpowers/specs/2026-07-21-issue-046-bold-terminal-colors-design.md. Shipped in Task 28.1: byte-for-byte match with the verified fix sketch, gate passed (576/576) |
2026-07-21 |
2026-07-22 |
| ISSUE-047 |
Task |
Done |
Normal |
Suspected repeated/redundant data sent to the LLM on every turn — Salvador's own impression that sasha "tends to repeat itself," suspecting cwd specifically. Investigation tooling shipped in Task 29.1: [llm_debug]-gated llm_debug.py module logs the exact outbound request (model/messages/tools) to llm_debug.jsonl, request-only, no compaction, no redaction — Design: docs/superpowers/specs/2026-07-22-issue-047-llm-debug-log-design.md; Task spec: docs/superpowers/specs/task-29.1-llm-debug-log.md. Confirmed via a real captured interaction: Session.build_messages() was synthesizing a bare {"role":"user","content":"<cwd>"} message on every tool-turn-loop continuation round (not just genuine new user turns), producing a non-standard conversation shape that a local model (bartowski/deepreinforce-ai_Ornith-1.0-35B-GGUF:Q4_K_M) was seen misreading as an implicit nudge — it called list_dir("/home/salvador") twice in a row right after one such bare turn, tripping the repetition guard ([sasha] stopping: repeated identical action / tool-turn limit reached). Fix design: docs/superpowers/specs/2026-07-23-issue-047-drop-synthetic-continuation-turn-design.md; Task spec: docs/superpowers/specs/task-30.1-no-synthetic-continuation-turn.md. Fixed in Task 30.1: build_messages()'s no-pending-user-message branch now only synthesizes a trailing context message when self.notes is non-empty (the exit-127 AUTO_CONSULT path's only channel for context, since it never calls add_user()), otherwise ends the array on the actual last history entry — byte-for-byte match with the verified fix sketch, gate passed (587/587). The full-system-prompt-resent-every-turn candidate cause remains a known, accepted, unaddressed characteristic (no caching layer requested); cwd-changing-mid-tool-turn-loop left as an accepted out-of-scope gap (Salvador's call) |
2026-07-21 |
2026-07-23 |
| ISSUE-048 |
Task |
Done |
Normal |
Cursor activity indicator (ISSUE-045) corrupts full-screen/interactive terminal apps (nano, mc, git log's pager) when they're run as raw-typed commands — root cause: repl.py's raw-command branch calls Executor.run() with a real tcsetpgrp foreground handoff (Task 4.1), so the child gets genuine interactive terminal control, and the indicator's background-thread cursor toggling fights with the app's own cursor management. The agent-driven tool-call path (tools.py, capture_stdout=True) doesn't have this problem since stdout is a pipe, not a tty. Fix: remove the ActivityIndicator start/stop wrap around the raw-command path only; LLM-thinking and agent-tool-call phases stay as Task 26.1 shipped them. Requested by Salvador. Design: docs/superpowers/specs/2026-07-21-issue-048-activity-indicator-raw-command-scope-design.md. Shipped in Task 27.1: byte-for-byte match with the verified fix sketch, gate passed (570/570) |
2026-07-21 |
2026-07-22 |
| ISSUE-049 |
Task |
Done |
Normal |
Two related terminal-output problems found while investigating a real "[sasha] stopping: repeated identical action / tool-turn limit reached" incident (llm_debug.jsonl/tool_log.jsonl study confirmed: model unsloth/GLM-4.7-Flash-REAP-23B-A3B-GGUF:UD-Q4_K_XL genuinely re-issued an identical list_dir call across two rounds, not a Sasha-side synthetic-turn artifact — ISSUE-047's fix holds). (1) Reasoning/thinking-stage text had no distinct color — confirmed live against Salvador's local server that it sends a clean, separate reasoning_content SSE delta field (no <think> tag parsing needed). (2) A real display bug: agent.py's run_consult() streamed every tool-turn round's text through the same raw, unframed out callback with no separator between rounds, and the "stopping:"/"ran (auto-approved):"/tool_log compaction messages went through that same raw channel instead of being line-framed — confirmed via a garbled real paste showing three copies of repeated text glued together with the stopping message spliced mid-line. Design: docs/superpowers/specs/2026-07-25-issue-049-reasoning-color-and-display-framing-design.md; Task spec: docs/superpowers/specs/task-31.1-reasoning-color-and-display-framing.md. Fixed in Task 31.1: ChatClient.chat() streams reasoning_content via a new on_reasoning callback, rendered in a configurable reasoning_color (default dim-white) via TerminalSink.write_reasoning_chunk; run_consult()'s new on_status/on_warning params (falling back to out when not supplied, preserving six existing immutable tests unmodified) route system messages through proper line-framing; Repl._on_activity_phase inserts a newline between tool-turn rounds (skipping the redundant first transition per consult); the stopping message gains a — name(args) suffix (truncated at 200 chars) only when the stop is a genuine repeated action, not a bare turn-limit. Byte-for-byte match with the verified fix sketch, gate passed (610/610) |
2026-07-24 |
2026-07-25 |
| ISSUE-050 |
Task |
Done |
Normal |
cwd was force-fed to the LLM compulsively (every single build_messages() call, regardless of whether it changed since the last turn) and in the wrong role (glued onto the user message's own content), so it read to the model as if the user typed it. Distinct from ISSUE-047 (Done): that fix (Task 30.1) only addressed the synthetic no-pending-user-message branch; the normal ??-question path was explicitly left untouched. Confirmed live: a weak local model (unsloth/GLM-4.7-Flash-REAP-23B-A3B-GGUF:UD-Q4_K_XL) read a plain "hola hermosa" greeting plus its appended cwd line as "the user gives me a path," derailing into unprompted directory exploration. First design direction (a non-initial role: "system" message inserted mid-history) was rejected after live verification: this profile's own chat template (checked via /props) hard-errors on any system message not at index 0, while the llm7 profile's backend handles one fine — genuinely incompatible across Sasha's own two shipped profiles. Settled design: a general, reusable [context:KEY] VALUE message convention (a user-role message, distinct from whatever the user actually typed, plus a one-time system-prompt anchor sentence explaining the convention), validated live against both profiles including the model responsible for the incident — correctly produces zero tool calls on a bare greeting and correctly calls list_dir (using the marker's actual value) when genuinely asked. Design: docs/superpowers/specs/2026-07-26-issue-050-context-injection-syntax-design.md; Task spec: docs/superpowers/specs/task-32.1-context-injection-cwd.md. Fixed in Task 32.1: Session tracks _last_told_cwd, inserts a [context:cwd] marker into history only when it changes (persisting forward through normal trimming), the system prompt gains the anchor sentence. 16 existing tests across 6 files needed Tier-1 amendment (more than the 3 originally scoped) — 6 because any system-prompt text change breaks byte-for-byte assertions of it, and 2 more because the marker's positional interaction with Task 30.1's no-pending-user branch wasn't part of the original estimate; two real bugs (a fresh marker, and separately a stale one from an earlier call, both mistakable for a genuine pending user message and folding self.notes into themselves) were found and fixed via scratch-worktree verification before dispatch. Byte-for-byte match with the verified fix sketch, gate passed (620/620) |
2026-07-25 |
2026-07-26 |
| ISSUE-051 |
Issue |
Done |
Normal |
Directory-recap on explicit cd — extend the existing git-repo place-shift recap (ISSUE-034/038, repl.py:252) to arbitrary directories, using tool_log/git history as the "last activity" signal instead of requiring project memory. Explored via a disposable probe script against real tool_log.jsonl + real directories (not committed to the repo): settled on an escalation cascade (cheap deterministic checks first, LLM last resort) after a pluggable "context engines" architecture was floated and rejected as premature. Found and fixed two real blind spots empirically — log-derived signals alone can't tell a thin/quiet project from a genuine multi-repo umbrella folder Sasha's never been run inside; the fix is a filesystem-shape signal (immediate child dirs that are themselves git repos), checked before any log-derived confidence, in every branch including the "confident single-repo" fast path. Exactly one real ambiguous case found across ~110 repos on Salvador's machine (/home/salvador/mqtt — a repo that itself nests 2 independent child repos) validates the LLM-escalation stage is genuinely needed, just rare. Full discussion: docs/superpowers/STEERING.md (2026-07-26 entry). Design: docs/superpowers/specs/2026-07-26-issue-051-directory-recap-design.md (amended to add sasha recap, an explicit always-LLM on-demand command bypassing the cascade). Task 51.1 (Done, 2026-07-26): directory_recap.py cascade + repl.py gap-filler/dedup wiring + [directory_recap].enabled config flag — task spec: docs/superpowers/specs/task-51.1-directory-recap-cascade.md. Verified in a scratch worktree before dispatch (20/20 new tests, 639/639 full suite); found and fixed a real bug during verification — _is_git_repo crashed with PermissionError scanning real system-owned directories under /tmp (e.g. systemd-private-*), encoded as AC20. Dispatched clean, byte-for-byte match with the verified fix sketch, gate passed (639/640 — see ISSUE-053, a confirmed-unrelated pre-existing timing-race flake in an unconnected test). Task 51.2 (Done, 2026-07-27): sasha recap (explicit, synchronous, always-LLM, bypasses the cascade entirely) + sasha directory-recap on|off builtin + config.set_directory_recap_enabled patcher (mirrors set_api_key's exact shape) — task spec: docs/superpowers/specs/task-51.2-sasha-recap-command.md. Verified in a scratch worktree before dispatch (12/12 new tests, 651/651 full suite); the one bug found during verification was in the test file itself (chunked write_chunk output checked against single-line assertions), not the implementation. Dispatched clean, byte-for-byte match with the verified fix sketch, gate passed (652/652, ISSUE-053's race didn't reproduce this run). Both tasks reviewed directly against their diffs for semantic drift/hardcoded-output gaming — none found. Phase 51 review (Task 51.3) additionally confirmed live by Salvador against real usage: the existing git-repo/memory recap correctly suppressed the new mechanism on cd into both Sasha and tracking.media (both have project memory, so gap-filler held); sasha recap run against two structurally different real models (unsloth/GLM-4.7-Flash-REAP-23B-A3B-GGUF:UD-Q4_K_XL and deepseek-v4-pro) on the same real data converged on near-identical, accurate output both times — once correctly restating a single available fact plainly rather than inventing detail (Sasha, whose real tool_log.jsonl currently holds only 4 entries, none inside the repo, confirmed by reproducing gather_context directly), and once correctly translating a Spanish commit subject (tracking.media: "esqueleto del paquete tracking y carga de configuracion") into a clean English one-liner, dropping the conventional-commit feat: prefix — real synthesis, not mere echoing. No issues found; recap richness is expected to grow naturally as tool_log.jsonl accumulates more real activity per directory |
2026-07-26 |
2026-07-27 |
| ISSUE-052 |
Issue |
Open |
Normal |
config.toml completeness + silent bad-value handling — found while brainstorming ISSUE-051's config surface (Salvador worried the file is "too fragile"; investigation showed whole-file TOML syntax errors already fail safely — config.py:311-336's ConfigError is caught by __main__.py:42-46, clean one-line stderr message + exit 2, no crash — but per-key bad types silently revert to hardcoded defaults with zero warning, confirmed e.g. a stray string where a bool is expected for any isinstance-guarded field). Two confirmed, separate gaps: (1) the self-install template (_DEFAULT_CONFIG_TOML, config.py:102-155) has never included every real config surface — [limits] has never appeared in it at all, and newer sections since added to the Config dataclass don't reliably make it in either; (2) already-existing installs never gain new sections regardless of template fixes, since the file's own header promises "re-running Sasha never overwrites this file" — confirmed directly against Salvador's real ~/.config/sasha/config.toml, missing [safe_commands], [tool_log], [activity_indicator], and reasoning_color entirely, all silently running on hardcoded defaults. Scope (not yet designed): template should list every available key with its default as a discoverable reference; some non-destructive way for existing files to catch up on missing sections without touching hand edits; bad-typed values should warn-and-continue rather than silently reverting. Salvador confirmed TOML itself stays (comments + multi-line-string ergonomics for the prompt template, consistent with the 2026-07-17-llm-setup-design.md rejection of switching to JSON) — this is a completeness/validation gap, not a format reconsideration. Deliberately kept out of ISSUE-051's scope |
2026-07-26 |
— |
| ISSUE-053 |
Issue |
Open |
Low |
test_task_24_1_kb_relay_helper.py::test_ac3_no_lingering_relay_helper_process is a timing-race flake, not a real leak — reproduced twice during Task 51.1's gate with two different real, transient PIDs both already exited by the time they were inspected (ps -p found nothing moments later). Confirmed unrelated to Task 51.1 (its diff touches tool_log.py/directory_recap.py/config.py/repl.py's cwd-recap logic only, nothing in executor.py/_kb_relay_helper.py) by reproducing the identical failure against the pre-Task-51.1 base directly. Likely cause: the test's time.sleep(0.2) before scanning /proc is tight relative to the relay helper's actual teardown time under load — needs a poll-with-timeout instead of a fixed sleep. Not fixed here since ISSUE-051 doesn't touch this code path |
2026-07-26 |
— |
| ISSUE-054 |
Issue |
Done |
Normal |
Multi-line pasted text is not accepted atomically at the Sasha prompt — reported by Salvador after a real paste (a 3-line block copied from Sasha's own boot banner via Terminator's Ctrl+Shift+C/Ctrl+Shift+V) resulted in each line being submitted as a separate top-level command, including Sasha's own banner text being re-executed as garbage shell commands (cascading command not found + repeated wasted LLM auto-consult round-trips per line). Root-caused via a pty_harness-based repro (scratch script, not committed): Python's input() + GNU readline 8.2 does correctly buffer a real multi-line paste as one atomic string when the terminal wraps it in bracketed-paste escape sequences (\e[200~...\e[201~) — confirmed with both a naive script and one replicating Sasha's exact readline config (completer/tab-binding) and a real Executor.run() call in between, neither of which interfered. The actual cause is external to Sasha: Salvador's terminal (Terminator, Ctrl+Shift+V) does not send bracketed-paste sequences for that paste action — confirmed conclusively by reproducing the identical line-by-line-split behavior in plain bash with zero Sasha involvement (python3 -c "import readline; print(repr(input('paste here: ')))", run standalone, split exactly the same way). Since bash itself doesn't solve this either, a fix requires Sasha to take over raw input handling with its own paste-detection heuristic (likely burst/timing-based, since bracketed-paste cooperation can't be assumed) rather than relying on plain input()/readline — not designed yet. Salvador separately wants a compact "[Pasted text #N +K lines]" placeholder (matching Claude Code's own terminal UI) shown in place of raw multi-line paste content, which requires the same raw-input takeover to implement. Design: docs/superpowers/specs/2026-07-27-issue-054-paste-join-design.md — settled on a select()-based burst-detection join (peek whether more input is already buffered right after each line, join with \n until nothing more is pending) rather than raw-terminal takeover, since it needs zero changes to readline's existing behavior (history, tab-complete, editing keys) and zero interaction with the historically fragile PTY/job-control machinery (ISSUE-040). Only activates on a real interactive tty, to avoid regressing the existing test suite's piped multi-line input. Unifies the REPL main prompt and the confirmation gate (y/n/e, edit, reply) behind one shared input function — they were two separate, unwrapped input paths before this (_repl_input vs. the raw input builtin passed to TerminalConfirmer). The "[Pasted text #N +K lines]" placeholder display remains explicitly out of scope, deferred as a separate follow-up. Task 54.1 (Done, 2026-07-28): paste_join.py + __main__.py wiring — task spec: docs/superpowers/specs/task-54.1-paste-join.md. Verified in a scratch worktree before dispatch (11/11 new tests, 662/662 full suite); found and fixed two bugs in test infrastructure during verification (not the implementation) — tests/contract/test_task_3_3_e2e.py's SashaProc helper needed a consumed-position cursor in read_until() plus a realistic delay in send(), since its own rapid zero-gap command sequence could otherwise be genuinely (and correctly) misdetected as one paste by the new heuristic; and a pty_harness gotcha where a forked child calling input() directly needs sys.stdin/sys.stdout reassigned to sys.__stdin__/__stdout__ before use. Dispatched clean, byte-for-byte match with the verified fix sketch, gate passed (663/663). Phase 54 review (Task 54.2, Tier 1): read the final shipped paste_join.py and __main__.py wiring together as a whole — the EOFError semantics (first read unwrapped and propagates; later reads caught) match the design exactly, both wiring points (_repl_input, TerminalConfirmer) route through the same _joined_input function, no drift from the already-diffed dispatch. Full suite re-confirmed green (663/663) on real HEAD |
2026-07-27 |
2026-07-28 |
| ISSUE-055 |
Issue |
Open |
Low |
The new directory-recap cascade (ISSUE-051/Task 51.1) can fire one command later than the transition that should have triggered it, when the existing git-repo/memory recap already had something to say at that transition. Found live (Salvador's own testing, 2026-07-27): boot into ~/Sasha correctly printed the existing "18 memories" line and correctly gap-filled (suppressed) the new cascade for that boot — but the very next raw command in the same directory printed the new cascade's direct-recap line anyway ("Sasha: last commit ... — Task 51.3 — Phase 51 review"), because _check_place_shift()'s own "cwd unchanged" fast path returns False on every subsequent same-directory command (not just non-transitions), giving _check_directory_recap() (which never got to run at boot, since gap-filler suppressed it then) its first real opportunity one command later rather than never. Not exactly wrong — at most one recap still ever prints per directory per session — but doesn't fully match the design's "at most one recap per transition" framing. Low priority, cosmetic timing nuance, not designed/fixed |
2026-07-27 |
— |
| ISSUE-056 |
Issue |
Done |
Normal |
Directory-recap (ISSUE-051) has nothing to say for a genuine cold-start directory — no git history and no tool_log activity (e.g. a freshly cloned/extracted project Sasha's never run inside). Salvador proposed a filesystem-content fallback (README/source files + mtime) and explicitly asked to be challenged on it. Two ideas were rejected empirically before settling on a design: (1) mtime/ctime-based recency — rejected because Linux ctime isn't creation time, and mtime is routinely bulk-reset by exactly the operations (git clone/checkout, tar extraction, rsync, docker COPY) that produce this cold-start case in the first place, so a false recency claim would be reported as fact by build_prompt()'s "use only the data given" instruction; (2) a "≥N files with a recognized source extension" heuristic — measured via a disposable, uncommitted scan script against Salvador's real $HOME (11,723 directories) at a 44% false-positive rate, driven by vendored language source trees (~/go/pkg/mod/...) and browser "Save Page As" asset folders (*_files/) neither of which relate to the user's own work. Settled design: a marker-file-only check (README/LICENSE/manifest at the top level, non-recursive) gates a new escalation branch in decide()'s passive cascade, plus a two-part vendor/cache exclusion confirmed empirically — (a) any dot-prefixed ancestor path component (verified against real ~/.cache, which alone hosts dozens of unrelated per-app caches — chrome, mesa shader cache, fontconfig, go-build, huggingface — under one dot-prefixed parent, the general case Salvador asked about after browser cache came to mind) and (b) a short named list (node_modules, venv, site-packages, dist, build, target, vendor, __pycache__) for conventionally non-dot vendor directories, measured at 6-32% marker-file false-positive rates in real node_modules/venv/site-packages trees (every npm package and Python dist-info folder ships its own LICENSE/package.json). ~/go/pkg/mod-style caches (neither dot-prefixed nor named) accepted as a known, low-consequence, unaddressed gap. The same content-sniff (README excerpt + top-level listing, added to gather_context()/build_prompt()) also applies to explicit sasha recap, unconditionally (no vendor/cache gate — that only controls passive firing frequency). Also naturally fixes a pre-existing gap: a git repo with no tool_log subtree entries previously returned SILENT unconditionally without ever checking for a commit. Design: docs/superpowers/specs/2026-07-28-issue-056-recap-cold-start-design.md. Task 56.1 (Done, 2026-07-29): _has_project_marker/_is_vendor_or_cache/_readme_excerpt/_top_level_listing helpers plus the decide()/gather_context()/build_prompt() wiring — byte-for-byte match with the design spec's constants and branch logic, 9/9 new contract tests pass (tests/contract/test_task_56_1_recap_cold_start.py). Tracking commit and Phase 56 review were delayed by an unrelated dirty-tree/out-of-band-commit state found during a 2026-08-07 Tier 1 reconciliation session (see Task 56.2 below) — resolved and gate reconfirmed green (670/672, the 2 remaining failures being a separately-logged pre-existing environment gap, ISSUE-058) before this issue closed |
2026-07-28 |
2026-08-07 |
| ISSUE-057 |
Issue |
Open |
Low |
test_task_3_1_repl.py::test_ac2_empty_accepts_pending_offer and ::test_ac5_127_auto_consults are timing-race flakes, not real bugs — found during Task 56.1's scratch-worktree verification (2 failures out of 672 in a full-suite run). Root cause: SpyExecutor.cwd is the fake path /spy, which is never a git repo and (once the test's own run_once calls log a tool_log entry for it) has non-empty subtree activity with self_is_repo == False — directory_recap.decide()'s pre-existing bottom-of-function fallback (else: return ESCALATE, present since Task 51.1, untouched by ISSUE-056) genuinely returns ESCALATE for this fake cwd, same as it would before ISSUE-056. _check_directory_recap() then spawns escalate_async()'s background thread, which races the test's own synchronous len(stub.requests) assertions — whether the thread's HTTP call lands before or after the assertion is pure thread-scheduling luck, unrelated to any diff's correctness. Confirmed unrelated to ISSUE-056 specifically: reproduced the exact same flake (1/5 runs) with ISSUE-056's directory_recap.py changes reverted (git stash) inside the same scratch worktree, while the real repo (not a worktree, presumably different disk/scheduling characteristics) passed 5/5 in the same run count. Not fixed here (unrelated to Task 56.1's scope, matches the ISSUE-053 precedent of a confirmed pre-existing, unrelated flake found during a gate/verification run) |
2026-07-29 |
— |
| ISSUE-058 |
Issue |
Done |
Low |
test_task_11_2_completion_bash_integration.py::test_ac1_filename_completion_via_minimal_fallback and test_task_12_1_completion_path_fixes.py::test_ac1_cwd_changes_where_bash_looks failed consistently — sasha.completion.complete_via_bash(["cat", "PLAN"], 1, cwd=...) returned [] where the tests expect "PLAN.md". Found during a 2026-08-07 Tier 1 reconciliation session, initially deselected as "environment-specific, not yet root-caused." Root-caused 2026-08-08 after Salvador hit the same bug via manual testing (nano filename.txt<TAB> finding nothing, tab-completing directory names via cd working fine): bash-completion's catch-all completer for any command without a bespoke completion script (_comp_complete_minimal — confirmed via direct bash -c probing that nano, cat, and even a nonexistent command name all resolve to it) never populates COMPREPLY itself; it calls compopt -o bashdefault -o default, a signal telling bash's own interactive readline C code to fall back to normal filename completion — entirely invisible to complete_via_bash()'s bash -c subprocess, which only ever inspected COMPREPLY. Not environment-specific after all — this is bash-completion's portable, documented default behavior for any command without a dedicated script; cd only worked because it has a real, COMPREPLY-populating completer (_comp_cmd_cd). Design: none (small single-file fix, matching the Task 8.1/28.1/30.1/58.1 precedent) — spec: docs/superpowers/specs/task-60.1-bash-completion-default-fallback.md. Task 60.1 (Done, 2026-08-08): complete_via_bash() shadows compopt inside its subprocess script to capture the -o default/-o bashdefault signal (also avoids the real builtin's "not currently executing completion function" error outside genuine interactive-completion context) and falls back to complete_filenames() only when that signal fires with an empty COMPREPLY — a real completer that legitimately finds nothing (e.g. git checkout against no matching branch) is unaffected, verified by regression test. 4/4 new contract tests plus both previously-deselected tests now pass for real; scripts/run-tests.sh's ISSUE-058 DESELECT array removed in the same spec commit |
2026-08-07 |
2026-08-08 |
| ISSUE-059 |
Issue |
Done |
Normal |
question tool — let the LLM pause and ask the user a structured, menu-driven clarifying question (single- or multi-select, optional free-text) instead of guessing between materially different approaches, mirroring opencode's own question tool (and, functionally, the assistant's own AskUserQuestion). Salvador's initial proposal: /home/salvador/sasha-test/question_tool.md (external scratch file, not yet in this repo) — a full schema (strict: true JSON Schema, array of {question, header, multiple, options[]}), an opencode-derived prompt-instruction set (when to ask, recommended-option-first labeling, no manual "Other" option since the client adds a custom-text fallback), a blocking-input() mechanism sketch reusing TerminalConfirmer's existing input-blocking primitive (repl.py:625), and error-state handling (Ctrl+C/empty-Enter → REJECTED, matching TerminalConfirmer.ask()'s existing empty-string-means-no behavior at repl.py:644). Fits sasha's existing tools.py Tool registry cleanly — requires_confirmation's existing bool-or-callable slot and agent.py:79's existing pre-execution branch are the natural interception point; no new event bus needed since the agent loop is already synchronous. Empirical probe run 2026-08-07 (disposable script, not committed) via the real ChatClient.chat() streaming path against llm7/gpt-oss:20b, deepseek/deepseek-v4-pro, and the local Ollama profile (qwen excluded — out of API credits, not a real signal): (1) the schema itself (including strict: true and nested additionalProperties: false) was accepted at the HTTP/parse level everywhere, no provider rejected it outright; (2) shape-adherence varies sharply by model — deepseek produced a fully correct nested {question,header,multiple,options[{label,description}]} payload through the real streaming reassembly path on a forced-use prompt, while llm7 (gpt-oss:20b, confirmed by Salvador to have "surprisingly unremarkable" tool-calling) flattened questions to plain strings on an easy trigger and failed outright (stream ended before [DONE]) on the harder forced-use prompt; (3) trigger calibration looked reasonable on deepseek — it chose to investigate the project via run_command before asking, a defensible read of an ambiguous-but-actionable prompt, not a bug. Conclusion: the handler must defensively validate parsed questions shape and degrade gracefully (never assume well-formed input) — now a hard design requirement, not a nice-to-have. Separately, unrelated finding logged as ISSUE-060: the local profile doesn't emit standard OpenAI-style streamed tool-call deltas at all for this model, leaking raw special-token tool-call markup into the content stream instead — affects every existing tool on that profile already, not just this one. Design: docs/superpowers/specs/2026-08-07-issue-059-question-tool-design.md. Task 57.1 (Done, 2026-08-07): schema, Questioner protocol, validate_shape/format_result, tools.py REGISTRY entry, agent.py dedicated dispatch branch, session.py system-prompt guidance (b573df4). Task 57.2 (Done, 2026-08-07): TerminalQuestioner (repl.py) + Repl/__main__.py wiring, 23/23 contract tests (ba2aa29). Manually verified end-to-end by Salvador against the deepseek profile (2026-08-08): multi-round single-select flow rendered and behaved exactly per spec, including the model correctly following the (Recommended) labeling guidance unprompted; multi-select, [C] custom-text, invalid-input reprompt, and Ctrl+C/bare-Enter abort remain contract-tested only, not manually exercised — low-risk, deliberately left as-is per Salvador ("I don't think it's necessary... I will bring it up later if I trip onto something") |
2026-08-07 |
2026-08-08 |
| ISSUE-060 |
Issue |
Open |
Normal |
The local profile (~/.config/sasha/config.toml: http://192.168.8.253:11434/v1, Ollama-served, model = "local") does not emit standard OpenAI-compatible streamed delta.tool_calls[] entries — confirmed via a disposable probe script (2026-08-07, part of ISSUE-059's empirical validation) driving the real ChatClient.chat() path with a prompt explicitly asking it to call a tool: the model attempted a tool call, but as raw DeepSeek-style special-token markup (<|tool▁calls▁begin|><|tool▁call▁begin|>function<|tool▁sep|>question...) leaked directly into the streamed content text, never populating delta.tool_calls at all, so llm.py's tool_call_fragments reassembly never sees it and no tool call is recovered. This is a pre-existing gap independent of ISSUE-059 — it would affect every tool sasha ships (run_command, write_file, etc.), not just the new question tool; the probe just happened to be the first place anyone drove this specific profile through a tool-forcing prompt and looked closely at the raw stream. Likely cause, now narrowed (Salvador confirmed the actual model behind the local alias): bartowski/DeepSeek-Coder-V2-Lite-Instruct-GGUF:Q6_K — a community GGUF import of a model whose native chat template uses DeepSeek's special-token tool-call syntax; Ollama's built-in Modelfile template for this import most likely lacks (or doesn't correctly implement) a tool-calling template section, so it falls back to emitting the model's raw native tool-call tokens as plain content instead of populating the OpenAI-compatible tool_calls delta. Still not fixed here (would require inspecting/patching the Modelfile's template on the Ollama server at 192.168.8.253, outside this repo) — out of scope for ISSUE-059, and no evidence yet of how often this bites real usage since sasha's default profile is local per config.toml but Salvador primarily drives interactive sessions through other profiles |
2026-08-07 |
— |
| ISSUE-061 |
Issue |
Open |
Low |
Neither TerminalConfirmer.ask() (repl.py:625-650) nor __main__.py catches KeyboardInterrupt/EOFError anywhere around the mid-consult confirmation gate — Repl.loop()'s own try/except EOFError/KeyboardInterrupt (repl.py:615-620) only wraps the outer main-prompt input() call, not self.run_once(line) itself (repl.py:621), and __main__.py:165's repl.loop() call has no surrounding handler either. So a real Ctrl+C while sasha is mid-consult and showing the y/n/e confirmation gate (e.g. run_command/write_file) would propagate KeyboardInterrupt uncaught all the way out of main(), crashing with a raw Python traceback instead of gracefully cancelling — unlike Ctrl+C at the main prompt, which is already handled cleanly (continues to a fresh prompt). Found while designing ISSUE-059's error-handling section (checking whether the new question tool could reuse existing Ctrl+C semantics — it can't, because there aren't any to reuse) — unrelated to ISSUE-059 itself and not fixed here; the new tool will define its own local KeyboardInterrupt/EOFError handling rather than either relying on or retroactively fixing this gap |
2026-08-07 |
— |
| ISSUE-062 |
Issue |
Open |
Low |
test_task_51_2_sasha_recap_command.py::test_ac4_recap_sends_gathered_context_prompt is a timing-race flake (~1/3 runs), not a real bug — found in a disposable scratch worktree while verifying Task 57.2 (unrelated: TerminalQuestioner + Repl/__main__.py wiring). The test computes expected_prompt via its own direct directory_recap.build_prompt(directory_recap.gather_context(repo)) call, then separately triggers repl._handle_builtin("sasha recap") and asserts the two produce byte-identical prompts; gather_context() reads real git/filesystem state (e.g. recency-sensitive data) twice, independently, so the two reads can observe different state depending on scheduling — same failure family as ISSUE-057 (a test asserting exact equality between two independently-timed reads of live state). Reproduced with Task 57.2's own diff stashed out, confirming pre-existing and unrelated. Not fixed here, matching the ISSUE-057/ISSUE-053 precedent of a confirmed pre-existing, unrelated flake found during a verification run |
2026-08-07 |
— |
| ISSUE-063 |
Issue |
Done |
Normal |
The stop message agent.py prints when run_consult()'s per-turn loop halts (agent.py:177-194) conflates two genuinely different causes — the repetition guard (current_key == previous_key, the model called the identical action twice in a row) and the max_tool_turns budget being exhausted — into one fixed string, "[sasha] stopping: repeated identical action / tool-turn limit reached", with an action-description suffix appended only for the repetition case. Salvador's framing: these deserve separate observability — repetition is worth surfacing carefully so the user can see exactly what kept repeating, while turn-limit exhaustion is a different signal entirely ("why is this action taking so many individual steps?") that shouldn't read like a suspected bug. Separately, related friction reported the same session: max_tool_turns (default 10, config.py:26) was already fully supported as a [limits] config.toml key (config.py:96-101,188,237, _merge_limits) but completely undiscoverable — Salvador's real ~/.config/sasha/config.toml had no [limits] section and he was hitting the default-10 ceiling frequently in extended interactive sessions (surfaced concretely by the new question tool's multi-round clarifying-question flows, e.g. a 3-round character-creation dialogue already using 3 of the 10 available turns). Immediate manual relief already applied directly to Salvador's config.toml ([limits]\nmax_tool_turns = 100); the self-install template still lacks a [limits] section, which is ISSUE-052's broader, already-logged scope, not duplicated here. Design not specced separately — small enough to go straight to a task spec (Task 58.1), matching the Task 8.1/28.1/30.1 precedent for well-scoped single-file fixes |
2026-08-08 |
2026-08-08 |
| ISSUE-064 |
Issue |
Done |
Normal |
Give sasha the ability to load and follow "skills" — packaged markdown playbooks describing a structured procedure, the same concept Claude Code (this assistant's own harness) uses. Raised by Salvador after seeing the new question tool in action and noting several popular skill libraries (ponytail, superpowers) are almost entirely SKILL.md prose with a frontmatter description — no dependency on harness-specific machinery — so they'd plausibly load into any capable model's context and work as-is. Two explicitly separate stages agreed with Salvador: (1) near-term, tractable — a sasha-native skills directory convention plus a new tool (parallel in size/shape to remember/question) that loads a chosen playbook's text into context, written against sasha's actual tool set (run_command/write_file/remember/question) — a brainstorming-style skill would naturally lean on the new question tool for its "ask one thing at a time" rhythm instead of loose free text; (2) long-term, explicitly deferred as "a long shot today" — real compatibility with the broader Claude-Code-centric skill marketplace, which requires reimplementing enough of the Agent/Artifact/TaskCreate/ToolSearch machinery that many published skills actually depend on (subagent dispatch, published-artifact hosting, structured task tracking, deferred-tool search) — a much larger, separate evaluation for later, not tracked by this issue. Design: docs/superpowers/specs/2026-08-08-issue-064-skill-loading-design.md. Task 59.1 (Done, 2026-08-08): skills.py (frontmatter parsing, marketplace/plugin/version/skills discovery with latest-semver-per-plugin and alphabetical-path collision tie-break, build_skills_listing(), load_skill()), tools.py's new use_skill REGISTRY entry, agent.py's unknown-tool tool_log signal (verdict unknown_tool, the durable record of what stage-2 machinery a loaded skill's own instructions reach for and don't find), session.py's skills_listing system-prompt composition, __main__.py wiring (f4014a3), 24/24 new contract tests plus 4 pre-existing files amended for the new tool and the deliberately-reversed unknown-tool-logging behavior. Verified in a Tier-1 scratch worktree before dispatch (byte-identical to the dispatched implementation) and reviewed at the Phase 59 boundary — no drift, no scope creep, two full-suite failures during review (test_task_4_1_job_control.py::test_ac4_termios_restored_after_ctrl_z_kill, and the already-logged ISSUE-057 family) confirmed pre-existing/environmental and unrelated, neither module touched by this task's diff. Stage 1 fully shipped; stage 2 (marketplace compatibility) remains explicitly out of scope, a separate future evaluation if ever pursued |
2026-08-08 |
2026-08-08 |
| ISSUE-065 |
Issue |
Done |
Normal |
skills.py's _parse_frontmatter() (Task 59.1) is a simple line-based key: value parser that doesn't understand YAML block scalars — description: > (folded) or `description: |
(literal) followed by indented continuation lines, a very common real-worldSKILL.mdpattern. Found immediately after Salvador installed real skill libraries into the new~/.config/sasha/skills/(ISSUE-064 stage 1):superpowersparsed fine (single-line descriptions), butponytail(https://github.com/DietrichGebert/ponytail) uses a foldeddescription: >block, andbuild_skills_listing()showed the useless literal stringponytail: >instead of the real description — worse, several of ponytail's continuation lines themselves contain colons ("...has seen everything: question...") which the old parser mistook for new key:value pairs, polluting the frontmatter dict with garbage keys. Confirms the stage-1 design's core premise (realSKILL.mdlibraries "load as-is") needs this fix to actually hold. Design: none (small single-file fix). Spec:docs/superpowers/specs/task-61.1-yaml-block-scalar-frontmatter.md` |
2026-08-08 |