{"id":"mala-01s","title":"Refactor: consolidate lock config + env in one place","description":"Goal: Centralize LOCK_DIR and SCRIPTS_DIR configuration (currently defined in multiple modules). Allow override via env/config where appropriate.\\n\\nScope/files: src/tools/env.py, src/tools/locking.py, any imports that need adjustment.\\n\\nNotes:\\n- Consider single source of truth in tools/env.py.\\n- Keep behavior unchanged unless explicit override is set.\\n\\nAcceptance criteria:\\n- No duplicate definitions for LOCK_DIR/SCRIPTS_DIR.\\n- Locking and scripts continue to work.\\n- Minimal, well-contained changes.","status":"closed","priority":3,"issue_type":"task","created_at":"2025-12-25T21:40:20.871689769Z","updated_at":"2026-01-01T08:19:23.320727877Z","closed_at":"2026-01-01T08:19:23.320727877Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-01s","depends_on_id":"mala-kpf","type":"blocks","created_at":"2025-12-25T21:47:40.701482404Z","created_by":"daemon"}]}
{"id":"mala-03qr","title":"[Review] 4 non-blocking findings from mala-20c3","description":"## Review Findings\n\nThis issue consolidates 4 non-blocking findings from code review.\n\n**Source issue:** mala-20c3\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Update architecture docs that still reference removed mcp.py\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** docs/architecture.md:301\n\n`docs/architecture.md` still lists `mcp.py` as a key module for “MCP server configuration and disallowed tools”, but `src/infra/mcp.py` was deleted in this commit. This makes the architecture doc inaccurate for anyone trying to follow where MCP/disallowed-tools wiring lives now (likely `src/infra/agent_runtime.py` + `src/infra/tool_config.py`).\n\n---\n\n### Finding 2: [P2] Fix docs constraint referencing src.infra.mcp after deletion\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** docs/architecture.md:546\n\n`docs/architecture.md` states that `src.infra.hooks` must not import `src.infra.mcp`, but that module no longer exists after this commit. This guidance should point at the current “configuration wiring” module(s) so the doc continues to encode the intended boundary (otherwise it’s confusing and no longer useful as a constraint).\n\n---\n\n### Finding 3: [P2] Update import-linter contract that forbids non-existent src.infra.mcp\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** pyproject.toml:550-555\n\n`pyproject.toml` keeps an import-linter forbidden contract targeting `src.infra.mcp`, which was removed in this commit. Even if the linter doesn’t error, the contract no longer enforces the real boundary you want (it can’t catch hooks importing the *current* MCP/config wiring). Update the contract to forbid the actual module(s) now responsible for MCP/config wiring, or remove/rename it to avoid stale enforcement.\n\n---\n\n### Finding 4: [P2] Lock enforcement hook blocks file writes when locking tools are missing\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/infra/agent_runtime.py:254-260\n\nThe updated error message in `_build_mcp_servers` suggests explicitly providing servers via `with_mcp(servers={...})` as an alternative to the factory. However, `AgentRuntimeBuilder.build()` always adds `make_lock_enforcement_hook`, which blocks all file writes unless a lock is held. If the provided servers do not include the locking MCP tools, the agent will be unable to acquire locks and thus unable to write to any files, effectively breaking file-write functionality without clear guidance on why or how to disable enforcement.\n\n---\n\n\u003c!-- fp:b698836f1183e056 --\u003e\n\u003c!-- fp:06a6ab14864bb4b1 --\u003e\n\u003c!-- fp:64fc4c2488d2f0d6 --\u003e\n\u003c!-- fp:b556636216b33059 --\u003e\n\u003c!-- review_finding:4c3aad776f85 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-05T04:28:55.133898972Z","created_by":"cyou","updated_at":"2026-01-05T04:54:41.977292645Z","closed_at":"2026-01-05T04:54:41.977292645Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-20c3"]}
{"id":"mala-03us","title":"on the last review gate, add issues for the remaining findings","status":"tombstone","priority":2,"issue_type":"feature","created_at":"2026-01-05T05:12:12.140101982Z","created_by":"cyou","updated_at":"2026-01-06T22:52:10.761723406Z","deleted_at":"2026-01-06T22:52:10.761723406Z","deleted_by":"batch delete","delete_reason":"batch delete","original_type":"feature"}
{"id":"mala-056m","title":"Disable TodoWrite tool for mala agents","description":"TodoWrite causes ~90K tokens waste (46% of sessions) from full-list rewrites on every status change.\n\n**Problem:**\n- Agents ignore prompt limit of 2-3 calls per session\n- Full list rewritten on every tick instead of delta updates\n- Pushes important instructions (Quality Checks, Output template) further back in context\n\n**Solution:**\n1. Disable TodoWrite tool in SDK for mala agents\n2. Add note to implementer_prompt.md that orchestrator verifies lint/format/test commands were run\n\n**Expected impact:** 10-15% token reduction (~90K tokens saved)\n\n**Oracle analysis:** TodoWrite is performative, not functional. The structured workflow + output template already enforce steps. Shorter context from removing spam will improve recall of important instructions.\n\nReference: docs/2026-01-03-token-waste.md","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-03T05:48:17.62656939Z","created_by":"cyou","updated_at":"2026-01-03T06:00:56.715027112Z","closed_at":"2026-01-03T06:00:56.715027112Z","close_reason":"Closed"}
{"id":"mala-08f","title":"Add protocol-completeness check to bd-breakdown skill","description":"Context:\n- Issue mala-ng3 implemented the quality gate side of ISSUE_NO_CHANGE/ISSUE_OBSOLETE markers\n- But no task was created to document these markers in the implementer prompt (mala-3uo created retroactively)\n- Root cause: when breaking down plans, bidirectional protocols need tasks for BOTH producer and consumer sides\n- The bd-breakdown skill didn't flag this gap\n\nScope:\n- In: Add guidance to bd-breakdown skill (commands/bd-breakdown.md) to check for protocol completeness\n- In: When a task involves parsing/receiving a signal, ensure a corresponding producer task exists\n- Out: Changing how mala-ng3 was implemented (already done)\n\nAcceptance Criteria:\n- bd-breakdown skill prompts agent to verify both sides of any interface/protocol are covered\n- Checklist includes: 'For log markers/signals: is there a task to update the component that produces them?'\n- Checklist includes: 'For prompt-driven behavior: is there a task to update the prompt that instructs agents?'\n\nTest Plan:\n- Manual review of skill changes\n- Apply to a sample breakdown to verify checklist is used\n\nNotes for Agent:\n- The skill is at commands/bd-breakdown.md in this repo\n- Key addition: when an issue involves 'parse X marker' or 'handle X signal', ask: who produces X and do they have instructions to do so?","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-27T06:25:20.359991212Z","updated_at":"2025-12-27T06:26:40.359583428Z","closed_at":"2025-12-27T06:26:40.359583428Z","close_reason":"Will fix directly via prompt"}
{"id":"mala-0d7k","title":"[Review] 1 non-blocking finding from mala-idx6.6","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-idx6.6\n**Highest priority:** P3\n\n---\n\n### Finding 1: [P3] Avoid misparsing non-icon messages as icon+rest\n\n**Priority:** P3\n**Reviewer:** codex\n**Location:** src/infra/io/log_output/console.py:420-423\n\nThe adapter treats any message whose second character is a space as having an icon prefix. That means legitimate messages like `A thing happened` or `I/O failed` (single-letter word + space) will be split into icon=`A`/rest=`thing happened`, dropping the first word and changing the icon. The LoggerPort contract doesn’t require callers to include icon prefixes, so this parsing violates the protocol for valid inputs. Consider only splitting when the prefix is one of the expected icon characters, or pass icon separately instead of inferring it from the message.\n\n---\n","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-03T15:26:10.496444313Z","created_by":"cyou","updated_at":"2026-01-03T16:08:51.30156367Z","closed_at":"2026-01-03T16:08:51.30156367Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_findings:dafa9a06dd0a","source:mala-idx6.6"]}
{"id":"mala-0dsz","title":"[Review] src/cli/cli.py:386-392: [P2] --orphans-only and --epic flags are mutually exclusive but not validated","description":"## Review Finding\n\nThis issue was auto-created from a P2 review finding.\n\n**Source issue:** mala-jnah\n**Reviewer:** claude\n**Location:** src/cli/cli.py:386-392\n\n## Details\n\nWhen `--epic` is used, issues are filtered to children of that epic. When `--orphans-only` is used, issues are filtered to those without a parent epic. These conditions are mutually exclusive - children of an epic by definition have that epic as their parent. Using both flags together will silently return an empty issue list rather than warning the user about the conflicting options.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-01T23:18:02.901411634Z","created_by":"cyou","updated_at":"2026-01-01T23:41:40.109838134Z","closed_at":"2026-01-01T23:41:40.109838134Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_finding:10fec5f4942c","source:mala-jnah"]}
{"id":"mala-0gb6","title":"early exit on run level validation failure","status":"tombstone","priority":2,"issue_type":"feature","created_at":"2026-01-06T05:21:20.629625351Z","created_by":"cyou","updated_at":"2026-01-06T22:52:10.761723406Z","deleted_at":"2026-01-06T22:52:10.761723406Z","deleted_by":"batch delete","delete_reason":"batch delete","original_type":"feature"}
{"id":"mala-0h7k","title":"Epic: Orchestrator Refactor - Extract DeadlockHandler and OrchestratorState","description":"## Overview\n\nRefactor MalaOrchestrator (~1023 LOC) to improve cohesion and testability by extracting:\n1. **OrchestratorState**: Dataclass encapsulating per-run mutable state\n2. **DeadlockHandler**: Service class for deadlock resolution and abort logic\n\n## Source\n\nArchitecture review finding (High severity): \"MalaOrchestrator has excessive responsibilities and state\"\n\n## PR Strategy\n\n- **PR1**: Extract OrchestratorState dataclass, update orchestrator\n- **PR2**: Extract DeadlockHandler (including abort logic), wire callbacks  \n- **PR3**: Test cleanup, verify integration\n\n## Acceptance Criteria\n\n- [ ] OrchestratorState dataclass created with agent_ids, completed, active_session_log_paths, deadlock_cleaned_agents\n- [ ] DeadlockHandler class created with callback-based DI\n- [ ] Orchestrator delegates deadlock/abort to handler\n- [ ] Unit tests for both extracted components\n- [ ] Existing integration tests pass unchanged\n- [ ] 85% coverage maintained\n\n## Plan Reference\n\nSee: `plans/2026-01-04-orchestrator-refactor-plan.md`","status":"closed","priority":1,"issue_type":"epic","created_at":"2026-01-04T20:50:25.158858583Z","created_by":"cyou","updated_at":"2026-01-04T23:08:06.631203485Z","closed_at":"2026-01-04T23:08:06.631203485Z","close_reason":"Closed"}
{"id":"mala-0h7k.1","title":"[T001] Create OrchestratorState dataclass","description":"## Goal\n\nCreate the OrchestratorState dataclass to encapsulate per-run mutable state, extracted from MalaOrchestrator.\n\n## Context\n\nMalaOrchestrator maintains several instance attributes that represent per-run state:\n- `agent_ids`: Maps issue_id to agent_id\n- `completed`: List of IssueResult objects\n- `_active_session_log_paths`: Session log paths for deadlock handling\n- `_deadlock_cleaned_agents`: Tracks agents cleaned during deadlock\n\nExtracting these into a dataclass makes state management explicit and testable.\n\n## Scope\n\n**In**: Create dataclass with all state fields, create unit tests\n**Out**: Modifying orchestrator to use it (T002), lock handling (stays with handler)\n\n## Changes\n\n- `src/orchestration/orchestrator_state.py` — **New** — Create OrchestratorState dataclass\n- `tests/unit/orchestration/test_orchestrator_state.py` — **New** — Unit tests for state initialization\n\n## Implementation Details\n\n```python\nfrom dataclasses import dataclass, field\nfrom pathlib import Path\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from src.pipeline.issue_result import IssueResult\n\n@dataclass\nclass OrchestratorState:\n    \"\"\"Encapsulates mutable state for a single orchestration run.\n\n    Created at the start of run() and passed to methods that need state.\n    Pure data container - no behavior, no locks.\n    \"\"\"\n    agent_ids: dict[str, str] = field(default_factory=dict)\n    completed: list[\"IssueResult\"] = field(default_factory=list)\n    active_session_log_paths: dict[str, Path] = field(default_factory=dict)\n    deadlock_cleaned_agents: set[str] = field(default_factory=set)\n```\n\n**Note**: IssueResult import is from `src.pipeline.issue_result`, not `src.core.models`.\n\n## Acceptance Criteria\n\n- [ ] OrchestratorState dataclass exists at src/orchestration/orchestrator_state.py\n- [ ] All four state fields defined with correct types and defaults\n- [ ] TYPE_CHECKING used for IssueResult import from `src.pipeline.issue_result`\n- [ ] Unit tests verify initialization with empty collections\n- [ ] Unit tests verify fields are independent\n\n## Verification\n\n```bash\nuv run pytest tests/unit/orchestration/test_orchestrator_state.py -v\nuvx ty check src/orchestration/orchestrator_state.py\n```\n\n## Notes for Agent\n\n- Follow existing dataclass patterns in types.py\n- Use `field(default_factory=...)` for mutable defaults\n- Import path: `from src.pipeline.issue_result import IssueResult` (under TYPE_CHECKING)","notes":"Quality gate failed: Quality gate failed: No commit with bd-mala-0h7k.1 found after run baseline (stale commits from previous runs are rejected)\nLog: /home/cyou/.claude/projects/-home-cyou-mala/c54be556-f177-4663-989d-691fda9165ee.jsonl","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-04T20:50:40.199677912Z","created_by":"cyou","updated_at":"2026-01-04T21:54:27.243412651Z","closed_at":"2026-01-04T21:54:27.243412651Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-0h7k.1","depends_on_id":"mala-0h7k","type":"parent-child","created_at":"2026-01-04T20:50:40.210943875Z","created_by":"daemon"}]}
{"id":"mala-0h7k.10","title":"[Review] 6 non-blocking findings from mala-0h7k.6","description":"## Review Findings\n\nThis issue consolidates 6 non-blocking findings from code review.\n\n**Source issue:** mala-0h7k.6\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] test_uses_default_reason leaves uncancelled task running\n\n**Priority:** P2\n**Reviewer:** claude\n**Location:** tests/unit/orchestration/test_deadlock_handler.py:451-465\n\nThe `test_uses_default_reason` test creates an async task that sleeps for 10 seconds but never awaits or cancels it after the test completes. While `abort_active_tasks` calls `task.cancel()`, the test doesn't await the task to ensure proper cleanup, unlike `test_cancels_running_tasks` which has the `asyncio.wait_for` pattern. This can cause \"Task was destroyed but it is pending\" warnings and potential test pollution.\n\n---\n\n### Finding 2: [P2] test_includes_session_log_path leaves task without cleanup\n\n**Priority:** P2\n**Reviewer:** claude\n**Location:** tests/unit/orchestration/test_deadlock_handler.py:497-513\n\nSame issue as `test_uses_default_reason` - creates a task sleeping 10 seconds without awaiting it after `abort_active_tasks` returns. The test asserts on the result but doesn't ensure the cancelled task reaches terminal state, which can cause asyncio warnings and test flakiness.\n\n---\n\n### Finding 3: [P2] Keep argument/count assertions in callback-order test\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** tests/unit/orchestration/test_deadlock_handler.py:98-140\n\n`test_acquires_lock_and_calls_callbacks_in_order` now only asserts a `call_order` sequence, which can pass even if `handle_deadlock` supplies wrong issue/agent IDs or calls callbacks extra times. This weakens the test’s ability to catch regressions; consider keeping the `call_order` assertion but also restoring key `assert_called/assert_awaited_once_with` checks for the most important callbacks (e.g., `add_dependency`, `mark_needs_followup`, and cleanup args).\n\n---\n\n### Finding 4: [P2] Make AsyncMock side_effect helpers robust to keyword calls\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** tests/unit/orchestration/test_deadlock_handler.py:111-126\n\nThe `side_effect` coroutines use concrete parameter names (e.g. `summary`, `log_path`). If `DeadlockHandler` invokes callbacks using keyword arguments with different names (e.g. `reason=...`), these helpers will raise `TypeError` and fail the test for a non-behavioral reason. Using `*args, **kwargs` (or matching the exact callback signature) makes the tests less brittle while still recording `call_order`/events.\n\n---\n\n### Finding 5: [P3] Update integration test naming/docstring after switching away from orchestrator\n\n**Priority:** P3\n**Reviewer:** codex\n**Location:** tests/integration/infra/test_lock.py:351-377\n\n`TestOrchestratorCleanup.test_orchestrator_cleans_up_locks_when_agent_crashes` now calls `cleanup_agent_locks()` directly and no longer exercises the orchestrator. That’s fine for avoiding private orchestrator APIs, but the test name/docstring is now misleading and can confuse future readers about what’s covered; rename the test/class or adjust the docstring to reflect it’s testing the lock cleanup function (or, if you still want orchestrator coverage, call a public orchestrator entry point).\n\n---\n\n### Finding 6: [P2] Task identification relies on exact issue ID match\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** tests/unit/orchestration/test_deadlock_handler.py:182-187\n\nIn `test_defers_self_cancellation`, the test relies on `active_tasks[\"issue-b\"] = current` to identify the victim task. While this works in the test, `DeadlockHandler.handle_deadlock` identifies the victim task using `info.victim_issue_id`. The test should ensure that `info.victim_issue_id` matches the key in `active_tasks` to avoid potential confusion or future fragility.\n\n---\n\n\u003c!-- fp:6be052520a9dfc25 --\u003e\n\u003c!-- fp:787f86aa53af6858 --\u003e\n\u003c!-- fp:584631babfc4a623 --\u003e\n\u003c!-- fp:de07b721cb7bc9d3 --\u003e\n\u003c!-- fp:35f7b5feed68bfd0 --\u003e\n\u003c!-- fp:5a6672c5ceb4b13a --\u003e\n\u003c!-- review_finding:2d2cb12e1628 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-04T23:02:32.168945096Z","created_by":"cyou","updated_at":"2026-01-04T23:06:01.050281387Z","closed_at":"2026-01-04T23:06:01.050281387Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-0h7k.6"],"dependencies":[{"issue_id":"mala-0h7k.10","depends_on_id":"mala-0h7k","type":"parent-child","created_at":"2026-01-04T23:02:32.169569892Z","created_by":"daemon"}]}
{"id":"mala-0h7k.11","title":"[Review] 1 non-blocking finding from mala-0h7k.10","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-0h7k.10\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Don’t assume positional args in callback assertions\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** tests/unit/orchestration/test_deadlock_handler.py:140-147\n\nIn `test_acquires_lock_and_calls_callbacks_in_order`, the new assertions assume callbacks are invoked positionally (e.g. `call_args[0][0]` and `assert_awaited_once_with(victim, blocker)`). If `DeadlockHandler` calls these mocks with keyword arguments (which Finding 4 explicitly guards against), `call_args[0]` can be empty and `assert_awaited_once_with` can fail even though the call is semantically correct. Prefer asserting on `await_args`/`call_args` using `.args`/`.kwargs` and checking the victim/blocker values regardless of whether they were passed positionally or by keyword.\n\n---\n\n\u003c!-- fp:fade311054608be2 --\u003e\n\u003c!-- review_finding:2555a5462440 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-04T23:08:06.655766543Z","created_by":"cyou","updated_at":"2026-01-04T23:12:32.195577111Z","closed_at":"2026-01-04T23:12:32.195577111Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-0h7k.10"],"dependencies":[{"issue_id":"mala-0h7k.11","depends_on_id":"mala-0h7k","type":"parent-child","created_at":"2026-01-04T23:08:06.65628419Z","created_by":"daemon"}]}
{"id":"mala-0h7k.12","title":"[Review] 3 non-blocking findings from mala-0h7k.11","description":"## Review Findings\n\nThis issue consolidates 3 non-blocking findings from code review.\n\n**Source issue:** mala-0h7k.11\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Keep asserting add_dependency awaited exactly once\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** tests/unit/orchestration/test_deadlock_handler.py:138-151\n\nIn `test_acquires_lock_and_calls_callbacks_in_order`, the prior `assert_awaited_once_with(...)` also enforced the call count. Switching to `await_args` without `assert_awaited_once()` allows the test to pass even if `add_dependency` is awaited multiple times (or with extra awaits) as long as the last call matches.\n\nA minimal fix is to keep the count assertion and then inspect args/kwargs.\n\n```py\nmock_callbacks.add_dependency.assert_awaited_once()\nadd_dep_args = mock_callbacks.add_dependency.await_args\n```\n\n---\n\n### Finding 2: [P2] Don’t use `or` when falling back from kwargs to args\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** tests/unit/orchestration/test_deadlock_handler.py:143-149\n\nThe new extraction pattern uses `kwargs.get(...) or args[i]`, which breaks when the kwarg is present but falsy (e.g. `None`, `0`, or `\"\"`). In that case it incorrectly falls back to positional access and can raise `IndexError` if the call was kwargs-only. This is especially relevant given there are tests for `None` issue IDs.\n\nPrefer checking key presence instead of truthiness:\n\n```py\nvictim = call.kwargs[\"victim\"] if \"victim\" in call.kwargs else call.args[0]\n```\n\n---\n\n### Finding 3: [P2] Incorrect keyword names and unsafe positional fallback in assertions\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** tests/unit/orchestration/test_deadlock_handler.py:144-147\n\nThe updated assertions for `add_dependency` use the keyword names `\"victim\"` and `\"blocker\"` (e.g., `add_dep_args.kwargs.get(\"victim\")`), but the `DeadlockHandlerCallbacks` docstring defines these parameters as `dependent_id` and `dependency_id`. If the implementation is refactored to use keyword arguments following the docstring, these tests will fail with an `IndexError` because the `or` fallback will attempt to access `args[0]` on an empty `args` tuple. \n\nAdditionally, using `or` for the fallback is unsafe for arguments that can be `None` (like `log_path` in `mark_needs_followup`). If `log_path` is passed as a keyword argument with the value `None`, `kwargs.get(\"log_path\")` returns `None`, causing the `or` to evaluate `args[2]`, which will raise an `IndexError` if the call used keyword arguments for other parameters.\n\nPrefer a safer lookup pattern:\n```python\nvictim_arg = add_dep_args.kwargs.get(\"dependent_id\", add_dep_args.args[0] if add_dep_args.args else None)\n```\n\n---\n\n\u003c!-- fp:f92ac9d0bf6ec5f1 --\u003e\n\u003c!-- fp:aa3c2d7a6c4c2bc8 --\u003e\n\u003c!-- fp:5f41dbf9606d8851 --\u003e\n\u003c!-- review_finding:a4d54aa89039 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-04T23:12:32.219532586Z","created_by":"cyou","updated_at":"2026-01-04T23:22:59.03022854Z","closed_at":"2026-01-04T23:22:59.03022854Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-0h7k.11"],"dependencies":[{"issue_id":"mala-0h7k.12","depends_on_id":"mala-0h7k","type":"parent-child","created_at":"2026-01-04T23:12:32.219944555Z","created_by":"daemon"}]}
{"id":"mala-0h7k.13","title":"[Review] 2 non-blocking findings from mala-0h7k.12","description":"## Review Findings\n\nThis issue consolidates 2 non-blocking findings from code review.\n\n**Source issue:** mala-0h7k.12\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Inconsistent call count assertions in TestAbortActiveTasks\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** tests/unit/orchestration/test_deadlock_handler.py:458-465\n\nThe commit adds `assert_awaited_once()` to tests in `TestHandleDeadlock` but omits it for `finalize_issue_result` in several tests within `TestAbortActiveTasks` (e.g., `test_uses_real_results_for_completed_tasks`, `test_handles_task_exception`, and `test_includes_session_log_path`). Adding these assertions ensures that the finalization logic is called exactly once per task, which aligns with the commit's stated goal of adding call count assertions.\n\n---\n\n### Finding 2: [P2] Inconsistent use of await_args for async mocks\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** tests/unit/orchestration/test_deadlock_handler.py:461\n\nThe updated tests in `TestHandleDeadlock` correctly use `await_args` for async mocks like `add_dependency`. However, tests in `TestAbortActiveTasks` still use `call_args` for `finalize_issue_result`. For consistency across the test suite and to ensure the call was actually awaited, `await_args` should be preferred for all async mocks.\n\n---\n\n\u003c!-- fp:d112debb951c9351 --\u003e\n\u003c!-- fp:768925cd91ebd1e0 --\u003e\n\u003c!-- review_finding:20e251694405 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-04T23:22:59.053848808Z","created_by":"cyou","updated_at":"2026-01-04T23:28:17.223695847Z","closed_at":"2026-01-04T23:28:17.223695847Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-0h7k.12"],"dependencies":[{"issue_id":"mala-0h7k.13","depends_on_id":"mala-0h7k","type":"parent-child","created_at":"2026-01-04T23:22:59.054221166Z","created_by":"daemon"}]}
{"id":"mala-0h7k.2","title":"[T002] Update orchestrator to use OrchestratorState","description":"## Goal\n\nModify MalaOrchestrator to use the new OrchestratorState dataclass instead of individual instance attributes.\n\n## Context\n\nAfter T001 creates OrchestratorState, we need to:\n1. Replace individual state attributes with a single `_state` instance\n2. Update all references throughout orchestrator.py\n3. Create new state instance at start of run()\n\n## Scope\n\n**In**: Replace state attributes, update all references, create state in run()\n**Out**: DeadlockHandler extraction (T003-T005)\n\n## Changes\n\n- `src/orchestration/orchestrator.py` — **Exists** — Replace state attrs with OrchestratorState\n\n## Implementation Details\n\nReplace in `_init_runtime_state()`:\n```python\n# Before\nself.agent_ids: dict[str, str] = {}\nself.completed: list[IssueResult] = []\nself._active_session_log_paths: dict[str, Path] = {}\nself._deadlock_cleaned_agents: set[str] = set()\n\n# After\nself._state = OrchestratorState()\n```\n\nUpdate all references:\n- `self.agent_ids` → `self._state.agent_ids`\n- `self.completed` → `self._state.completed`\n- `self._active_session_log_paths` → `self._state.active_session_log_paths`\n- `self._deadlock_cleaned_agents` → `self._state.deadlock_cleaned_agents`\n\n## Acceptance Criteria\n\n- [ ] OrchestratorState imported and instantiated in _init_runtime_state\n- [ ] All 4 state fields accessed via self._state\n- [ ] No duplicate state attributes remain\n- [ ] Existing tests pass (may need mock updates)\n- [ ] Type checking passes\n\n## Verification\n\n```bash\nuv run pytest tests/unit/orchestration/test_orchestrator.py -v\nuv run pytest tests/integration/domain/test_deadlock.py -v\nuvx ty check src/orchestration/orchestrator.py\n```\n\n## Notes for Agent\n\n- ~15-20 reference updates needed\n- Watch for property accessors that may need updating\n- Keep _state as private (underscore prefix)","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-04T20:50:53.312696918Z","created_by":"cyou","updated_at":"2026-01-04T22:00:35.181794882Z","closed_at":"2026-01-04T22:00:35.181794882Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-0h7k.2","depends_on_id":"mala-0h7k","type":"parent-child","created_at":"2026-01-04T20:50:53.32420065Z","created_by":"daemon"},{"issue_id":"mala-0h7k.2","depends_on_id":"mala-0h7k.1","type":"blocks","created_at":"2026-01-04T20:51:00.644645956Z","created_by":"daemon"}]}
{"id":"mala-0h7k.3","title":"[T003] Create DeadlockHandler class with callbacks","description":"## Goal\n\nCreate the DeadlockHandler service class with callback-based dependency injection for external operations.\n\n## Context\n\nDeadlockHandler will own:\n- The asyncio.Lock for serializing deadlock resolution\n- Methods: handle_deadlock, abort_active_tasks, cleanup_agent_locks\n- Callbacks for beads (add_dependency, mark_needs_followup) and event_sink\n\nThis follows the existing FinalizerCallbackRefs pattern.\n\n## Scope\n\n**In**: Create handler class, callbacks dataclass, method signatures\n**Out**: Method implementations (T004), orchestrator wiring (T005)\n\n## Changes\n\n- `src/orchestration/deadlock_handler.py` — **New** — DeadlockHandler class + DeadlockHandlerCallbacks\n- `src/orchestration/types.py` — **Exists** — May add shared types if needed\n\n## Implementation Details\n\n```python\n@dataclass\nclass DeadlockHandlerCallbacks:\n    add_dependency: Callable[[str, str], Awaitable[bool]]\n    mark_needs_followup: Callable[[str, str, Path | None], Awaitable[None]]\n    on_deadlock_detected: Callable[[DeadlockInfo], None]\n    on_locks_cleaned: Callable[[str, int], None]\n    on_tasks_aborting: Callable[[int, str], None]\n    cleanup_agent_locks: Callable[[str], tuple[int, list[str]]]\n    unregister_agent: Callable[[str], None]\n\nclass DeadlockHandler:\n    def __init__(self, callbacks: DeadlockHandlerCallbacks) -\u003e None:\n        self._callbacks = callbacks\n        self._resolution_lock = asyncio.Lock()\n\n    async def handle_deadlock(...) -\u003e None: ...\n    async def abort_active_tasks(...) -\u003e None: ...\n    def cleanup_agent_locks(...) -\u003e None: ...\n```\n\n## Acceptance Criteria\n\n- [ ] DeadlockHandler class exists at src/orchestration/deadlock_handler.py\n- [ ] DeadlockHandlerCallbacks dataclass with all 7 callback fields\n- [ ] Handler owns asyncio.Lock internally\n- [ ] Method signatures match plan (handle_deadlock, abort_active_tasks, cleanup_agent_locks)\n- [ ] TYPE_CHECKING used for DeadlockInfo, OrchestratorState imports\n- [ ] Type checking passes\n\n## Verification\n\n```bash\nuvx ty check src/orchestration/deadlock_handler.py\npython -c \"from src.orchestration.deadlock_handler import DeadlockHandler, DeadlockHandlerCallbacks; print('OK')\"\n```\n\n## Notes for Agent\n\n- Methods can have placeholder implementations (pass/raise NotImplementedError)\n- Follow imports pattern from orchestration_wiring.py\n- Include proper docstrings following codebase style","notes":"Quality gate failed: Quality gate failed: No commit with bd-mala-0h7k.3 found after run baseline (stale commits from previous runs are rejected)\nLog: /home/cyou/.claude/projects/-home-cyou-mala/f6c7afe1-38b0-453a-bc39-9494a2587ed5.jsonl","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-04T20:51:16.923376045Z","created_by":"cyou","updated_at":"2026-01-04T22:00:35.583886484Z","closed_at":"2026-01-04T22:00:35.583886484Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-0h7k.3","depends_on_id":"mala-0h7k","type":"parent-child","created_at":"2026-01-04T20:51:16.933485584Z","created_by":"daemon"}]}
{"id":"mala-0h7k.4","title":"[T004] Move deadlock/abort methods to DeadlockHandler","description":"## Goal\n\nImplement the deadlock/abort methods in DeadlockHandler by copying logic from MalaOrchestrator.\n\n**IMPORTANT**: This task COPIES methods to the handler. Do NOT remove the original methods from orchestrator - T005 will update them to delegate to the handler.\n\n## Context\n\nThis is the core extraction task. The methods handle:\n- `_handle_deadlock`: Acquires lock, cleans up victim, shields resolution, cancels task\n- `_resolve_deadlock`: Adds dependency, marks needs-followup (private helper)\n- `_cleanup_agent_locks`: Removes locks, emits event, unregisters from monitor\n- `_abort_active_tasks`: Cancels tasks, finalizes with real/abort results\n\nLogic must be preserved exactly to avoid regressions.\n\n## Scope\n\n**In**: Implement all 4 methods in DeadlockHandler (copy from orchestrator)\n**Out**: Removing/updating orchestrator methods (that's T005), tests (T006)\n\n## Changes\n\n- `src/orchestration/deadlock_handler.py` — **Exists** — Implement all methods\n\n## Implementation Details\n\nKey patterns to preserve:\n1. `asyncio.shield` for protecting resolution from cancellation\n2. Self-cancellation detection with deferred `loop.call_soon(task.cancel)`\n3. `_deadlock_cleaned_agents` tracking to avoid double cleanup\n4. Task.done() check before marking as aborted\n\nMethods receive OrchestratorState and active_tasks as parameters:\n```python\nasync def handle_deadlock(\n    self,\n    info: DeadlockInfo,\n    state: OrchestratorState,\n    active_tasks: dict[str, asyncio.Task],\n) -\u003e None:\n```\n\nThe handler also needs a private `_resolve_deadlock` helper method.\n\n## Acceptance Criteria\n\n- [ ] All 4 methods fully implemented in DeadlockHandler\n- [ ] Private `_resolve_deadlock` helper implemented\n- [ ] Logic preserved line-by-line from orchestrator\n- [ ] Callbacks used instead of direct self.beads/self.event_sink access\n- [ ] State updates via passed OrchestratorState parameter\n- [ ] **Original orchestrator methods left unchanged** (T005 handles delegation)\n- [ ] Type checking passes\n\n## Verification\n\n```bash\nuvx ty check src/orchestration/deadlock_handler.py\npython -c \"from src.orchestration.deadlock_handler import DeadlockHandler; print('OK')\"\n```\n\n## Notes for Agent\n\n- Copy existing implementation first, then adapt to use callbacks\n- ~150 lines of logic to copy\n- Replace `self.beads.X` with `self._callbacks.X`\n- Replace `self.event_sink.X` with `self._callbacks.X`\n- Replace `self._active_session_log_paths` with `state.active_session_log_paths`\n- Edge cases: self-cancellation, CancelledError during shield, task.done() race","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-04T20:51:33.378110973Z","created_by":"cyou","updated_at":"2026-01-04T22:26:53.205282119Z","closed_at":"2026-01-04T22:26:53.205282119Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-0h7k.4","depends_on_id":"mala-0h7k","type":"parent-child","created_at":"2026-01-04T20:51:33.387909473Z","created_by":"daemon"},{"issue_id":"mala-0h7k.4","depends_on_id":"mala-0h7k.3","type":"blocks","created_at":"2026-01-04T20:51:40.634576626Z","created_by":"daemon"}]}
{"id":"mala-0h7k.5","title":"[T005] Wire DeadlockHandler into orchestrator","description":"## Goal\n\nWire the DeadlockHandler into MalaOrchestrator: instantiate handler, connect callbacks, and update orchestrator methods to delegate to handler.\n\n## Context\n\nAfter T002 (state) and T004 (handler implementation), we need to:\n1. Create DeadlockHandler instance in orchestrator init\n2. Wire callbacks to beads, event_sink, and infra functions\n3. Update deadlock_monitor.on_deadlock callback to use handler\n4. **Replace orchestrator's _handle_deadlock/_abort_active_tasks to delegate to handler**\n5. Remove now-unused private methods from orchestrator\n\n## Scope\n\n**In**: Create handler instance, wire callbacks, update/remove orchestrator methods\n**Out**: Tests (T006), integration verification (T007)\n\n## Changes\n\n- `src/orchestration/orchestrator.py` — **Exists** — Wire handler, replace methods with delegation\n\n## Wiring Map\n\n```\nDeadlockHandlerCallbacks:\n  add_dependency        → self.beads.add_dependency_async\n  mark_needs_followup   → self.beads.mark_needs_followup_async\n  on_deadlock_detected  → self.event_sink.on_deadlock_detected\n  on_locks_cleaned      → self.event_sink.on_locks_cleaned\n  on_tasks_aborting     → self.event_sink.on_tasks_aborting\n  cleanup_agent_locks   → cleanup_agent_locks (from src.infra.tools.locking)\n  unregister_agent      → self.deadlock_monitor.unregister_agent\n```\n\n## Implementation Details\n\n```python\n# In _init_pipeline_runners or similar:\nfrom src.infra.tools.locking import cleanup_agent_locks\n\nself._deadlock_handler = DeadlockHandler(\n    callbacks=DeadlockHandlerCallbacks(\n        add_dependency=self.beads.add_dependency_async,\n        mark_needs_followup=self.beads.mark_needs_followup_async,\n        on_deadlock_detected=self.event_sink.on_deadlock_detected,\n        on_locks_cleaned=self.event_sink.on_locks_cleaned,\n        on_tasks_aborting=self.event_sink.on_tasks_aborting,\n        cleanup_agent_locks=cleanup_agent_locks,\n        unregister_agent=lambda aid: self.deadlock_monitor and self.deadlock_monitor.unregister_agent(aid),\n    )\n)\n\n# Wire to deadlock monitor:\nif self.deadlock_monitor is not None:\n    self.deadlock_monitor.on_deadlock = lambda info: self._deadlock_handler.handle_deadlock(\n        info, self._state, self.active_tasks\n    )\n```\n\n**Method delegation**: Replace the original orchestrator methods:\n- `_handle_deadlock` → delegates to `self._deadlock_handler.handle_deadlock`\n- `_abort_active_tasks` → delegates to `self._deadlock_handler.abort_active_tasks`\n- `_cleanup_agent_locks` → delegates to `self._deadlock_handler.cleanup_agent_locks`\n- `_resolve_deadlock` → can be deleted (now internal to handler)\n\n## Acceptance Criteria\n\n- [ ] DeadlockHandler instantiated in orchestrator init\n- [ ] All 7 callbacks correctly wired\n- [ ] cleanup_agent_locks imported from src.infra.tools.locking\n- [ ] deadlock_monitor.on_deadlock wired to handler\n- [ ] Original _handle_deadlock/_abort_active_tasks/_cleanup_agent_locks now delegate to handler\n- [ ] _resolve_deadlock removed from orchestrator\n- [ ] Type checking passes\n\n## Verification [integration-path-test]\n\n```bash\nuv run pytest tests/unit/orchestration/test_orchestrator.py -v\nuv run pytest tests/integration/domain/test_deadlock.py -v\nuvx ty check src/orchestration/orchestrator.py\n```\n\n## Notes for Agent\n\n- This is the integration point - most risk of regressions\n- Verify callback signatures match handler expectations\n- Lambda for unregister_agent handles None deadlock_monitor case\n- After this task, orchestrator methods are thin wrappers that delegate to handler","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-04T20:51:58.101818113Z","created_by":"cyou","updated_at":"2026-01-04T22:35:01.155178733Z","closed_at":"2026-01-04T22:35:01.155178733Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-0h7k.5","depends_on_id":"mala-0h7k","type":"parent-child","created_at":"2026-01-04T20:51:58.11035863Z","created_by":"daemon"},{"issue_id":"mala-0h7k.5","depends_on_id":"mala-0h7k.4","type":"blocks","created_at":"2026-01-04T20:52:04.139680262Z","created_by":"daemon"},{"issue_id":"mala-0h7k.5","depends_on_id":"mala-0h7k.2","type":"blocks","created_at":"2026-01-04T20:52:04.156157809Z","created_by":"daemon"}]}
{"id":"mala-0h7k.6","title":"[T006] Create DeadlockHandler unit tests","description":"## Goal\n\nCreate comprehensive unit tests for DeadlockHandler, and delete obsolete tests that mock orchestrator private methods.\n\n## Context\n\nThe plan specifies:\n- Unit tests for handler with mock callbacks\n- Delete internal tests that mock _handle_deadlock etc.\n- Rewrite as tests for extracted components\n\n## Scope\n\n**In**: Create handler tests, delete obsolete orchestrator internal tests\n**Out**: Integration test verification (T007)\n\n## Changes\n\n- `tests/unit/orchestration/test_deadlock_handler.py` — **New** — Handler unit tests\n- `tests/unit/orchestration/test_orchestrator.py` — **Exists** — Delete obsolete private method tests\n\n## Test Cases\n\nFrom plan Testing \u0026 Validation Strategy:\n\n1. `handle_deadlock` acquires lock, calls callbacks in correct order\n2. `handle_deadlock` cleans up locks and tracks in state\n3. `handle_deadlock` cancels victim task (non-self case)\n4. `handle_deadlock` defers self-cancellation correctly\n5. `handle_deadlock` shields resolution from cancellation\n6. `abort_active_tasks` cancels running tasks, uses real results for completed\n7. `cleanup_agent_locks` is idempotent via state tracking\n\n## Implementation Details\n\n```python\n@pytest.fixture\ndef mock_callbacks() -\u003e DeadlockHandlerCallbacks:\n    return DeadlockHandlerCallbacks(\n        add_dependency=AsyncMock(return_value=True),\n        mark_needs_followup=AsyncMock(),\n        on_deadlock_detected=MagicMock(),\n        on_locks_cleaned=MagicMock(),\n        on_tasks_aborting=MagicMock(),\n        cleanup_agent_locks=MagicMock(return_value=(1, [\"/path/to/lock\"])),\n        unregister_agent=MagicMock(),\n    )\n\n@pytest.fixture\ndef handler(mock_callbacks) -\u003e DeadlockHandler:\n    return DeadlockHandler(callbacks=mock_callbacks)\n```\n\n## Acceptance Criteria\n\n- [ ] test_deadlock_handler.py created with 7+ test cases\n- [ ] All edge cases from plan covered (self-cancel, shield, idempotent)\n- [ ] Mock callbacks used to isolate handler logic\n- [ ] Obsolete _handle_deadlock tests removed from test_orchestrator.py\n- [ ] 85% coverage on deadlock_handler.py\n\n## Verification\n\n```bash\nuv run pytest tests/unit/orchestration/test_deadlock_handler.py -v --cov=src/orchestration/deadlock_handler\nuv run pytest tests/unit/orchestration/test_orchestrator.py -v\n```\n\n## Notes for Agent\n\n- Review existing test_orchestrator.py for patterns to follow\n- Some orchestrator tests may need mock updates for _deadlock_handler\n- Focus on callback invocation order and state mutations","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-04T20:52:21.70321718Z","created_by":"cyou","updated_at":"2026-01-04T23:02:32.137108058Z","closed_at":"2026-01-04T23:02:32.137108058Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-0h7k.6","depends_on_id":"mala-0h7k","type":"parent-child","created_at":"2026-01-04T20:52:21.712442874Z","created_by":"daemon"},{"issue_id":"mala-0h7k.6","depends_on_id":"mala-0h7k.4","type":"blocks","created_at":"2026-01-04T20:52:29.922691345Z","created_by":"daemon"}]}
{"id":"mala-0h7k.7","title":"[T007] Verify integration tests and coverage","description":"## Goal\n\nFinal verification that all integration tests pass unchanged and coverage thresholds are met.\n\n## Context\n\nThis is the final task (PR3) that validates the complete refactor:\n- Existing integration tests must pass without modification\n- 85% coverage maintained\n- E2E deadlock scenarios work correctly\n\n## Scope\n\n**In**: Run all tests, verify coverage, manual verification\n**Out**: This is the final verification task\n\n## Changes\n\n- `tests/integration/domain/test_deadlock.py` — **Exists** — Verify passes unchanged\n\n## Verification Steps\n\n1. **Integration tests unchanged**:\n   ```bash\n   uv run pytest tests/integration/domain/test_deadlock.py -v\n   ```\n\n2. **Full test suite**:\n   ```bash\n   uv run pytest -m \"unit or integration\" -n auto --reruns 2\n   ```\n\n3. **Coverage check**:\n   ```bash\n   uv run pytest --cov=src/orchestration --cov-report=term-missing\n   ```\n\n4. **Type checking**:\n   ```bash\n   uvx ty check src/orchestration/\n   ```\n\n## Acceptance Criteria\n\n- [ ] tests/integration/domain/test_deadlock.py passes without modification\n- [ ] All unit tests pass\n- [ ] 85% coverage on src/orchestration/ maintained\n- [ ] Type checking passes\n- [ ] No regressions in orchestrator behavior\n\n## Manual Verification\n\n- Run orchestrator with parallel agents that trigger deadlock\n- Verify logs show same sequence of events as before refactor\n\n## Notes for Agent\n\n- If integration tests fail, investigate before modifying them\n- Coverage gaps may indicate missing test cases in T006\n- This task gates the merge of PR3","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-04T20:52:43.40675175Z","created_by":"cyou","updated_at":"2026-01-04T23:04:38.078252237Z","closed_at":"2026-01-04T23:04:38.078252237Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-0h7k.7","depends_on_id":"mala-0h7k","type":"parent-child","created_at":"2026-01-04T20:52:43.415475666Z","created_by":"daemon"},{"issue_id":"mala-0h7k.7","depends_on_id":"mala-0h7k.5","type":"blocks","created_at":"2026-01-04T20:52:49.977093541Z","created_by":"daemon"},{"issue_id":"mala-0h7k.7","depends_on_id":"mala-0h7k.6","type":"blocks","created_at":"2026-01-04T20:52:49.992576812Z","created_by":"daemon"}]}
{"id":"mala-0h7k.8","title":"[Review] 4 non-blocking findings from mala-0h7k.3","description":"## Review Findings\n\nThis issue consolidates 4 non-blocking findings from code review.\n\n**Source issue:** mala-0h7k.3\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Naming collision between callback and method\n\n**Priority:** P2\n**Reviewer:** claude\n**Location:** src/orchestration/deadlock_handler.py:43-44\n\nThe callback `cleanup_agent_locks` (line 43) has the same name as the method `cleanup_agent_locks` (line 95). When T004 implements the method, it will likely need to call `self._callbacks.cleanup_agent_locks(agent_id)` but having the same name creates confusion about which is which. Consider renaming either the callback (e.g., `do_cleanup_agent_locks`) or the method to distinguish them clearly.\n\n---\n\n### Finding 2: [P2] Missed acceptance criteria: OrchestratorState import\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/orchestration/deadlock_handler.py:10-17\n\nThe acceptance criteria explicitly required using `TYPE_CHECKING` for both `DeadlockInfo` and `OrchestratorState` imports. While `DeadlockInfo` is imported, `OrchestratorState` is missing from the `TYPE_CHECKING` block. Even if not currently used in the placeholder signatures, it should be included to satisfy the AC.\n\n---\n\n### Finding 3: [P3] Typo: Extra space before @dataclass decorator\n\n**Priority:** P3\n**Reviewer:** gemini\n**Location:** src/orchestration/deadlock_handler.py:20\n\nThere is an extra space before the `@dataclass` decorator ( ` @dataclass`), which is inconsistent with the project's standard formatting and appears to be a typo.\n\n---\n\n### Finding 4: [P3] Inconsistent docstring style for callback attributes\n\n**Priority:** P3\n**Reviewer:** gemini\n**Location:** src/orchestration/deadlock_handler.py:21-48\n\nThe `DeadlockHandlerCallbacks` class uses inline docstrings for its fields. Other callback classes in the project (e.g., `IssueFinalizeCallbacks` in `src/pipeline/issue_finalizer.py`) document their fields in an `Attributes` section within the class docstring.\n\n---\n\n\u003c!-- fp:9bfefa5d584b7684 --\u003e\n\u003c!-- fp:661cd88ecbcb8eac --\u003e\n\u003c!-- fp:bcb9c8e3bcc6244b --\u003e\n\u003c!-- fp:6e9341110272c716 --\u003e\n\u003c!-- review_finding:9e4789decd0a --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-04T22:00:35.61593148Z","created_by":"cyou","updated_at":"2026-01-04T22:04:29.876641901Z","closed_at":"2026-01-04T22:04:29.876641901Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-0h7k.3"],"dependencies":[{"issue_id":"mala-0h7k.8","depends_on_id":"mala-0h7k","type":"parent-child","created_at":"2026-01-04T22:00:35.616310188Z","created_by":"daemon"}]}
{"id":"mala-0h7k.9","title":"[Review] 2 non-blocking findings from mala-0h7k.8","description":"## Review Findings\n\nThis issue consolidates 2 non-blocking findings from code review.\n\n**Source issue:** mala-0h7k.8\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P3] Unused OrchestratorState import suppressed with noqa comment\n\n**Priority:** P3\n**Reviewer:** claude\n**Location:** src/orchestration/deadlock_handler.py:18\n\nThe `OrchestratorState` import on line 18 is never used in this file - it's imported solely to satisfy the AC, then suppressed with `# noqa: F401`. If the type isn't needed for any type hints in this file, the import should be removed rather than keeping dead code. If future work (T004) will use it, it can be added when needed.\n\n---\n\n### Finding 2: [P2] Incomplete documentation for on_deadlock_detected attribute\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/orchestration/deadlock_handler.py:30\n\nWhile moving inline docstrings to the `Attributes` section, the author missed providing argument information for `on_deadlock_detected`. All other callbacks in this section specify their arguments (e.g., `Args: (agent_id)`), but this one remains undocumented despite taking a `DeadlockInfo` parameter.\n\n---\n\n\u003c!-- fp:4043d41975be1c94 --\u003e\n\u003c!-- fp:684d0f28dd6875d9 --\u003e\n\u003c!-- review_finding:3241bdeae4d9 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-04T22:04:29.909774314Z","created_by":"cyou","updated_at":"2026-01-04T22:06:29.48073156Z","closed_at":"2026-01-04T22:06:29.48073156Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-0h7k.8"],"dependencies":[{"issue_id":"mala-0h7k.9","depends_on_id":"mala-0h7k","type":"parent-child","created_at":"2026-01-04T22:04:29.910177472Z","created_by":"daemon"}]}
{"id":"mala-0hn","title":"Health monitoring system","description":"Context:\n- Need visibility into system and agent health during runs\n- Currently no centralized health monitoring\n- Related to mala-e8f (periodic health checks)\n\nLocation:\n- src/orchestration/orchestrator.py (main orchestration loop)\n- src/pipeline/issue_execution_coordinator.py (run_loop)\n- src/infra/io/event_sink.py (event emission)\n\nScope:\n- In: Health metrics collection (agent status, resource usage, error rates)\n- In: Health status API/endpoint for external monitoring\n- In: Alerting thresholds for unhealthy conditions\n- Out: TUI dashboard (see mala-y3b); automated remediation\n\nAcceptance Criteria:\n- Health status queryable via `mala health` or API\n- Metrics include: active agents, queued issues, error count, avg duration\n- Unhealthy conditions (high error rate, stuck agents) are flagged\n- Health data persists across runs for trend analysis\n\nTest Plan:\n- Unit: Test health metric collection and threshold logic\n- Integration: Simulate unhealthy conditions, verify detection\n- Manual: Run mala, query health status during execution\n\nNotes for Agent:\n- Consider prometheus-style metrics format for compatibility\n- May want to expose via HTTP endpoint for external tools\n- Coordinate with mala-e8f for periodic health check automation","status":"closed","priority":3,"issue_type":"feature","created_at":"2025-12-28T06:21:39.303646583Z","updated_at":"2026-01-04T03:15:13.593746618Z","closed_at":"2026-01-04T03:15:13.593746618Z","close_reason":"Closed"}
{"id":"mala-0hsg","title":"[Review] 3 non-blocking findings from mala-cy0b","description":"## Review Findings\n\nThis issue consolidates 3 non-blocking findings from code review.\n\n**Source issue:** mala-cy0b\n**Highest priority:** P3\n\n---\n\n### Finding 1: [P3] Misleading 'Victim' log label in general cleanup path\n\n**Priority:** P3\n**Reviewer:** claude\n**Location:** src/orchestration/orchestrator.py:453\n\nThe log message `\"Victim locks cleaned: agent_id=%s count=%d\"` in `_cleanup_agent_locks` is misleading because this function is called in two contexts: (1) during deadlock resolution where 'victim' is accurate, and (2) during normal session cleanup in `run_implementer`'s finally block at line 796, where the agent is simply finishing normally or timing out—not a deadlock victim. Consider using a neutral term like 'Agent locks cleaned' instead.\n\n---\n\n### Finding 2: [P3] Implement create_options on FakeSDKClientFactory\n\n**Priority:** P3\n**Reviewer:** codex\n**Location:** tests/test_context_pressure_handler.py:84-93\n\n`FakeSDKClientFactory` now explicitly subclasses `SDKClientFactoryProtocol`, but it only defines `create` and omits the required `create_options` method. Any code (or future refactor) that calls `create_options` on this factory will raise `AttributeError` despite the protocol claim. Consider adding a minimal `create_options` stub (or delegating to a real factory) to keep the fake aligned with the protocol.\n\n`class FakeSDKClientFactory(SDKClientFactoryProtocol):`\n`    def create(self, options: object) -\u003e SDKClientProtocol:`\n\n---\n\n### Finding 3: [P3] Inconsistent graph state logging in handle_event\n\n**Priority:** P3\n**Reviewer:** gemini\n**Location:** src/domain/deadlock.py:377-378\n\nIn `DeadlockMonitor.handle_event`, the debug log showing the updated graph state (holds and waits counts) is skipped for `WAITING` events because the method returns early after detecting a deadlock. This makes the log stream inconsistent when debugging wait-for graph state over time.\n\n---\n\n\u003c!-- fp:7f345c91b139eddd --\u003e\n\u003c!-- fp:dfeff2ef297442e1 --\u003e\n\u003c!-- fp:a40093c96fd6b6e0 --\u003e\n\u003c!-- review_finding:36fe051c571c --\u003e","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-04T03:48:11.371637369Z","created_by":"cyou","updated_at":"2026-01-04T03:51:39.557124548Z","closed_at":"2026-01-04T03:51:39.557124548Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-cy0b"]}
{"id":"mala-0k7","title":"Close epics after each agent completion, not just at run end","description":"## Problem\n\nCurrently, epics are only checked for closure at the end of a run. If an agent completes the last child issue of an epic mid-run, the epic stays open until the entire run finishes.\n\nThis means:\n- Epic status doesn't reflect reality during the run\n- Other agents may not see updated epic status for dependency resolution\n- `bd ready` may return stale results if epic completion affects other issues\n\n## Desired Behavior\n\nAfter each agent successfully completes an issue:\n1. Check if the issue belongs to any epics (via parent-child dependency)\n2. For each parent epic, check if all children are now closed\n3. If all children closed, close the epic with reason \"All children completed\"\n\n## Implementation\n\nIn `src/orchestrator.py`, after closing an issue in the agent completion flow:\n\n```python\n# After: bd close \u003cissue_id\u003e\n# Add: Check and close parent epics if all children done\nawait self._close_completed_epics(issue_id)\n```\n\nThe `_close_completed_epics` method should:\n1. Get parent epic(s) for the issue via `bd show \u003cissue_id\u003e --json`\n2. For each parent, check if all children are closed via `bd epic children \u003cepic_id\u003e --json`\n3. If all closed, run `bd close \u003cepic_id\u003e --reason \"All children completed\"`\n\n## Acceptance Criteria\n\n- [ ] Epics close immediately when last child completes\n- [ ] Works for nested epics (closing child epic triggers parent check)\n- [ ] Logged when epic is auto-closed\n- [ ] Tests verify epic closure after child completion\n\n## Files\n- src/orchestrator.py\n- tests/test_orchestrator.py","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-27T06:38:21.964459481Z","updated_at":"2025-12-27T08:01:07.948833639Z","closed_at":"2025-12-27T08:01:07.948833639Z","close_reason":"Closed"}
{"id":"mala-0kfe","title":"fixers should know the exact commands that are run in the validation","status":"closed","priority":2,"issue_type":"bug","created_at":"2026-01-05T05:37:33.300076607Z","created_by":"cyou","updated_at":"2026-01-06T23:31:17.428326806Z","closed_at":"2026-01-06T23:31:17.428326806Z","close_reason":"Closed"}
{"id":"mala-0ooe","title":"Validate trigger interval and max_retries bounds","description":"periodic.interval should be \u003e=1 to avoid ZeroDivisionError and nonsensical behavior; max_retries should be \u003e=0. Add bounds checks in config parsing. Files: src/domain/validation/config_loader.py (parse_periodic_trigger, _parse_max_retries).","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-09T16:53:17.369576928Z","created_by":"cyou","updated_at":"2026-01-09T21:37:44.718854029Z","closed_at":"2026-01-09T21:37:44.718854029Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-0ooe","depends_on_id":"mala-553g","type":"blocks","created_at":"2026-01-09T16:54:22.599469161Z","created_by":"cyou"},{"issue_id":"mala-0ooe","depends_on_id":"mala-smaj","type":"parent-child","created_at":"2026-01-09T16:55:06.588076745Z","created_by":"cyou"}]}
{"id":"mala-159o","title":"[Review] 1 non-blocking finding from mala-mb29","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-mb29\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Strengthen non-git repo unit assertion\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** tests/unit/infra/test_lint_cache.py:104-109\n\n`test_handles_non_git_repo` currently only asserts `isinstance(result, bool)`, which is effectively always true and won’t catch regressions where a git failure incorrectly results in skipping. Since `mark_passed()` returns early when git state can’t be determined, the comment about an “empty head_sha” is also misleading. A tighter assertion like `assert cache.should_skip(\"ruff check\") is False` (and optionally asserting no cache entry/file is written) would preserve the intent of the test.\n\n---\n\n\u003c!-- fp:c4a664c15997a259 --\u003e\n\u003c!-- review_finding:eca717391b71 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-05T16:08:48.962473655Z","created_by":"cyou","updated_at":"2026-01-05T16:18:08.329110561Z","closed_at":"2026-01-05T16:18:08.329110561Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-mb29"]}
{"id":"mala-19j7","title":"Epic: Remove legacy modules","description":"Remove all legacy top-level modules that are outside the layered architecture (src.cli → src.orchestration → src.pipeline → src.domain → src.infra → src.core). These modules exist at the src.* level but duplicate functionality that now lives in proper layers.\n\nLegacy modules to remove:\n- src.orchestrator (→ use src.orchestration)\n- src.main (→ use src.cli.main)\n- src.validation (→ use src.domain.validation)\n- src.quality_gate (→ use src.domain.quality_gate)\n- src.beads_client (→ use src.infra.clients.beads_client)\n- src.hooks (→ use src.infra.hooks)\n- src.git_utils (→ use src.infra.git_utils)\n- src.logging (→ use src.infra.io.log_output)\n- src.braintrust_integration (→ use src.infra.clients.braintrust_integration)\n- src.codex_review (→ use src.infra.clients.cerberus_review)\n\nCurrently src.cli imports src.orchestrator which chains to the other legacy modules. Need to update src.cli to use proper layers.","status":"tombstone","priority":2,"issue_type":"epic","created_at":"2026-01-04T04:54:09.350063675Z","created_by":"cyou","updated_at":"2026-01-04T05:00:36.946640309Z","deleted_at":"2026-01-04T05:00:36.946640309Z","deleted_by":"daemon","delete_reason":"delete","original_type":"epic"}
{"id":"mala-19j7.1","title":"Remove src.orchestrator (update src.cli to use src.orchestration)","status":"tombstone","priority":2,"issue_type":"task","created_at":"2026-01-04T04:54:24.5815767Z","created_by":"cyou","updated_at":"2026-01-04T05:00:23.555872069Z","dependencies":[{"issue_id":"mala-19j7.1","depends_on_id":"mala-19j7","type":"parent-child","created_at":"2026-01-04T04:54:24.590428777Z","created_by":"daemon"}],"deleted_at":"2026-01-04T05:00:23.555872069Z","deleted_by":"daemon","delete_reason":"delete","original_type":"task"}
{"id":"mala-19j7.10","title":"Remove src.codex_review (use src.infra.clients.cerberus_review)","status":"tombstone","priority":2,"issue_type":"task","created_at":"2026-01-04T04:54:24.853182095Z","created_by":"cyou","updated_at":"2026-01-04T05:00:23.570732168Z","dependencies":[{"issue_id":"mala-19j7.10","depends_on_id":"mala-19j7","type":"parent-child","created_at":"2026-01-04T04:54:24.853668352Z","created_by":"daemon"}],"deleted_at":"2026-01-04T05:00:23.570732168Z","deleted_by":"daemon","delete_reason":"delete","original_type":"task"}
{"id":"mala-19j7.2","title":"Remove src.main (entry point should use src.cli.main)","status":"tombstone","priority":2,"issue_type":"task","created_at":"2026-01-04T04:54:24.621000455Z","created_by":"cyou","updated_at":"2026-01-04T05:00:23.563768088Z","dependencies":[{"issue_id":"mala-19j7.2","depends_on_id":"mala-19j7","type":"parent-child","created_at":"2026-01-04T04:54:24.621473342Z","created_by":"daemon"}],"deleted_at":"2026-01-04T05:00:23.563768088Z","deleted_by":"daemon","delete_reason":"delete","original_type":"task"}
{"id":"mala-19j7.3","title":"Remove src.validation (use src.domain.validation)","status":"tombstone","priority":2,"issue_type":"task","created_at":"2026-01-04T04:54:24.649145408Z","created_by":"cyou","updated_at":"2026-01-04T05:00:23.564695396Z","dependencies":[{"issue_id":"mala-19j7.3","depends_on_id":"mala-19j7","type":"parent-child","created_at":"2026-01-04T04:54:24.649553476Z","created_by":"daemon"}],"deleted_at":"2026-01-04T05:00:23.564695396Z","deleted_by":"daemon","delete_reason":"delete","original_type":"task"}
{"id":"mala-19j7.4","title":"Remove src.quality_gate (use src.domain.quality_gate)","status":"tombstone","priority":2,"issue_type":"task","created_at":"2026-01-04T04:54:24.678733342Z","created_by":"cyou","updated_at":"2026-01-04T05:00:23.565671825Z","dependencies":[{"issue_id":"mala-19j7.4","depends_on_id":"mala-19j7","type":"parent-child","created_at":"2026-01-04T04:54:24.679541537Z","created_by":"daemon"}],"deleted_at":"2026-01-04T05:00:23.565671825Z","deleted_by":"daemon","delete_reason":"delete","original_type":"task"}
{"id":"mala-19j7.5","title":"Remove src.beads_client (use src.infra.clients.beads_client)","status":"tombstone","priority":2,"issue_type":"task","created_at":"2026-01-04T04:54:24.711540107Z","created_by":"cyou","updated_at":"2026-01-04T05:00:23.566575834Z","dependencies":[{"issue_id":"mala-19j7.5","depends_on_id":"mala-19j7","type":"parent-child","created_at":"2026-01-04T04:54:24.712119683Z","created_by":"daemon"}],"deleted_at":"2026-01-04T05:00:23.566575834Z","deleted_by":"daemon","delete_reason":"delete","original_type":"task"}
{"id":"mala-19j7.6","title":"Remove src.hooks (use src.infra.hooks)","status":"tombstone","priority":2,"issue_type":"task","created_at":"2026-01-04T04:54:24.73785629Z","created_by":"cyou","updated_at":"2026-01-04T05:00:23.567416252Z","dependencies":[{"issue_id":"mala-19j7.6","depends_on_id":"mala-19j7","type":"parent-child","created_at":"2026-01-04T04:54:24.738305458Z","created_by":"daemon"}],"deleted_at":"2026-01-04T05:00:23.567416252Z","deleted_by":"daemon","delete_reason":"delete","original_type":"task"}
{"id":"mala-19j7.7","title":"Remove src.git_utils (use src.infra.git_utils)","status":"tombstone","priority":2,"issue_type":"task","created_at":"2026-01-04T04:54:24.766064613Z","created_by":"cyou","updated_at":"2026-01-04T05:00:23.568222211Z","dependencies":[{"issue_id":"mala-19j7.7","depends_on_id":"mala-19j7","type":"parent-child","created_at":"2026-01-04T04:54:24.76653642Z","created_by":"daemon"}],"deleted_at":"2026-01-04T05:00:23.568222211Z","deleted_by":"daemon","delete_reason":"delete","original_type":"task"}
{"id":"mala-19j7.8","title":"Remove src.logging (use src.infra.io.log_output)","status":"tombstone","priority":2,"issue_type":"task","created_at":"2026-01-04T04:54:24.791104524Z","created_by":"cyou","updated_at":"2026-01-04T05:00:23.56902745Z","dependencies":[{"issue_id":"mala-19j7.8","depends_on_id":"mala-19j7","type":"parent-child","created_at":"2026-01-04T04:54:24.791584821Z","created_by":"daemon"}],"deleted_at":"2026-01-04T05:00:23.56902745Z","deleted_by":"daemon","delete_reason":"delete","original_type":"task"}
{"id":"mala-19j7.9","title":"Remove src.braintrust_integration (use src.infra.clients.braintrust_integration)","status":"tombstone","priority":2,"issue_type":"task","created_at":"2026-01-04T04:54:24.818257172Z","created_by":"cyou","updated_at":"2026-01-04T05:00:23.569912939Z","dependencies":[{"issue_id":"mala-19j7.9","depends_on_id":"mala-19j7","type":"parent-child","created_at":"2026-01-04T04:54:24.818905959Z","created_by":"daemon"}],"deleted_at":"2026-01-04T05:00:23.569912939Z","deleted_by":"daemon","delete_reason":"delete","original_type":"task"}
{"id":"mala-1by","title":"Refactor: extract console logging helpers","description":"## Context\nConsole logging helpers (`Colors`, `log`, `log_tool`, `log_result`) live in `src/main.py`. They should move to `src/logging/console.py` to reduce CLI/orchestrator coupling.\n\n## Scope\nCreate the new module only. Do **not** modify `src/main.py` in this issue; integration will wire it up later.\n\n### Files\n- Add: `src/logging/console.py`\n\n## Requirements\n- Move `Colors`, `log`, `log_tool`, and `log_result` into `src/logging/console.py`.\n- Behavior and output formatting must remain identical.\n\n## Acceptance Criteria\n- `src/logging/console.py` contains the logging helpers with identical behavior.\n- No other files are modified in this issue.\n\n## Notes\n- Integration issue will update imports in `src/main.py` and any other modules.\n- Keep ASCII-only files.\n","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-25T19:54:51.142161242Z","updated_at":"2025-12-25T20:09:18.911369058Z","closed_at":"2025-12-25T20:09:18.911369058Z","close_reason":"Closed"}
{"id":"mala-1gb","title":"Agent mail: inter-agent communication system","description":"Context:\nAllow agents to communicate with each other during a run.\n\nLocation:\n- src/infra/tools/ (new mail scripts: send-mail.sh, check-mail.sh)\n- src/infra/hooks/ (mail injection hook)\n- src/orchestration/orchestrator.py (mail directory setup)\n\n**Use cases:**\n- Agent A discovers issue needs API change, notifies Agent B working on API\n- Agent requests specialist help (e.g., 'need SQL optimization advice')\n- Handoff context when one agent completes prerequisite work\n\nScope:\n- In: Mailbox directory per agent; send/check/read mail tools\n- In: PreToolUse hook to inject unread mail as system message\n- In: Optional synchronous request/response pattern\n- Out: Real-time chat; blocking communication (deadlock risk)\n\n**Implementation ideas:**\n- Mailbox directory per agent in lock dir\n- Tools: send-mail.sh, check-mail.sh, read-mail.sh\n- PreToolUse hook to inject unread mail as system message\n- Optional: synchronous request/response pattern\n\nAcceptance Criteria:\n- Agent can send message to another agent by ID\n- Receiving agent sees message in next prompt\n- Messages are non-blocking (avoid deadlock)\n- Orchestrator can inject mail (human hints)\n- Mail log persisted for debugging\n\nTest Plan:\n- Unit: Test mail file read/write operations\n- Unit: Test mail injection hook\n- Integration: Two agents, verify message delivery\n- Manual: Send mail during run, observe receipt\n\nNotes for Agent:\n- Messages are advisory, not blocking (avoid deadlock)\n- Mail log persisted for debugging\n- Consider rate limiting to prevent spam","status":"tombstone","priority":3,"issue_type":"feature","created_at":"2025-12-28T06:31:49.761390022Z","updated_at":"2026-01-06T22:52:10.761723406Z","deleted_at":"2026-01-06T22:52:10.761723406Z","deleted_by":"batch delete","delete_reason":"batch delete","original_type":"feature"}
{"id":"mala-1h9t","title":"Deadlock handler should reset victim issue status to open","notes":"When a deadlock is resolved, the victim agent is cancelled but the issue remains in_progress status. Since the orchestrator normally runs without --wip, the victim issue won't be picked up again after the blocker finishes.\n\nThe deadlock handler currently only:\n1. Adds a needs-followup label\n2. Adds a dependency on the blocker\n\nIt should also reset the victim issue status to open so it can be picked up in a future run.\n\nFix: Have DeadlockHandler call bd update \u003cissue_id\u003e --status open after marking needs-followup.\n\nRelevant code: src/orchestration/deadlock_handler.py#L177-L185","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-05T02:09:02.115325484Z","created_by":"cyou","updated_at":"2026-01-05T02:22:11.826676683Z","closed_at":"2026-01-05T02:22:11.826676683Z","close_reason":"Closed"}
{"id":"mala-1l2","title":"Make morphllm optional","description":"Make MorphLLM optional: allow running without MORPH_API_KEY by disabling the Morph MCP server and related tool restrictions.","notes":"REVIEW (2025-12-27): Plan solid. Set status to open (no blockers). Add acceptance criteria: Edit/Grep allowed when MORPH_API_KEY missing; morph-disabled must not install hooks/restrictions anywhere. Edge cases: any shared validation helpers must not implicitly require MORPH_API_KEY. Tests: add/adjust a morph-disabled logging/behavior assertion. Docs: update any required-env/.env samples beyond README.","status":"closed","priority":4,"issue_type":"epic","created_at":"2025-12-26T00:55:42.42123181Z","updated_at":"2025-12-27T05:57:33.787813045Z","closed_at":"2025-12-27T05:57:33.787813045Z","close_reason":"Closed"}
{"id":"mala-1l2.1","title":"Validation prereqs for optional MorphLLM","description":"Context:\\n- Break out validation-related changes needed to make MORPH_API_KEY optional.\\n- Focused on e2e/validation prechecks and required_env gating.\\n\\nScope:\\n- In: tasks updating validation spec/runner/e2e prereqs + their focused tests.\\n- Out: CLI/orchestrator behavior changes; docs updates.\\n\\nAcceptance Criteria:\\n- Child issues cover spec, e2e, and runner validation behavior plus tests.\\n\\nTest Plan:\\n- Covered by child tasks.\\n\\nNotes for Agent:\\n- Keep validation changes isolated to their files and corresponding tests.","status":"closed","priority":4,"issue_type":"epic","created_at":"2025-12-27T00:46:40.769608139Z","updated_at":"2025-12-27T01:53:32.920933073Z","closed_at":"2025-12-27T01:53:32.920933073Z","close_reason":"All children completed","dependencies":[{"issue_id":"mala-1l2.1","depends_on_id":"mala-1l2","type":"parent-child","created_at":"2025-12-27T00:46:40.769965987Z","created_by":"daemon"}]}
{"id":"mala-1l2.1.1","title":"Relax required_env for Morph in validation spec (with tests)","description":"Primary files: src/validation/spec.py, tests/test_spec.py\\n\\nContext:\\n- Validation spec currently treats MORPH_API_KEY as required for e2e runs.\\n- Optional Morph should not fail validation solely due to missing key.\\n\\nScope:\\n- In: remove MORPH_API_KEY from required_env for e2e (or gate on morph_enabled if available in spec inputs).\\n- Out: runner/e2e prereq logic.\\n\\nAcceptance Criteria:\\n- required_env no longer unconditionally includes MORPH_API_KEY for e2e.\\n- tests/test_spec.py updated to match new required_env expectations.\\n\\nTest Plan:\\n- TDD: adjust tests/test_spec.py to fail on current required_env behavior.\\n- Implement spec change; refactor if needed.\\n- Run: uv run pytest tests/test_spec.py\\n\\nNotes for Agent:\\n- If spec has no morph_enabled input, prefer removing the requirement entirely.","status":"closed","priority":4,"issue_type":"task","created_at":"2025-12-27T00:47:14.858287475Z","updated_at":"2025-12-27T01:14:50.347672201Z","closed_at":"2025-12-27T01:14:50.347672201Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-1l2.1.1","depends_on_id":"mala-1l2.1","type":"parent-child","created_at":"2025-12-27T00:47:14.867609402Z","created_by":"daemon"}]}
{"id":"mala-1l2.1.2","title":"Remove hard MORPH_API_KEY prereq in validation e2e (with tests)","description":"Primary files: src/validation/e2e.py, tests/test_e2e.py\\n\\nContext:\\n- E2E validation should not fail when MORPH_API_KEY is missing.\\n- Morph-specific tests can still be skipped when keys are absent.\\n\\nScope:\\n- In: remove any hard prereq requiring MORPH_API_KEY in e2e validation; keep skip_if_no_keys behavior for Morph-specific checks.\\n- Out: validation spec required_env and runner prereqs.\\n\\nAcceptance Criteria:\\n- E2E validation does not error solely due to missing MORPH_API_KEY.\\n- Morph-specific E2E tests continue to skip when key is missing.\\n- tests/test_e2e.py updated to cover new prereq behavior.\\n\\nTest Plan:\\n- TDD: update tests/test_e2e.py to fail on current prereq requirement.\\n- Implement e2e validation changes; refactor if needed.\\n- Run: uv run pytest tests/test_e2e.py\\n\\nNotes for Agent:\\n- Keep backward-compatible skip semantics; do not enable Morph tests without a key.","status":"closed","priority":4,"issue_type":"task","created_at":"2025-12-27T00:47:26.211628366Z","updated_at":"2025-12-27T01:21:46.329883547Z","closed_at":"2025-12-27T01:21:46.329883547Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-1l2.1.2","depends_on_id":"mala-1l2.1","type":"parent-child","created_at":"2025-12-27T00:47:26.220050469Z","created_by":"daemon"},{"issue_id":"mala-1l2.1.2","depends_on_id":"mala-1l2.1.1","type":"blocks","created_at":"2025-12-27T00:48:10.128669054Z","created_by":"daemon"}]}
{"id":"mala-1l2.1.3","title":"Relax MORPH_API_KEY prereqs in validation runner (with tests)","description":"Primary files: src/validation/runner.py, tests/test_validation.py\\n\\nContext:\\n- Runner-level validation should not treat MORPH_API_KEY as a hard requirement for all e2e runs.\\n- Optional Morph should still allow general validation to proceed.\\n\\nScope:\\n- In: remove or gate MORPH_API_KEY prereq at runner level; preserve skip behavior for Morph-only checks.\\n- Out: validation spec required_env and e2e module logic.\\n\\nAcceptance Criteria:\\n- Runner no longer blocks validation solely due to missing MORPH_API_KEY.\\n- tests/test_validation.py updated to match new prereq behavior.\\n\\nTest Plan:\\n- TDD: adjust tests/test_validation.py to fail on current prereq requirement.\\n- Implement runner changes; refactor if needed.\\n- Run: uv run pytest tests/test_validation.py\\n\\nNotes for Agent:\\n- Keep compatibility with existing skip_if_no_keys behavior.","status":"closed","priority":4,"issue_type":"task","created_at":"2025-12-27T00:47:36.507485416Z","updated_at":"2025-12-27T01:24:47.858660441Z","closed_at":"2025-12-27T01:24:47.858660441Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-1l2.1.3","depends_on_id":"mala-1l2.1","type":"parent-child","created_at":"2025-12-27T00:47:36.514793237Z","created_by":"daemon"},{"issue_id":"mala-1l2.1.3","depends_on_id":"mala-1l2.1.1","type":"blocks","created_at":"2025-12-27T00:48:13.488191772Z","created_by":"daemon"}]}
{"id":"mala-1l2.2","title":"Gate Morph MCP config in orchestrator (with tests)","description":"Primary files: src/orchestrator.py, tests/test_morph_integration.py\\n\\nContext:\\n- Morph MCP should be optional when MORPH_API_KEY is missing.\\n- Orchestrator owns MCP server configuration and tool restrictions.\\n\\nScope:\\n- In: add morph_enabled parameter/default; when false, return no Morph MCP servers, skip disallowed_tools + hook registration; log enabled/disabled state.\\n- Out: CLI env parsing; validation/e2e prereqs.\\n\\nAcceptance Criteria:\\n- Without MORPH_API_KEY (morph_enabled false), Morph MCP server is not configured and no tool restrictions/hooks are applied.\\n- With MORPH_API_KEY, behavior remains unchanged.\\n- Logs clearly indicate Morph enabled vs disabled.\\n- tests/test_morph_integration.py updated to cover enabled/disabled cases.\\n\\nTest Plan:\\n- TDD: adjust/add failing tests in tests/test_morph_integration.py first.\\n- Implement minimal orchestrator changes; refactor if needed.\\n- Run: uv run pytest tests/test_morph_integration.py\\n\\nNotes for Agent:\\n- Ensure default keeps existing callers working (safe default for morph_enabled).","status":"closed","priority":4,"issue_type":"task","created_at":"2025-12-27T00:46:53.269468392Z","updated_at":"2025-12-27T01:36:56.0650868Z","closed_at":"2025-12-27T01:36:56.0650868Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-1l2.2","depends_on_id":"mala-1l2","type":"parent-child","created_at":"2025-12-27T00:46:53.276443605Z","created_by":"daemon"}]}
{"id":"mala-1l2.3","title":"Make CLI accept missing MORPH_API_KEY (with tests)","description":"Primary files: src/cli.py, tests/test_cli.py\\n\\nContext:\\n- CLI currently hard-fails when MORPH_API_KEY is missing.\\n- Optional Morph requires CLI to compute morph_enabled and proceed.\\n\\nScope:\\n- In: replace hard validation with optional getter; compute morph_enabled = bool(MORPH_API_KEY); pass to orchestrator; warn/log when disabled.\\n- Out: orchestrator MCP server logic; validation prereqs.\\n\\nAcceptance Criteria:\\n- Running without MORPH_API_KEY no longer exits early.\\n- morph_enabled is passed through to orchestrator.\\n- Logs warn/indicate Morph is disabled when key missing.\\n- tests/test_cli.py updated to reflect optional key behavior.\\n\\nTest Plan:\\n- TDD: update/add tests in tests/test_cli.py to fail on current behavior.\\n- Implement CLI changes; refactor if needed.\\n- Run: uv run pytest tests/test_cli.py\\n\\nNotes for Agent:\\n- Coordinate with orchestrator change so the new parameter is accepted.","status":"closed","priority":4,"issue_type":"task","created_at":"2025-12-27T00:47:03.034540213Z","updated_at":"2025-12-27T01:48:34.295154358Z","closed_at":"2025-12-27T01:48:34.295154358Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-1l2.3","depends_on_id":"mala-1l2","type":"parent-child","created_at":"2025-12-27T00:47:03.034931741Z","created_by":"daemon"},{"issue_id":"mala-1l2.3","depends_on_id":"mala-1l2.2","type":"blocks","created_at":"2025-12-27T00:48:04.407695212Z","created_by":"daemon"}]}
{"id":"mala-1l2.4","title":"Document optional MorphLLM (README + quality hardening doc)","description":"Primary files: README.md, docs/quality-hardening-plan.md\\n\\nContext:\\n- Docs currently imply MORPH_API_KEY is required.\\n- Need to document optional Morph MCP and fallback behavior.\\n\\nScope:\\n- In: update README to mark MorphLLM MCP optional; explain enablement via MORPH_API_KEY; note fallback to built-in Edit/Grep when disabled.\\n- In: update docs/quality-hardening-plan.md (and any required-env lists in these files).\\n- Out: code changes or tests.\\n\\nAcceptance Criteria:\\n- README clearly states Morph is optional and how it is enabled.\\n- Any required-env sections in these docs no longer mark MORPH_API_KEY as mandatory.\\n\\nTest Plan:\\n- Not applicable (docs-only).\\n\\nNotes for Agent:\\n- Keep wording consistent with actual runtime behavior and logs.","status":"closed","priority":4,"issue_type":"chore","created_at":"2025-12-27T00:47:46.694517979Z","updated_at":"2025-12-27T01:35:17.82530074Z","closed_at":"2025-12-27T01:35:17.82530074Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-1l2.4","depends_on_id":"mala-1l2","type":"parent-child","created_at":"2025-12-27T00:47:46.70338778Z","created_by":"daemon"}]}
{"id":"mala-1l2.5","title":"QA: verify optional MorphLLM behavior","description":"Primary files: (none)\\n\\nContext:\\n- After code/test updates, verify behavior without MORPH_API_KEY.\\n\\nScope:\\n- In: run targeted tests and do a quick manual run without MORPH_API_KEY to confirm morph disabled log and no crash.\\n- Out: code or doc changes.\\n\\nAcceptance Criteria:\\n- Targeted pytest runs pass: tests/test_cli.py, tests/test_morph_integration.py, tests/test_spec.py, tests/test_e2e.py, tests/test_validation.py.\\n- Manual run without MORPH_API_KEY starts successfully and logs morph disabled.\\n\\nTest Plan:\\n- Run: uv run pytest tests/test_cli.py tests/test_morph_integration.py tests/test_spec.py tests/test_e2e.py tests/test_validation.py\\n- Optional: run command to start mala without MORPH_API_KEY and confirm log.\\n\\nNotes for Agent:\\n- If manual run is too expensive, document why and what was verified instead.","status":"closed","priority":4,"issue_type":"task","created_at":"2025-12-27T00:47:57.157193532Z","updated_at":"2025-12-27T01:53:32.839930036Z","closed_at":"2025-12-27T01:53:32.839930036Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-1l2.5","depends_on_id":"mala-1l2","type":"parent-child","created_at":"2025-12-27T00:47:57.164709042Z","created_by":"daemon"},{"issue_id":"mala-1l2.5","depends_on_id":"mala-1l2.2","type":"blocks","created_at":"2025-12-27T00:48:17.588479082Z","created_by":"daemon"},{"issue_id":"mala-1l2.5","depends_on_id":"mala-1l2.3","type":"blocks","created_at":"2025-12-27T00:48:22.938022111Z","created_by":"daemon"},{"issue_id":"mala-1l2.5","depends_on_id":"mala-1l2.1.1","type":"blocks","created_at":"2025-12-27T00:48:28.737891014Z","created_by":"daemon"},{"issue_id":"mala-1l2.5","depends_on_id":"mala-1l2.1.2","type":"blocks","created_at":"2025-12-27T00:48:31.649321978Z","created_by":"daemon"},{"issue_id":"mala-1l2.5","depends_on_id":"mala-1l2.1.3","type":"blocks","created_at":"2025-12-27T00:48:34.702018781Z","created_by":"daemon"}]}
{"id":"mala-1lx","title":"Explore Ralph Wiggum completion loop integration","description":"Investigate adding Ralph-style completion promise loop to Mala (pre-gate loop). Evaluate impact on gate/review retries, proposed CLI flags, cancellation path, and telemetry. This is exploratory; not yet scheduled for implementation.","notes":"BLOCKERS: Awaiting decision on whether to proceed with completion-promise loop experiments and scope (minimal vs full).","status":"closed","priority":4,"issue_type":"task","created_at":"2025-12-27T17:50:02.937540116Z","updated_at":"2026-01-01T21:40:44.516249907Z","closed_at":"2026-01-01T21:40:44.516249907Z","close_reason":"Closed"}
{"id":"mala-1qoc","title":"Clean up uncommitted changes after agent timeout","description":"When an agent times out, any uncommitted changes it made remain in the working directory. This can cause issues:\n\n1. Partial/incomplete code left in working tree\n2. Other agents may see and be confused by uncommitted changes\n3. No cleanup of modified files after timeout\n\nProposed solution:\n- After timeout, run `git checkout -- \u003cfiles\u003e` for any uncommitted changes in the working directory\n- Or use `git stash` to preserve changes for manual review\n- Log which files had uncommitted changes for debugging\n\nSee mala-drqh timeout in run 4bcf1d4b for example case (though in that case, the agent was stuck in lock contention and hadn't made changes yet).","status":"tombstone","priority":2,"issue_type":"feature","created_at":"2026-01-02T15:21:34.932237532Z","created_by":"cyou","updated_at":"2026-01-06T22:52:10.761723406Z","deleted_at":"2026-01-06T22:52:10.761723406Z","deleted_by":"batch delete","delete_reason":"batch delete","original_type":"feature"}
{"id":"mala-1vhh","title":"[Review] 1 non-blocking finding from mala-0d7k","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-0d7k\n**Highest priority:** P3\n\n---\n\n### Finding 1: [P3] KNOWN_ICONS missing icons used elsewhere in codebase\n\n**Priority:** P3\n**Reviewer:** claude\n**Location:** src/infra/io/log_output/console.py:399-411\n\nThe allowlist is missing several icon characters that are used elsewhere: `◐`, `◌`, `▶`, `!`, `🧹`, `🔍`. While `ConsoleLoggerAdapter` is currently unused (so this is not an active bug), if it gets wired up in the future without updating the list, messages using these icons would get their icons replaced with the default `→` icon instead of being preserved.\n\n---\n","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-03T16:08:51.324348217Z","created_by":"cyou","updated_at":"2026-01-03T16:12:00.467018272Z","closed_at":"2026-01-03T16:12:00.467018272Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_findings:0155e7e49e1c","source:mala-0d7k"]}
{"id":"mala-1yy7","title":"[Review] 1 non-blocking finding from mala-hx3m","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-hx3m\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Bound lock_acquire timeout to avoid 30s unit test stalls\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** tests/unit/infra/tools/test_locking_mcp.py:336-345\n\n`lock_acquire` defaults to `timeout_seconds=30`, so if the re-entrant behavior regresses (e.g., `try_lock` returns `False` for same-agent re-acquire), this test can block for up to 30 seconds before failing. That makes the unit suite slow/flaky under failure conditions. Pass a small `timeout_seconds` in these calls so failures are fast, e.g. `await handlers.lock_acquire.handler({\"filepaths\": [test_file], \"timeout_seconds\": 0.1})`.\n\n---\n\n\u003c!-- fp:6fd321d32753a8b6 --\u003e\n\u003c!-- review_finding:ed76bcab5cb7 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-04T23:36:39.299018104Z","created_by":"cyou","updated_at":"2026-01-04T23:46:32.572431632Z","closed_at":"2026-01-04T23:46:32.572431632Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-hx3m"]}
{"id":"mala-1z6","title":"Disallow destructive git commands (reset, push --force, etc.)","description":"Prevent agents from running destructive git commands that could cause data loss or rewrite history.\n\n## Commands to Block\n\n| Pattern | Reason |\n|---------|--------|\n| `git reset --hard` | Discards uncommitted changes permanently |\n| `git push --force` / `git push -f` | Rewrites remote history (already blocked for main/master) |\n| `git clean -fd` | Deletes untracked files permanently |\n| `git checkout -- .` | Discards all uncommitted changes |\n| `git rebase` | Rewrites commit history |\n| `git branch -D` | Force-deletes branch without merge check |\n\n## Implementation\n\n**File:** `src/hooks.py`\n\n### 1. Add new pattern list after DANGEROUS_PATTERNS\n\n```python\n# Destructive git command patterns to block\nDESTRUCTIVE_GIT_PATTERNS = [\n    \"git reset --hard\",\n    \"git clean -fd\",\n    \"git clean -f\",\n    \"git checkout -- .\",\n    \"git rebase\",\n    \"git branch -D\",\n    \"git branch -d -f\",\n]\n```\n\n### 2. Update block_dangerous_commands function\n\nAdd a new check after the existing dangerous patterns loop:\n\n```python\n# Block destructive git patterns\nfor pattern in DESTRUCTIVE_GIT_PATTERNS:\n    if pattern in command:\n        return {\n            \"decision\": \"block\",\n            \"reason\": f\"Blocked destructive git command: {pattern}\",\n        }\n\n# Expand force push blocking to ALL branches (not just main/master)\nif \"git push\" in command and (\"--force\" in command or \"-f \" in command):\n    return {\n        \"decision\": \"block\",\n        \"reason\": \"Blocked force push (use --force-with-lease if needed)\",\n    }\n```\n\n### 3. Add tests in `tests/test_morph_integration.py`\n\nAdd to `TestBlockDangerousCommands` class:\n\n```python\n@pytest.mark.asyncio\n@pytest.mark.parametrize(\"cmd\", [\n    \"git reset --hard\",\n    \"git reset --hard HEAD~1\",\n    \"git clean -fd\",\n    \"git clean -f .\",\n    \"git checkout -- .\",\n    \"git rebase main\",\n    \"git rebase -i HEAD~3\",\n    \"git branch -D feature\",\n    \"git push --force origin feature\",\n    \"git push -f origin feature\",\n])\nasync def test_blocks_destructive_git_commands(self, make_hook_input, cmd):\n    \"\"\"Destructive git commands should be blocked.\"\"\"\n    from src.hooks import block_dangerous_commands\n    \n    result = await block_dangerous_commands(\n        make_hook_input(\"Bash\", cmd), None, {\"signal\": None}\n    )\n    assert result.get(\"decision\") == \"block\", f\"Should block: {cmd}\"\n```\n\n## Acceptance Criteria\n\n- [ ] All patterns in the table are blocked\n- [ ] Force push blocked on ALL branches (not just main/master)\n- [ ] Tests cover each blocked pattern\n- [ ] Existing tests still pass\n- [ ] Safe git commands still work: `git status`, `git diff`, `git log`, `git add`, `git commit`, `git push` (without --force)\n\n## Notes\n\n- Pattern matching is substring-based; edge cases like `git $VAR --hard` won't be caught\n- Consider suggesting `--force-with-lease` as safer alternative in block message\n- `git checkout -- \u003cspecific-file\u003e` could be allowed (only block `-- .` for all files)\n","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-26T01:05:31.653651896Z","updated_at":"2025-12-26T16:46:32.669493412Z","closed_at":"2025-12-26T16:46:32.669493412Z","close_reason":"Closed"}
{"id":"mala-204d","title":"Epic: --validate-every in Non-Watch Mode","description":"Make `--validate-every N` trigger periodic validation every N issues regardless of watch mode.\n\n**Source**: `mala-mkow` — \"refactor --validate-every command -- should be more general, including mode to run only at end\"\n\n**Related**: `mala-sj18` may be a duplicate and can be closed after implementation.\n\n## Goals\n- Split `WatchConfig` into `WatchConfig` (polling) + `ValidationConfig` (validate_every)\n- Make `--validate-every` an `Optional[int]` (default `None`) to detect explicit usage\n- Remove `watch_enabled` guards from validation callback invocations\n- Preserve existing watch mode behavior (default 10 when `--watch` is set)\n\n## Acceptance Criteria\n- `--validate-every N` without `--watch` triggers periodic validation every N issues\n- `--watch` without explicit `--validate-every` still validates every 10 (default)\n- Plain `mala run` (no flags) does NOT trigger periodic validation\n- Documentation updated to reflect new behavior\n\n## Plan\n`plans/2026-01-06-validate-every-non-watch-plan.md`","status":"closed","priority":1,"issue_type":"epic","created_at":"2026-01-06T04:18:56.359222002Z","created_by":"cyou","updated_at":"2026-01-06T15:29:20.769415612Z","closed_at":"2026-01-06T15:29:20.769415612Z","close_reason":"Closed"}
{"id":"mala-204d.1","title":"[T001] Split WatchConfig into WatchConfig + ValidationConfig","description":"## Goal\nSplit the existing `WatchConfig` dataclass into two separate config classes to decouple validation configuration from watch mode.\n\n## Context\nCurrently `WatchConfig` bundles watch-specific settings (enabled, poll_interval) with validation settings (validate_every). This coupling prevents using periodic validation without watch mode.\n\n## Scope\n- **In**: Create `ValidationConfig` dataclass, update `WatchConfig`, update `WatchState` docstring\n- **Out**: No wiring changes yet (handled in T002)\n\n## Changes\n- `src/core/models.py` — [Exists] — Split `WatchConfig`:\n  ```python\n  @dataclass\n  class WatchConfig:\n      \"\"\"Configuration for watch mode polling behavior.\"\"\"\n      enabled: bool = False\n      poll_interval_seconds: float = 60.0\n\n  @dataclass\n  class ValidationConfig:\n      \"\"\"Configuration for periodic validation triggering.\n\n      Attributes:\n          validate_every: Run validation after this many issues complete.\n              None means periodic validation is disabled.\n      \"\"\"\n      validate_every: int | None = None  # None means disabled\n  ```\n- `src/core/models.py` — [Exists] — Update `WatchState` docstring to reference `ValidationConfig.validate_every`\n\n## Acceptance Criteria\n- `ValidationConfig` dataclass exists with `validate_every: int | None` field (default `None`)\n- `WatchConfig` no longer has `validate_every` field\n- `WatchState` docstring references `ValidationConfig.validate_every` for `next_validation_threshold`\n- All existing tests still pass (imports may need updating in later tasks)\n\n## Verification\n```bash\nuvx ruff check src/core/models.py\nuvx ty check src/core/models.py\nuv run pytest tests/unit/core/ -v\n```\n\n## Notes for Agent\n- Keep both classes in `src/core/models.py` near existing `WatchConfig`\n- Use `int | None` type annotation (not `Optional[int]`) per codebase style\n- This is a pure data model change; wiring updates are in T002","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-06T04:22:02.927599172Z","created_by":"cyou","updated_at":"2026-01-06T04:30:56.964154949Z","closed_at":"2026-01-06T04:30:56.964154949Z","close_reason":"Restructuring tasks - build stability issue","dependencies":[{"issue_id":"mala-204d.1","depends_on_id":"mala-204d","type":"parent-child","created_at":"2026-01-06T04:22:02.92827825Z","created_by":"cyou"}]}
{"id":"mala-204d.10","title":"[T003] Remove watch_enabled guards from validation callback","description":"## Goal\nRemove `watch_enabled` guards from validation callback invocations in `IssueExecutionCoordinator`. Use callback presence to gate validation triggering.\n\n## Context\nThis is the task that enables the feature. Currently the coordinator checks:\n```python\nif watch_enabled and validation_callback:\n```\n\nAfter this change:\n```python\nif validation_callback:\n```\n\nThe callback presence itself indicates validation is enabled (CLI only passes callback when `validate_every` is configured).\n\n## Scope\n- **In**: Remove guards (5 locations), verify integration test passes\n- **Out**: CLI changes (T002), test file updates (done in T001)\n\n## Changes\n- `src/pipeline/issue_execution_coordinator.py` — [Exists] — Remove `watch_enabled` guards at **5 locations**:\n  \n  **Location 1** (line 179): Initial validation check\n  ```python\n  # Before: if watch_enabled and validation_callback:\n  # After:  if validation_callback:\n  ```\n  \n  **Location 2** (lines 319-322): Mid-loop validation (multi-line condition)\n  ```python\n  # Before: if (watch_enabled and watch_state.completed_count \u003e ...)\n  # After:  if (validation_callback and watch_state.completed_count \u003e ...)\n  ```\n  \n  **Location 3** (line 407): Post-completion validation\n  ```python\n  # Before: if watch_enabled and validation_callback:\n  # After:  if validation_callback:\n  ```\n  \n  **Location 4** (line 475): Threshold validation\n  ```python\n  # Before: if watch_enabled and validation_callback:\n  # After:  if validation_callback:\n  ```\n  \n  **Location 5** (lines 549-554): Threshold block guard - change gate condition\n  ```python\n  # Before:\n  if (\n      watch_config\n      and watch_config.enabled\n      and watch_state.completed_count \u003e= watch_state.next_validation_threshold\n  ):\n  \n  # After:\n  if (\n      validation_config is not None\n      and watch_state.completed_count \u003e= watch_state.next_validation_threshold\n  ):\n  ```\n\n## Acceptance Criteria\n- No `watch_enabled` guards remain for validation callback invocations\n- `validation_callback` is called when present, regardless of `watch_config.enabled`\n- Threshold block uses `validation_config is not None` as gate\n- Integration test from T001 now PASSES\n- All existing watch mode tests still pass\n\n## Verification\n```bash\nuvx ruff check src/pipeline/issue_execution_coordinator.py\nuvx ty check src/pipeline/issue_execution_coordinator.py\n\n# All tests should pass now\nuv run pytest tests/unit/pipeline/test_watch_mode.py -v\nuv run pytest tests/integration/pipeline/test_watch_mode.py -v\n\n# Verify no watch_enabled guards remain for validation\ngrep -n \"watch_enabled and validation_callback\" src/pipeline/issue_execution_coordinator.py\n# Expected: no matches\n\n# Verify threshold block uses validation_config\ngrep -n \"validation_config is not None\" src/pipeline/issue_execution_coordinator.py\n# Expected: at least one match in threshold block\n```\n\n## Notes for Agent\n- Search for `watch_enabled and validation_callback` to find locations 1, 3, 4\n- Search for `watch_enabled` with context to find location 2 (multi-line condition)\n- Location 5 is the threshold block - search for `watch_config.enabled` around line 549\n- After this task, the feature is fully functional\n- Run the full test suite to verify no regressions\n- The `watch_enabled` variable can remain defined (line 224) - it's still used for passing to interrupt handlers","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-06T04:32:40.485796554Z","created_by":"cyou","updated_at":"2026-01-06T07:24:14.17694925Z","closed_at":"2026-01-06T07:24:14.17694925Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-204d.10","depends_on_id":"mala-204d","type":"parent-child","created_at":"2026-01-06T04:32:40.494591032Z","created_by":"cyou"},{"issue_id":"mala-204d.10","depends_on_id":"mala-204d.8","type":"blocks","created_at":"2026-01-06T04:33:49.703178621Z","created_by":"cyou"}]}
{"id":"mala-204d.11","title":"[T004] Add non-watch periodic validation unit tests","description":"## Goal\nCreate comprehensive unit tests for periodic validation in non-watch mode.\n\n## Context\nThis provides dedicated test coverage for the new feature behavior. Tests verify that validation callback is invoked at correct intervals when `--validate-every` is used without `--watch`.\n\n## Scope\n- **In**: New unit test file for periodic validation behavior\n- **Out**: Integration tests (already in T001)\n\n## Changes\n- `tests/unit/pipeline/test_periodic_validation.py` — [New] — Create new test file:\n  ```python\n  \"\"\"Tests for periodic validation in non-watch mode.\n  \n  Verifies that ValidationConfig controls validation triggering\n  independently of WatchConfig.enabled.\n  \"\"\"\n  \n  @pytest.mark.parametrize(\"validate_every,completions,expected_validations\", [\n      (1, 3, 3),   # Validate after each issue\n      (2, 5, 2),   # Validate at 2, 4 (5th doesn't trigger periodic)\n      (3, 9, 3),   # Validate at 3, 6, 9\n      (5, 3, 0),   # Threshold not reached\n  ])\n  async def test_periodic_validation_thresholds(\n      validate_every, completions, expected_validations\n  ):\n      \"\"\"Validation triggers at N, 2N, 3N intervals.\"\"\"\n      \n  async def test_validation_without_watch_mode():\n      \"\"\"Validation works when watch_config.enabled=False.\"\"\"\n      \n  async def test_no_validation_when_config_none():\n      \"\"\"No periodic validation when validation_config=None.\"\"\"\n      \n  async def test_exact_boundary_validation():\n      \"\"\"Validate at exact threshold (e.g., 5 issues with validate_every=5).\"\"\"\n  ```\n\n## Acceptance Criteria\n- Test file `tests/unit/pipeline/test_periodic_validation.py` exists\n- Tests verify validation at N, 2N, 3N intervals without watch mode\n- Tests verify exact-boundary behavior (validation at threshold)\n- Tests verify no validation when `validation_config=None`\n- All tests pass\n\n## Verification\n```bash\nuv run pytest tests/unit/pipeline/test_periodic_validation.py -v\n\n# Full pipeline test suite\nuv run pytest tests/unit/pipeline/ -v\n```\n\n## Notes for Agent\n- Follow testing philosophy: behavior over implementation, fakes over mocks\n- Use table-driven tests with `@pytest.mark.parametrize` for threshold scenarios\n- Test the observable outcome (callback invoked at right times)\n- Reference existing `test_watch_mode.py` for patterns and fixtures\n- Don't test call counts; test that validation happened after expected completions","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-06T04:32:58.80686074Z","created_by":"cyou","updated_at":"2026-01-06T07:52:54.087099361Z","closed_at":"2026-01-06T07:52:54.087099361Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-204d.11","depends_on_id":"mala-204d","type":"parent-child","created_at":"2026-01-06T04:32:58.815916617Z","created_by":"cyou"},{"issue_id":"mala-204d.11","depends_on_id":"mala-204d.10","type":"blocks","created_at":"2026-01-06T04:33:49.71697703Z","created_by":"cyou"}]}
{"id":"mala-204d.12","title":"[T005] Update CLI documentation for --validate-every","description":"## Goal\nUpdate documentation to reflect that `--validate-every` works in non-watch mode.\n\n## Context\nThe CLI help text was updated in T002. This task updates external documentation to match the new behavior.\n\n## Scope\n- **In**: Update docs/cli-reference.md\n- **Out**: Code changes\n\n## Changes\n- `docs/cli-reference.md` — [Exists] — Update `--validate-every` documentation:\n  \n  **Remove**: \"(watch mode only)\" or \"(only in watch mode)\"\n  \n  **Add** explanation of new behavior:\n  ```markdown\n  ### --validate-every N\n  \n  Run validation after every N issues complete.\n  \n  Behavior:\n  - With `--validate-every N`: Enables periodic validation at N-issue intervals\n  - With `--watch` (no explicit N): Uses default of 10\n  - Without either flag: No periodic validation (only final validation)\n  \n  Periodic validation runs at N, 2N, 3N, ... completed issues. Final validation\n  always runs at the end of the run when any issues completed successfully.\n  ```\n\n## Acceptance Criteria\n- Documentation no longer mentions \"watch mode only\" for `--validate-every`\n- Documentation explains when periodic validation is enabled\n- Documentation matches actual CLI behavior\n\n## Verification\n```bash\n# Check for removed text\ngrep -i \"watch mode only\" docs/cli-reference.md\n# Expected: no matches\n\n# Check for new behavior explanation\ngrep -A5 \"validate-every\" docs/cli-reference.md\n```\n\n## Notes for Agent\n- Keep documentation concise and clear\n- Mirror the CLI help text style\n- Ensure examples are accurate","status":"closed","priority":3,"issue_type":"chore","created_at":"2026-01-06T04:33:21.113333011Z","created_by":"cyou","updated_at":"2026-01-06T15:24:30.046872849Z","closed_at":"2026-01-06T15:24:30.046872849Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-204d.12","depends_on_id":"mala-204d","type":"parent-child","created_at":"2026-01-06T04:33:21.122497258Z","created_by":"cyou"},{"issue_id":"mala-204d.12","depends_on_id":"mala-204d.9","type":"blocks","created_at":"2026-01-06T04:33:49.730108357Z","created_by":"cyou"}]}
{"id":"mala-204d.13","title":"[Review] 2 non-blocking findings from mala-204d.8","description":"## Review Findings\n\nThis issue consolidates 2 non-blocking findings from code review.\n\n**Source issue:** mala-204d.8\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Prevent periodic validation when validate_every is None\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/pipeline/issue_execution_coordinator.py:580-626\n\n`PeriodicValidationConfig` documents that `validate_every=None` disables periodic validation, but `IssueExecutionCoordinator.run_loop()` can still enter the threshold-crossing block whenever `watch_config.enabled` and `validation_callback` are set.\n\nConcrete repro: call `run_loop(..., watch_config=WatchConfig(enabled=True), validation_config=PeriodicValidationConfig(validate_every=None), validation_callback=...)` and complete 10+ issues — validation will run (default threshold/step of 10), contradicting “disabled”. This also makes the comment “validate_every is guaranteed non-None” incorrect.\n\nFix suggestion: require `validation_config` and `validation_config.validate_every is not None` in the threshold-crossing guard, and remove the `else 10` fallback inside the threshold-advance block (or set `next_validation_threshold` to a sentinel when disabled).\n\n---\n\n### Finding 2: [P3] Remove stale WatchConfig.validate_every usage in coordinator unit tests\n\n**Priority:** P3\n**Reviewer:** codex\n**Location:** tests/unit/pipeline/test_issue_execution_coordinator.py:598-603\n\nThese tests still set `mock_watch_config.validate_every` and comment that it’s “Required by coordinator init”. After the split, `WatchConfig` no longer has `validate_every`, and the coordinator init reads from `validation_config` instead. This is now misleading and will break if the mock is tightened (e.g., `spec_set=WatchConfig`).\n\n---\n\n\u003c!-- fp:1bbcde63fda4dd65 --\u003e\n\u003c!-- fp:9143d5386aa3f66a --\u003e\n\u003c!-- review_finding:c4bdd6e5f448 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-06T06:46:00.621591236Z","created_by":"cyou","updated_at":"2026-01-06T06:53:21.1702173Z","closed_at":"2026-01-06T06:53:21.1702173Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-204d.8"],"dependencies":[{"issue_id":"mala-204d.13","depends_on_id":"mala-204d","type":"parent-child","created_at":"2026-01-06T06:46:00.622023914Z","created_by":"cyou"}]}
{"id":"mala-204d.14","title":"[Review] 1 non-blocking finding from mala-204d.9","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-204d.9\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] CLI metadata logs wrong validate_every in watch default case\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/cli/cli.py:875-885\n\nWhen running with `--watch` and no explicit `--validate-every`, `effective_validate_every` becomes `10` and `validation_config` is constructed, but `cli_args` is built earlier with `validate_every=None`. This makes logged metadata (and anything consuming `OrchestratorConfig.cli_args`) incorrectly indicate periodic validation is disabled in the common `--watch` default scenario.\n\nRepro: `mala run --watch` (no `--validate-every`) =\u003e `cli_args[\"validate_every\"]` is `None` while validation runs every 10.\n\nFix: derive `effective_validate_every` before `_build_cli_args_metadata(...)` and pass the effective value (or log both explicit and effective fields).\n\n---\n\n\u003c!-- fp:034bc79471713103 --\u003e\n\u003c!-- review_finding:32bb7ff88cf0 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-06T06:51:32.725718508Z","created_by":"cyou","updated_at":"2026-01-06T06:57:12.75543061Z","closed_at":"2026-01-06T06:57:12.75543061Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-204d.9"],"dependencies":[{"issue_id":"mala-204d.14","depends_on_id":"mala-204d","type":"parent-child","created_at":"2026-01-06T06:51:32.726155019Z","created_by":"cyou"}]}
{"id":"mala-204d.15","title":"[Review] 1 non-blocking finding from mala-204d.13","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-204d.13\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Avoid Optional validate_every arithmetic after guard\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/pipeline/issue_execution_coordinator.py:612-624\n\nEven with the runtime guard `validation_config.validate_every is not None`, most Python type checkers (incl. attribute-narrowing limitations) will still treat `validation_config.validate_every` as `int | None` inside the block, so `next_validation_threshold += validation_config.validate_every` can fail type-checking (and makes the “guaranteed” comment a bit shaky if the config were ever mutated).\n\nFix by binding to a local `int` once after the guard, e.g. `validate_every = validation_config.validate_every` + `assert validate_every is not None`, then use `validate_every` in the loop.\n\n---\n\n\u003c!-- fp:d7c42b14999b30f9 --\u003e\n\u003c!-- review_finding:31640b8ca8a9 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-06T06:53:21.211770524Z","created_by":"cyou","updated_at":"2026-01-06T06:55:02.512041683Z","closed_at":"2026-01-06T06:55:02.512041683Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-204d.13"],"dependencies":[{"issue_id":"mala-204d.15","depends_on_id":"mala-204d","type":"parent-child","created_at":"2026-01-06T06:53:21.212341553Z","created_by":"cyou"}]}
{"id":"mala-204d.16","title":"[Review] 6 non-blocking findings from mala-204d.10","description":"## Review Findings\n\nThis issue consolidates 6 non-blocking findings from code review.\n\n**Source issue:** mala-204d.10\n**Highest priority:** P3\n\n---\n\n### Finding 1: [P3] Remove unused watch_enabled parameter from _handle_interrupt\n\n**Priority:** P3\n**Reviewer:** claude\n**Location:** src/pipeline/issue_execution_coordinator.py:177\n\nThe `watch_enabled` parameter on line 177 is no longer used inside `_handle_interrupt` after removing the watch-enabled guards. The method body (lines 187-211) never references this parameter. It should be removed from the signature, and the 4 call sites (lines 295, 413, 516, 601) should be updated to stop passing it.\n\n---\n\n### Finding 2: [P3] Update drain_event docstring to reflect callback-based validation\n\n**Priority:** P3\n**Reviewer:** claude\n**Location:** src/pipeline/issue_execution_coordinator.py:243-246\n\nThe docstring says \"validation is triggered (if in watch mode)\" but validation is now triggered when the callback is present, regardless of watch mode. Should read \"validation is triggered (if validation_callback present)\" or similar.\n\n---\n\n### Finding 3: [P3] Remove unused `watch_enabled` from `_handle_interrupt` and callers\n\n**Priority:** P3\n**Reviewer:** codex\n**Location:** src/pipeline/issue_execution_coordinator.py:171-178\n\nAfter removing `watch_enabled` guards for validation, `_handle_interrupt()` still accepts `watch_enabled` but no longer uses it. This is dead plumbing that can confuse future changes (it reads like interrupts behave differently in watch mode when they don’t).\n\nExample:\n```py\nasync def _handle_interrupt(..., watch_enabled: bool) -\u003e RunResult:\n    ...\n    if validation_callback:\n```\n\nSuggested fix: drop the `watch_enabled` parameter, remove the local `watch_enabled = ...` in `run_loop`, and update the `_handle_interrupt(...)` call sites accordingly.\n\n---\n\n### Finding 4: [P3] Update `drain_event` docstring to match new validation behavior\n\n**Priority:** P3\n**Reviewer:** codex\n**Location:** src/pipeline/issue_execution_coordinator.py:243-246\n\nThe `drain_event` arg docstring still says validation runs “if in watch mode”, but the code now runs validation whenever `validation_callback` is present (even when watch mode is disabled). This can mislead callers/users when reading `run_loop` docs.\n\nCurrent doc:\n```py\nfinish, validation is triggered (if in watch mode) and the loop exits.\n```\n\nSuggested fix: update wording to “if `validation_callback` is provided” (or similar) to reflect the new contract.\n\n---\n\n### Finding 5: [P3] Remove unused 'watch_enabled' argument from '_handle_interrupt'\n\n**Priority:** P3\n**Reviewer:** gemini\n**Location:** src/pipeline/issue_execution_coordinator.py:161\n\nAfter removing the `watch_enabled` guards from validation callback invocations, the `watch_enabled` parameter in `_handle_interrupt` is no longer used but is still being passed. It should be removed to keep the interface clean.\n\n---\n\n### Finding 6: [P3] Remove redundant 'watch_enabled' variable initialization\n\n**Priority:** P3\n**Reviewer:** gemini\n**Location:** src/pipeline/issue_execution_coordinator.py:249\n\nThe `watch_enabled` variable initialized on line 249 is now only used to be passed to `_handle_interrupt`, which itself no longer uses it. This variable should be removed along with its usages.\n\n---\n\n\u003c!-- fp:6983052f49d7e2ad --\u003e\n\u003c!-- fp:a9725d0f7b1cf856 --\u003e\n\u003c!-- fp:25b3cd6c642200ce --\u003e\n\u003c!-- fp:d9f73c95284b3ac6 --\u003e\n\u003c!-- fp:e6386b0e6992433c --\u003e\n\u003c!-- fp:6abe1ce54f25ccda --\u003e\n\u003c!-- review_finding:80031a7dc6e5 --\u003e","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-06T07:24:14.220660344Z","created_by":"cyou","updated_at":"2026-01-06T07:32:59.000609807Z","closed_at":"2026-01-06T07:32:59.000609807Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-204d.10"],"dependencies":[{"issue_id":"mala-204d.16","depends_on_id":"mala-204d","type":"parent-child","created_at":"2026-01-06T07:24:14.221174882Z","created_by":"cyou"}]}
{"id":"mala-204d.2","title":"[T002] [integration-path-test] Wiring skeleton + failing integration test","description":"## Goal\nUpdate wiring layers to accept split configs and create a failing integration test that exercises the CLI → orchestrator → coordinator path.\n\n## Context\nAfter T001 splits the config, all wiring code needs to propagate `ValidationConfig` separately from `WatchConfig`. This task creates the compile-ready skeleton and a failing integration test that will pass once implementation tasks (T003, T004) are complete.\n\n## Scope\n- **In**: Update wiring in orchestration layer, create failing integration test\n- **Out**: Actual implementation logic (T003, T004)\n\n## Changes\n- `src/orchestration/orchestration_wiring.py` — [Exists] — Update to construct/accept `ValidationConfig`\n- `src/orchestration/orchestrator.py` — [Exists] — Update method signatures to accept `validation_config: ValidationConfig | None`\n- `src/orchestration/types.py` — [Exists] — Add `ValidationConfig` to type hints if needed\n- `tests/integration/pipeline/test_watch_mode.py` — [Exists] — Add failing integration test for non-watch periodic validation\n\n## Wiring Map\n`CLI --validate-every → ValidationConfig → orchestration_wiring → MalaOrchestrator → IssueExecutionCoordinator`\n\n## Acceptance Criteria\n- Project compiles/type-checks with updated signatures\n- Integration test exists that fails with \"validation not triggered\" (behavior unimplemented)\n- Test exercises: `mala run --validate-every 1 --max-issues 2` should trigger validation\n\n## Verification\n```bash\nuvx ruff check src/orchestration/\nuvx ty check src/orchestration/\nuv run pytest tests/integration/pipeline/test_watch_mode.py -v -k \"non_watch\" --tb=short\n# Expected: test fails (not yet implemented)\n```\n\n## Integration Path Test\nThis task includes the integration-path test for the feature:\n- Test must traverse: CLI parsing → orchestration wiring → orchestrator → coordinator\n- Test verifies validation callback is invoked in non-watch mode\n\n## Notes for Agent\n- Stub implementations can use `pass` or raise `NotImplementedError`\n- The goal is compile-ready wiring; behavior comes in T003/T004\n- Test should assert validation callback was called, which will fail until T003/T004","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-06T04:22:22.976654699Z","created_by":"cyou","updated_at":"2026-01-06T04:30:56.985974298Z","closed_at":"2026-01-06T04:30:56.985974298Z","close_reason":"Restructuring tasks - build stability issue","dependencies":[{"issue_id":"mala-204d.2","depends_on_id":"mala-204d","type":"parent-child","created_at":"2026-01-06T04:22:22.977221667Z","created_by":"cyou"},{"issue_id":"mala-204d.2","depends_on_id":"mala-204d.1","type":"blocks","created_at":"2026-01-06T04:24:35.627142176Z","created_by":"cyou"}]}
{"id":"mala-204d.3","title":"[T003] Implement CLI enablement logic for --validate-every","description":"## Goal\nImplement the CLI logic that determines when periodic validation is enabled based on `--validate-every` and `--watch` flags.\n\n## Context\nThe key insight is:\n1. CLI must distinguish \"user specified `--validate-every`\" from \"using default\"\n2. Watch mode defaults to 10 if `--validate-every` is not explicit\n3. Callback presence (not `watch_enabled`) gates validation triggering\n\n## Scope\n- **In**: CLI argument changes, enablement derivation logic, help text update\n- **Out**: Coordinator changes (T004)\n\n## Changes\n- `src/cli/cli.py` — [Exists] — Change `--validate-every` type and add enablement logic:\n  ```python\n  validate_every: Annotated[\n      int | None,\n      typer.Option(\"--validate-every\", min=1, help=\"Run validation after N issues complete\"),\n  ] = None  # Changed from default=10 to default=None\n\n  # Derive effective validate_every value\n  effective_validate_every: int | None\n  if validate_every is not None:\n      # User explicitly specified --validate-every N\n      effective_validate_every = validate_every\n  elif watch:\n      # Watch mode enabled, use default 10\n      effective_validate_every = 10\n  else:\n      # No watch, no explicit --validate-every → disabled\n      effective_validate_every = None\n\n  # Only construct ValidationConfig if enabled\n  validation_config = ValidationConfig(validate_every=effective_validate_every) if effective_validate_every is not None else None\n  ```\n- `src/cli/cli.py` — [Exists] — Update help text to remove \"(watch mode only)\"\n\n## Acceptance Criteria\n- `--validate-every N` without `--watch` creates `ValidationConfig(validate_every=N)`\n- `--watch` without `--validate-every` creates `ValidationConfig(validate_every=10)`\n- Plain `mala run` (no flags) creates `validation_config=None`\n- Help text no longer mentions \"watch mode only\"\n\n## Verification\n```bash\nuvx ruff check src/cli/cli.py\nuvx ty check src/cli/cli.py\nuv run pytest tests/unit/cli/test_cli.py -v -k \"validate_every\"\n```\n\n## Notes for Agent\n- Use `is not None` checks (not truthiness) per plan guidance\n- The `min=1` constraint on typer.Option prevents `--validate-every 0`\n- Update tests in tests/unit/cli/test_cli.py for new behavior","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-06T04:23:21.820982785Z","created_by":"cyou","updated_at":"2026-01-06T04:30:56.999120869Z","closed_at":"2026-01-06T04:30:56.999120869Z","close_reason":"Restructuring tasks - build stability issue","dependencies":[{"issue_id":"mala-204d.3","depends_on_id":"mala-204d","type":"parent-child","created_at":"2026-01-06T04:23:21.821771212Z","created_by":"cyou"},{"issue_id":"mala-204d.3","depends_on_id":"mala-204d.2","type":"blocks","created_at":"2026-01-06T04:24:35.64688228Z","created_by":"cyou"}]}
{"id":"mala-204d.4","title":"[T004] Update coordinator to use callback presence instead of watch_enabled","description":"## Goal\nRemove `watch_enabled` guards from validation callback invocations in `IssueExecutionCoordinator`. Use callback presence to gate validation triggering.\n\n## Context\nCurrently the coordinator checks `if watch_enabled and validation_callback:` at 4 locations. After this change:\n- Before: `if watch_enabled and validation_callback:`\n- After: `if validation_callback:`\n\nThe callback presence itself indicates validation is enabled (CLI only passes callback when `validate_every` is configured).\n\n## Scope\n- **In**: Remove `watch_enabled` guards, update WatchState initialization, update threshold references\n- **Out**: CLI changes (T003), test updates (T005, T006)\n\n## Changes\n- `src/pipeline/issue_execution_coordinator.py` — [Exists] — Remove `watch_enabled` guards at 4 locations\n- `src/pipeline/issue_execution_coordinator.py` — [Exists] — Update WatchState initialization:\n  ```python\n  watch_state = WatchState(\n      next_validation_threshold=(\n          validation_config.validate_every if validation_config and validation_config.validate_every is not None else 10\n      )\n  )\n  ```\n- `src/pipeline/issue_execution_coordinator.py` — [Exists] — Update threshold advancement to use `validation_config.validate_every`\n\n## Acceptance Criteria\n- No `watch_enabled` guards remain for validation callback invocations\n- `validation_callback` is called when present, regardless of `watch_config.enabled`\n- WatchState uses `validation_config.validate_every` for threshold initialization\n- Threshold advancement uses `validation_config.validate_every`\n\n## Verification\n```bash\nuvx ruff check src/pipeline/issue_execution_coordinator.py\nuvx ty check src/pipeline/issue_execution_coordinator.py\nuv run pytest tests/unit/pipeline/ -v\n# Integration test from T002 should now pass\nuv run pytest tests/integration/pipeline/test_watch_mode.py -v -k \"non_watch\"\n```\n\n## Notes for Agent\n- The 4 guard locations are approximately at lines 179, 320, 407, 475 (verify by searching `watch_enabled and validation_callback`)\n- Threshold block (lines 548-585) needs entire block gated on `validation_config is not None`\n- Agent wait logic (lines 556-564) stays inside the block\n- Use `is not None` checks per plan guidance","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-06T04:23:34.044415176Z","created_by":"cyou","updated_at":"2026-01-06T04:30:57.013182009Z","closed_at":"2026-01-06T04:30:57.013182009Z","close_reason":"Restructuring tasks - build stability issue","dependencies":[{"issue_id":"mala-204d.4","depends_on_id":"mala-204d","type":"parent-child","created_at":"2026-01-06T04:23:34.053570832Z","created_by":"cyou"},{"issue_id":"mala-204d.4","depends_on_id":"mala-204d.2","type":"blocks","created_at":"2026-01-06T04:24:35.65985678Z","created_by":"cyou"}]}
{"id":"mala-204d.5","title":"[T005] Update existing watch mode and orchestration tests for config split","description":"## Goal\nUpdate existing tests that reference `WatchConfig.validate_every` to use the new `ValidationConfig` structure.\n\n## Context\nAfter the config split, existing tests that construct `WatchConfig` with `validate_every` will fail. This task updates them to use the new config structure.\n\n## Scope\n- **In**: Update test fixtures and assertions for config split\n- **Out**: New test cases (T006)\n\n## Changes\n- `tests/unit/pipeline/test_watch_mode.py` — [Exists] — Update tests to use split configs\n- `tests/unit/orchestration/test_orchestrator.py` — [Exists] — Update orchestrator tests\n- `tests/unit/orchestration/test_orchestration_wiring.py` — [Exists] — Update wiring tests\n\n## Acceptance Criteria\n- All existing watch mode tests pass with new config structure\n- Tests correctly construct `ValidationConfig` for validation scenarios\n- Tests correctly construct `WatchConfig` for watch-specific behavior (polling)\n- No test uses `WatchConfig.validate_every` (field no longer exists)\n\n## Verification\n```bash\nuv run pytest tests/unit/pipeline/test_watch_mode.py -v\nuv run pytest tests/unit/orchestration/ -v\n# Full test suite should pass\nuv run pytest tests/unit/ -v\n```\n\n## Notes for Agent\n- Search for `validate_every` in test files to find all usages\n- Watch mode tests should still test that validation happens every 10 by default\n- Orchestration tests may need updated fixtures for split configs","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-06T04:23:47.513835511Z","created_by":"cyou","updated_at":"2026-01-06T04:30:57.026993511Z","closed_at":"2026-01-06T04:30:57.026993511Z","close_reason":"Restructuring tasks - build stability issue","dependencies":[{"issue_id":"mala-204d.5","depends_on_id":"mala-204d","type":"parent-child","created_at":"2026-01-06T04:23:47.523250836Z","created_by":"cyou"},{"issue_id":"mala-204d.5","depends_on_id":"mala-204d.3","type":"blocks","created_at":"2026-01-06T04:24:35.674152535Z","created_by":"cyou"},{"issue_id":"mala-204d.5","depends_on_id":"mala-204d.4","type":"blocks","created_at":"2026-01-06T04:24:35.687351265Z","created_by":"cyou"}]}
{"id":"mala-204d.6","title":"[T006] Add non-watch periodic validation unit tests","description":"## Goal\nCreate comprehensive unit tests for periodic validation in non-watch mode.\n\n## Context\nThis is the primary test coverage for the new feature behavior. Tests verify that validation callback is invoked at correct intervals when `--validate-every` is used without `--watch`.\n\n## Scope\n- **In**: New unit test file for periodic validation behavior\n- **Out**: Integration tests (already in T002)\n\n## Changes\n- `tests/unit/pipeline/test_periodic_validation.py` — [New] — Create new test file with:\n  - Test coordinator with `watch_enabled=False` and `validation_callback` present\n  - Verify callback is invoked after N issues\n  - Test threshold advancement (validates at N, 2N, 3N)\n  - Test exact-boundary case: `validate_every=5` with exactly 5 completions\n  - Test small issue count (\u003c validate_every) triggers final validation only\n\n## Acceptance Criteria\n- Test file `tests/unit/pipeline/test_periodic_validation.py` exists\n- Tests verify validation at N, 2N, 3N intervals without watch mode\n- Tests verify exact-boundary behavior (validation at threshold)\n- Tests verify no validation when `validation_config=None`\n- All tests pass\n\n## Verification\n```bash\nuv run pytest tests/unit/pipeline/test_periodic_validation.py -v\n# Full pipeline tests\nuv run pytest tests/unit/pipeline/ -v\n```\n\n## Notes for Agent\n- Follow testing philosophy: behavior over implementation, fakes over mocks\n- Use table-driven tests with `@pytest.mark.parametrize` for threshold scenarios\n- Test the observable outcome (callback invoked at right times)\n- Don't test call counts; test that validation happened after expected completions","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-06T04:23:58.38604739Z","created_by":"cyou","updated_at":"2026-01-06T04:30:57.040683436Z","closed_at":"2026-01-06T04:30:57.040683436Z","close_reason":"Restructuring tasks - build stability issue","dependencies":[{"issue_id":"mala-204d.6","depends_on_id":"mala-204d","type":"parent-child","created_at":"2026-01-06T04:23:58.396342192Z","created_by":"cyou"},{"issue_id":"mala-204d.6","depends_on_id":"mala-204d.3","type":"blocks","created_at":"2026-01-06T04:24:35.701927278Z","created_by":"cyou"},{"issue_id":"mala-204d.6","depends_on_id":"mala-204d.4","type":"blocks","created_at":"2026-01-06T04:24:35.715588016Z","created_by":"cyou"}]}
{"id":"mala-204d.7","title":"[T007] Update CLI documentation for --validate-every","description":"## Goal\nUpdate documentation to reflect that `--validate-every` works in non-watch mode.\n\n## Context\nThe CLI help text was updated in T003. This task updates external documentation to match the new behavior.\n\n## Scope\n- **In**: Update docs/cli-reference.md\n- **Out**: Code changes\n\n## Changes\n- `docs/cli-reference.md` — [Exists] — Remove \"(watch mode only)\" from `--validate-every` description\n- `docs/cli-reference.md` — [Exists] — Add explanation of new behavior:\n  - Without `--watch`: explicitly enables periodic validation at interval N\n  - With `--watch`: defaults to 10 if not specified\n  - Without either flag: no periodic validation\n\n## Acceptance Criteria\n- Documentation no longer mentions \"watch mode only\" for `--validate-every`\n- Documentation explains when periodic validation is enabled\n- Documentation matches actual CLI behavior\n\n## Verification\n```bash\n# Manual review of docs/cli-reference.md\ngrep -i \"validate-every\" docs/cli-reference.md\n```\n\n## Notes for Agent\n- Keep documentation concise and clear\n- Mirror the CLI help text style","status":"closed","priority":3,"issue_type":"chore","created_at":"2026-01-06T04:24:12.004574952Z","created_by":"cyou","updated_at":"2026-01-06T04:30:57.05356963Z","closed_at":"2026-01-06T04:30:57.05356963Z","close_reason":"Restructuring tasks - build stability issue","dependencies":[{"issue_id":"mala-204d.7","depends_on_id":"mala-204d","type":"parent-child","created_at":"2026-01-06T04:24:12.014642954Z","created_by":"cyou"},{"issue_id":"mala-204d.7","depends_on_id":"mala-204d.3","type":"blocks","created_at":"2026-01-06T04:24:35.729526652Z","created_by":"cyou"}]}
{"id":"mala-204d.8","title":"[T001] [integration-path-test] Config split skeleton + Failing integration test","description":"## Goal\nCreate compile-ready skeleton for the config split, updating all layers atomically so the build stays green. Add a failing integration test that will pass once the feature is enabled.\n\n## Context\nThis is the foundation task that establishes the new `ValidationConfig` type and wires it through all layers. The key constraint is that the build must pass after this task completes - all call sites that reference `WatchConfig.validate_every` must be updated atomically.\n\n**Current wiring path:**\n```\nCLI → WatchConfig(enabled, validate_every) → orchestrator.run() → coordinator\n```\n\n**New wiring path:**\n```\nCLI → WatchConfig(enabled) + ValidationConfig(validate_every) → orchestrator.run() → coordinator\n```\n\n## Scope\n- **In**: Add ValidationConfig, update WatchConfig, update ALL call sites, update existing test fixtures, add failing integration test\n- **Out**: CLI enablement logic (T002), removing watch_enabled guards (T003)\n\n## Changes\n\n### Production Code\n- `src/core/models.py` — [Exists] — Add `ValidationConfig` dataclass, remove `validate_every` from `WatchConfig`, update `WatchState` docstring:\n  ```python\n  @dataclass\n  class WatchConfig:\n      \"\"\"Configuration for watch mode polling behavior.\"\"\"\n      enabled: bool = False\n      poll_interval_seconds: float = 60.0\n\n  @dataclass\n  class ValidationConfig:\n      \"\"\"Configuration for periodic validation triggering.\"\"\"\n      validate_every: int | None = None  # None means disabled\n  ```\n\n- `src/cli/cli.py` — [Exists] — Update to construct both configs:\n  ```python\n  # Line ~902-906: Build both configs\n  watch_config = WatchConfig(enabled=watch)\n  validation_config = ValidationConfig(validate_every=validate_every)\n  success_count, total = asyncio.run(\n      orchestrator.run(watch_config=watch_config, validation_config=validation_config)\n  )\n  ```\n\n- `src/orchestration/orchestrator.py` — [Exists] — Update `run()` method:\n  - Add `validation_config: ValidationConfig | None = None` parameter to `run()` signature\n  - **CRITICAL**: Make validation_callback creation CONDITIONAL on validation_config:\n    ```python\n    # Around line 939 - Only create callback if validation is configured\n    validation_callback = None\n    if validation_config and validation_config.validate_every is not None:\n        async def _validation_callback() -\u003e bool:\n            success_count = sum(1 for r in self._state.completed if r.success)\n            if success_count == 0 or self.abort_run:\n                return True\n            validation_input = RunLevelValidationInput(run_metadata=run_metadata)\n            validation_output = await self.run_coordinator.run_validation(\n                validation_input\n            )\n            return validation_output.passed\n        validation_callback = _validation_callback\n    ```\n  - Update `_run_main_loop()` signature to accept `validation_config` parameter\n  - Pass `validation_config` to `_run_main_loop()`\n\n- `src/orchestration/orchestration_wiring.py` — [Exists] — Update wiring to pass `validation_config` to coordinator's run_loop\n\n- `src/pipeline/issue_execution_coordinator.py` — [Exists] — Accept `validation_config` parameter, update references:\n  - Add `validation_config: ValidationConfig | None = None` to `run_loop()` signature\n  - Change `watch_config.validate_every` → `validation_config.validate_every` (2 locations: lines 221, 584)\n  - Update WatchState initialization (lines 219-223):\n    ```python\n    watch_state = WatchState(\n        next_validation_threshold=(\n            validation_config.validate_every \n            if validation_config and validation_config.validate_every is not None \n            else 10\n        )\n    )\n    ```\n  - **KEEP** the `watch_enabled and validation_callback` guards - removing them is T003\n  - **KEEP** the threshold block guard at lines 549-554 as-is - changing it is T003\n\n### Test Updates (to maintain build)\n- `tests/unit/pipeline/test_watch_mode.py` — [Exists] — Update fixtures from `WatchConfig(validate_every=N)` to use both configs:\n  ```python\n  # Before:\n  watch_config = WatchConfig(enabled=True, validate_every=3)\n  \n  # After:\n  watch_config = WatchConfig(enabled=True)\n  validation_config = ValidationConfig(validate_every=3)\n  ```\n\n- `tests/integration/pipeline/test_watch_mode.py` — [Exists] — Update existing test fixtures + add NEW failing integration test:\n  ```python\n  async def test_periodic_validation_without_watch_mode():\n      \"\"\"Verify periodic validation triggers in non-watch mode.\n      \n      This test should FAIL until T003 removes watch_enabled guards.\n      \"\"\"\n      # Setup: watch_config.enabled=False, validation_config.validate_every=1\n      # Run 2 issues\n      # Assert: validation callback was invoked (will fail - guards still present)\n  ```\n\n## Wiring Map\n`CLI --validate-every → ValidationConfig → orchestrator.run() → _run_main_loop() → IssueExecutionCoordinator.run_loop()`\n\n## Acceptance Criteria\n- Project compiles and type-checks: `uvx ty check src/`\n- All existing tests pass: `uv run pytest tests/unit/pipeline/test_watch_mode.py -v`\n- New integration test exists and FAILS for the right reason (watch_enabled guard, not import error)\n- `ValidationConfig` is a dataclass with `validate_every: int | None = None`\n- `WatchConfig` no longer has `validate_every` field\n- validation_callback in orchestrator.py is only created when validation_config is present\n\n## Verification\n```bash\n# Must pass\nuvx ruff check src/\nuvx ty check src/\nuv run pytest tests/unit/pipeline/test_watch_mode.py -v\nuv run pytest tests/unit/orchestration/ -v\n\n# Expected to fail (feature not yet enabled)\nuv run pytest tests/integration/pipeline/test_watch_mode.py -v -k \"without_watch\" || echo \"Expected failure - T003 will fix\"\n```\n\n## Integration Path Test\nThis task includes the integration-path test for the feature:\n- Test traverses: CLI args → orchestration wiring → orchestrator → coordinator\n- Test verifies validation callback invocation in non-watch mode\n- Test will fail until T003 removes the `watch_enabled` guards\n\n## Notes for Agent\n- This is a large skeleton task by design - all call sites must update atomically\n- grep for `validate_every` and `WatchConfig` to find all references\n- The key locations in coordinator are lines 221 and 584 (verify by searching)\n- KEEP the `watch_enabled and validation_callback` guards - removing them is T003\n- KEEP the threshold block guard (lines 549-554) - changing it is T003\n- Use `int | None` not `Optional[int]` per codebase style\n- The failing integration test should fail because validation doesn't trigger, NOT because of import/type errors\n- The conditional callback creation is CRITICAL - it establishes the pattern that callback presence = validation enabled","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-06T04:31:45.05563152Z","created_by":"cyou","updated_at":"2026-01-06T06:46:00.575758254Z","closed_at":"2026-01-06T06:46:00.575758254Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-204d.8","depends_on_id":"mala-204d","type":"parent-child","created_at":"2026-01-06T04:31:45.065267012Z","created_by":"cyou"}]}
{"id":"mala-204d.9","title":"[T002] Implement CLI enablement logic for --validate-every","description":"## Goal\nImplement the CLI logic that determines when periodic validation is enabled based on `--validate-every` and `--watch` flags.\n\n## Context\nAfter T001, the CLI passes `validate_every` directly to `ValidationConfig`. This task adds the enablement logic:\n1. Make `--validate-every` an `Optional[int]` (default `None`) to detect explicit usage\n2. Derive effective value: explicit \u003e watch default (10) \u003e disabled (None)\n3. Only construct `ValidationConfig` when validation is enabled\n\n## Scope\n- **In**: CLI argument type change, enablement derivation, help text, CLI tests\n- **Out**: Coordinator guard removal (T003)\n\n## Changes\n- `src/cli/cli.py` — [Exists] — Change `--validate-every` type and add enablement logic:\n  ```python\n  # Change argument type (around line 762)\n  validate_every: Annotated[\n      int | None,\n      typer.Option(\n          \"--validate-every\",\n          min=1,\n          help=\"Run validation after every N issues complete (default: 10 in watch mode)\",\n      ),\n  ] = None  # Changed from 10 to None\n\n  # Add derivation logic (around line 900, before constructing configs)\n  effective_validate_every: int | None\n  if validate_every is not None:\n      # User explicitly specified --validate-every N\n      effective_validate_every = validate_every\n  elif watch:\n      # Watch mode enabled, use default 10\n      effective_validate_every = 10\n  else:\n      # No watch, no explicit --validate-every → disabled\n      effective_validate_every = None\n\n  # Construct ValidationConfig only if enabled\n  validation_config = (\n      ValidationConfig(validate_every=effective_validate_every)\n      if effective_validate_every is not None\n      else None\n  )\n  ```\n\n- `tests/unit/cli/test_cli.py` — [Exists] — Add/update tests for new behavior:\n  ```python\n  def test_validate_every_explicit_without_watch():\n      \"\"\"--validate-every N without --watch creates ValidationConfig(N)\"\"\"\n      \n  def test_watch_without_validate_every_uses_default():\n      \"\"\"--watch without --validate-every creates ValidationConfig(10)\"\"\"\n      \n  def test_no_flags_disables_periodic_validation():\n      \"\"\"No --watch, no --validate-every creates validation_config=None\"\"\"\n  ```\n\n## Acceptance Criteria\n- `--validate-every N` without `--watch` creates `ValidationConfig(validate_every=N)`\n- `--watch` without explicit `--validate-every` creates `ValidationConfig(validate_every=10)`\n- Plain `mala run` (no flags) passes `validation_config=None` to orchestrator\n- CLI help text says \"(default: 10 in watch mode)\" not \"(only in watch mode)\"\n- All CLI tests pass\n\n## Verification\n```bash\nuvx ruff check src/cli/cli.py\nuvx ty check src/cli/cli.py\nuv run pytest tests/unit/cli/test_cli.py -v -k \"validate\"\n\n# Manual verification\nuv run python -m src.cli.cli run --help | grep validate-every\n```\n\n## Notes for Agent\n- Use `is not None` checks (not truthiness) per plan guidance\n- The `min=1` constraint on typer.Option prevents `--validate-every 0`\n- Help text change: remove \"only in watch mode\", add \"default: 10 in watch mode\"\n- Integration test from T001 still fails after this task (guards still present)","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-06T04:32:20.199783138Z","created_by":"cyou","updated_at":"2026-01-06T06:51:32.6825927Z","closed_at":"2026-01-06T06:51:32.6825927Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-204d.9","depends_on_id":"mala-204d","type":"parent-child","created_at":"2026-01-06T04:32:20.210081846Z","created_by":"cyou"},{"issue_id":"mala-204d.9","depends_on_id":"mala-204d.8","type":"blocks","created_at":"2026-01-06T04:33:49.683630644Z","created_by":"cyou"}]}
{"id":"mala-20c3","title":"[Review] 8 non-blocking findings from mala-gvg4","description":"## Review Findings\n\nThis issue consolidates 8 non-blocking findings from code review.\n\n**Source issue:** mala-gvg4\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Test helper has redundant agent_id None check\n\n**Priority:** P2\n**Reviewer:** claude\n**Location:** tests/integration/infra/test_locking_mcp.py:643-651\n\nThe test helper `_create_mcp_server_factory` in `test_locking_mcp.py` includes `if agent_id is not None:` at line 645, but the production code in `orchestration_wiring.py` removed this redundant check (since `agent_id` is a required `str` parameter). The test helper should be updated to match the production code for consistency. This is a minor inconsistency that doesn't break tests but makes the test less accurate as a mirror of production code.\n\n---\n\n### Finding 2: [P3] src/infra/mcp.py is now an empty module\n\n**Priority:** P3\n**Reviewer:** claude\n**Location:** src/infra/mcp.py:1-9\n\nAfter this refactoring, `mcp.py` contains only a module docstring and an import statement. The `get_disallowed_tools()` function was removed in the second commit. If no code imports from this module (which appears to be the case), consider deleting it entirely rather than leaving an empty shell. Empty modules add noise to the codebase.\n\n---\n\n### Finding 3: [P2] Update AgentRuntimeBuilder mcp_server_factory docstring\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/infra/agent_runtime.py:107-115\n\n`AgentRuntimeBuilder.__init__` still says that when `mcp_server_factory` is `None`, MCP servers “will be empty”, but `_build_mcp_servers()` now raises a `ValueError` instead. This mismatch makes it easy for callers to misconfigure MCP/locking and only discover it at runtime.\n\n---\n\n### Finding 4: [P2] Fix with_mcp doc: emit_lock_event=None does not disable locking\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/infra/agent_runtime.py:188-196\n\n`with_mcp()` documents `emit_lock_event=None` as “disable locking”, but the implementation (and `create_mcp_server_factory`) still builds the locking server and just swaps in a no-op handler. This is a behavioral/documentation mismatch that can cause incorrect assumptions when debugging lock-related behavior.\n\n---\n\n### Finding 5: [P2] Fix MCP factory error message and \"no locking\" guidance\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/infra/agent_runtime.py:254-260\n\nThe `ValueError` text refers to `with_mcp_servers()` (which doesn’t exist) and suggests `mcp_servers={}` to “run without locking tools”. With `make_lock_enforcement_hook(...)` always enabled, an empty MCP server set makes file-write tools block forever because `lock_acquire` is unavailable. The message should either point to the real API (`with_mcp(servers=...)`) and clarify the consequences, or the builder should conditionally disable lock enforcement when locking is intentionally disabled.\n\n---\n\n### Finding 6: [P3] Update or remove stale src/infra/mcp.py docstring\n\n**Priority:** P3\n**Reviewer:** codex\n**Location:** src/infra/mcp.py:1-6\n\n`src/infra/mcp.py` claims SDK-dependent MCP server configuration is “handled directly in agent_runtime.py”, but MCP server creation is now injected via `create_mcp_server_factory()` (or similar) rather than implemented in `agent_runtime.py`. Since the module currently contains no APIs, the docstring is misleading (and the file may be removable if truly unused).\n\n---\n\n### Finding 7: [P2] Misleading docstring in AgentRuntimeBuilder.__init__\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/infra/agent_runtime.py:109-110\n\nThe docstring for `mcp_server_factory` says \"If None, MCP servers must be explicitly provided or will be empty.\" However, the implementation in Commit 2 changed this behavior to raise a `ValueError` if the factory is `None` and `with_mcp` hasn't been called with explicit servers. The docstring should be updated to reflect that the factory is required unless servers are explicitly provided.\n\n---\n\n### Finding 8: [P2] Incorrect method name in AgentRuntimeBuilder error message\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/infra/agent_runtime.py:257\n\nThe error message in `_build_mcp_servers` refers to a non-existent method `with_mcp_servers()`. It should refer to `with_mcp(servers={})`.\n\n---\n\n\u003c!-- fp:92432428b8a44bc3 --\u003e\n\u003c!-- fp:2d0be500ce37704b --\u003e\n\u003c!-- fp:b534719bc5c3d05a --\u003e\n\u003c!-- fp:4c98969f441b4009 --\u003e\n\u003c!-- fp:541600f9cbc88649 --\u003e\n\u003c!-- fp:fa4867ad111aa558 --\u003e\n\u003c!-- fp:516f249472dc815d --\u003e\n\u003c!-- fp:b2b22d943a207c45 --\u003e\n\u003c!-- review_finding:b04e438f7be3 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-05T04:23:50.773491923Z","created_by":"cyou","updated_at":"2026-01-05T04:28:55.101302042Z","closed_at":"2026-01-05T04:28:55.101302042Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-gvg4"]}
{"id":"mala-20u","title":"Harden PreToolUse hook: robust tool-name matching + tests","description":"Problem: block_dangerous_commands in src/cli.py only checks tool_name == 'Bash'. If SDK uses 'bash' or other names, the safety hook is bypassed.\\n\\nScope/files: src/cli.py, tests (new or existing).\\n\\nWork:\\n- Confirm/assume tool names may vary; make matching robust (case-insensitive set, or accept multiple known names).\\n- Add unit tests for block_dangerous_commands covering allowed/blocked and tool name variations.\\n\\nAcceptance criteria:\\n- Hook blocks dangerous patterns regardless of tool name casing.\\n- Tests cover at least 1 allowed and 1 blocked command for multiple tool names.\\n\\nTDD: Write tests first, then update implementation.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-25T21:38:16.919383816Z","updated_at":"2025-12-25T23:59:03.895437001Z","closed_at":"2025-12-25T23:59:03.895437001Z","close_reason":"Closed"}
{"id":"mala-23sx","title":"Add max_idle_retries and idle_timeout_seconds to mala.yaml","description":"Currently hardcoded defaults in AgentSessionRunnerConfig (max_idle_retries=2, idle_timeout derived from agent timeout). Should be configurable in mala.yaml for tuning agent idle/hang recovery.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-10T21:24:37.269842531Z","created_by":"cyou","updated_at":"2026-01-10T23:29:57.838555219Z","closed_at":"2026-01-10T23:29:57.838555219Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-23sx","depends_on_id":"mala-6axc","type":"blocks","created_at":"2026-01-10T21:26:27.807588234Z","created_by":"cyou"}]}
{"id":"mala-242x","title":"CLI flag to disable MorphLLM","description":"Add a command-line flag (e.g., --no-morph or --disable-morph) to disable MorphLLM model routing. This allows users to bypass MorphLLM and use models directly when needed for debugging, cost control, or when MorphLLM is unavailable.\n\nAcceptance criteria:\n- Add --no-morph flag to CLI argument parsing\n- When set, skip MorphLLM routing and use the model directly\n- Update help text to document the flag","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-29T02:02:50.359221366Z","created_by":"cyou","updated_at":"2025-12-29T02:52:47.33145069Z","closed_at":"2025-12-29T02:52:47.33145069Z","close_reason":"Closed"}
{"id":"mala-252","title":"Implement validation worktree utility","description":"Context:\n- Clean-room validation requires deterministic worktree creation and cleanup.\n- Plan specifies unique worktree paths and keep-on-failure behavior.\n\nScope:\n- In: implement worktree create/remove helpers with unique paths and state tracking.\n- Out: validation command execution (runner handles).\n\nAcceptance Criteria:\n- Worktree paths follow the run/issue/attempt format.\n- Cleanup honors --keep-worktrees and records worktree_state.\n- Failures surface clear errors without leaving stale worktrees.\n\nTest Plan:\n- Add unit tests for worktree create/remove paths (mock git calls).\n- Run targeted tests for worktree module.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-26T23:00:01.66635989Z","updated_at":"2025-12-27T00:05:55.340798599Z","closed_at":"2025-12-27T00:05:55.340798599Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-252","depends_on_id":"mala-j6p","type":"blocks","created_at":"2025-12-26T23:00:26.466631948Z","created_by":"daemon"}]}
{"id":"mala-264j","title":"[Review] .beads/issues.jsonl:243: [P2] Incomplete metadata update for mala-iy6l.5 in issues.jsonl","description":"## Review Finding\n\nThis issue was auto-created from a P2 review finding.\n\n**Source issue:** mala-iy6l.5\n**Reviewer:** gemini\n**Location:** .beads/issues.jsonl:243\n\n## Details\n\nThe commit message states that this change \"completes the domain package structure\" for issue `mala-iy6l.5`, but the status in `.beads/issues.jsonl` remains `in_progress`, the `needs-followup` label is still present, and a stale error note about a timeout remains. These should be updated to reflect the task's completion.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-01T15:27:37.721620987Z","created_by":"cyou","updated_at":"2026-01-01T21:23:20.75553695Z","closed_at":"2026-01-01T21:23:20.75553695Z","close_reason":"Not actionable - issues.jsonl metadata is auto-generated, not code to fix","labels":["auto_generated","review_finding","review_finding:1466b8f55162","source:mala-iy6l.5"]}
{"id":"mala-26jj","title":"[Review] src/infra/io/event_sink.py:1135-1325: [P2] ConsoleEventSink methods updated but out of scope per task description","description":"## Review Finding\n\nThis issue was auto-created from a P2 review finding.\n\n**Source issue:** mala-40pg.2\n**Reviewer:** claude\n**Location:** src/infra/io/event_sink.py:1135-1325\n\n## Details\n\nThe task description explicitly states \"This task does NOT touch ConsoleEventSink (lines 837+)\" and scope is limited to \"protocol and NullEventSink\". However, the diff includes updates to ConsoleEventSink methods adding `issue_id` parameters. While these changes are correct and needed for consistency, they exceed the stated scope. This may cause coordination issues with mala-40pg.3 which is specifically tasked with \"Implement ConsoleEventSink logging with issue_id\".","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-01T19:33:10.841469864Z","created_by":"cyou","updated_at":"2026-01-01T21:23:20.786399145Z","closed_at":"2026-01-01T21:23:20.786399145Z","close_reason":"Wontfix - changes were intentionally out of scope per original task description","labels":["auto_generated","review_finding","review_finding:eacfa8235066","source:mala-40pg.2"]}
{"id":"mala-26pf","title":"[Review] src/domain/validation/config.py:198-202: [P3] Reject boolean thresholds in coverage config","description":"## Review Finding\n\nThis issue was auto-created from a P3 review finding.\n\n**Source issue:** mala-fdp1.1\n**Reviewer:** codex\n**Location:** src/domain/validation/config.py:198-202\n\n## Details\n\n`threshold: true` is accepted because `bool` satisfies `int | float`, then it’s coerced to `1.0` and passes the range check. That silently turns a boolean into a 1% threshold. You likely want to reject booleans explicitly here (and the same applies to the `timeout` check a few lines below).","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-02T03:19:13.895562712Z","created_by":"cyou","updated_at":"2026-01-02T03:40:45.301134263Z","closed_at":"2026-01-02T03:40:45.301134263Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_finding:8ff1a65d015b","source:mala-fdp1.1"]}
{"id":"mala-28sg","title":"[Review] src/infra/io/event_sink.py:1327-1329: [P2] Pass issue_id on validation success logs","description":"## Review Finding\n\nThis issue was auto-created from a P2 review finding.\n\n**Source issue:** mala-40pg.3\n**Reviewer:** codex\n**Location:** src/infra/io/event_sink.py:1327-1329\n\n## Details\n\n`on_validation_result` now accepts `issue_id`, but the success branch calls `log_verbose` without it, so verbose-mode \"Validation passed\" messages will continue to show `[agent-...]` instead of `[ISSUE-...]`. This is inconsistent with the failure branch and the stated goal of showing issue IDs in major logs. Pass `issue_id=issue_id` into the `log_verbose` call to keep output consistent.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-01T19:41:09.689813145Z","created_by":"cyou","updated_at":"2026-01-01T21:23:29.780887893Z","closed_at":"2026-01-01T21:23:29.780887893Z","close_reason":"Duplicate of mala-apsz which covers this finding more comprehensively","labels":["auto_generated","review_finding","review_finding:b6578355c458","source:mala-40pg.3"]}
{"id":"mala-29eu","title":"Epic: Architecture Refactor 2025-12-31 (hooks, factory, evidence)","description":"Addresses 3 high-severity issues from architecture review.\n\n## Context\n\n- `src/hooks.py` is 806 lines mixing unrelated concerns (security, caching, locking)\n- `MalaOrchestrator.__init__` is 250+ lines, hard to understand and test\n- `_finalize_issue_result` has ~180 lines with duplicate evidence extraction\n- All changes are pure refactors with no behavior changes\n\n## Scope\n\n**In:**\n- Phase 1: Split hooks.py into focused subpackage\n- Phase 2: Extract orchestrator factory function\n- Phase 3: Extract evidence extraction into pure functions\n\n**Out:**\n- Runtime behavior changes\n- Protocol interface changes\n- IssueRunner refactoring (medium-severity, deferred)\n\n## Acceptance Criteria\n\n- [ ] `src/hooks.py` replaced by `src/hooks/` subpackage with 4 focused modules\n- [ ] `MalaOrchestrator.__init__` under 60 lines with factory function\n- [ ] Evidence extraction in testable pure functions\n- [ ] All existing tests pass unchanged\n- [ ] Linting passes: `uvx ruff check . \u0026\u0026 uvx ty check`\n\n## Spec\n\nSee: docs/2025-12-31-architecture-refactor-spec.md","status":"closed","priority":1,"issue_type":"epic","created_at":"2025-12-31T04:49:31.732444088Z","created_by":"cyou","updated_at":"2026-01-01T15:24:42.723289935Z","closed_at":"2026-01-01T15:24:42.723289935Z","close_reason":"Closed","labels":["refactor"]}
{"id":"mala-29eu.1","title":"Split hooks.py into focused subpackage","description":"Split the 806-line `src/hooks.py` into a focused subpackage with 4 modules.\n\n## Context\n\n- `src/hooks.py` mixes 4 unrelated concerns in one file\n- Security patterns (DANGEROUS_PATTERNS, block_dangerous_commands) ~120 lines\n- File read caching (FileReadCache, make_file_read_cache_hook) ~200 lines\n- Lint caching (LintCache, make_lint_cache_hook) ~200 lines\n- Locking hooks (make_lock_enforcement_hook, make_stop_hook) ~80 lines\n- Matches existing subpackage patterns in `src/pipeline/` and `src/validation/`\n\n## Scope\n\n**In:**\n- Create `src/hooks/` directory with 4 focused modules\n- Create `src/hooks/__init__.py` with re-exports for backward compatibility\n- Delete `src/hooks.py`\n\n**Out:**\n- Changing hook behavior\n- Modifying external consumers (they use re-exports)\n\n## New Structure\n\n```\nsrc/hooks/\n├── __init__.py           # Re-exports all public symbols\n├── dangerous_commands.py # DANGEROUS_PATTERNS, DESTRUCTIVE_GIT_PATTERNS, SAFE_GIT_ALTERNATIVES, BASH_TOOL_NAMES, block_dangerous_commands(), block_morph_replaced_tools()\n├── file_cache.py         # CachedFileInfo, FileReadCache, make_file_read_cache_hook(), FILE_WRITE_TOOLS, FILE_PATH_KEYS\n├── lint_cache.py         # LINT_COMMAND_PATTERNS, LintCacheEntry, LintCache, _get_git_state(), _detect_lint_command(), make_lint_cache_hook()\n└── locking.py            # make_lock_enforcement_hook(), make_stop_hook()\n```\n\n## Acceptance Criteria\n\n- [ ] `src/hooks/dangerous_commands.py` contains security patterns and blocking hooks\n- [ ] `src/hooks/file_cache.py` contains file read caching logic\n- [ ] `src/hooks/lint_cache.py` contains lint command caching logic\n- [ ] `src/hooks/locking.py` contains lock enforcement hooks\n- [ ] `src/hooks/__init__.py` re-exports all public symbols including PreToolUseHook and StopHook type aliases\n- [ ] `from src.hooks import X` continues to work for all existing consumers\n- [ ] `src/hooks.py` deleted\n- [ ] All existing tests pass: `uv run pytest -m \"unit or integration\"`\n- [ ] Linting passes: `uvx ruff check . \u0026\u0026 uvx ty check`\n\n## Test Plan\n\n1. Run `uv run pytest -m \"unit or integration\"` - all existing hook tests must pass via re-exports\n2. Run `uvx ruff check . \u0026\u0026 uvx ty check`\n3. Verify no external import changes needed (check agent_session_runner.py imports work)\n\n## Notes for Agent\n\n- Each new module needs its own TYPE_CHECKING imports for SDK types (HookContext, PreToolUseHookInput, etc.)\n- FILE_WRITE_TOOLS and FILE_PATH_KEYS stay in file_cache.py (used by make_file_read_cache_hook)\n- BASH_TOOL_NAMES used by both dangerous_commands.py and lint_cache.py - put in dangerous_commands.py, import from there in lint_cache.py\n- Preserve the import from mcp module: `from .mcp import MORPH_DISALLOWED_TOOLS` in dangerous_commands.py","notes":"Quality gate failed: Timeout after 60 minutes\nLog: /home/cyou/.claude/projects/-home-cyou-mala/48dc63f8-23a9-4aaf-92d2-8dbce15d7f3c.jsonl","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-31T04:49:54.360423932Z","created_by":"cyou","updated_at":"2025-12-31T17:54:27.083530134Z","closed_at":"2025-12-31T17:54:27.083530134Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-29eu.1","depends_on_id":"mala-29eu","type":"parent-child","created_at":"2025-12-31T04:49:54.368107598Z","created_by":"daemon"}]}
{"id":"mala-29eu.2","title":"Extract orchestrator initialization into factory function","description":"Extract the 250+ line `MalaOrchestrator.__init__` into a factory function.\n\n## Context\n\n- `MalaOrchestrator.__init__` spans lines 147-395 (~250 lines)\n- Mixes config derivation, feature flags, DI setup, pipeline runner construction\n- Hard to test in isolation; new developers must trace 250+ lines to understand initialization\n- Matches existing factory pattern in `build_validation_spec()` in `src/validation/spec.py`\n\n## Scope\n\n**In:**\n- Create `src/orchestrator_factory.py` with OrchestratorConfig, OrchestratorDependencies, create_orchestrator()\n- Refactor `__init__` to support both legacy and new signatures\n- Update cli.py to use factory\n\n**Out:**\n- Changing runtime behavior\n- Modifying protocol interfaces\n\n## Technical Design\n\n```python\n@dataclass\nclass OrchestratorConfig:\n    repo_path: Path\n    max_agents: int | None = None\n    timeout_minutes: int | None = None\n    # ... all config fields from current __init__\n\n@dataclass\nclass OrchestratorDependencies:\n    issue_provider: IssueProvider\n    code_reviewer: CodeReviewer\n    gate_checker: GateChecker\n    # ... all protocol implementations\n\ndef create_orchestrator(\n    config: OrchestratorConfig,\n    *,\n    mala_config: MalaConfig | None = None,\n    deps: OrchestratorDependencies | None = None,\n    # Individual overrides for testing...\n) -\u003e MalaOrchestrator:\n    \"\"\"Factory function encapsulating all initialization logic.\"\"\"\n```\n\n## Acceptance Criteria\n\n- [ ] `src/orchestrator_factory.py` contains OrchestratorConfig and OrchestratorDependencies dataclasses\n- [ ] `create_orchestrator()` factory function encapsulates all initialization logic\n- [ ] `MalaOrchestrator.__init__` under 60 lines\n- [ ] Supports both legacy (individual params) and new (_config, _deps) patterns\n- [ ] Legacy callers continue to work unchanged (backward compatible)\n- [ ] `src/cli.py` uses `create_orchestrator()` for production instantiation\n- [ ] All existing tests pass: `uv run pytest -m \"unit or integration\"`\n- [ ] Linting passes: `uvx ruff check . \u0026\u0026 uvx ty check`\n\n## Test Plan\n\n1. Run `uv run pytest -m \"unit or integration\"` - all orchestrator tests must pass\n2. Run `uvx ruff check . \u0026\u0026 uvx ty check`\n3. Add unit tests for create_orchestrator() with various config combinations\n4. Verify legacy __init__ still works with individual parameters\n\n## Notes for Agent\n\n- Use `_init_from_config()` and `_init_legacy()` helper methods to keep __init__ clean\n- When _config is provided but _deps is None, raise ValueError (don't silently fall back)\n- Config precedence: explicit overrides \u003e OrchestratorConfig \u003e MalaConfig \u003e defaults\n- AgentSessionRunner is NOT in OrchestratorDependencies (constructed per-issue in run_implementer)","notes":"Quality gate failed: Timeout after 60 minutes\nLog: /home/cyou/.claude/projects/-home-cyou-mala/64125c1a-959e-44f6-bd64-9950e2a7e16c.jsonl","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-31T04:50:16.033010911Z","created_by":"cyou","updated_at":"2025-12-31T18:05:50.704813262Z","closed_at":"2025-12-31T18:05:50.704813262Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-29eu.2","depends_on_id":"mala-29eu","type":"parent-child","created_at":"2025-12-31T04:50:16.040684028Z","created_by":"daemon"}]}
{"id":"mala-29eu.3","title":"Extract evidence extraction into pure functions","description":"Extract duplicate evidence extraction logic from `_finalize_issue_result` into pure functions.\n\n## Context\n\n- `_finalize_issue_result` spans lines 477-657 (~180 lines) with 3 branches\n- Each branch duplicates evidence extraction logic (~60% identical code)\n- Hard to unit test; changes require updating multiple locations\n- Pure function extraction enables isolated testing\n\n## Scope\n\n**In:**\n- Add GateMetadata dataclass to src/orchestrator.py\n- Implement `_build_gate_metadata()` pure function for stored gate results\n- Implement `_build_gate_metadata_from_logs()` for fallback log parsing\n- Refactor `_finalize_issue_result()` to use new functions\n- Extract helper methods for remaining logic\n\n**Out:**\n- Changing behavior of finalization\n- Extracting to separate file (keep in orchestrator.py)\n\n## Technical Design\n\n```python\n@dataclass\nclass GateMetadata:\n    quality_gate_result: QualityGateResult | None\n    validation_result: MetaValidationResult | None\n\ndef _build_gate_metadata(\n    gate_result: GateResult | None,\n    log_path: Path | None,\n    quality_gate: GateChecker,\n    per_issue_spec: ValidationSpec | None,\n) -\u003e GateMetadata:\n    \"\"\"Extract metadata from stored gate result.\"\"\"\n\ndef _build_gate_metadata_from_logs(\n    log_path: Path,\n    result: IssueResult,\n    quality_gate: GateChecker,\n    per_issue_spec: ValidationSpec | None,\n) -\u003e GateMetadata:\n    \"\"\"Fallback: parse logs directly when no stored result.\"\"\"\n```\n\n## Acceptance Criteria\n\n- [ ] `GateMetadata` dataclass added to src/orchestrator.py\n- [ ] `_build_gate_metadata()` extracts evidence from stored gate result\n- [ ] `_build_gate_metadata_from_logs()` handles fallback log parsing\n- [ ] Fallback function respects result.success for passed status (not hardcoded False)\n- [ ] When per_issue_spec is None, fallback returns GateMetadata(None, None)\n- [ ] `_finalize_issue_result()` reduced to ~80 lines using the new functions\n- [ ] Helper methods extracted: `_check_epic_closure()`, `_record_issue_run()`, `_cleanup_session_paths()`, `_emit_completion()`\n- [ ] All existing tests pass: `uv run pytest -m \"unit or integration\"`\n- [ ] Linting passes: `uvx ruff check . \u0026\u0026 uvx ty check`\n\n## Test Plan\n\n1. Run `uv run pytest -m \"unit or integration\"` - all orchestrator tests must pass\n2. Run `uvx ruff check . \u0026\u0026 uvx ty check`\n3. Add unit tests for `_build_gate_metadata()` with:\n   - Successful gate result with full evidence\n   - Failed gate result with partial evidence\n   - None gate result (returns empty metadata)\n   - Empty failure reasons, missing commit hash\n4. Add unit tests for `_build_gate_metadata_from_logs()` with:\n   - Valid spec (parses evidence)\n   - None spec (returns empty metadata)\n\n## Notes for Agent\n\n- When per_issue_spec is None, `_build_gate_metadata_from_logs()` returns GateMetadata(None, None)\n- Use result.success to determine passed status in fallback function, not hardcoded False\n- Keep functions in orchestrator.py (don't extract to separate file) - they're tightly coupled","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-31T04:50:37.909789534Z","created_by":"cyou","updated_at":"2025-12-31T19:09:53.048957797Z","closed_at":"2025-12-31T19:09:53.048957797Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-29eu.3","depends_on_id":"mala-29eu","type":"parent-child","created_at":"2025-12-31T04:50:37.916701071Z","created_by":"daemon"},{"issue_id":"mala-29eu.3","depends_on_id":"mala-29eu.2","type":"blocks","created_at":"2025-12-31T04:50:47.815353862Z","created_by":"daemon"}]}
{"id":"mala-29eu.4","title":"[Remediation] MalaOrchestrator.__init__ under 60 lines with factory functi...","description":"## Context\nThis issue was auto-created by epic verification for epic `mala-29eu`.\n\n## Epic Description / Acceptance Criteria\nTitle: Epic: Architecture Refactor 2025-12-31 (hooks, factory, evidence)\n\nAddresses 3 high-severity issues from architecture review.\n\n## Context\n\n- `src/hooks.py` is 806 lines mixing unrelated concerns (security, caching, locking)\n- `MalaOrchestrator.__init__` is 250+ lines, hard to understand and test\n- `_finalize_issue_result` has ~180 lines with duplicate evidence extraction\n- All changes are pure refactors with no behavior changes\n\n## Scope\n\n**In:**\n- Phase 1: Split hooks.py into focused subpackage\n- Phase 2: Extract orchestrator factory function\n- Phase 3: Extract evidence extraction into pure functions\n\n**Out:**\n- Runtime behavior changes\n- Protocol interface changes\n- IssueRunner refactoring (medium-severity, deferred)\n\n## Acceptance Criteria\n\n- [ ] `src/hooks.py` replaced by `src/hooks/` subpackage with 4 focused modules\n- [ ] `MalaOrchestrator.__init__` under 60 lines with factory function\n- [ ] Evidence extraction in testable pure functions\n- [ ] All existing tests pass unchanged\n- [ ] Linting passes: `uvx ruff check . \u0026\u0026 uvx ty check`\n\n## Spec\n\nSee: docs/2025-12-31-architecture-refactor-spec.md\n\n## Commit Scope\n- Range hint: fe43d72c1f2b303b0a6317b32a45b48ea497be44^..ace3f68fb775964afb998669d62bbe5c986b9ffe\n- Commits:\n- f85005c6ab0c3dc0bd8ac590a559b4122e731044 bd-mala-29eu.2: Add unit tests for orchestrator factory function\n- af8739de9442a7f5583259e0bb8472c3cd026d45 bd-mala-29eu.2: Review only issue commits via Cerberus\n- 1de9b755be81d4bdb0b3916e0b18c7eeb17489d5 bd-mala-29eu.2: Fix review issues - re-exports and factory behavior\n- 32f9c0a64290112120fc5859713539af443b7636 bd-mala-29eu.2: Add orchestrator factory with OrchestratorConfig\n- 8a414a4368f69ac64c3a09713d5c59e71a379d3c Revert epic mala-29eu: Architecture Refactor 2025-12-31\n- 181119bb40b8f14b8617ce638cb6f1c75a648339 bd-mala-29eu.2: Add orchestrator factory with OrchestratorConfig\n- ace3f68fb775964afb998669d62bbe5c986b9ffe bd-mala-n5du: Complete evidence extraction with validation_result in fallback path\n- d37c432ffcbb3eb395171153c0ba1415ac83f99a bd-mala-29eu.1: Restore strict validation for aggregated_findings\n- 21d0a84eadeaf6aff0a8ada9f1fb1f502b7ebbab bd-mala-29eu.1: Re-export get_lock_holder and run_command for backward compat\n- c1aabf576d21c12957aeef08b334b97733f38236 bd-mala-29eu.1: Format test file\n- 7d0e046fa5bd45720fa974e37996d9f68d8e246f bd-mala-29eu.1: Split hooks.py into focused subpackage\n- fe43d72c1f2b303b0a6317b32a45b48ea497be44 bd-mala-29eu.1: Split hooks.py into focused subpackage\n- bd38e5c54ae129c1016d7d0424e66cc1d957264b bd-mala-29eu.3: Fix lint errors in test imports\n\n## Child Issues\n- mala-29eu.1\n- mala-29eu.2\n- mala-29eu.3\n- mala-n5du\n\n## Unmet Criterion\nMalaOrchestrator.__init__ under 60 lines with factory function\n\n## Evidence\nThe __init__ method is currently 84 lines (lines 360-443) which exceeds the 60-line requirement. While a factory function create_orchestrator() was implemented and works correctly, the original __init__ method size was not reduced to meet the acceptance criteria.\n\n## Severity\nmajor\n\n## Resolution\nAddress this criterion to unblock epic closure. When complete, close this issue.\n","notes":"USER FEEDBACK: Code style issues like method line counts shouldn't block epic closure. This type of criterion should be informational only, not create a blocking remediation task. The factory function was implemented correctly and achieves the architectural goal - the init method being 84 vs 60 lines is a stylistic detail, not a functional requirement.","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-01T05:55:33.086667643Z","created_by":"cyou","updated_at":"2026-01-01T06:00:16.362339222Z","closed_at":"2026-01-01T06:00:16.362339222Z","close_reason":"Code style issues (line counts) should be informational, not blockers. The factory function achieves the architectural goal - 84 vs 60 lines is stylistic, not functional.","labels":["auto_generated","epic_remediation:mala-29eu:13a69444f9e71224d8b3ef6280cb5557d33ca04d9d94317c2463053522a43ed3"],"dependencies":[{"issue_id":"mala-29eu.4","depends_on_id":"mala-29eu","type":"parent-child","created_at":"2026-01-01T05:55:33.087121269Z","created_by":"daemon"}]}
{"id":"mala-2eu","title":"Update CLI parsing for disable-validations + related flags","description":"Context:\n- CLI must accept --disable-validations and map values into config/spec inputs.\n- Other flags include --coverage-threshold, --lint-only-for-docs, --skip-e2e-if-no-keys.\n\nScope:\n- In: parse --disable-validations CSV and pass to config; validate values; wire other flags.\n- Out: orchestrator execution changes (separate task).\n\nAcceptance Criteria:\n- Unknown disable values produce a clear CLI error.\n- Parsed options are available to spec/orchestrator configuration.\n- Help text matches the plan.\n\nTest Plan:\n- Add CLI parsing tests for valid/invalid disable lists.\n- Run CLI unit tests.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-26T23:00:01.741979177Z","updated_at":"2025-12-27T00:55:51.216408694Z","closed_at":"2025-12-27T00:55:51.216408694Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-2eu","depends_on_id":"mala-5us","type":"blocks","created_at":"2025-12-26T23:00:26.581177502Z","created_by":"daemon"}]}
{"id":"mala-2exn","title":"[Review] src/infra/io/event_sink.py:1196-1201: [P3] Update Args section in ConsoleEventSink.on_gate_result docstring","description":"## Review Finding\n\nThis issue was auto-created from a P3 review finding.\n\n**Source issue:** mala-40pg.2\n**Reviewer:** gemini\n**Location:** src/infra/io/event_sink.py:1196-1201\n\n## Details\n\nThe `issue_id` parameter was added to the signature but not to the `Args` section of the docstring for `ConsoleEventSink.on_gate_result`. For consistency with the Protocol and other methods that document their arguments, this should be added.","notes":"Quality gate failed: External review failed: spawn failed: Artifact written: /home/cyou/.claude/projects/-home-cyou-mala/cerberus/4d55972e-ae6a-4e3d-81dc-74b8a86f103d/latest.md\nReview gate already active (status: pending, age: 57s)\nUse /home/cyou/.claude/plugins/cache/cerberus/cerberus/1.10.6/bin/review-gate resolve to complete the existing gate first.\nLog: /home/cyou/.claude/projects/-home-cyou-mala/4d55972e-ae6a-4e3d-81dc-74b8a86f103d.jsonl","status":"closed","priority":4,"issue_type":"task","created_at":"2026-01-01T19:33:10.867611486Z","created_by":"cyou","updated_at":"2026-01-02T00:02:32.569180808Z","closed_at":"2026-01-01T23:53:31.188783532Z","labels":["auto_generated","needs-followup","review_finding","review_finding:9f804d3a72f0","source:mala-40pg.2"],"dependencies":[{"issue_id":"mala-2exn","depends_on_id":"mala-apsz","type":"blocks","created_at":"2026-01-01T21:29:27.017529522Z","created_by":"daemon"}]}
{"id":"mala-2ez","title":"Agent mail using filesystem","description":"Agent mail using filesystem: allow agents to send/receive coordination messages via files to reduce lock contention and support handoffs.","status":"closed","priority":4,"issue_type":"epic","created_at":"2025-12-26T00:55:42.268473542Z","updated_at":"2025-12-26T18:28:06.757887173Z","closed_at":"2025-12-26T18:28:06.757887173Z","close_reason":"Closed per user request"}
{"id":"mala-2fdc","title":"Wire checkpoint/continuation prompts into AgentSessionConfig","description":"Design doc requires checkpoint_request and continuation prompts to be used for context restart. orchestration_wiring.build_session_config currently omits these fields, so AgentSessionRunner sees empty prompts and falls back to empty checkpoint + fallback continuation prompt. Wire deps.prompts.checkpoint_request_prompt and deps.prompts.continuation_prompt into AgentSessionConfig.","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-03T22:18:51.187301015Z","created_by":"cyou","updated_at":"2026-01-03T22:32:10.029937552Z","closed_at":"2026-01-03T22:32:10.029937552Z","close_reason":"Closed"}
{"id":"mala-2g8e","title":"Clarify and enforce locking MCP availability without deadlock monitor","description":"Problem: locking MCP server is only registered when a deadlock monitor is configured. Fixer agents run lock enforcement hooks but no locking tools, which may conflict with the \"move locking to tools\" epic intent.\n\nAcceptance Criteria:\n- Decide intended behavior: always register locking MCP tools when agent_id is present, or explicitly document/avoid tool usage when no monitor.\n- Implement the chosen behavior in AgentRuntimeBuilder/get_mcp_servers.\n- Add tests for the chosen path (e.g., runtime includes locking server even without monitor, or prompt/tool usage avoids this path).\n- Update any relevant docs/prompts to match the decision (no separate test ticket).","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-04T21:51:26.317868739Z","created_by":"cyou","updated_at":"2026-01-04T23:16:29.465455802Z","closed_at":"2026-01-04T23:16:29.465455802Z","close_reason":"Closed"}
{"id":"mala-2i7","title":"Epic: Validation module refactor + tooling","description":"Context:\n- Covers refactor of validation module into a package and new validation helpers.\n- Derived from docs/quality-hardening-plan.md (v5).\n\nScope:\n- In: validation package scaffolding + core modules (spec, worktree, coverage, e2e, runner).\n- Out: orchestrator/gate integration (handled in a separate epic).\n\nAcceptance Criteria:\n- All child issues closed and validation modules ready for integration.\n\nTest Plan:\n- Defer to child issue test plans.","status":"closed","priority":1,"issue_type":"epic","created_at":"2025-12-26T23:00:01.608651792Z","updated_at":"2025-12-27T01:06:32.783600955Z","closed_at":"2025-12-27T01:06:32.783600955Z","close_reason":"All 6 child issues closed: validation module refactored into package with spec, coverage, worktree, e2e, runner, deps_sync modules. 239 unit tests passing, 88% coverage.","dependencies":[{"issue_id":"mala-2i7","depends_on_id":"mala-j6p","type":"blocks","created_at":"2025-12-26T23:00:26.343216812Z","created_by":"daemon"},{"issue_id":"mala-2i7","depends_on_id":"mala-5us","type":"blocks","created_at":"2025-12-26T23:00:26.354537591Z","created_by":"daemon"},{"issue_id":"mala-2i7","depends_on_id":"mala-252","type":"blocks","created_at":"2025-12-26T23:00:26.365551792Z","created_by":"daemon"},{"issue_id":"mala-2i7","depends_on_id":"mala-c1o","type":"blocks","created_at":"2025-12-26T23:00:26.37697623Z","created_by":"daemon"},{"issue_id":"mala-2i7","depends_on_id":"mala-4dh","type":"blocks","created_at":"2025-12-26T23:00:26.387839923Z","created_by":"daemon"},{"issue_id":"mala-2i7","depends_on_id":"mala-cij","type":"blocks","created_at":"2025-12-26T23:00:26.399183121Z","created_by":"daemon"}]}
{"id":"mala-2im","title":"CLI status should be directory-scoped by default","description":"## Problem\n\nCurrently `mala status` (or similar status command) reports on all running mala instances globally. This is confusing when working in multiple repos simultaneously - you see status for unrelated runs.\n\n## Desired Behavior\n\n- **Default**: Show status only for mala instance(s) running in the current directory\n- **--all flag**: Show status for all running mala instances across all directories\n\n### Example output:\n\n```bash\n# In /home/cyou/mala\n$ mala status\nRunning: 3 agents processing 5 issues\nStarted: 10 minutes ago\nIssues: 2 completed, 3 in progress\n\n# In /home/cyou/other-repo (no mala running)\n$ mala status\nNo mala instance running in this directory.\n\n# Show all instances\n$ mala status --all\n/home/cyou/mala:\n  Running: 3 agents processing 5 issues\n  Started: 10 minutes ago\n\n/home/cyou/another-repo:\n  Running: 1 agent processing 2 issues\n  Started: 5 minutes ago\n```\n\n## Implementation\n\n**Files:**\n- `src/cli.py`: Update status command to filter by cwd, add --all flag\n- `src/tools/locking.py` or status module: Add helper to get running instances by directory\n\n### Key changes:\n\n1. **Store repo path in lock/status file**: Ensure running instances record their repo path\n2. **Filter by cwd**: Default status checks if current directory matches any running instance's repo path\n3. **--all flag**: Bypasses directory filter, shows all instances grouped by repo\n\n### CLI changes:\n\n```python\n@app.command()\ndef status(\n    all_instances: bool = typer.Option(False, \"--all\", help=\"Show all running instances\")\n):\n    \"\"\"Show status of mala instance(s).\"\"\"\n    cwd = Path.cwd().resolve()\n    \n    if all_instances:\n        instances = get_all_running_instances()\n    else:\n        instances = get_running_instances_for_dir(cwd)\n    \n    if not instances:\n        if all_instances:\n            print(\"No mala instances running.\")\n        else:\n            print(\"No mala instance running in this directory.\")\n        return\n    \n    for instance in instances:\n        display_instance_status(instance)\n```\n\n## Acceptance Criteria\n\n- [ ] `mala status` only shows instance running in current directory\n- [ ] `mala status` prints clear message when no instance running in cwd\n- [ ] `mala status --all` shows all running instances grouped by directory\n- [ ] Repo path is recorded when mala starts (for filtering)\n\n## Test Plan\n\n- Unit tests for directory filtering logic\n- Manual: start mala in two repos, verify `status` shows only local instance\n- Manual: verify `--all` shows both instances\n\n## Dependencies\n\n- May benefit from mala-db2 (per-repo log directories) for consistent path handling","status":"closed","priority":3,"issue_type":"task","created_at":"2025-12-27T22:52:41.322367853Z","updated_at":"2025-12-28T05:51:58.825582959Z","closed_at":"2025-12-28T05:51:58.825582959Z","close_reason":"Closed"}
{"id":"mala-2l59","title":"[Review] 2 non-blocking findings from mala-y6yw","description":"## Review Findings\n\nThis issue consolidates 2 non-blocking findings from code review.\n\n**Source issue:** mala-y6yw\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P3] Update test file docstring to remove human review mention\n\n**Priority:** P3\n**Reviewer:** claude\n**Location:** tests/test_epic_verifier.py:6\n\nThe test file docstring at line 6 mentions \"Human review issue creation\" but the `TestHumanReviewCreation` class has been removed. The docstring should be updated to reflect the current test coverage.\n\n---\n\n### Finding 2: [P2] Dead field `human_review_count` in `EpicVerificationResult`\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/core/models.py:105\n\nThe `human_review_count` field remains in the `EpicVerificationResult` dataclass and is still being aggregated in `EpicVerifier.verify_and_close_eligible`, but it is now hardcoded to `0` in all constructor calls. Since the human review feature is being entirely removed, this field should be deleted from the dataclass and its usages cleaned up to avoid maintaining dead code.\n\n---\n\n\u003c!-- fp:f32fc154fc6486ef --\u003e\n\u003c!-- fp:14671c5bfa279e4e --\u003e\n\u003c!-- review_finding:0437d2d4e48c --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T18:14:06.690822435Z","created_by":"cyou","updated_at":"2026-01-03T18:52:09.537175928Z","closed_at":"2026-01-03T18:52:09.537175928Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-y6yw"]}
{"id":"mala-2m5","title":"Scope down permissions, setup sandbox/dev container","description":"Context:\n- Currently agents run with permission_mode='bypassPermissions' which grants broad access\n- Need tighter permission controls for safer multi-agent execution\n- SDK client options are set in agent_session_runner.py, run_coordinator.py, epic_verifier.py\n\nLocation:\n- src/pipeline/agent_session_runner.py:506 (ClaudeAgentOptions permission_mode)\n- src/pipeline/run_coordinator.py:420 (permission_mode setting)\n- src/infra/epic_verifier.py:229 (permission_mode setting)\n\nScope:\n- In: Change permission_mode from 'bypassPermissions' to more restrictive mode; configure allowed tools/paths\n- Out: Docker/devcontainer setup (separate concern); network restrictions\n\nAcceptance Criteria:\n- Agents run with restricted permission_mode (not bypassPermissions)\n- File access limited to repository directory and necessary system paths\n- Blocked operations result in clear error messages\n- Existing agent workflows still function correctly\n\nTest Plan:\n- Unit: Test permission configuration is applied correctly\n- Integration: Run agent session with restricted permissions, verify expected tools work\n- Manual: Attempt disallowed operations, verify they're blocked\n\nNotes for Agent:\n- Review Claude SDK permission_mode options for available restriction levels\n- Consider --allowedTools flag in addition to permission_mode\n- May need to update hooks to handle permission-denied scenarios","status":"tombstone","priority":0,"issue_type":"feature","created_at":"2025-12-28T06:21:39.150409473Z","updated_at":"2026-01-06T22:52:10.761723406Z","deleted_at":"2026-01-06T22:52:10.761723406Z","deleted_by":"batch delete","delete_reason":"batch delete","original_type":"feature"}
{"id":"mala-2mwk","title":"[Review] src/domain/validation/coverage.py:743: [P3] Error message should reference configured coverage file, not hardcoded 'coverage.xml'","description":"## Review Finding\n\nThis issue was auto-created from a P3 review finding.\n\n**Source issue:** mala-fdp1.9\n**Reviewer:** claude\n**Location:** src/domain/validation/coverage.py:743\n\n## Details\n\nThe error message on line 743 still says \"No coverage.xml generated\" but the code now supports custom coverage file paths via `coverage_config.file`. This could be confusing when using a custom filename like `cov-report.xml`. Consider using the configured filename in the error message for clarity.","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-02T16:10:51.319514778Z","created_by":"cyou","updated_at":"2026-01-02T17:05:07.679229925Z","closed_at":"2026-01-02T17:05:07.679229925Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_finding:72ac84c39604","source:mala-fdp1.9"],"dependencies":[{"issue_id":"mala-2mwk","depends_on_id":"mala-wxzn","type":"blocks","created_at":"2026-01-02T16:44:11.953856164Z","created_by":"daemon"}]}
{"id":"mala-2u3","title":"Epic: Quality Gate Re-entry + Codex Review","description":"Implements same-session re-entry on quality gate failure, shifts issue closing to the orchestrator, and adds a Codex review step with structured JSON output and guardrails.","status":"closed","priority":1,"issue_type":"epic","created_at":"2025-12-26T19:04:48.636008473Z","updated_at":"2025-12-26T19:32:39.524157706Z","closed_at":"2025-12-26T19:32:39.524157706Z","close_reason":"All children completed"}
{"id":"mala-2u3.1","title":"Scope quality gate evidence by attempt","description":"Primary files: src/quality_gate.py, tests/test_quality_gate.py\n\nContext:\n- Gate evidence currently scans the full session log and can pass on stale validation\n- Re-entry needs evidence scoped to the latest attempt\n- No-progress detection should treat unchanged commit + no new evidence as a failure\n\nScope:\n- In: add log-offset-based parsing, return/update offset per attempt, expose a helper that only counts evidence after offset\n- In: add no-progress detection helper (unchanged commit + no new evidence)\n- Out: changes to orchestrator retry loop or prompts\n\nAcceptance Criteria:\n- Evidence parsing can start at a byte offset and ignore earlier log lines\n- Gate result can indicate when no new evidence was found since the last attempt\n- No-progress detection is implemented and covered by tests\n\nTest Plan:\n- TDD: add failing tests for offset parsing and no-progress detection, then implement\n- Run unit tests for quality gate parsing (targeted test file)\n\nNotes for Agent:\n- Keep public API small; prefer a new optional offset argument or a small helper returning (evidence, new_offset)\n","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-26T19:05:00.499151942Z","updated_at":"2025-12-26T19:12:12.110042901Z","closed_at":"2025-12-26T19:12:12.110042901Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-2u3.1","depends_on_id":"mala-2u3","type":"parent-child","created_at":"2025-12-26T19:05:00.510288361Z","created_by":"daemon"}]}
{"id":"mala-2u3.2","title":"Add retry/review config to CLI and run metadata","description":"Primary files: src/cli.py, src/logging/run_metadata.py\n\nContext:\n- New gate/review retry limits and a codex-review toggle need to be configurable\n- Run metadata should record these config values and per-issue attempt counts\n\nScope:\n- In: add CLI flags --max-gate-retries, --max-review-retries, --codex-review\n- In: extend RunConfig and IssueRun metadata to capture retry limits and attempt counts\n- Out: orchestrator behavior changes (handled separately)\n\nAcceptance Criteria:\n- CLI help shows new flags and defaults\n- Run metadata JSON includes new config fields\n- IssueRun records gate/review attempts (even if defaulted)\n\nTest Plan:\n- Manual: run \"mala run --help\" and verify new flags\n- Manual: run a short session and confirm metadata JSON includes new fields\n\nNotes for Agent:\n- Keep new fields optional/backward compatible in RunMetadata serialization\n","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-26T19:05:08.142483863Z","updated_at":"2025-12-26T19:10:26.476631609Z","closed_at":"2025-12-26T19:10:26.476631609Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-2u3.2","depends_on_id":"mala-2u3","type":"parent-child","created_at":"2025-12-26T19:05:08.154749276Z","created_by":"daemon"}]}
{"id":"mala-2u3.3","title":"Shift issue closing to orchestrator","description":"Primary files: src/orchestrator.py, src/beads_client.py, src/prompts/implementer_prompt.md\n\nContext:\n- Agent currently closes issues, which conflicts with same-session re-entry\n- Orchestrator should close only after all gates pass\n\nScope:\n- In: remove bd close instruction from implementer prompt\n- In: add BeadsClient.close_async(issue_id) wrapper\n- In: orchestrator closes issue after gate+review pass\n- Out: re-entry loop or codex review logic (separate tasks)\n\nAcceptance Criteria:\n- Agents no longer call bd close in the prompt\n- Orchestrator closes issues after successful completion\n- No regressions in .beads/issues.jsonl commit flow\n\nTest Plan:\n- Manual: run one issue end-to-end and confirm orchestrator performs bd close\n\nNotes for Agent:\n- Keep orchestrator success criteria aligned with new close flow\n","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-26T19:05:17.09218776Z","updated_at":"2025-12-26T19:15:17.759359304Z","closed_at":"2025-12-26T19:15:17.759359304Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-2u3.3","depends_on_id":"mala-2u3","type":"parent-child","created_at":"2025-12-26T19:05:17.111639583Z","created_by":"daemon"},{"issue_id":"mala-2u3.3","depends_on_id":"mala-2u3.2","type":"blocks","created_at":"2025-12-26T19:06:12.608088508Z","created_by":"daemon"}]}
{"id":"mala-2u3.4","title":"Implement same-session re-entry on gate failure","description":"Primary files: src/orchestrator.py\n\nContext:\n- Quality gate failures should trigger a same-session fix attempt, not a reopen\n- Claude SDK supports session resume by passing resume: \u003csession_id\u003e\n- Gate retries need guardrails to avoid infinite loops\n\nScope:\n- In: capture session_id from the init system message and store per issue\n- In: implement a gate retry loop with resume calls and failure-reason prompts\n- In: track attempt counts and log offsets per attempt\n- In: avoid adding to failed_issues until retries are exhausted\n- Out: codex review integration (separate task)\n\nAcceptance Criteria:\n- Gate failure triggers a follow-up prompt in the SAME session (resume, no fork)\n- Retries stop after max attempts and mark needs-followup\n- No-progress detection stops retries when commit hash is unchanged and no new evidence exists\n\nTest Plan:\n- Manual: force a gate failure (skip validations) and confirm resume + retry count\n- Manual: confirm retries stop after max\n\nNotes for Agent:\n- Keep retry state per issue to support parallel agents\n","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-26T19:05:26.093985944Z","updated_at":"2025-12-26T19:22:37.521894989Z","closed_at":"2025-12-26T19:22:37.521894989Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-2u3.4","depends_on_id":"mala-2u3","type":"parent-child","created_at":"2025-12-26T19:05:26.106458545Z","created_by":"daemon"},{"issue_id":"mala-2u3.4","depends_on_id":"mala-2u3.3","type":"blocks","created_at":"2025-12-26T19:06:16.691077263Z","created_by":"daemon"},{"issue_id":"mala-2u3.4","depends_on_id":"mala-2u3.1","type":"blocks","created_at":"2025-12-26T19:06:19.606198443Z","created_by":"daemon"}]}
{"id":"mala-2u3.5","title":"Add Codex review step with strict JSON + retry","description":"Primary files: src/orchestrator.py (optionally new helper module)\n\nContext:\n- After deterministic gates pass, we want an automated Codex review\n- Review output must be strict JSON with one repair retry, then fail closed\n\nScope:\n- In: invoke codex review --commit \u003csha\u003e with a strict JSON schema prompt\n- In: parse JSON output; if invalid, retry once with stricter prompt\n- In: if review fails, resume SAME Claude session with review issues\n- In: re-run deterministic gate after review fixes\n- Out: changes to quality gate parsing logic (separate task)\n\nAcceptance Criteria:\n- Codex review runs after deterministic gate pass\n- JSON parsing is strict with a single retry on parse failure\n- Review failures trigger same-session fixes and re-gating\n\nTest Plan:\n- Manual: run review on a known commit and verify JSON parsing\n- Manual: simulate invalid JSON (e.g., by altering prompt) and verify retry + fail-closed behavior\n\nNotes for Agent:\n- Keep prompts ASCII-only and avoid code fences in the JSON response\n","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-26T19:05:35.484550183Z","updated_at":"2025-12-26T19:29:10.812795467Z","closed_at":"2025-12-26T19:29:10.812795467Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-2u3.5","depends_on_id":"mala-2u3","type":"parent-child","created_at":"2025-12-26T19:05:35.484834411Z","created_by":"daemon"},{"issue_id":"mala-2u3.5","depends_on_id":"mala-2u3.4","type":"blocks","created_at":"2025-12-26T19:06:23.642607996Z","created_by":"daemon"}]}
{"id":"mala-2u3.6","title":"Update README for orchestrator-close + gate/review retries","description":"Primary files: README.md\n\nContext:\n- Workflow docs currently say the agent closes issues and gate failures reopen later\n- We are changing flow to orchestrator-close + same-session re-entry + codex review\n\nScope:\n- In: update workflow and quality gate sections to reflect new behavior\n- In: document retry limits and codex review step\n- Out: code changes\n\nAcceptance Criteria:\n- README describes orchestrator closing behavior\n- README describes same-session re-entry and review retries\n\nTest Plan:\n- N/A (documentation change)\n\nNotes for Agent:\n- Align wording with actual implemented behavior\n","status":"closed","priority":3,"issue_type":"chore","created_at":"2025-12-26T19:05:43.082927169Z","updated_at":"2025-12-26T19:32:31.343813389Z","closed_at":"2025-12-26T19:32:31.343813389Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-2u3.6","depends_on_id":"mala-2u3","type":"parent-child","created_at":"2025-12-26T19:05:43.094365345Z","created_by":"daemon"},{"issue_id":"mala-2u3.6","depends_on_id":"mala-2u3.5","type":"blocks","created_at":"2025-12-26T19:06:26.62064641Z","created_by":"daemon"}]}
{"id":"mala-2y0n","title":"refine clean command behavior: dont delete logs, and exit early if there is a instance running, add a --force -f flag if user really wants to still do it","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T19:13:51.554130372Z","created_by":"cyou","updated_at":"2026-01-03T19:20:16.8008108Z","closed_at":"2026-01-03T19:20:16.8008108Z","close_reason":"Closed"}
{"id":"mala-3bu9","title":"Epic: Test Suite Migration - Mocks to Fakes","description":"Migrate the mala test suite from heavy mock usage to a fakes-based, behavior-focused approach per CLAUDE.md/AGENTS.md testing philosophy.\n\n## Goals\n1. Replace mock-heavy tests with fakes-based behavioral tests\n2. Delete tests that don't catch meaningful bugs (dataclass defaults, stdlib behavior)\n3. Reduce test maintenance burden while preserving defect-catching capability\n\n## Success Criteria\n- Tests assert on observable outcomes (state changes, events emitted, return values) rather than internal call sequences\n- All fakes have contract tests validating fake-vs-real parity\n- `uvx ty check` passes with all fakes implementing full protocol interfaces\n- Coverage stays ≥72%\n\n## Key Deliverables\n- `tests/fakes/` module with FakeIssueProvider, FakeCommandRunner, FakeLockManager, FakeEpicVerificationModel, FakeEventSink\n- `tests/contracts/` with parity tests\n- `tests/README.md` documenting testing philosophy\n- Rewritten orchestrator, validation, and epic verification tests\n- Deleted low-value tests (dataclass defaults)\n\n## Plan Reference\nplans/2026-01-05-test-migration-plan.md","status":"closed","priority":1,"issue_type":"epic","created_at":"2026-01-05T01:43:55.88153803Z","created_by":"cyou","updated_at":"2026-01-05T05:38:00.614402822Z","closed_at":"2026-01-05T05:38:00.614402822Z","close_reason":"Closed"}
{"id":"mala-3bu9.1","title":"[T001] Setup: Create tests/fakes/ module structure and tests/README.md","description":"## Goal\nCreate the foundational test infrastructure: `tests/fakes/` module directory and `tests/README.md` documenting the testing philosophy.\n\n## Context\nThis is the foundation task that enables all subsequent fake implementations. The README establishes team alignment on the testing philosophy per CLAUDE.md/AGENTS.md.\n\n## Scope\n**In:**\n- Create `tests/fakes/` directory\n- Create `tests/fakes/__init__.py` with module docstring\n- Create `tests/README.md` linking to CLAUDE.md/AGENTS.md testing philosophy\n- Capture baseline metrics (MagicMock, patch, assert_called counts)\n\n**Out:**\n- Actual fake implementations (separate tasks)\n- Contract tests (separate tasks)\n\n## Changes\n- `tests/fakes/__init__.py` — New — Module init with docstring explaining fakes-based testing\n- `tests/README.md` — New — Testing philosophy documentation linking to CLAUDE.md/AGENTS.md\n\n## Acceptance Criteria\n- `tests/fakes/__init__.py` exists and is importable\n- `tests/README.md` exists with links to CLAUDE.md and AGENTS.md testing philosophy sections\n- Baseline metrics captured and documented (can be in README or separate file)\n\n## Verification\n- `python -c \"import tests.fakes\"` succeeds\n- `cat tests/README.md` shows testing philosophy content\n- `uvx ty check` passes\n\n## Notes for Agent\n- Baseline metrics commands from plan:\n  ```bash\n  rg -c 'MagicMock|AsyncMock' tests/ --type py | awk -F: '{sum+=$2} END {print sum}'\n  rg -c 'with patch|@patch' tests/ --type py | awk -F: '{sum+=$2} END {print sum}'\n  rg -c '\\.assert_called' tests/ --type py | awk -F: '{sum+=$2} END {print sum}'\n  ```","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-05T01:44:12.112463479Z","created_by":"cyou","updated_at":"2026-01-05T02:04:52.29515573Z","closed_at":"2026-01-05T02:04:52.29515573Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-3bu9.1","depends_on_id":"mala-3bu9","type":"parent-child","created_at":"2026-01-05T01:44:12.121102466Z","created_by":"cyou"}]}
{"id":"mala-3bu9.10","title":"[T010] Rewrite validation tests to use FakeCommandRunner","description":"## Goal\nRewrite validation tests to use FakeCommandRunner instead of patching subprocess/CommandRunner, asserting on behavioral outcomes.\n\n## Context\ntest_validation.py patches subprocess.run and CommandRunner.run. This task migrates to FakeCommandRunner with fail-closed semantics.\n\n## Scope\n**In:**\n- Replace CommandRunner patches with FakeCommandRunner injection\n- Register expected commands in FakeCommandRunner.results\n- Convert call assertions to behavioral assertions using helper methods\n- Parametrize tests for common validation scenarios\n\n**Out:**\n- test_validation_config.py (may not need changes if already behavioral)\n- test_validation_gating.py (may not need changes)\n\n## Changes\n- `tests/unit/domain/test_validation.py` — Exists — Rewrite to use FakeCommandRunner\n\n## Acceptance Criteria\n- No `with patch(...)` in validation tests\n- No `assert_called*` assertions\n- Tests use FakeCommandRunner with registered results\n- Tests assert via `fake_runner.has_call_with_prefix(...)` and result values\n- Coverage maintained\n\n## Verification\n- `rg 'with patch|@patch' tests/unit/domain/test_validation.py` — Returns empty\n- `rg '\\.assert_called' tests/unit/domain/test_validation.py` — Returns empty\n- `uv run pytest tests/unit/domain/test_validation*.py --cov-fail-under=72` — Tests pass\n\n## Notes for Agent\n- FakeCommandRunner is fail-closed; register all expected commands\n- Use `has_call_with_prefix((\"pytest\",))` for flexible assertions","notes":"Quality gate failed: Quality gate failed: No commit with bd-mala-3bu9.10 found after run baseline (stale commits from previous runs are rejected)\nLog: /home/cyou/.claude/projects/-home-cyou-mala/2df7e67a-6eb8-45de-a988-94b172b75eff.jsonl","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-05T01:46:29.255521105Z","created_by":"cyou","updated_at":"2026-01-05T03:32:15.791510022Z","closed_at":"2026-01-05T03:32:15.791510022Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-3bu9.10","depends_on_id":"mala-3bu9","type":"parent-child","created_at":"2026-01-05T01:46:29.264362354Z","created_by":"cyou"},{"issue_id":"mala-3bu9.10","depends_on_id":"mala-3bu9.3","type":"blocks","created_at":"2026-01-05T01:46:32.92890769Z","created_by":"cyou"},{"issue_id":"mala-3bu9.10","depends_on_id":"mala-3bu9.7","type":"blocks","created_at":"2026-01-05T01:46:32.947955508Z","created_by":"cyou"}]}
{"id":"mala-3bu9.11","title":"[T011] Rewrite epic verification retry tests to use FakeEpicVerificationModel","description":"## Goal\nRewrite epic verification retry tests to use FakeEpicVerificationModel instead of MagicMock, asserting on observable retry behavior.\n\n## Context\ntest_epic_verification_retry.py has 101 MagicMock uses and tests retry logic by verifying call counts. This task migrates to observable verdict sequences.\n\n## Scope\n**In:**\n- Replace epic verifier mocks with FakeEpicVerificationModel\n- Configure verdict sequences for retry scenarios\n- Assert on observable `attempts` list instead of `_call_index`\n- Parametrize retry scenarios: [pass], [fail, pass], [fail, fail, fail]\n\n**Out:**\n- test_epic_scope.py (may not need changes)\n\n## Changes\n- `tests/unit/domain/test_epic_verification_retry.py` — Exists — Rewrite to use FakeEpicVerificationModel\n\n## Acceptance Criteria\n- No MagicMock in epic retry tests\n- Tests configure verdict sequences via FakeEpicVerificationModel(verdicts=[...])\n- Tests assert on `len(fake_model.attempts)` and `fake_model.attempts[i].verdict.passed`\n- Coverage maintained\n\n## Verification\n- `rg 'MagicMock|AsyncMock' tests/unit/domain/test_epic_verification_retry.py` — Returns empty\n- `uv run pytest tests/unit/domain/test_epic*.py --cov-fail-under=72` — Tests pass\n\n## Notes for Agent\n- Observable assertions example:\n  - `len(fake_model.attempts) == 3`\n  - `fake_model.attempts[0].verdict.passed is False`\n  - `fake_model.attempts[2].verdict.passed is True`","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-05T01:46:45.248927688Z","created_by":"cyou","updated_at":"2026-01-05T02:44:31.515273345Z","closed_at":"2026-01-05T02:44:31.515273345Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-3bu9.11","depends_on_id":"mala-3bu9","type":"parent-child","created_at":"2026-01-05T01:46:45.257831687Z","created_by":"cyou"},{"issue_id":"mala-3bu9.11","depends_on_id":"mala-3bu9.4","type":"blocks","created_at":"2026-01-05T01:46:49.755221606Z","created_by":"cyou"},{"issue_id":"mala-3bu9.11","depends_on_id":"mala-3bu9.8","type":"blocks","created_at":"2026-01-05T01:46:49.775031569Z","created_by":"cyou"}]}
{"id":"mala-3bu9.12","title":"[T012] Delete low-value config tests (dataclass defaults)","description":"## Goal\nDelete tests in test_config.py that only verify Python dataclass defaults work, keeping only tests for mala-specific validation logic.\n\n## Context\ntest_config.py contains ~45 tests that just verify dataclass defaults. Per AGENTS.md: \"Don't test the obvious: Skip tests for dataclass defaults.\"\n\n## Scope\n**In:**\n- Delete tests that only assert default values\n- Keep tests that verify mala-specific validation logic (e.g., BRAINTRUST_API_KEY required when enabled)\n- Ensure coverage doesn't drop below 72%\n\n**Out:**\n- Rewriting tests (just deletion)\n- Other config test files (test_config_merger.py, test_config_loader.py)\n\n## Changes\n- `tests/unit/infra/test_config.py` — Exists — Delete low-value tests\n\n## Acceptance Criteria\n- No tests that only assert dataclass defaults remain\n- Tests for validation logic preserved\n- Coverage maintained (--cov-fail-under=72)\n\n## Verification\n- Review remaining tests: each should test mala-specific logic\n- `uv run pytest tests/unit/infra/test_config*.py --cov-fail-under=72` — Tests pass\n\n## Notes for Agent\n- Pattern to DELETE: `assert config.runs_dir == Path.home() / \".config\" / \"mala\" / \"runs\"`\n- Pattern to KEEP: `assert any(\"BRAINTRUST_API_KEY\" in e for e in errors)` (validation logic)","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-05T01:48:05.816023415Z","created_by":"cyou","updated_at":"2026-01-05T01:56:42.135026873Z","closed_at":"2026-01-05T01:56:42.135026873Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-3bu9.12","depends_on_id":"mala-3bu9","type":"parent-child","created_at":"2026-01-05T01:48:05.824930465Z","created_by":"cyou"}]}
{"id":"mala-3bu9.13","title":"[T013] Suite-wide cleanup: eliminate remaining patches and assert_called","description":"## Goal\nSystematic cleanup of remaining patch() and assert_called usage across the entire test suite.\n\n## Context\nAfter major test rewrites (orchestrator, validation, epic retry), some patches and call assertions may remain scattered across other test files. This is a mechanical sweep to eliminate them.\n\n## Scope\n**In:**\n- Find all remaining `with patch` and `@patch` in tests/unit/\n- Find all remaining `.assert_called*` in tests/\n- For each: replace with fake injection or behavioral assertion, or delete if test has no value\n\n**Out:**\n- tests/integration/ and tests/e2e/ (may use real components)\n\n## Changes\n- Various test files in tests/unit/ — Exists — Remove patches and call assertions\n\n## Acceptance Criteria\n- `rg 'with patch|@patch' tests/unit/` returns zero matches\n- `rg '\\.assert_called' tests/unit/` returns zero matches (or only in explicitly documented exceptions)\n- Coverage maintained\n\n## Verification\n- `rg 'with patch|@patch' tests/unit/ --type py` — Empty or documented exceptions\n- `rg '\\.assert_called' tests/unit/ --type py` — Empty or documented exceptions\n- `uv run pytest --cov-fail-under=72` — All tests pass\n\n## Notes for Agent\n- This is a sweep task - touch many files with same pattern\n- Document any exceptions in tests/README.md (e.g., third-party SDK mocks)\n- MagicMock acceptable for Braintrust SDK internals where no fake exists","notes":"Quality gate failed: External review failed: tests/unit/infra/hooks/test_hooks.py:1312: [P1] Remove real git subprocess usage from unit tests; tests/unit/pipeline/test_idle_retry_policy.py:424: [P1] Meet stated acceptance criteria by removing remaining patch usage\nLog: /home/cyou/.claude/projects/-home-cyou-mala/e626aed4-98e0-41c9-972c-ecab17f0cd48.jsonl","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-05T01:48:18.618136798Z","created_by":"cyou","updated_at":"2026-01-05T05:33:40.16017989Z","closed_at":"2026-01-05T05:33:40.16017989Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-3bu9.13","depends_on_id":"mala-3bu9","type":"parent-child","created_at":"2026-01-05T01:48:18.627503105Z","created_by":"cyou"},{"issue_id":"mala-3bu9.13","depends_on_id":"mala-3bu9.9","type":"blocks","created_at":"2026-01-05T01:48:22.712408511Z","created_by":"cyou"},{"issue_id":"mala-3bu9.13","depends_on_id":"mala-3bu9.10","type":"blocks","created_at":"2026-01-05T01:48:22.731414612Z","created_by":"cyou"},{"issue_id":"mala-3bu9.13","depends_on_id":"mala-3bu9.11","type":"blocks","created_at":"2026-01-05T01:48:22.744388634Z","created_by":"cyou"},{"issue_id":"mala-3bu9.13","depends_on_id":"mala-3bu9.12","type":"blocks","created_at":"2026-01-05T01:48:22.756588752Z","created_by":"cyou"}]}
{"id":"mala-3bu9.14","title":"[T014] Consolidate existing scattered fakes (FakeSDKClient, FakeGateChecker)","description":"## Goal\nConsolidate multiple duplicate FakeSDKClient and FakeGateChecker implementations into single canonical versions in `tests/fakes/`.\n\n## Context\nCurrently there are scattered fake implementations:\n- **FakeSDKClient**: 4 separate implementations in:\n  - `tests/unit/pipeline/test_idle_retry_policy.py:39`\n  - `tests/unit/pipeline/test_context_pressure_handler.py:45`\n  - `tests/integration/pipeline/test_agent_session_runner.py:75`\n  - `tests/unit/infra/test_agent_runtime.py` (factory only)\n- **FakeGateChecker**: 2 implementations in:\n  - `tests/unit/pipeline/test_gate_runner.py:30`\n  - `tests/unit/pipeline/test_review_runner.py:91`\n\nThis duplication violates DRY and makes maintenance harder.\n\n## Scope\n**In:**\n- Create `tests/fakes/sdk_client.py` with canonical FakeSDKClient and FakeSDKClientFactory\n- Create `tests/fakes/gate_checker.py` with canonical FakeGateChecker\n- Update all files that define duplicates to import from `tests/fakes/`\n- Ensure consolidated fakes are feature-complete (union of all existing capabilities)\n\n**Out:**\n- FakeIssueProvider (separate task T002)\n- FakeEventSink (separate task T005)\n\n## Changes\n- `tests/fakes/sdk_client.py` — New — Canonical FakeSDKClient + FakeSDKClientFactory\n- `tests/fakes/gate_checker.py` — New — Canonical FakeGateChecker\n- `tests/unit/pipeline/test_idle_retry_policy.py` — Exists — Remove local fake, import from fakes/\n- `tests/unit/pipeline/test_context_pressure_handler.py` — Exists — Remove local fake, import from fakes/\n- `tests/integration/pipeline/test_agent_session_runner.py` — Exists — Remove local fake, import from fakes/\n- `tests/unit/pipeline/test_gate_runner.py` — Exists — Remove local fake, import from fakes/\n- `tests/unit/pipeline/test_review_runner.py` — Exists — Remove local fake, import from fakes/\n\n## Acceptance Criteria\n- Single FakeSDKClient definition in `tests/fakes/sdk_client.py`\n- Single FakeGateChecker definition in `tests/fakes/gate_checker.py`\n- All tests that previously defined local fakes now import from `tests/fakes/`\n- All affected tests pass\n- `uvx ty check` passes\n\n## Verification\n- `rg 'class FakeSDKClient' tests/` — Only in `tests/fakes/sdk_client.py`\n- `rg 'class FakeGateChecker' tests/` — Only in `tests/fakes/gate_checker.py`\n- `uv run pytest tests/unit/pipeline/ tests/integration/pipeline/ -v` — All tests pass\n\n## Notes for Agent\n- Consolidated fake should have superset of capabilities from all existing fakes\n- Use observable state pattern from plan (`.calls`, `.responses`, etc.)\n- May need to merge constructor signatures if they differ","notes":"Quality gate failed: Quality gate failed: No commit with bd-mala-3bu9.14 found after run baseline (stale commits from previous runs are rejected)\nLog: /home/cyou/.claude/projects/-home-cyou-mala/8e9fcf28-d7c4-4a90-8375-1e6b655fc2c5.jsonl","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-05T01:52:47.30775071Z","created_by":"cyou","updated_at":"2026-01-05T02:53:34.08247342Z","closed_at":"2026-01-05T02:53:34.08247342Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-3bu9.14","depends_on_id":"mala-3bu9","type":"parent-child","created_at":"2026-01-05T01:52:47.319875662Z","created_by":"cyou"},{"issue_id":"mala-3bu9.14","depends_on_id":"mala-3bu9.1","type":"blocks","created_at":"2026-01-05T01:52:51.722619284Z","created_by":"cyou"}]}
{"id":"mala-3bu9.15","title":"[Review] 2 non-blocking findings from mala-3bu9.1","description":"## Review Findings\n\nThis issue consolidates 2 non-blocking findings from code review.\n\n**Source issue:** mala-3bu9.1\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Use async/await and protocol methods in usage examples\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** tests/README.md:53-62\n\nThe usage example in `tests/README.md` uses synchronous calls (e.g., `provider.list_issues()`) and a method not defined in the `IssueProvider` protocol. Since `IssueProvider` (defined in `src/core/protocols.py`) is an async protocol and its methods like `get_ready_async` return lists of strings (IDs), the example should use `await` and call actual protocol methods to accurately demonstrate how the system interacts with the provider. For instance: `issues = await provider.get_ready_async()`.\n\n---\n\n### Finding 2: [P2] Fix broken shell commands in README metrics table\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** tests/README.md:77-79\n\nThe baseline metric commands in the `tests/README.md` table use backslashes to escape pipes (e.g., `MagicMock\\|AsyncMock` and `\\| awk`). These backslashes remain in the code block and will cause the commands to fail or return incorrect results when copy-pasted. Specifically, the regex `MagicMock\\|AsyncMock` will search for a literal pipe instead of performing a logical OR. Use `rg -e MagicMock -e AsyncMock` to avoid the pipe in the regex, and consider moving complex commands out of the table to avoid delimiter escaping issues.\n\n---\n\n\u003c!-- fp:c50d2387dd88f4d3 --\u003e\n\u003c!-- fp:81a05d8a7f437521 --\u003e\n\u003c!-- review_finding:4b5410d10883 --\u003e","notes":"Quality gate failed: Quality gate failed: Missing validation evidence for: format, test, lint, typecheck\nLog: /home/cyou/.claude/projects/-home-cyou-mala/7e907d49-bb98-4d70-923b-3cc424b3d038.jsonl","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-05T02:04:52.337548692Z","created_by":"cyou","updated_at":"2026-01-05T02:52:23.969425319Z","closed_at":"2026-01-05T02:52:23.969425319Z","close_reason":"Closed","labels":["auto_generated","needs-followup","review_finding","source:mala-3bu9.1"],"dependencies":[{"issue_id":"mala-3bu9.15","depends_on_id":"mala-3bu9","type":"parent-child","created_at":"2026-01-05T02:04:52.337999759Z","created_by":"cyou"},{"issue_id":"mala-3bu9.15","depends_on_id":"mala-3bu9.5","type":"blocks","created_at":"2026-01-05T02:27:41.869868566Z","created_by":"cyou"}]}
{"id":"mala-3bu9.16","title":"[Review] 2 non-blocking findings from mala-3bu9.3","description":"## Review Findings\n\nThis issue consolidates 2 non-blocking findings from code review.\n\n**Source issue:** mala-3bu9.3\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Use original `cmd` type in `CommandResult` for unregistered commands\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** tests/fakes/command_runner.py:82\n\nWhen `allow_unregistered` is `True`, `_execute` returns a `CommandResult` where the `command` attribute is forced to a list via `list(cmd_tuple)`. If the original `cmd` was a string (e.g., a shell command), this changes the type from `str` to `list[str]`, which differs from the real `CommandRunner` behavior and may break tests asserting on the exact command value or type.\n\n---\n\n### Finding 2: [P2] Normalize prefix in helper methods to avoid silent matching failures\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** tests/fakes/command_runner.py:144-169\n\nThe `has_call_with_prefix` and `get_calls_with_prefix` methods expect a `tuple[str, ...]`. If a user passes a string or a list (standard for commands in this codebase), the prefix matching will fail silently (e.g., `(\"git\",) == [\"git\"]` is `False`). These helpers should use `_normalize_cmd` on the prefix to be robust and consistent with the `run` method.\n\n---\n\n\u003c!-- fp:36d80e3b1b3130f1 --\u003e\n\u003c!-- fp:b357d3211da32b68 --\u003e\n\u003c!-- review_finding:045a3a144933 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-05T02:09:22.896000091Z","created_by":"cyou","updated_at":"2026-01-05T02:13:49.510867133Z","closed_at":"2026-01-05T02:13:49.510867133Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-3bu9.3"],"dependencies":[{"issue_id":"mala-3bu9.16","depends_on_id":"mala-3bu9","type":"parent-child","created_at":"2026-01-05T02:09:22.896450008Z","created_by":"cyou"}]}
{"id":"mala-3bu9.17","title":"[Review] 1 non-blocking finding from mala-3bu9.2","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-3bu9.2\n**Highest priority:** P3\n\n---\n\n### Finding 1: [P3] Make xfail strict to avoid silent XPASS\n\n**Priority:** P3\n**Reviewer:** codex\n**Location:** tests/unit/pipeline/test_watch_mode.py:44-46\n\n`pytest.mark.xfail` is non-strict by default, so if watch mode sleep/interrupt/validation gets implemented and these tests start passing, they’ll show up as XPASS but typically won’t fail CI. That makes it easy to forget to remove the xfail and can hide real regressions later (the test still “doesn’t count”). Consider `@pytest.mark.xfail(strict=True, reason=...)` (same applies to the other two xfails in this file).\n\n---\n\n\u003c!-- fp:f1959e419064f821 --\u003e\n\u003c!-- review_finding:99f8fbd440f7 --\u003e","notes":"Quality gate failed: Quality gate failed: No commit with bd-mala-3bu9.17 found after run baseline (stale commits from previous runs are rejected)\nLog: /home/cyou/.claude/projects/-home-cyou-mala/68f53e26-347e-4bdb-9a69-4f1ed6c2b64b.jsonl","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-05T02:46:44.21813233Z","created_by":"cyou","updated_at":"2026-01-05T03:10:09.578963172Z","closed_at":"2026-01-05T03:10:09.578963172Z","close_reason":"Closed","labels":["auto_generated","needs-followup","review_finding","source:mala-3bu9.2"],"dependencies":[{"issue_id":"mala-3bu9.17","depends_on_id":"mala-3bu9","type":"parent-child","created_at":"2026-01-05T02:46:44.218505689Z","created_by":"cyou"}]}
{"id":"mala-3bu9.18","title":"[Review] 4 non-blocking findings from mala-3bu9.10","description":"## Review Findings\n\nThis issue consolidates 4 non-blocking findings from code review.\n\n**Source issue:** mala-3bu9.10\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Two tests still use subprocess.Popen patches instead of FakeCommandRunner\n\n**Priority:** P2\n**Reviewer:** claude\n**Location:** tests/unit/domain/test_validation.py:2028-2031\n\nThe tests `test_baseline_refresh_forces_cov_report_to_match_config_file` and `test_baseline_refresh_strips_explicit_cov_report_path` at lines 1937 and 2060 still patch `src.infra.tools.command_runner.subprocess.Popen` directly and use the real `CommandRunner` class. These should be converted to use `FakeCommandRunner` with dependency injection to be consistent with the migration goal and eliminate the remaining `with patch(...)` for subprocess mocking.\n\n---\n\n### Finding 2: [P3] CommandRunner import may be partially unused after migration\n\n**Priority:** P3\n**Reviewer:** claude\n**Location:** tests/unit/domain/test_validation.py:32\n\nThe `CommandRunner` import at line 32 is only used in two tests that still use the real implementation (`test_baseline_refresh_forces_cov_report_to_match_config_file` and `test_baseline_refresh_strips_explicit_cov_report_path`). If those tests are migrated to FakeCommandRunner, this import could be removed. Currently it's still needed, but worth noting for completeness.\n\n---\n\n### Finding 3: [P2] Assert mutex wrapper and pytest in same call (SpecValidationRunner)\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** tests/unit/domain/test_validation.py:534-536\n\n`has_call_containing(\"test-mutex.sh\")` and `has_call_containing(\"pytest\")` can pass even if those substrings appear in *different* calls (e.g., one command references the mutex script, another runs pytest without it). Prefer asserting on a single call containing both.\n\n```py\nassert any(\"test-mutex.sh\" in c[0] and \"pytest\" in c[0] for c, _ in fake_runner.calls)\n```\n\n---\n\n### Finding 4: [P2] Assert mutex wrapper and pytest in same call (SpecCommandExecutor)\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** tests/unit/domain/test_validation.py:2794-2796\n\nSame issue as above: separate substring checks can pass even if the mutex wrapper isn’t actually applied to the pytest invocation. Assert both substrings occur within the same recorded command call.\n\n```py\nassert any(\"test-mutex.sh\" in c[0] and \"pytest\" in c[0] for c, _ in fake_runner.calls)\n```\n\n---\n\n\u003c!-- fp:9bf03353bff9273e --\u003e\n\u003c!-- fp:009505526af52415 --\u003e\n\u003c!-- fp:ffc0944ffe80875d --\u003e\n\u003c!-- fp:d1576d35bfb1615e --\u003e\n\u003c!-- review_finding:59a76fceae85 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-05T03:32:15.834541188Z","created_by":"cyou","updated_at":"2026-01-05T03:38:13.317547915Z","closed_at":"2026-01-05T03:38:13.317547915Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-3bu9.10"],"dependencies":[{"issue_id":"mala-3bu9.18","depends_on_id":"mala-3bu9","type":"parent-child","created_at":"2026-01-05T03:32:15.834948736Z","created_by":"cyou"}]}
{"id":"mala-3bu9.19","title":"[Review] 2 non-blocking findings from mala-3bu9.18","description":"## Review Findings\n\nThis issue consolidates 2 non-blocking findings from code review.\n\n**Source issue:** mala-3bu9.18\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Don’t `join()` command args without `str()` coercion\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** tests/unit/domain/test_validation.py:534-538\n\nThe new mutex+pytest assertions do `\" \".join(c)` directly. If `FakeCommandRunner` ever records argv items as non-`str` (common with `pathlib.Path`), `join()` raises `TypeError`, making the test flaky/brittle.\n\nSafer pattern:\n```py\ncmd_str = \" \".join(map(str, c))\n```\nThen check substrings against `cmd_str` (and avoid joining twice).\n\n---\n\n### Finding 2: [P2] Same `join()` brittleness in SpecCommandExecutor mutex assertion\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** tests/unit/domain/test_validation.py:2794-2798\n\nSame issue as the earlier mutex assertion: `\" \".join(c)` assumes every argv element is a `str`. If any element is a `Path` (or similar), this fails with `TypeError`.\n\nUse:\n```py\ncmd_str = \" \".join(map(str, c))\n```\n\n---\n\n\u003c!-- fp:11b2b54a0c6080db --\u003e\n\u003c!-- fp:48f4e40cf35075b1 --\u003e\n\u003c!-- review_finding:ea85a15a73c3 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-05T03:38:13.35899046Z","created_by":"cyou","updated_at":"2026-01-05T04:05:50.273561324Z","closed_at":"2026-01-05T04:05:50.273561324Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-3bu9.18"],"dependencies":[{"issue_id":"mala-3bu9.19","depends_on_id":"mala-3bu9","type":"parent-child","created_at":"2026-01-05T03:38:13.359390248Z","created_by":"cyou"}]}
{"id":"mala-3bu9.2","title":"[T002] [integration-path-test] FakeIssueProvider skeleton + contract tests","description":"## Goal\nCreate FakeIssueProvider implementing the IssueProvider protocol with observable state, plus contract tests that validate fake-vs-real parity.\n\n**Note:** A partial FakeIssueProvider exists in `tests/unit/orchestration/test_orchestration_helpers.py:260`. This task creates the canonical version in `tests/fakes/` and may deprecate/consolidate the existing one.\n\n## Context\nFakeIssueProvider is the most critical fake - it replaces BeadsClient mocks in orchestrator tests. Contract tests ensure the fake doesn't diverge from real implementation behavior.\n\n## Scope\n**In:**\n- Create `tests/fakes/issue_provider.py` with FakeIssue dataclass and FakeIssueProvider class\n- Implement all ~15 IssueProvider protocol methods\n- Add observable state: `claimed`, `closed`, `reset_calls`, `created_issues`\n- Create `tests/contracts/` directory\n- Create `tests/contracts/test_issue_provider_contract.py` with skip-if prerequisites\n- **Consolidate/deprecate** existing partial fake in `test_orchestration_helpers.py`\n\n**Out:**\n- Rewriting existing tests to use the fake (separate tasks)\n\n## Changes\n- `tests/fakes/issue_provider.py` — New — FakeIssue dataclass + FakeIssueProvider implementing IssueProvider protocol\n- `tests/contracts/__init__.py` — New — Contracts module init\n- `tests/contracts/test_issue_provider_contract.py` — New — Contract tests with fake/real parametrization\n- `tests/unit/orchestration/test_orchestration_helpers.py` — Exists — Update to import from `tests/fakes/` or mark existing as deprecated\n\n## Acceptance Criteria\n- FakeIssueProvider implements all IssueProvider protocol methods\n- `uvx ty check` passes (protocol compliance verified)\n- Contract tests run with `pytest tests/contracts/test_issue_provider_contract.py`\n- Real provider tests skip gracefully when bd CLI or BEADS_TEST_WORKSPACE missing\n- No duplicate FakeIssueProvider definitions (consolidated)\n\n## Verification\n- `uvx ty check` — No type errors for FakeIssueProvider\n- `uv run pytest tests/contracts/test_issue_provider_contract.py -v` — Tests pass (fake param), skip (real param if no prerequisites)\n- `rg 'class FakeIssueProvider' tests/` — Only one definition in `tests/fakes/`\n\n## Notes for Agent\n- See plan for FakeIssueProvider design with observable state\n- Contract tests use `@pytest.mark.integration` marker\n- Skip conditions: `_has_bd_cli()` checks `shutil.which(\"bd\")`, `_has_beads_workspace()` checks env var\n- Existing partial fake at `tests/unit/orchestration/test_orchestration_helpers.py:260`","notes":"Quality gate failed: External review failed: internal_error\nLog: /home/cyou/.claude/projects/-home-cyou-mala/88f53ed8-7b99-4de8-ab9f-be51084a9d51.jsonl","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-05T01:44:26.552129562Z","created_by":"cyou","updated_at":"2026-01-05T02:46:44.180105769Z","closed_at":"2026-01-05T02:46:44.180105769Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-3bu9.2","depends_on_id":"mala-3bu9","type":"parent-child","created_at":"2026-01-05T01:44:26.560813609Z","created_by":"cyou"},{"issue_id":"mala-3bu9.2","depends_on_id":"mala-3bu9.1","type":"blocks","created_at":"2026-01-05T01:44:30.175767273Z","created_by":"cyou"}]}
{"id":"mala-3bu9.3","title":"[T003] FakeCommandRunner with fail-closed semantics","description":"## Goal\nCreate FakeCommandRunner implementing CommandRunnerPort with fail-closed semantics - unregistered commands raise errors by default.\n\n## Context\nFakeCommandRunner replaces patches of subprocess/command execution in validation tests. Fail-closed design prevents tests from silently passing when wrong commands are executed.\n\n## Scope\n**In:**\n- Create `tests/fakes/command_runner.py` with UnregisteredCommandError and FakeCommandRunner\n- Implement `run()` and `run_async()` methods with exact tuple matching\n- Add `allow_unregistered` escape hatch parameter\n- Add helper methods: `has_call_with_prefix()`, `get_calls_with_prefix()`\n- Observable state: `calls` list with (cmd_tuple, kwargs)\n\n**Out:**\n- Contract tests for CommandRunner (real impl uses subprocess, not practical to contract test)\n\n## Changes\n- `tests/fakes/command_runner.py` — New — UnregisteredCommandError + FakeCommandRunner\n\n## Acceptance Criteria\n- FakeCommandRunner raises UnregisteredCommandError for unregistered commands by default\n- `allow_unregistered=True` suppresses error and returns success result\n- Helper methods work correctly for prefix matching\n- `uvx ty check` passes\n\n## Verification\n- `uvx ty check` — No type errors\n- Write a quick test inline:\n  ```python\n  runner = FakeCommandRunner({(\"pytest\",): CommandResult([\"pytest\"], 0, \"\", \"\")})\n  runner.run([\"pytest\"])  # OK\n  runner.run([\"unknown\"])  # Raises UnregisteredCommandError\n  ```\n\n## Notes for Agent\n- See plan for exact FakeCommandRunner design\n- CommandResult import from `src.infra.tools.command_runner`","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-05T01:44:44.349299624Z","created_by":"cyou","updated_at":"2026-01-05T02:09:22.855263209Z","closed_at":"2026-01-05T02:09:22.855263209Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-3bu9.3","depends_on_id":"mala-3bu9","type":"parent-child","created_at":"2026-01-05T01:44:44.357645344Z","created_by":"cyou"},{"issue_id":"mala-3bu9.3","depends_on_id":"mala-3bu9.1","type":"blocks","created_at":"2026-01-05T01:44:47.874986081Z","created_by":"cyou"}]}
{"id":"mala-3bu9.4","title":"[T004] FakeLockManager and FakeEpicVerificationModel","description":"## Goal\nCreate FakeLockManager implementing LockManagerPort and FakeEpicVerificationModel implementing EpicVerificationModel, both with observable state.\n\n## Context\nThese fakes are smaller and can be combined into one task. FakeLockManager replaces lock patches, FakeEpicVerificationModel replaces epic verifier mocks with configurable verdict sequences.\n\n## Scope\n**In:**\n- Create `tests/fakes/lock_manager.py` with FakeLockManager\n- Create `tests/fakes/epic_model.py` with VerificationAttempt dataclass and FakeEpicVerificationModel\n- Observable state for FakeLockManager: `locks`, `acquire_calls`\n- Observable state for FakeEpicVerificationModel: `attempts` list (not private `_call_index`)\n\n**Out:**\n- Contract tests (simple enough that type checking is sufficient)\n\n## Changes\n- `tests/fakes/lock_manager.py` — New — FakeLockManager implementing LockManagerPort\n- `tests/fakes/epic_model.py` — New — VerificationAttempt + FakeEpicVerificationModel\n\n## Acceptance Criteria\n- FakeLockManager correctly tracks lock acquisition and prevents double-locking\n- FakeEpicVerificationModel returns configured verdict sequence\n- Observable `attempts` list records all verification calls with epic_id and verdict\n- `uvx ty check` passes for both fakes\n\n## Verification\n- `uvx ty check` — No type errors\n- Quick inline test for lock semantics and verdict sequencing\n\n## Notes for Agent\n- VerificationAttempt dataclass stores epic_id + verdict for observable assertions\n- FakeEpicVerificationModel.attempts is public (not _call_index) for behavioral assertions","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-05T01:45:01.039984747Z","created_by":"cyou","updated_at":"2026-01-05T02:11:23.012407308Z","closed_at":"2026-01-05T02:11:23.012407308Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-3bu9.4","depends_on_id":"mala-3bu9","type":"parent-child","created_at":"2026-01-05T01:45:01.04940815Z","created_by":"cyou"},{"issue_id":"mala-3bu9.4","depends_on_id":"mala-3bu9.1","type":"blocks","created_at":"2026-01-05T01:45:04.249173583Z","created_by":"cyou"}]}
{"id":"mala-3bu9.5","title":"[T005] FakeEventSink with completeness contract test","description":"## Goal\nCreate FakeEventSink implementing all ~50 MalaEventSink protocol methods explicitly, plus a contract test that detects protocol drift.\n\n## Context\nFakeEventSink is the largest fake (~50 methods). All methods must be explicitly implemented (no `__getattr__` fallback) for type safety. A completeness contract test ensures the fake stays in sync with the protocol.\n\n## Scope\n**In:**\n- Create `tests/fakes/event_sink.py` with FakeEventSink\n- Implement all MalaEventSink methods via `_record()` helper\n- Add assertion helpers: `has_event()`, `get_events()`\n- Create `tests/contracts/test_event_sink_completeness.py` with protocol sync test\n\n**Out:**\n- Behavioral contract tests for event semantics (covered by integration tests)\n\n## Changes\n- `tests/fakes/event_sink.py` — New — FakeEventSink implementing all MalaEventSink methods\n- `tests/contracts/test_event_sink_completeness.py` — New — Protocol completeness verification test\n\n## Acceptance Criteria\n- FakeEventSink implements all public methods of MalaEventSink protocol\n- `test_fake_event_sink_implements_all_protocol_methods` passes\n- `has_event()` and `get_events()` helpers work correctly\n- `uvx ty check` passes\n\n## Verification\n- `uvx ty check` — Protocol compliance verified\n- `uv run pytest tests/contracts/test_event_sink_completeness.py -v` — Completeness test passes\n\n## Notes for Agent\n- Generate method stubs from `src/core/protocols.py` MalaEventSink definition\n- Each method calls `self._record(event_type, **kwargs)`\n- Completeness test uses `inspect.getmembers()` to compare protocol vs fake methods","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-05T01:45:16.868817315Z","created_by":"cyou","updated_at":"2026-01-05T02:52:25.701519845Z","closed_at":"2026-01-05T02:52:25.701519845Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-3bu9.5","depends_on_id":"mala-3bu9","type":"parent-child","created_at":"2026-01-05T01:45:16.885619736Z","created_by":"cyou"},{"issue_id":"mala-3bu9.5","depends_on_id":"mala-3bu9.1","type":"blocks","created_at":"2026-01-05T01:45:20.372244083Z","created_by":"cyou"}]}
{"id":"mala-3bu9.6","title":"[T006] Add DI parameters to Orchestrator for runs_dir and lock_releaser","description":"## Goal\nAdd keyword-only constructor parameters to Orchestrator for dependency injection of runs_dir and lock_releaser, enabling tests to inject fakes instead of patching.\n\n## Context\nThe orchestrator currently hardcodes `get_runs_dir()` and `release_run_locks()`. Adding optional keyword-only parameters allows tests to inject fakes without patching module globals.\n\n## Scope\n**In:**\n- Add keyword-only `runs_dir: Path | None = None` parameter to Orchestrator.__init__\n- Add keyword-only `lock_releaser: Callable[[], None] | None = None` parameter\n- Default to current behavior when None\n- Verify no positional call sites exist before landing\n\n**Out:**\n- Rewriting tests (separate task)\n\n## Changes\n- `src/orchestration/orchestrator.py` — Exists — Add keyword-only constructor params with defaults\n\n## Acceptance Criteria\n- Orchestrator accepts optional `runs_dir` and `lock_releaser` keyword-only params\n- Defaults to existing behavior (get_runs_dir(), release_run_locks) when None\n- `rg 'Orchestrator\\([^)]*[^=,]\\s*\\)' src/ tests/` returns no positional arg violations\n- Existing tests pass unchanged\n\n## Verification\n- `rg 'Orchestrator\\([^)]*[^=,]\\s*\\)' src/ tests/` — No positional violations\n- `uvx ty check` — No type errors\n- `uv run pytest tests/unit/orchestration/ -v` — Existing tests pass\n\n## Notes for Agent\n- Parameters must be keyword-only (after `*,`) to avoid breaking existing calls\n- Pattern: `def __init__(self, existing_params, *, runs_dir: Path | None = None, ...)`","notes":"Quality gate failed: Quality gate failed: No commit with bd-mala-3bu9.6 found after run baseline (stale commits from previous runs are rejected)\nLog: /home/cyou/.claude/projects/-home-cyou-mala/032e4d56-3967-4922-a864-72f8f70fcd7e.jsonl","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-05T01:45:36.329962684Z","created_by":"cyou","updated_at":"2026-01-05T02:56:43.866053906Z","closed_at":"2026-01-05T02:56:43.866053906Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-3bu9.6","depends_on_id":"mala-3bu9","type":"parent-child","created_at":"2026-01-05T01:45:36.338540763Z","created_by":"cyou"}]}
{"id":"mala-3bu9.7","title":"[T007] Add DI parameter to SpecExecutor for command_runner","description":"## Goal\n**[UPDATE: Task simplified - DI seam already exists]**\n\nVerify that existing DI for `command_runner` in `SpecCommandExecutor` via `ExecutorConfig` works for test injection, and update any test helpers if needed.\n\n## Context\nSpecCommandExecutor already accepts `command_runner: CommandRunnerPort` via `ExecutorConfig` dataclass. The DI seam exists - this task verifies tests can use it.\n\n## Scope\n**In:**\n- Verify `ExecutorConfig.command_runner` accepts FakeCommandRunner\n- Update test fixtures/helpers if needed to construct ExecutorConfig with fake\n- Document the injection pattern for other tests\n\n**Out:**\n- Production code changes (none needed - DI exists)\n- Rewriting validation tests (separate task)\n\n## Changes\n- `tests/unit/domain/test_validation.py` — Exists — May need fixture updates\n- `tests/conftest.py` — Exists — May need shared fixture for ExecutorConfig with fake\n\n## Acceptance Criteria\n- FakeCommandRunner can be injected via ExecutorConfig\n- At least one test demonstrates the injection pattern\n- `uvx ty check` passes\n\n## Verification\n- `uv run pytest tests/unit/domain/test_validation*.py -v` — Tests pass\n- `uvx ty check` — No type errors\n\n## Notes for Agent\n- DI seam already exists at `src/domain/validation/spec_executor.py:52`:\n  ```python\n  command_runner: CommandRunnerPort\n  ```\n- Focus on test-side setup, not production changes","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-05T01:45:50.444442304Z","created_by":"cyou","updated_at":"2026-01-05T01:53:13.770573549Z","closed_at":"2026-01-05T01:53:13.770573549Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-3bu9.7","depends_on_id":"mala-3bu9","type":"parent-child","created_at":"2026-01-05T01:45:50.461529524Z","created_by":"cyou"}]}
{"id":"mala-3bu9.8","title":"[T008] Add DI parameter to EpicVerificationCoordinator for verification_model","description":"## Goal\n**[UPDATE: Target is EpicVerifier, not EpicVerificationCoordinator]**\n\nVerify that `EpicVerifier.__init__` accepts `model: EpicVerificationModel` for DI (it already does), and ensure test infrastructure can inject `FakeEpicVerificationModel`.\n\n## Context\n- `EpicVerificationCoordinator` uses callbacks, not direct model injection - it doesn't need changes\n- `EpicVerifier` in `src/infra/epic_verifier.py` already takes `model: EpicVerificationModel` in constructor\n- The DI seam exists - this task verifies tests can use it\n\n## Scope\n**In:**\n- Verify `EpicVerifier.__init__(model=...)` accepts FakeEpicVerificationModel\n- Update orchestrator factory or test helpers if needed to inject fake model\n- Document the injection pattern\n\n**Out:**\n- Production code changes (DI exists at `EpicVerifier`)\n- Changes to `EpicVerificationCoordinator` (uses callbacks)\n\n## Changes\n- `tests/unit/domain/test_epic_verification_retry.py` — Exists — May need fixture updates\n- `src/orchestration/factory.py` — Exists — Review how model is created (may need optional override)\n\n## Acceptance Criteria\n- FakeEpicVerificationModel can be injected into EpicVerifier\n- At least one test demonstrates the injection pattern\n- `uvx ty check` passes\n\n## Verification\n- `uv run pytest tests/unit/domain/test_epic*.py -v` — Tests pass\n- `uvx ty check` — No type errors\n\n## Notes for Agent\n- EpicVerifier already accepts `model: EpicVerificationModel` at line 370\n- EpicVerificationCoordinator uses callbacks (`verify_epic: Callable`) - no direct model access\n- May need to trace how the factory wires the model to find injection point","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-05T01:45:58.77993431Z","created_by":"cyou","updated_at":"2026-01-05T01:53:16.015334911Z","closed_at":"2026-01-05T01:53:16.015334911Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-3bu9.8","depends_on_id":"mala-3bu9","type":"parent-child","created_at":"2026-01-05T01:45:58.789479133Z","created_by":"cyou"}]}
{"id":"mala-3bu9.9","title":"[T009] Rewrite orchestrator tests to use fakes instead of patches","description":"## Goal\nRewrite orchestrator tests to use FakeIssueProvider and FakeEventSink instead of MagicMock and patch(), asserting on behavioral outcomes.\n\n## Context\ntest_orchestrator.py has 85 mocks and patches module globals like get_runs_dir and release_run_locks. This task migrates to behavioral tests using fakes.\n\n## Scope\n**In:**\n- Replace BeadsClient mocks with FakeIssueProvider\n- Replace event sink mocks with FakeEventSink\n- Replace patch(get_runs_dir) with DI via runs_dir param\n- Replace patch(release_run_locks) with DI via lock_releaser param\n- Convert assert_called* assertions to behavioral assertions\n- Delete tests that only verify call sequences\n\n**Out:**\n- Splitting into multiple files (optional, can do in cleanup phase)\n\n## Changes\n- `tests/unit/orchestration/test_orchestrator.py` — Exists — Rewrite to use fakes\n- `tests/unit/orchestration/test_orchestrator_state.py` — Exists — Verify no changes needed (already behavioral)\n\n## Acceptance Criteria\n- No `with patch(...)` in orchestrator tests\n- No `assert_called*` assertions\n- Tests assert on observable outcomes: `fake_issues.claimed`, `fake_events.has_event(...)`\n- Coverage maintained (--cov-fail-under=72)\n\n## Verification\n- `rg 'with patch|@patch' tests/unit/orchestration/` — Returns empty\n- `rg '\\.assert_called' tests/unit/orchestration/` — Returns empty\n- `uv run pytest tests/unit/orchestration/ --cov-fail-under=72` — Tests pass with coverage\n\n## Notes for Agent\n- Use make_orchestrator fixture with injected fakes\n- Behavioral assertions example: `assert \"issue-1\" in fake_issues.claimed`","notes":"Quality gate failed: Timeout after 60 minutes\nLog: /home/cyou/.claude/projects/-home-cyou-mala/fd760296-743f-40e8-a4ea-33b3dd9e5ffe.jsonl","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-05T01:46:13.180039525Z","created_by":"cyou","updated_at":"2026-01-05T04:26:19.821644453Z","closed_at":"2026-01-05T04:26:19.821644453Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-3bu9.9","depends_on_id":"mala-3bu9","type":"parent-child","created_at":"2026-01-05T01:46:13.187270485Z","created_by":"cyou"},{"issue_id":"mala-3bu9.9","depends_on_id":"mala-3bu9.2","type":"blocks","created_at":"2026-01-05T01:46:17.979405391Z","created_by":"cyou"},{"issue_id":"mala-3bu9.9","depends_on_id":"mala-3bu9.5","type":"blocks","created_at":"2026-01-05T01:46:17.998465788Z","created_by":"cyou"},{"issue_id":"mala-3bu9.9","depends_on_id":"mala-3bu9.6","type":"blocks","created_at":"2026-01-05T01:46:18.010375105Z","created_by":"cyou"}]}
{"id":"mala-3iqx","title":"pretty print deadlock log","status":"closed","priority":2,"issue_type":"feature","created_at":"2026-01-05T14:45:00.822321492Z","created_by":"cyou","updated_at":"2026-01-06T02:57:56.885099621Z","closed_at":"2026-01-06T02:57:56.885099621Z","close_reason":"Closed"}
{"id":"mala-3js","title":"Consolidate agent receive loop","description":"Context:\n- agent_loop.py duplicates the receive loop in MalaOrchestrator and is currently unused.\n- Divergent logic increases maintenance risk.\n\nScope:\n- In: consolidate to a single agent receive loop (either delete agent_loop.py or adopt it in orchestrator).\n- Out: changing logging format or agent behavior beyond consolidation.\n\nAcceptance Criteria:\n- Only one receive loop implementation remains.\n- Orchestrator behavior and logging remain consistent with current output.\n\nTest Plan:\n- TDD: if adopting agent_loop, add/adjust tests to cover loop integration; otherwise verify orchestrator tests still pass.\n\nNotes for Agent:\n- Keep JSONL and Braintrust logging order unchanged.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-26T17:36:04.305912715Z","updated_at":"2025-12-26T19:06:24.780657053Z","closed_at":"2025-12-26T19:06:24.780657053Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-3js","depends_on_id":"mala-jnq","type":"parent-child","created_at":"2025-12-26T17:36:15.734362257Z","created_by":"daemon"},{"issue_id":"mala-3js","depends_on_id":"mala-vdf","type":"blocks","created_at":"2025-12-26T17:36:21.629842741Z","created_by":"daemon"}]}
{"id":"mala-3l4b","title":"safe keyboard interrupt: remaining tasks finish, no new work picked up","status":"closed","priority":2,"issue_type":"feature","created_at":"2026-01-05T01:38:14.007129896Z","created_by":"cyou","updated_at":"2026-01-06T03:43:30.641686882Z","closed_at":"2026-01-06T03:43:30.641686882Z","close_reason":"Closed"}
{"id":"mala-3m7","title":"Deadlock detection between agents","description":"Detect deadlocks between agents (e.g., two agents waiting on each other's locks). Surface the deadlock and provide a resolution path (alert, timeout, or automatic recovery).","status":"closed","priority":4,"issue_type":"epic","created_at":"2025-12-26T00:55:42.798438926Z","updated_at":"2025-12-26T18:28:06.819113751Z","closed_at":"2025-12-26T18:28:06.819113751Z","close_reason":"Closed per user request"}
{"id":"mala-3qp","title":"Epic: Architecture review 2025-12-29 (bootstrap, boundaries, SDK coupling)","description":"## Context\nArchitecture review from 2025-12-29 using multi-model synthesis (Codex gpt-5.2-codex, Gemini gemini-3-flash-preview).\n\n## Key Findings\n\n### Critical\n1. **Bootstrap bypass** - SDK imports at module load defeat lazy-loading; 4 files need deferred imports\n\n### High Priority\n2. **Dual locking implementations** - Shell scripts and Python duplicate locking logic\n3. **CommandRunner boundary inversion** - Lives in validation/ but used by core modules\n4. **Logging depends on validation types** - run_metadata imports from validation.spec\n5. **SDK log coupling** - Hardcoded paths and schema knowledge spread across modules\n6. **Orchestrator monolith** - 1654 lines mixing policy, I/O, presentation\n\n### Medium Priority\n7. **Event sink not wired** (existing mala-7si.1)\n8. **SpecValidationRunner oversized** - 813 lines, mixed concerns\n9. **ValidationEvidence rigid** - Hardcoded booleans vs spec-driven\n10. **Duplicate validation runs** - Gate and metadata run validation separately\n\n### Low Priority\n11. **BeadsClient mixed concerns**\n12. **Duplicate dataclasses**\n\n## Execution Strategy\n1. Fix bootstrap bypass first (Critical, blocks clean testing)\n2. Boundary fixes can parallelize (CommandRunner move, shared types)\n3. Larger refactors (orchestrator decomposition, SDK abstraction) after boundaries clean\n\n## Related Epics\n- mala-869: Previous architecture work (closed issues for protocols, CommandRunner migrations)\n- mala-7si: Healthcheck cleanup (MalaConfig injection in progress)\n- mala-7si.1: EventSink injection (deferred P4)\n\n## Primary Files\n- src/orchestrator.py, src/__init__.py, src/hooks.py, src/braintrust_integration.py (bootstrap)\n- src/tools/locking.py, src/scripts/lock-*.sh (locking)\n- src/validation/command_runner.py (boundary)\n- src/logging/run_metadata.py, src/validation/spec.py (shared types)","status":"closed","priority":1,"issue_type":"epic","created_at":"2025-12-29T00:01:44.520404643Z","created_by":"cyou","updated_at":"2025-12-30T03:27:37.758139778Z","closed_at":"2025-12-30T03:27:37.758139778Z","close_reason":"All mala-3qp child issues are closed; removed healthcheck epic linkage to allow closure."}
{"id":"mala-3qp.1","title":"Defer SDK imports to fix bootstrap bypass (orchestrator, hooks, braintrust_integration)","description":"## Context\n- `src/orchestrator.py:12-20` imports `claude_agent_sdk` at module-level, but correct behavior depends on `cli.bootstrap()` running first to load env and patch Braintrust\n- CLI lazy-loading via `__getattr__` is defeated by multiple paths:\n  1. Direct import: `from src.orchestrator import MalaOrchestrator`\n  2. Package import: `src/__init__.py:3` re-exports MalaOrchestrator\n  3. Transitive via hooks: `src/hooks.py:12-17` imports `claude_agent_sdk.types`\n  4. Transitive via braintrust: `src/braintrust_integration.py:34-40` imports SDK types\n- This causes **silent failures**: Braintrust tracing won't work, environment variables may be missing\n\n## Primary Files\n- src/orchestrator.py:12-20 (defer SDK imports)\n- src/hooks.py:12-17 (defer SDK type imports)\n- src/braintrust_integration.py:34-40 (defer SDK imports)\n- src/__init__.py:3 (use __getattr__ for lazy export)\n\n## Scope\n**In:** Defer all `claude_agent_sdk` imports using TYPE_CHECKING guards and runtime imports\n**In:** Change `src/__init__.py` to use `__getattr__` for lazy MalaOrchestrator export\n**In:** Update type aliases in hooks.py to use string annotations\n**Out:** Changing CLI entry point structure\n**Out:** Removing Braintrust integration\n\n## Acceptance Criteria\n- `import src` does not import `claude_agent_sdk`\n- `from src.orchestrator import MalaOrchestrator` does not import SDK until bootstrap runs\n- `from src.hooks import ...` does not import SDK until bootstrap runs\n- All modules with SDK type hints use TYPE_CHECKING guards\n- Unit test demonstrates both CLI and direct-import paths work correctly\n- No silent failures when BRAINTRUST_API_KEY is set but bootstrap wasn't called\n\n## Test Plan\n- TDD: Add test that imports orchestrator directly and verifies SDK not loaded\n- Add test that imports src.hooks and verifies SDK not loaded\n- Test with and without BRAINTRUST_API_KEY set\n- Verify `import src` does not trigger SDK import\n\n## Notes for Agent\n- Use `if TYPE_CHECKING:` guard for type hints, move actual imports to method bodies\n- For hooks.py type aliases: `Callable[[\"PreToolUseHookInput\", ...], ...]` with string annotations\n- orchestrator.py imports hooks at module level, so hooks must be fixed too\n- Package __init__.py pattern:\n  ```python\n  def __getattr__(name: str):\n      if name == \"MalaOrchestrator\":\n          from .orchestrator import MalaOrchestrator\n          return MalaOrchestrator\n      raise AttributeError(f\"module {__name__!r} has no attribute {name!r}\")\n  ```","notes":"Quality gate failed: Timeout after 30 minutes\nLog: /home/cyou/.claude/projects/-home-cyou-mala/671bb8c9-9098-4bfa-97ae-0f8de3d811bf.jsonl","status":"closed","priority":0,"issue_type":"bug","created_at":"2025-12-29T00:02:07.754689932Z","created_by":"cyou","updated_at":"2025-12-29T04:12:37.639324189Z","closed_at":"2025-12-29T04:12:37.639324189Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-3qp.1","depends_on_id":"mala-3qp","type":"parent-child","created_at":"2025-12-29T00:02:07.762817184Z","created_by":"daemon"}]}
{"id":"mala-3qp.10","title":"Extract baseline refresh from SpecValidationRunner","description":"## Context\n- SpecValidationRunner is 813 lines combining worktree management, baseline coverage refresh, command execution, and E2E coordination\n- Baseline refresh logic (lines ~168-301) involves file-based locking, temporary worktrees, and specialized pytest flag overrides\n- This is a distinct sub-domain buried inside the validation runner\n- Confidence: High (from Gemini review)\n\n## Primary Files\n- src/validation/spec_runner.py:168-301 (baseline refresh logic to extract)\n- src/validation/coverage.py (existing, could host BaselineCoverageService)\n\n## Scope\n**In:** Extract baseline refresh into BaselineCoverageService\n**In:** Use composition in SpecValidationRunner to coordinate\n**Out:** Changing validation spec format\n**Out:** Modifying E2E test behavior\n\n## Acceptance Criteria\n- SpecValidationRunner focused strictly on executing ValidationSpec\n- Baseline refresh logic in separate module\n- Clear boundaries between worktree management and validation execution\n- Existing behavior unchanged\n\n## Test Plan\n- Unit test BaselineCoverageService with mocked CommandRunner\n- Test baseline refresh concurrently from two processes (locking behavior)\n- Run existing coverage tests\n\n## Notes for Agent\n- Baseline refresh is tightly integrated with validation flow\n- Extract as callable service rather than class hierarchy\n- Service should accept worktree path, return baseline coverage result\n- Keep locking behavior intact","notes":"Quality gate failed: Timeout after 30 minutes","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-29T00:04:49.799006251Z","created_by":"cyou","updated_at":"2025-12-29T04:37:11.978546758Z","closed_at":"2025-12-29T04:37:11.978546758Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-3qp.10","depends_on_id":"mala-3qp","type":"parent-child","created_at":"2025-12-29T00:04:49.808937494Z","created_by":"daemon"},{"issue_id":"mala-3qp.10","depends_on_id":"mala-3qp.3","type":"blocks","created_at":"2025-12-29T00:05:41.636059202Z","created_by":"daemon"}]}
{"id":"mala-3qp.11","title":"Extract sorting logic from BeadsClient to IssueManager","description":"## Context\n- BeadsClient contains complex issue sorting (`_sort_by_epic_groups`) and filtering logic\n- As a \"client,\" it should focus on I/O with the bd CLI\n- Domain-specific sorting and grouping should be in a higher-level service\n- Confidence: Medium (from Gemini review)\n\n## Primary Files\n- src/beads_client.py:91-180 (sorting logic to extract)\n- src/beads_client.py:344-770 (filtering logic)\n\n## Scope\n**In:** Move sorting and filtering logic to IssueManager or WorkloadManager service\n**In:** BeadsClient returns raw issue data\n**Out:** Changing bd CLI interface\n**Out:** Modifying issue data structures\n\n## Acceptance Criteria\n- BeadsClient methods focus on CRUD-like operations for issues\n- Sorting logic is pure and testable without subprocess mocking\n- Existing orchestrator behavior unchanged\n\n## Test Plan\n- Unit tests for IssueManager using fixture data\n- Verify sorting produces same order as before\n- Run existing beads_client tests\n\n## Notes for Agent\n- Lower priority (P3) - existing tests work\n- Consider if this is worth the abstraction overhead\n- May be good opportunity to simplify complex sorting logic","notes":"Quality gate failed: Codex review failed: src/beads_client.py:193: Scope gap: BeadsClient still filters WIP issues (`blocked_by`), and `enrich_with_epics_async` filters blocked epics. That keeps filtering logic in the client and returns non-raw issue data, so the “move filtering logic / BeadsClient returns raw issue data” scope item isn’t fully addressed.\nLog: /home/cyou/.claude/projects/-home-cyou-mala/56b47f4a-a187-41ff-9534-4e4c661f24e5.jsonl","status":"closed","priority":3,"issue_type":"chore","created_at":"2025-12-29T00:05:01.859430741Z","created_by":"cyou","updated_at":"2025-12-29T15:00:52.145545339Z","closed_at":"2025-12-29T15:00:52.145545339Z","close_reason":"IssueManager extracted to src/issue_manager.py; BeadsClient delegates sorting/filtering to IssueManager methods.","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-3qp.11","depends_on_id":"mala-3qp","type":"parent-child","created_at":"2025-12-29T00:05:01.869216455Z","created_by":"daemon"},{"issue_id":"mala-3qp.11","depends_on_id":"mala-3qp.3","type":"blocks","created_at":"2025-12-29T00:05:55.887086363Z","created_by":"daemon"}]}
{"id":"mala-3qp.12","title":"Epic: SpecValidationRunner decomposition into stages","description":"## Context\\n- SpecValidationRunner is oversized (~800+ lines) with mixed concerns (workspace setup, command execution, coverage, E2E, result assembly)\\n- Medium-confidence finding from 2025-12-29 architecture review (needs verification)\\n\\n## Goal\\nSplit SpecValidationRunner into focused, testable stages with explicit inputs/outputs while preserving behavior.\\n\\n## Proposed Stages\\n1. Workspace/Baseline setup (log dir, run_id, baseline refresh, worktree lifecycle)\\n2. Command execution (lint cache, step logging, command runner)\\n3. Post-processing (coverage/E2E checks, result assembly)\\n\\n## Acceptance Criteria\\n- SpecValidationRunner becomes a thin coordinator delegating to stage modules\\n- Each stage module has explicit input/output types and unit tests with fakes\\n- No behavioral regressions in validation results\\n\\n## Notes\\n- Land stages sequentially to minimize diff size\\n- Avoid changing validation policy or metadata format","status":"closed","priority":2,"issue_type":"epic","created_at":"2025-12-29T15:24:39.402065862Z","created_by":"cyou","updated_at":"2025-12-29T23:11:31.418794572Z","closed_at":"2025-12-29T23:11:31.418794572Z","close_reason":"All children completed","dependencies":[{"issue_id":"mala-3qp.12","depends_on_id":"mala-3qp","type":"parent-child","created_at":"2025-12-29T15:24:39.414571063Z","created_by":"daemon"}]}
{"id":"mala-3qp.12.1","title":"SpecValidationRunner: extract workspace/baseline/worktree setup","description":"## Context\n- SpecValidationRunner handles log dir setup, run_id/issue_id, baseline refresh, and worktree lifecycle inline\n- This makes workspace concerns hard to test and reuse\n- Needs verification (medium-confidence review finding)\n\n## Primary Files\n- src/validation/spec_runner.py\n- src/validation/spec_workspace.py (new)\n- tests/test_validation.py\n\n## Scope\n**In:** Move log_dir/run_id/issue_id generation, baseline refresh, and worktree setup/cleanup into a helper (e.g., SpecRunWorkspace)\n**In:** Return explicit context (validation_cwd, artifacts, baseline_percent) and cleanup hook\n**Out:** Changing validation command semantics or coverage policy\n\n## Acceptance Criteria\n- SpecValidationRunner delegates workspace/baseline/worktree setup to new helper module\n- Cleanup behavior (worktree keep/remove) remains unchanged\n- New helper has unit tests with fakes/mocks for baseline and worktree\n\n## Test Plan\n- Add focused unit test for workspace helper (TDD: test first, then implement)\n- Run `uv run pytest`\n\n## Notes for Agent\n- Keep behavior identical; refactor only\n- Prefer dataclass for returned workspace context\n","notes":"Quality gate failed: Codex review failed: codex exited with code 1: OpenAI Codex v0.77.0 (research preview)\n--------\nworkdir: /home/cyou/mala\nmodel: gpt-5.2-codex\nprovider: openai\napproval: never\nsandbox: workspace-write [workdir, /tmp, $TMPDIR]\nreasoning effort: high\nreasoning summaries: auto\nsession id: 019b6ade-accd-7563-b2b6-ec561d7e21b7\n--------\nuser\nReview the diff from commit 8665bba to b70821e (cumulative changes).\n\n## Issue Requirements\n\nTitle: SpecValidationRunner: extract workspace/baseline/worktree setup\n\n## Context\n- SpecValidationRunner handles log dir setup, run_id/issue_id, baseline refresh, and worktree lifecycle inline\n- This makes workspace concerns hard to test and reuse\n- Needs verification (medium-confidence review finding)\n\n## Primary Files\n- src/validation/spec_runner.py\n- src/validation/spec_workspace.py (new)\n- tests/test_validation.py\n\n## Scope\n**In:** Move log_dir/run_id/issue_id generation, baseline refresh, and worktree setup/cleanup into a helper (e.g., SpecRunWorkspace)\n**In:** Return explicit context (validation_cwd, artifacts, baseline_percent) and cleanup hook\n**Out:** Changing validation command semantics or coverage policy\n\n## Acceptance Criteria\n- SpecValidationRunner delegates workspace/baseline/worktree setup to new helper module\n- Cleanup behavior (worktree keep/remove) remains unchanged\n- New helper has unit tests with fakes/mocks for baseline and worktree\n\n## Test Plan\n- Add focused unit test for workspace helper (TDD: test first, then implement)\n- Run `uv run pytest`\n\n## Notes for Agent\n- Keep behavior identical; refactor only\n- Prefer dataclass for returned workspace context\n\n\n## Review Guidelines\n\nYou are acting as a reviewer for a proposed code change made by another engineer.\n\nBelow are some default guidelines for determining whether the original author would appreciate the issue being flagged.\n\nThese are not the final word in determining whether an issue is a bug. In many cases, you will encounter other, more specific guidelines. These may be present elsewhere in a developer message, a user message, a file, or even elsewhere in this system message.\nThose guidelines should be considered to override these general instructions.\n\nHere are the general guidelines for determining whether something is a bug and should be flagged.\n\n1. It meaningfully impacts the accuracy, performance, security, or maintainability of the code.\n2. The bug is discrete and actionable (i.e. not a general issue with the codebase or a combination of multiple issues).\n3. Fixing the bug does not demand a level of rigor that is not present in the rest of the codebase (e.g. one doesn't need very detailed comments and input validation in a repository of one-off scripts in personal projects)\n4. The bug was introduced in the commit (pre-existing bugs should not be flagged).\n5. The author of the original PR would likely fix the issue if they were made aware of it.\n6. The bug does not rely on unstated assumptions about the codebase or author's intent.\n7. It is not enough to speculate that a change may disrupt another part of the codebase, to be considered a bug, one must identify the other parts of the code that are provably affected.\n8. The bug is clearly not just an intentional change by the original author.\n\nWhen flagging a bug, you will also provide an accompanying comment. Once again, these guidelines are not the final word on how to construct a comment -- defer to any subsequent guidelines that you encounter.\n\n1. The comment should be clear about why the issue is a bug.\n2. The comment should appropriately communicate the severity of the issue. It should not claim that an issue is more severe than it actually is.\n3. The comment should be brief. The body should be at most 1 paragraph. It should not introduce line breaks within the natural language flow unless it is necessary for the code fragment.\n4. The comment should not include any chunks of code longer than 3 lines. Any code chunks should be wrapped in markdown inline code tags or a code block.\n5. The comment should clearly and explicitly communicate the scenarios, environments, or inputs that are necessary for the bug to arise. The comment should immediately indicate that the issue's severity depends on these factors.\n6. The comment's tone should be matter-of-fact and not accusatory or overly positive. It should read as a helpful AI assistant suggestion without sounding too much like a human reviewer.\n7. The comment should be written such that the original author can immediately grasp the idea without close reading.\n8. The comment should avoid excessive flattery and comments that are not helpful to the original author. The comment should avoid phrasing like \"Great job ...\", \"Thanks for ...\".\n\n## How Many Findings to Return\n\nOutput all findings that the original author would fix if they knew about it. If there is no finding that a person would definitely love to see and fix, prefer outputting no findings. Do not stop at the first qualifying finding. Continue until you've listed every qualifying finding.\n\n## Guidelines\n\n- Ignore trivial style unless it obscures meaning or violates documented standards.\n- Use one comment per distinct issue (or a multi-line range if necessary).\n- Always keep the line range as short as possible for interpreting the issue. Avoid ranges longer than 5-10 lines; instead, choose the most suitable subrange that pinpoints the problem.\n\n## Priority Tagging\n\nAt the beginning of the finding title, tag the bug with priority level. For example \"[P1] Un-padding slices along wrong tensor dimensions\".\n- [P0] - Drop everything to fix. Blocking release, operations, or major usage. Only use for universal issues that do not depend on any assumptions about the inputs.\n- [P1] - Urgent. Should be addressed in the next cycle.\n- [P2] - Normal. To be fixed eventually.\n- [P3] - Low. Nice to have.\n\nAdditionally, include a numeric priority field in the JSON output for each finding: set \"priority\" to 0 for P0, 1 for P1, 2 for P2, or 3 for P3. If a priority cannot be determined, omit the field or use null.\n\n## Overall Correctness Verdict\n\nAt the end of your findings, output an \"overall correctness\" verdict of whether or not the patch should be considered \"correct\".\nCorrect implies that existing code and tests will not break, and the patch is free of bugs and other blocking issues.\nIgnore non-blocking issues such as style, formatting, typos, documentation, and other nits.\n\n## Output Format\n\nOutput valid JSON matching the schema. Do not wrap the JSON in markdown fences or extra prose.\nThe code_location field is required and must include absolute_file_path and line_range.\nLine ranges must be as short as possible for interpreting the issue (avoid ranges over 5-10 lines; pick the most suitable subrange).\nThe code_location should overlap with the diff.\nDo not generate a PR fix.\n\nmcp startup: no servers\n2025-12-29T16:08:57.936412Z ERROR codex_api::endpoint::responses: error=http 400 Bad Request: Some(\"{\\n  \\\"error\\\": {\\n    \\\"message\\\": \\\"Invalid schema for response_format 'codex_output_schema': In context=('properties', 'findings', 'items'), 'required' is required to be supplied and to be an array including every key in properties. Missing 'priority'.\\\",\\n    \\\"type\\\": \\\"invalid_request_error\\\",\\n    \\\"param\\\": \\\"text.format.schema\\\",\\n    \\\"code\\\": \\\"invalid_json_schema\\\"\\n  }\\n}\")\nERROR: {\n  \"error\": {\n    \"message\": \"Invalid schema for response_format 'codex_output_schema': In context=('properties', 'findings', 'items'), 'required' is required to be supplied and to be an array including every key in properties. Missing 'priority'.\",\n    \"type\": \"invalid_request_error\",\n    \"param\": \"text.format.schema\",\n    \"code\": \"invalid_json_schema\"\n  }\n}\nWarning: no last agent message; wrote empty content to /tmp/tmpoermc6ff.json\nLog: /home/cyou/.claude/projects/-home-cyou-mala/d88d5392-a61c-46cd-81fd-158f0c45af76.jsonl","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-29T15:25:30.781286673Z","created_by":"cyou","updated_at":"2025-12-29T17:50:31.308893363Z","closed_at":"2025-12-29T17:50:31.308893363Z","close_reason":"SpecRunWorkspace helper added (src/validation/spec_workspace.py); SpecValidationRunner delegates setup/cleanup; unit tests in tests/test_spec_workspace.py.","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-3qp.12.1","depends_on_id":"mala-3qp.12","type":"parent-child","created_at":"2025-12-29T15:25:30.793756187Z","created_by":"daemon"}]}
{"id":"mala-3qp.12.2","title":"SpecValidationRunner: extract command execution into SpecCommandExecutor","description":"## Context\n- _run_commands in SpecValidationRunner handles lint cache checks, step execution, log file writing, and error handling\n- This mixes execution details with runner orchestration and is hard to unit-test in isolation\n- Needs verification (medium-confidence review finding)\n\n## Primary Files\n- src/validation/spec_runner.py\n- src/validation/spec_executor.py (new)\n- tests/test_validation.py\n\n## Scope\n**In:** Move command execution loop and lint-cache handling into SpecCommandExecutor (or similar)\n**In:** SpecValidationRunner delegates to executor and receives step results/errors\n**Out:** Changing command ordering, timeouts, or log formats\n\n## Acceptance Criteria\n- New executor module encapsulates command execution and lint cache logic\n- SpecValidationRunner calls executor instead of _run_commands\n- Lint-cache skipping behavior remains identical\n- Unit tests cover executor with a fake CommandRunner\n\n## Test Plan\n- Add/adjust tests for command execution and lint-cache skipping (TDD where feasible)\n- Run `uv run pytest`\n\n## Notes for Agent\n- Keep behavior identical; refactor only\n- Prefer small dataclass for executor inputs/outputs\n","notes":"Quality gate failed: Timeout after 30 minutes\nLog: /home/cyou/.claude/projects/-home-cyou-mala/f4b053e9-6212-46a8-a396-d2517cd902db.jsonl","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-29T15:25:45.839218866Z","created_by":"cyou","updated_at":"2025-12-29T22:54:17.740707744Z","closed_at":"2025-12-29T22:54:17.740707744Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-3qp.12.2","depends_on_id":"mala-3qp.12","type":"parent-child","created_at":"2025-12-29T15:25:45.850630989Z","created_by":"daemon"},{"issue_id":"mala-3qp.12.2","depends_on_id":"mala-3qp.12.1","type":"blocks","created_at":"2025-12-29T15:27:12.051212688Z","created_by":"daemon"}]}
{"id":"mala-3qp.12.3","title":"SpecValidationRunner: extract coverage/E2E/result assembly stage","description":"## Context\n- SpecValidationRunner handles coverage checks, E2E execution, and ValidationResult assembly inline\n- These post-processing steps are intertwined with orchestration logic\n- Needs verification (medium-confidence review finding)\n\n## Primary Files\n- src/validation/spec_runner.py\n- src/validation/spec_result_builder.py (new)\n- tests/test_validation.py\n\n## Scope\n**In:** Move coverage/E2E checks and ValidationResult assembly into a dedicated stage (e.g., SpecResultBuilder)\n**In:** SpecValidationRunner delegates post-processing and result creation to the new stage\n**Out:** Changing coverage/E2E policy or result schema\n\n## Acceptance Criteria\n- New result-builder module owns coverage/E2E handling and ValidationResult assembly\n- SpecValidationRunner uses the new stage and shrinks in size\n- Existing coverage/E2E tests continue to pass\n\n## Test Plan\n- Add/adjust unit tests for result-builder behaviors (TDD where feasible)\n- Run `uv run pytest`\n\n## Notes for Agent\n- Keep behavior identical; refactor only\n- Avoid altering ValidationResult/ValidationArtifacts schemas\n","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-29T15:26:00.306377261Z","created_by":"cyou","updated_at":"2025-12-29T23:11:31.402881255Z","closed_at":"2025-12-29T23:11:31.402881255Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-3qp.12.3","depends_on_id":"mala-3qp.12","type":"parent-child","created_at":"2025-12-29T15:26:00.318763116Z","created_by":"daemon"},{"issue_id":"mala-3qp.12.3","depends_on_id":"mala-3qp.12.2","type":"blocks","created_at":"2025-12-29T15:27:17.086009882Z","created_by":"daemon"}]}
{"id":"mala-3qp.2","title":"Unify locking: shell scripts delegate to Python implementation","description":"## Context\n- Locking is implemented twice: shell scripts in `src/scripts/lock-*.sh` (used by agents via Bash tool) and Python in `src/tools/locking.py` (used by hooks and validation)\n- Both use same lock file format (SHA-256 hash, hardlink-based atomic creation), but any behavioral divergence would cause subtle concurrency bugs\n- Changes must be synchronized manually between implementations\n- Confidence: High (from synthesis of Codex + Gemini review)\n\n## Primary Files\n- src/tools/locking.py (add CLI entry point)\n- src/scripts/lock-try.sh (delegate to Python)\n- src/scripts/lock-wait.sh (delegate to Python)\n- src/scripts/lock-check.sh (delegate to Python)\n- src/scripts/lock-holder.sh (delegate to Python)\n- src/scripts/lock-release.sh (delegate to Python)\n\n## Scope\n**In:** Add `if __name__ == \"__main__\"` CLI wrapper to locking.py with subcommands (try, wait, check, holder, release)\n**In:** Update shell scripts to be thin wrappers calling `python -m src.tools.locking \u003ccmd\u003e \u003cargs\u003e`\n**Out:** Changing lock file format\n**Out:** Removing shell script interface (agents need Bash-callable tools)\n\n## Acceptance Criteria\n- Single Python implementation of all locking logic\n- Shell scripts become 3-5 line wrappers\n- Unit tests cover Python implementation\n- Integration tests verify shell interface works\n- Lock interoperability between agent (shell) and validation (Python) verified\n\n## Test Plan\n- Add tests that exercise both shell and Python interfaces on same lock\n- Verify lock acquired via shell is visible to Python get_lock_holder()\n- Verify lock acquired via Python is visible to shell lock-holder.sh\n- Run existing lock tests to confirm no regression\n\n## Notes for Agent\n- Shell scripts in src/scripts/ are exposed to agents via MCP path configuration\n- Python functions used by spec_runner.py:318 for baseline locking and hooks.py:236 for lock enforcement\n- Trade-off: Shell-calls-Python adds ~50ms overhead per lock operation; negligible for infrequent lock ops\n- Keep LOCK_DIR and AGENT_ID env var interface for shell compatibility","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-29T00:02:27.739357349Z","created_by":"cyou","updated_at":"2025-12-29T02:48:41.435709157Z","closed_at":"2025-12-29T02:48:41.435709157Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-3qp.2","depends_on_id":"mala-3qp","type":"parent-child","created_at":"2025-12-29T00:02:27.747225103Z","created_by":"daemon"}]}
{"id":"mala-3qp.3","title":"Move CommandRunner from validation/ to tools/ (boundary fix)","description":"## Context\n- `CommandRunner` lives in `src/validation/command_runner.py` but is imported by core modules: beads_client.py, git_utils.py, codex_review.py, quality_gate.py\n- This inverts expected dependency direction: core/CLI should not depend on validation package\n- Validation package becomes grab-bag utility module rather than cohesive domain\n- Confidence: High (from synthesis of Codex + Gemini review)\n\n## Primary Files\n- src/validation/command_runner.py → src/tools/command_runner.py (move file)\n- src/beads_client.py:18 (update import)\n- src/git_utils.py:9 (update import)\n- src/codex_review.py:16 (update import)\n- src/quality_gate.py:22 (update import)\n- src/validation/spec_runner.py (update import)\n- src/validation/worktree.py (update import)\n\n## Scope\n**In:** Move command_runner.py to src/tools/\n**In:** Update all imports to use new location\n**In:** Add re-export from src/validation/command_runner.py for backwards compat (optional)\n**Out:** Changing CommandRunner's API\n**Out:** Refactoring validation logic\n\n## Acceptance Criteria\n- `src/validation/` contains only validation-specific code\n- Core modules import from `src/tools/`, not `src/validation/`\n- No circular import risks between layers\n- All tests pass\n\n## Test Plan\n- `uv run pytest` - all tests pass\n- Verify import graph has clean layering (tools → core → validation)\n- No import errors at runtime\n\n## Notes for Agent\n- This is a straightforward file move with import updates\n- Can be done incrementally: move file first, update imports, verify tests pass\n- ~6 files need import updates\n- Consider leaving a deprecation re-export in old location for external users\n\n## ⚠️ Potential Conflict\n**mala-869.7** (\"Migrate git_utils.py to use CommandRunner\") is in_progress with needs-followup label.\n- Check if mala-869.7 has uncommitted changes to git_utils.py\n- If mala-869.7 is stalled/abandoned, close it before proceeding\n- This issue will update git_utils.py imports anyway; the migration work in 869.7 may already be done or superseded","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-29T00:02:43.344581729Z","created_by":"cyou","updated_at":"2025-12-29T02:18:39.562498548Z","closed_at":"2025-12-29T02:18:39.562498548Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-3qp.3","depends_on_id":"mala-3qp","type":"parent-child","created_at":"2025-12-29T00:02:43.351337241Z","created_by":"daemon"}]}
{"id":"mala-3qp.4","title":"Extract shared types to src/models.py (IssueResolution, ValidationArtifacts)","description":"## Context\n- `src/logging/run_metadata.py:16-20` imports `IssueResolution`, `ResolutionOutcome`, `ValidationArtifacts` from `src.validation.spec`\n- This couples logging/infrastructure layer to validation domain\n- Makes logging unusable without validation, creates circular dependency risk\n- The `ValidationResult` dataclass in run_metadata.py embeds `ValidationArtifacts` directly\n- Confidence: High (from synthesis of Codex + Gemini review)\n\n## Primary Files\n- src/models.py (NEW - create with shared types)\n- src/validation/spec.py (move IssueResolution, ResolutionOutcome, ValidationArtifacts OUT)\n- src/logging/run_metadata.py (update imports to use models.py)\n- src/quality_gate.py (update imports if needed)\n- src/orchestrator.py (update imports if needed)\n\n## Scope\n**In:** Create src/models.py with shared domain-agnostic dataclasses\n**In:** Move IssueResolution, ResolutionOutcome, ValidationArtifacts to models.py\n**In:** Update imports in logging/, validation/, and orchestrator\n**Out:** Changing serialization format of run metadata\n**Out:** Removing validation tracking from metadata\n\n## Acceptance Criteria\n- `src/logging/` has no imports from `src/validation/`\n- Shared types live in neutral `src/models.py`\n- Run metadata can be imported without validation module\n- All tests pass\n\n## Test Plan\n- `uv run pytest` - all tests pass\n- Verify `from src.logging.run_metadata import RunMetadata` works without importing validation\n- Verify existing serialization still works (JSON roundtrip)\n\n## Notes for Agent\n- This also addresses \"duplicate dataclasses\" issue - models.py becomes single source of truth\n- Keep backward compat imports in validation/spec.py for external users:\n  ```python\n  from src.models import IssueResolution, ResolutionOutcome, ValidationArtifacts  # re-export\n  ```\n- Consider also moving duplicate RunConfig, QualityGateResult if they exist","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-29T00:02:59.302534216Z","created_by":"cyou","updated_at":"2025-12-29T02:20:33.087647412Z","closed_at":"2025-12-29T02:20:33.087647412Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-3qp.4","depends_on_id":"mala-3qp","type":"parent-child","created_at":"2025-12-29T00:02:59.310763898Z","created_by":"daemon"}]}
{"id":"mala-3qp.5","title":"Define LogProvider protocol to abstract SDK log storage/schema","description":"## Context\n- Orchestrator computes log paths via `get_claude_log_path()` which hardcodes SDK's internal file layout: `~/.claude/projects/{encoded-path}/{session_id}.jsonl`\n- `QualityGate` and `SessionLogParser` parse JSONL logs for evidence and resolution markers, depending on SDK's log schema\n- `log_events.py` defines types matching SDK's internal schema\n- Any SDK changes to log format or storage location would break gates silently\n- This coupling makes non-local execution (CI, remote agents) fragile\n- Confidence: High (from synthesis of Codex + Gemini review)\n\n## Primary Files\n- src/protocols.py (add LogProvider protocol)\n- src/session_log_parser.py (implement FileSystemLogProvider)\n- src/orchestrator.py (accept LogProvider, use instead of get_claude_log_path)\n- src/quality_gate.py (accept LogProvider)\n\n## Scope\n**In:** Define LogProvider protocol with methods: get_log_path(), iter_events(), get_end_offset()\n**In:** Implement FileSystemLogProvider wrapping current filesystem logic\n**In:** Inject LogProvider into orchestrator and quality_gate\n**Out:** Changing how agents write logs (SDK responsibility)\n**Out:** Supporting multiple SDK versions simultaneously\n\n## Acceptance Criteria\n- LogProvider protocol defined in src/protocols.py\n- FileSystemLogProvider implements current behavior\n- Orchestrator and QualityGate depend on LogProvider protocol, not filesystem paths\n- Tests can inject mock LogProvider without filesystem setup\n- Existing behavior unchanged\n\n## Test Plan\n- TDD: Add unit test with mock LogProvider returning synthetic events\n- Verify existing integration tests still work with FileSystemLogProvider\n- Test evidence detection with mock provider\n\n## Notes for Agent\n- `log_events.py` already provides typed wrappers - build on this\n- Start by extracting `get_claude_log_path` calls behind interface\n- LogProvider should be injectable like other protocols (GateChecker, CodeReviewer)\n- This enables future: remote log storage, SDK API access, test isolation","notes":"Quality gate failed: Timeout after 30 minutes\nLog: /home/cyou/.claude/projects/-home-cyou-mala/f024907b-cf3f-4b1b-8943-5ca9f1fb1792.jsonl","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-29T00:03:20.411029921Z","created_by":"cyou","updated_at":"2025-12-29T15:00:42.731986226Z","closed_at":"2025-12-29T15:00:42.731986226Z","close_reason":"Implemented LogProvider protocol in src/protocols.py; FileSystemLogProvider conforms and orchestrator/quality_gate accept injected LogProvider.","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-3qp.5","depends_on_id":"mala-3qp","type":"parent-child","created_at":"2025-12-29T00:03:20.420647464Z","created_by":"daemon"},{"issue_id":"mala-3qp.5","depends_on_id":"mala-3qp.1","type":"blocks","created_at":"2025-12-29T00:05:21.121733597Z","created_by":"daemon"}]}
{"id":"mala-3qp.6","title":"Epic: Orchestrator decomposition into pipeline stages","description":"## Context\n- `MalaOrchestrator` is 1654 lines mixing SDK streaming, prompt assembly, lock management, validation gating, code review, run metadata, and console output\n- `ImplementerLifecycle` state machine exists but most control flow still embedded in orchestrator\n- Methods `run_implementer` (~300 lines) and `run` (~300 lines) mix high-level orchestration with low-level details\n- Confidence: High (from synthesis of Codex + Gemini review)\n\n## Problem\n- Changes are risky due to tangled concerns\n- Blocks reuse in non-CLI contexts\n- Unit testing requires mocking everything\n\n## Proposed Solution\nDecompose into explicit pipeline stages with data-in/data-out boundaries:\n1. **AgentSessionRunner** - SDK streaming, agent lifecycle\n2. **GateRunner** - Gate 4 validation and fixer logic\n3. **ReviewRunner** - Code review orchestration\n4. **RunCoordinator** - High-level run loop\n\n## Subtasks (to be created)\n1. Extract GateRunner (clearest boundaries)\n2. Extract AgentSessionRunner\n3. Extract ReviewRunner\n4. Slim down orchestrator to coordinator\n\n## Trade-offs\n- Significant refactor with medium-term payoff\n- Rollout strategy: extract one stage at a time, run parallel paths during transition\n- Estimated 3-5 PRs to complete safely\n- Immediate benefit: each extracted stage becomes independently testable\n\n## Primary Files\n- src/orchestrator.py:217-1654\n\n## Acceptance Criteria\n- `run_implementer` and `run` reduced to \u003c100 lines each\n- Each stage module has explicit inputs/outputs\n- Unit tests can run pipeline with in-memory fakes without SDK\n- Orchestrator depends on interfaces for logging, gate, review, issue provider\n\n## Deferred\n- Rewriting lifecycle state machine\n- Changing SDK integration API\n\n[Added Context]\n- Preferred approach: incremental stage extraction (GateRunner → AgentSessionRunner → ReviewRunner → RunCoordinator) to keep behavior stable\n- New stage modules should live under src/pipeline/ with explicit input/output types\n- Land changes sequentially; add focused unit tests per stage with fakes (no SDK)\n","status":"closed","priority":2,"issue_type":"epic","created_at":"2025-12-29T00:03:39.199946884Z","created_by":"cyou","updated_at":"2025-12-29T23:45:15.968615908Z","closed_at":"2025-12-29T23:45:15.968615908Z","close_reason":"All children completed","dependencies":[{"issue_id":"mala-3qp.6","depends_on_id":"mala-3qp","type":"parent-child","created_at":"2025-12-29T00:03:39.207505311Z","created_by":"daemon"},{"issue_id":"mala-3qp.6","depends_on_id":"mala-3qp.1","type":"blocks","created_at":"2025-12-29T00:05:48.716972295Z","created_by":"daemon"},{"issue_id":"mala-3qp.6","depends_on_id":"mala-3qp.4","type":"blocks","created_at":"2025-12-29T00:05:48.749132981Z","created_by":"daemon"},{"issue_id":"mala-3qp.6","depends_on_id":"mala-3qp.5","type":"blocks","created_at":"2025-12-29T00:05:48.7702443Z","created_by":"daemon"}]}
{"id":"mala-3qp.6.1","title":"Orchestrator: extract GateRunner stage module","description":"## Context\n- Gate/fixer logic is embedded in MalaOrchestrator, mixing policy checks with orchestration\n- Extracting GateRunner is the clearest first step toward pipeline stages\n\n## Primary Files\n- src/orchestrator.py\n- src/pipeline/gate_runner.py (new)\n- tests/test_orchestrator.py\n\n## Scope\n**In:** Move gate+fixer loop (quality gate checks, retry decision flow) into GateRunner with explicit inputs/outputs\n**In:** Orchestrator delegates to GateRunner without changing behavior\n**Out:** Changing quality gate policy or validation spec semantics\n\n## Acceptance Criteria\n- GateRunner module exists with explicit input/output types\n- MalaOrchestrator calls GateRunner instead of inline logic\n- Existing behavior and logging remain unchanged\n- Unit tests cover GateRunner using in-memory fakes (no SDK)\n\n## Test Plan\n- Add focused unit tests for GateRunner (TDD where feasible)\n- Run `uv run pytest`\n\n## Notes for Agent\n- Keep behavior identical; refactor only\n- Avoid touching quality_gate.py unless strictly required\n","notes":"Quality gate failed: Codex review failed: codex exited with code 1: OpenAI Codex v0.77.0 (research preview)\n--------\nworkdir: /home/cyou/mala\nmodel: gpt-5.2-codex\nprovider: openai\napproval: never\nsandbox: workspace-write [workdir, /tmp, $TMPDIR]\nreasoning effort: high\nreasoning summaries: auto\nsession id: 019b6ad1-feac-79b3-a737-c9434a994b38\n--------\nuser\nReview the diff from commit 7c7350b to 8513054 (cumulative changes).\n\n## Issue Requirements\n\nTitle: Orchestrator: extract GateRunner stage module\n\n## Context\n- Gate/fixer logic is embedded in MalaOrchestrator, mixing policy checks with orchestration\n- Extracting GateRunner is the clearest first step toward pipeline stages\n\n## Primary Files\n- src/orchestrator.py\n- src/pipeline/gate_runner.py (new)\n- tests/test_orchestrator.py\n\n## Scope\n**In:** Move gate+fixer loop (quality gate checks, retry decision flow) into GateRunner with explicit inputs/outputs\n**In:** Orchestrator delegates to GateRunner without changing behavior\n**Out:** Changing quality gate policy or validation spec semantics\n\n## Acceptance Criteria\n- GateRunner module exists with explicit input/output types\n- MalaOrchestrator calls GateRunner instead of inline logic\n- Existing behavior and logging remain unchanged\n- Unit tests cover GateRunner using in-memory fakes (no SDK)\n\n## Test Plan\n- Add focused unit tests for GateRunner (TDD where feasible)\n- Run `uv run pytest`\n\n## Notes for Agent\n- Keep behavior identical; refactor only\n- Avoid touching quality_gate.py unless strictly required\n\n\n## Review Guidelines\n\nYou are acting as a reviewer for a proposed code change made by another engineer.\n\nBelow are some default guidelines for determining whether the original author would appreciate the issue being flagged.\n\nThese are not the final word in determining whether an issue is a bug. In many cases, you will encounter other, more specific guidelines. These may be present elsewhere in a developer message, a user message, a file, or even elsewhere in this system message.\nThose guidelines should be considered to override these general instructions.\n\nHere are the general guidelines for determining whether something is a bug and should be flagged.\n\n1. It meaningfully impacts the accuracy, performance, security, or maintainability of the code.\n2. The bug is discrete and actionable (i.e. not a general issue with the codebase or a combination of multiple issues).\n3. Fixing the bug does not demand a level of rigor that is not present in the rest of the codebase (e.g. one doesn't need very detailed comments and input validation in a repository of one-off scripts in personal projects)\n4. The bug was introduced in the commit (pre-existing bugs should not be flagged).\n5. The author of the original PR would likely fix the issue if they were made aware of it.\n6. The bug does not rely on unstated assumptions about the codebase or author's intent.\n7. It is not enough to speculate that a change may disrupt another part of the codebase, to be considered a bug, one must identify the other parts of the code that are provably affected.\n8. The bug is clearly not just an intentional change by the original author.\n\nWhen flagging a bug, you will also provide an accompanying comment. Once again, these guidelines are not the final word on how to construct a comment -- defer to any subsequent guidelines that you encounter.\n\n1. The comment should be clear about why the issue is a bug.\n2. The comment should appropriately communicate the severity of the issue. It should not claim that an issue is more severe than it actually is.\n3. The comment should be brief. The body should be at most 1 paragraph. It should not introduce line breaks within the natural language flow unless it is necessary for the code fragment.\n4. The comment should not include any chunks of code longer than 3 lines. Any code chunks should be wrapped in markdown inline code tags or a code block.\n5. The comment should clearly and explicitly communicate the scenarios, environments, or inputs that are necessary for the bug to arise. The comment should immediately indicate that the issue's severity depends on these factors.\n6. The comment's tone should be matter-of-fact and not accusatory or overly positive. It should read as a helpful AI assistant suggestion without sounding too much like a human reviewer.\n7. The comment should be written such that the original author can immediately grasp the idea without close reading.\n8. The comment should avoid excessive flattery and comments that are not helpful to the original author. The comment should avoid phrasing like \"Great job ...\", \"Thanks for ...\".\n\n## How Many Findings to Return\n\nOutput all findings that the original author would fix if they knew about it. If there is no finding that a person would definitely love to see and fix, prefer outputting no findings. Do not stop at the first qualifying finding. Continue until you've listed every qualifying finding.\n\n## Guidelines\n\n- Ignore trivial style unless it obscures meaning or violates documented standards.\n- Use one comment per distinct issue (or a multi-line range if necessary).\n- Always keep the line range as short as possible for interpreting the issue. Avoid ranges longer than 5-10 lines; instead, choose the most suitable subrange that pinpoints the problem.\n\n## Priority Tagging\n\nAt the beginning of the finding title, tag the bug with priority level. For example \"[P1] Un-padding slices along wrong tensor dimensions\".\n- [P0] - Drop everything to fix. Blocking release, operations, or major usage. Only use for universal issues that do not depend on any assumptions about the inputs.\n- [P1] - Urgent. Should be addressed in the next cycle.\n- [P2] - Normal. To be fixed eventually.\n- [P3] - Low. Nice to have.\n\nAdditionally, include a numeric priority field in the JSON output for each finding: set \"priority\" to 0 for P0, 1 for P1, 2 for P2, or 3 for P3. If a priority cannot be determined, omit the field or use null.\n\n## Overall Correctness Verdict\n\nAt the end of your findings, output an \"overall correctness\" verdict of whether or not the patch should be considered \"correct\".\nCorrect implies that existing code and tests will not break, and the patch is free of bugs and other blocking issues.\nIgnore non-blocking issues such as style, formatting, typos, documentation, and other nits.\n\n## Output Format\n\nOutput valid JSON matching the schema. Do not wrap the JSON in markdown fences or extra prose.\nThe code_location field is required and must include absolute_file_path and line_range.\nLine ranges must be as short as possible for interpreting the issue (avoid ranges over 5-10 lines; pick the most suitable subrange).\nThe code_location should overlap with the diff.\nDo not generate a PR fix.\n\nmcp startup: no servers\n2025-12-29T15:55:06.844487Z ERROR codex_api::endpoint::responses: error=http 400 Bad Request: Some(\"{\\n  \\\"error\\\": {\\n    \\\"message\\\": \\\"Invalid schema for response_format 'codex_output_schema': In context=('properties', 'findings', 'items'), 'required' is required to be supplied and to be an array including every key in properties. Missing 'priority'.\\\",\\n    \\\"type\\\": \\\"invalid_request_error\\\",\\n    \\\"param\\\": \\\"text.format.schema\\\",\\n    \\\"code\\\": \\\"invalid_json_schema\\\"\\n  }\\n}\")\nERROR: {\n  \"error\": {\n    \"message\": \"Invalid schema for response_format 'codex_output_schema': In context=('properties', 'findings', 'items'), 'required' is required to be supplied and to be an array including every key in properties. Missing 'priority'.\",\n    \"type\": \"invalid_request_error\",\n    \"param\": \"text.format.schema\",\n    \"code\": \"invalid_json_schema\"\n  }\n}\nWarning: no last agent message; wrote empty content to /tmp/tmp2al3t8_2.json\nLog: /home/cyou/.claude/projects/-home-cyou-mala/b3a929fe-6c9d-4c21-814a-1e9714c788c0.jsonl","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-29T15:26:15.137087475Z","created_by":"cyou","updated_at":"2025-12-29T17:50:21.484442689Z","closed_at":"2025-12-29T17:50:21.484442689Z","close_reason":"GateRunner extracted (src/pipeline/gate_runner.py), orchestrator delegates to it, and unit tests added (tests/test_gate_runner.py).","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-3qp.6.1","depends_on_id":"mala-3qp.6","type":"parent-child","created_at":"2025-12-29T15:26:15.149362962Z","created_by":"daemon"}]}
{"id":"mala-3qp.6.2","title":"Orchestrator: extract AgentSessionRunner stage module","description":"## Context\n- SDK streaming + ImplementerLifecycle control flow is embedded in MalaOrchestrator\n- Extracting AgentSessionRunner isolates SDK-specific logic and improves testability\n\n## Primary Files\n- src/orchestrator.py\n- src/pipeline/agent_session_runner.py (new)\n- tests/test_orchestrator.py\n\n## Scope\n**In:** Move agent session execution (SDK streaming, lifecycle handling, session metadata) into AgentSessionRunner\n**In:** Orchestrator delegates to AgentSessionRunner via explicit input/output types\n**Out:** Changing SDK integration API or lifecycle semantics\n\n## Acceptance Criteria\n- AgentSessionRunner module exists with clear inputs/outputs\n- MalaOrchestrator uses AgentSessionRunner instead of inline session logic\n- Unit tests can run AgentSessionRunner with faked SDK client\n\n## Test Plan\n- Add unit tests for AgentSessionRunner using fake SDK client (TDD where feasible)\n- Run `uv run pytest`\n\n## Notes for Agent\n- Keep behavior identical; refactor only\n- Avoid touching SDK client implementation\n","notes":"Quality gate failed: Timeout after 30 minutes\nLog: /home/cyou/.claude/projects/-home-cyou-mala/69e60452-1606-471b-8f81-6c61c5cac30a.jsonl","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-29T15:26:29.652296765Z","created_by":"cyou","updated_at":"2025-12-29T22:52:01.170506785Z","closed_at":"2025-12-29T22:52:01.170506785Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-3qp.6.2","depends_on_id":"mala-3qp.6","type":"parent-child","created_at":"2025-12-29T15:26:29.664341234Z","created_by":"daemon"},{"issue_id":"mala-3qp.6.2","depends_on_id":"mala-3qp.6.1","type":"blocks","created_at":"2025-12-29T15:27:22.126333171Z","created_by":"daemon"}]}
{"id":"mala-3qp.6.3","title":"Orchestrator: extract ReviewRunner stage module","description":"## Context\n- Code review orchestration (codex review invocation, retry decisions, logging) is embedded in MalaOrchestrator\n- Extracting ReviewRunner isolates review behavior and enables testing without full orchestration\n\n## Primary Files\n- src/orchestrator.py\n- src/pipeline/review_runner.py (new)\n- tests/test_orchestrator.py\n\n## Scope\n**In:** Move code review orchestration into ReviewRunner with explicit inputs/outputs\n**In:** Orchestrator delegates review stage to ReviewRunner\n**Out:** Changing review prompt content or review policy\n\n## Acceptance Criteria\n- ReviewRunner module exists with explicit input/output types\n- MalaOrchestrator uses ReviewRunner instead of inline review logic\n- ReviewRunner has unit tests with faked reviewer\n\n## Test Plan\n- Add focused unit tests for ReviewRunner (TDD where feasible)\n- Run `uv run pytest`\n\n## Notes for Agent\n- Keep behavior identical; refactor only\n- Avoid modifying codex_review.py unless required for injection\n","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-29T15:26:44.525725408Z","created_by":"cyou","updated_at":"2025-12-29T23:09:17.888212175Z","closed_at":"2025-12-29T23:09:17.888212175Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-3qp.6.3","depends_on_id":"mala-3qp.6","type":"parent-child","created_at":"2025-12-29T15:26:44.541774904Z","created_by":"daemon"},{"issue_id":"mala-3qp.6.3","depends_on_id":"mala-3qp.6.2","type":"blocks","created_at":"2025-12-29T15:27:27.16217299Z","created_by":"daemon"}]}
{"id":"mala-3qp.6.4","title":"Orchestrator: slim coordinator and reduce run methods","description":"## Context\n- After stage extractions, MalaOrchestrator should be a thin coordinator with minimal logic\n- Acceptance criteria require run_implementer/run to be \u003c100 LOC each\n\n## Primary Files\n- src/orchestrator.py\n- src/pipeline/run_coordinator.py (new)\n- tests/test_orchestrator.py\n\n## Scope\n**In:** Slim orchestrator to a coordinator that wires stage modules together\n**In:** Ensure run_implementer and run methods are each \u003c100 LOC\n**Out:** Rewriting ImplementerLifecycle or changing external CLI behavior\n\n## Acceptance Criteria\n- run_implementer and run are each \u003c100 LOC\n- Orchestrator primarily delegates to pipeline stage modules\n- Tests continue to pass without SDK\n\n## Test Plan\n- Run `uv run pytest`\n- Add/adjust orchestrator tests to cover coordinator wiring if needed\n\n## Notes for Agent\n- Keep behavior identical; refactor only\n- Prefer simple data flow objects between stages\n","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-29T15:26:58.720812406Z","created_by":"cyou","updated_at":"2025-12-29T23:45:15.95313893Z","closed_at":"2025-12-29T23:45:15.95313893Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-3qp.6.4","depends_on_id":"mala-3qp.6","type":"parent-child","created_at":"2025-12-29T15:26:58.733794589Z","created_by":"daemon"},{"issue_id":"mala-3qp.6.4","depends_on_id":"mala-3qp.6.3","type":"blocks","created_at":"2025-12-29T15:27:32.20394556Z","created_by":"daemon"}]}
{"id":"mala-3qp.7","title":"Make ValidationEvidence spec-driven (replace hardcoded booleans)","description":"## Context\n- `ValidationEvidence` in quality_gate.py:54-94 hardcodes pytest_ran, ruff_check_ran, ty_check_ran booleans\n- `ValidationSpec` in validation/spec.py supports arbitrary commands and repo types\n- Adding a new validation command requires edits across evidence parsing, mapping, and metadata\n- This creates drift risk between spec and evidence models\n- Confidence: Medium (from synthesis of Codex + Gemini review)\n\n## Primary Files\n- src/quality_gate.py:54-94 (ValidationEvidence dataclass)\n- src/quality_gate.py:355-450 (parse_validation_evidence_with_spec)\n- src/validation/spec.py:46-120 (ValidationSpec, CommandKind)\n\n## Scope\n**In:** Replace boolean flags with dict keyed by CommandKind or command name\n**In:** Drive evidence parsing entirely from ValidationSpec\n**In:** Update quality gate and metadata to use new structure\n**Out:** Changing how agents report validation results\n**Out:** Modifying bd CLI integration\n\n## Acceptance Criteria\n- Evidence supports any ValidationCommand without code changes\n- Quality gate and metadata use same evidence structure\n- No tool-specific boolean flags in core data structures\n- Existing detection patterns still work\n\n## Test Plan\n- TDD: Add test with custom command in spec, verify evidence detection\n- Verify existing pytest/ruff/ty detection still works\n- Run full test suite\n\n## Notes for Agent\n- Evidence structure could be: `evidence: dict[str, bool]` where key is command name\n- Or use CommandKind enum as key: `evidence: dict[CommandKind, bool]`\n- Keep backward compat for any external consumers of ValidationEvidence\n\n## Current State (2025-12-29)\n- ValidationEvidence still exposes pytest/ruff/ty boolean properties in `src/quality_gate.py`.\n- Orchestrator still maps `evidence.pytest_ran`/`ruff_check_ran`/`ty_check_ran` into metadata in `src/orchestrator.py`.\n- No dict-backed, spec-driven evidence structure implemented yet.\n\n## Follow-up Work\n- Replace boolean flags with dict keyed by command name or CommandKind.\n- Update `parse_validation_evidence_with_spec` and downstream metadata mapping.\n- Adjust protocols/typing for new evidence shape.\n- Add tests for custom command + existing pytest/ruff/ty detection.\n","notes":"FOLLOW-UP: ValidationEvidence still has hardcoded booleans (pytest_ran/ruff_check_ran/ty_check_ran) in src/quality_gate.py, and orchestrator still maps evidence.* into metadata. No spec-driven dict structure yet. Remaining work: replace booleans with dict keyed by command name/CommandKind, update parse_validation_evidence_with_spec and orchestrator metadata mapping, adjust protocols, and add tests for custom command + existing pytest/ruff/ty detection.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-29T00:04:07.07610822Z","created_by":"cyou","updated_at":"2025-12-29T18:55:21.481989301Z","closed_at":"2025-12-29T18:55:21.481989301Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-3qp.7","depends_on_id":"mala-3qp","type":"parent-child","created_at":"2025-12-29T00:04:07.076587296Z","created_by":"daemon"},{"issue_id":"mala-3qp.7","depends_on_id":"mala-3qp.5","type":"blocks","created_at":"2025-12-29T00:05:33.825131445Z","created_by":"daemon"},{"issue_id":"mala-3qp.7","depends_on_id":"mala-3qp.4","type":"blocks","created_at":"2025-12-29T00:11:19.05037917Z","created_by":"daemon"}]}
{"id":"mala-3qp.8","title":"Eliminate duplicate validation runs between gate and metadata","description":"## Context\n- Gate checks evidence from logs via QualityGate.parse_validation_evidence_with_spec()\n- After success, run() re-runs SpecValidationRunner to populate metadata\n- Run-level validation adds another layer\n- This duplicates cost and spreads validation policy across modules\n- Risk of inconsistent outcomes between gate decision and metadata\n- Confidence: Medium (from synthesis of Codex + Gemini review)\n\n## Primary Files\n- src/orchestrator.py:1360-1420 (metadata population after success)\n- src/quality_gate.py:538 (check_with_resolution)\n- src/validation/spec_runner.py:33 (SpecValidationRunner)\n- src/logging/run_metadata.py:21 (ValidationResult storage)\n\n## Scope\n**In:** Make validation a single explicit stage with one result object\n**In:** Gate decisions and metadata derive from same result object\n**Out:** Removing run-level validation entirely\n**Out:** Changing metadata format\n\n## Acceptance Criteria\n- Per-issue validation executes at most once per attempt\n- Gate decisions and metadata derive from same result object\n- Clear single point of validation policy\n- No duplicate subprocess calls for same validation\n\n## Test Plan\n- TDD: Add test asserting metadata and gate share same validation result\n- Verify gate decision matches metadata record\n- Performance: confirm validation runs once not twice\n\n## Notes for Agent\n- Key insight: validation evidence from logs IS the validation result\n- Don't need to re-run validation if gate already parsed evidence\n- Consider: store evidence in ValidationResult, use for both gate and metadata","notes":"Quality gate failed: Timeout after 30 minutes\nLog: /home/cyou/.claude/projects/-home-cyou-mala/742a0e24-0f70-4859-aa43-903ddbead197.jsonl","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-29T00:04:21.457052159Z","created_by":"cyou","updated_at":"2025-12-30T03:16:03.606698618Z","closed_at":"2025-12-30T03:16:03.606698618Z","close_reason":"Implemented reuse of per-issue gate validation evidence for metadata; orchestrator stores GateResult and derives ValidationResult without rerunning validation. Added tests in tests/test_orchestrator.py covering shared evidence + metadata population.","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-3qp.8","depends_on_id":"mala-3qp","type":"parent-child","created_at":"2025-12-29T00:04:21.466605874Z","created_by":"daemon"},{"issue_id":"mala-3qp.8","depends_on_id":"mala-3qp.1","type":"blocks","created_at":"2025-12-29T00:05:26.548179816Z","created_by":"daemon"},{"issue_id":"mala-3qp.8","depends_on_id":"mala-3qp.4","type":"blocks","created_at":"2025-12-29T00:11:19.121014389Z","created_by":"daemon"},{"issue_id":"mala-3qp.8","depends_on_id":"mala-3qp.5","type":"blocks","created_at":"2025-12-29T00:11:19.21209636Z","created_by":"daemon"},{"issue_id":"mala-3qp.8","depends_on_id":"mala-3qp.7","type":"blocks","created_at":"2025-12-29T00:11:19.320011777Z","created_by":"daemon"},{"issue_id":"mala-3qp.8","depends_on_id":"mala-3qp.12.3","type":"blocks","created_at":"2025-12-29T15:27:37.243921275Z","created_by":"daemon"},{"issue_id":"mala-3qp.8","depends_on_id":"mala-3qp.6.4","type":"blocks","created_at":"2025-12-29T15:27:42.278278965Z","created_by":"daemon"}]}
{"id":"mala-3qp.9","title":"Route all subprocess calls through CommandRunner","description":"## Context\n- `CommandRunner` standardizes timeouts and process-group termination\n- Several modules still use `subprocess.run` directly:\n  - src/validation/helpers.py:9-65 (fixture setup)\n  - src/hooks.py:274 (hook execution)\n- These calls bypass timeout policy and complicate testing\n- Confidence: Medium (from Codex review)\n\n## Primary Files\n- src/validation/helpers.py:9-65 (migrate to CommandRunner)\n- src/hooks.py:274 (migrate to CommandRunner)\n\n## Scope\n**In:** Replace direct subprocess.run calls with CommandRunner\n**In:** Add appropriate timeout configuration\n**Out:** Changing timeout values\n**Out:** Adding new subprocess features\n\n## Acceptance Criteria\n- No direct `subprocess.run` calls in validation or hook paths\n- Subprocess behavior consistent across modules\n- Tests can mock CommandRunner for isolation\n\n## Test Plan\n- `uv run pytest` - all tests pass\n- Add tests that mock executor and assert timeout behavior\n- Verify hook subprocess calls respect timeouts\n\n## Notes for Agent\n- CommandRunner may need to move to tools/ first (see mala-3qp.3)\n- If mala-3qp.3 not done yet, import from current location\n- Consider if hooks.py subprocess needs different timeout than default","notes":"Quality gate failed: Codex review failed: codex exited with code 1: OpenAI Codex v0.77.0 (research preview)\n--------\nworkdir: /home/cyou/mala\nmodel: gpt-5.2-codex\nprovider: openai\napproval: never\nsandbox: workspace-write [workdir, /tmp, $TMPDIR]\nreasoning effort: high\nreasoning summaries: auto\nsession id: 019b6ad4-6ff2-7fb1-a28f-a141ec6114f8\n--------\nuser\nReview the diff from commit 6df9d7b3bd9f849faf1599ee3179e10244f0d6db to 8665bba (cumulative changes).\n\n## Issue Requirements\n\nTitle: Route all subprocess calls through CommandRunner\n\n## Context\n- `CommandRunner` standardizes timeouts and process-group termination\n- Several modules still use `subprocess.run` directly:\n  - src/validation/helpers.py:9-65 (fixture setup)\n  - src/hooks.py:274 (hook execution)\n- These calls bypass timeout policy and complicate testing\n- Confidence: Medium (from Codex review)\n\n## Primary Files\n- src/validation/helpers.py:9-65 (migrate to CommandRunner)\n- src/hooks.py:274 (migrate to CommandRunner)\n\n## Scope\n**In:** Replace direct subprocess.run calls with CommandRunner\n**In:** Add appropriate timeout configuration\n**Out:** Changing timeout values\n**Out:** Adding new subprocess features\n\n## Acceptance Criteria\n- No direct `subprocess.run` calls in validation or hook paths\n- Subprocess behavior consistent across modules\n- Tests can mock CommandRunner for isolation\n\n## Test Plan\n- `uv run pytest` - all tests pass\n- Add tests that mock executor and assert timeout behavior\n- Verify hook subprocess calls respect timeouts\n\n## Notes for Agent\n- CommandRunner may need to move to tools/ first (see mala-3qp.3)\n- If mala-3qp.3 not done yet, import from current location\n- Consider if hooks.py subprocess needs different timeout than default\n\n## Review Guidelines\n\nYou are acting as a reviewer for a proposed code change made by another engineer.\n\nBelow are some default guidelines for determining whether the original author would appreciate the issue being flagged.\n\nThese are not the final word in determining whether an issue is a bug. In many cases, you will encounter other, more specific guidelines. These may be present elsewhere in a developer message, a user message, a file, or even elsewhere in this system message.\nThose guidelines should be considered to override these general instructions.\n\nHere are the general guidelines for determining whether something is a bug and should be flagged.\n\n1. It meaningfully impacts the accuracy, performance, security, or maintainability of the code.\n2. The bug is discrete and actionable (i.e. not a general issue with the codebase or a combination of multiple issues).\n3. Fixing the bug does not demand a level of rigor that is not present in the rest of the codebase (e.g. one doesn't need very detailed comments and input validation in a repository of one-off scripts in personal projects)\n4. The bug was introduced in the commit (pre-existing bugs should not be flagged).\n5. The author of the original PR would likely fix the issue if they were made aware of it.\n6. The bug does not rely on unstated assumptions about the codebase or author's intent.\n7. It is not enough to speculate that a change may disrupt another part of the codebase, to be considered a bug, one must identify the other parts of the code that are provably affected.\n8. The bug is clearly not just an intentional change by the original author.\n\nWhen flagging a bug, you will also provide an accompanying comment. Once again, these guidelines are not the final word on how to construct a comment -- defer to any subsequent guidelines that you encounter.\n\n1. The comment should be clear about why the issue is a bug.\n2. The comment should appropriately communicate the severity of the issue. It should not claim that an issue is more severe than it actually is.\n3. The comment should be brief. The body should be at most 1 paragraph. It should not introduce line breaks within the natural language flow unless it is necessary for the code fragment.\n4. The comment should not include any chunks of code longer than 3 lines. Any code chunks should be wrapped in markdown inline code tags or a code block.\n5. The comment should clearly and explicitly communicate the scenarios, environments, or inputs that are necessary for the bug to arise. The comment should immediately indicate that the issue's severity depends on these factors.\n6. The comment's tone should be matter-of-fact and not accusatory or overly positive. It should read as a helpful AI assistant suggestion without sounding too much like a human reviewer.\n7. The comment should be written such that the original author can immediately grasp the idea without close reading.\n8. The comment should avoid excessive flattery and comments that are not helpful to the original author. The comment should avoid phrasing like \"Great job ...\", \"Thanks for ...\".\n\n## How Many Findings to Return\n\nOutput all findings that the original author would fix if they knew about it. If there is no finding that a person would definitely love to see and fix, prefer outputting no findings. Do not stop at the first qualifying finding. Continue until you've listed every qualifying finding.\n\n## Guidelines\n\n- Ignore trivial style unless it obscures meaning or violates documented standards.\n- Use one comment per distinct issue (or a multi-line range if necessary).\n- Always keep the line range as short as possible for interpreting the issue. Avoid ranges longer than 5-10 lines; instead, choose the most suitable subrange that pinpoints the problem.\n\n## Priority Tagging\n\nAt the beginning of the finding title, tag the bug with priority level. For example \"[P1] Un-padding slices along wrong tensor dimensions\".\n- [P0] - Drop everything to fix. Blocking release, operations, or major usage. Only use for universal issues that do not depend on any assumptions about the inputs.\n- [P1] - Urgent. Should be addressed in the next cycle.\n- [P2] - Normal. To be fixed eventually.\n- [P3] - Low. Nice to have.\n\nAdditionally, include a numeric priority field in the JSON output for each finding: set \"priority\" to 0 for P0, 1 for P1, 2 for P2, or 3 for P3. If a priority cannot be determined, omit the field or use null.\n\n## Overall Correctness Verdict\n\nAt the end of your findings, output an \"overall correctness\" verdict of whether or not the patch should be considered \"correct\".\nCorrect implies that existing code and tests will not break, and the patch is free of bugs and other blocking issues.\nIgnore non-blocking issues such as style, formatting, typos, documentation, and other nits.\n\n## Output Format\n\nOutput valid JSON matching the schema. Do not wrap the JSON in markdown fences or extra prose.\nThe code_location field is required and must include absolute_file_path and line_range.\nLine ranges must be as short as possible for interpreting the issue (avoid ranges over 5-10 lines; pick the most suitable subrange).\nThe code_location should overlap with the diff.\nDo not generate a PR fix.\n\nmcp startup: no servers\n2025-12-29T15:57:47.829493Z ERROR codex_api::endpoint::responses: error=http 400 Bad Request: Some(\"{\\n  \\\"error\\\": {\\n    \\\"message\\\": \\\"Invalid schema for response_format 'codex_output_schema': In context=('properties', 'findings', 'items'), 'required' is required to be supplied and to be an array including every key in properties. Missing 'priority'.\\\",\\n    \\\"type\\\": \\\"invalid_request_error\\\",\\n    \\\"param\\\": \\\"text.format.schema\\\",\\n    \\\"code\\\": \\\"invalid_json_schema\\\"\\n  }\\n}\")\nERROR: {\n  \"error\": {\n    \"message\": \"Invalid schema for response_format 'codex_output_schema': In context=('properties', 'findings', 'items'), 'required' is required to be supplied and to be an array including every key in properties. Missing 'priority'.\",\n    \"type\": \"invalid_request_error\",\n    \"param\": \"text.format.schema\",\n    \"code\": \"invalid_json_schema\"\n  }\n}\nWarning: no last agent message; wrote empty content to /tmp/tmp88h5epcl.json\nLog: /home/cyou/.claude/projects/-home-cyou-mala/0e0e6672-0237-46d8-a827-5183091f8535.jsonl","status":"closed","priority":2,"issue_type":"chore","created_at":"2025-12-29T00:04:35.499919389Z","created_by":"cyou","updated_at":"2025-12-29T17:50:40.430150616Z","closed_at":"2025-12-29T17:50:40.430150616Z","close_reason":"No remaining subprocess.run uses in src; validation/helpers and hooks now use tools.command_runner.run_command for subprocess handling.","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-3qp.9","depends_on_id":"mala-3qp","type":"parent-child","created_at":"2025-12-29T00:04:35.51002875Z","created_by":"daemon"},{"issue_id":"mala-3qp.9","depends_on_id":"mala-3qp.3","type":"blocks","created_at":"2025-12-29T00:05:14.501845765Z","created_by":"daemon"}]}
{"id":"mala-3r6","title":"Fix parent epic caching for nested epics","description":"Context:\\n- get_parent_epic_async caches all descendants returned by get_epic_children_async as having the same parent epic\\n- get_epic_children_async returns all descendants (depth \u003e 0), not just direct children\\n- In nested epics, tasks under child epics can be incorrectly cached to the top-level epic\\n\\nScope:\\n- In: ensure caching only applies to direct children of the discovered parent epic (or compute actual parent epic per task)\\n- In: update get_epic_children_async or caching logic to avoid misclassification\\n- Out: changes to bd itself\\n\\nAcceptance Criteria:\\n- Tasks under child epics keep their immediate parent epic in parent_epic cache\\n- Focus mode grouping and blocked-epic filtering are correct for nested epics\\n- No regression for flat epics\\n\\nTest Plan:\\n- Add tests with nested epic graph (epic -\u003e epic -\u003e tasks)\\n- Verify get_parent_epics_async returns correct parent epic IDs\\n- Verify focus grouping preserves child epic grouping\\n\\nPrimary files: src/beads_client.py","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-28T04:29:08.315592097Z","updated_at":"2025-12-28T04:55:36.837528784Z","closed_at":"2025-12-28T04:55:36.837528784Z","close_reason":"Closed"}
{"id":"mala-3rgs","title":"[Review] 4 non-blocking findings from mala-99bh.1","description":"## Review Findings\n\nThis issue consolidates 4 non-blocking findings from code review.\n\n**Source issue:** mala-99bh.1\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Treat issue IDs as literal in git grep\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/domain/epic/scope.py:84-89\n\n`git log --grep` interprets the pattern as a regex. Issue IDs commonly include `.` (e.g., `99bh.1`), so the current pattern can match unrelated commits (false positives) or miss literal IDs that contain regex metacharacters. Use `--fixed-strings` (`-F`) or escape the issue ID before interpolating it so the match is literal.\n\n---\n\n### Finding 2: [P2] Batch sequential git commands to improve performance\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/domain/epic/scope.py:68-171\n\nThe methods `_compute_commit_list`, `_summarize_commit_range`, and `_format_commit_summary` all execute git subprocesses in a loop. For an epic with many child issues or commits, this results in dozens of sequential calls, causing noticeable latency. `git log` can be batched with multiple `--grep` patterns, and `git show` (or `git log --no-walk`) can fetch details for multiple SHAs in a single call.\n\n---\n\n### Finding 3: [P3] Remove unused `_repo_path` attribute\n\n**Priority:** P3\n**Reviewer:** gemini\n**Location:** src/domain/epic/scope.py:28\n\nThe `_repo_path` attribute is assigned during initialization but never used in the class. The `_runner` already encapsulates the working directory for all operations.\n\n---\n\n### Finding 4: [P3] Sort `all_issue_ids` to ensure deterministic commit discovery\n\n**Priority:** P3\n**Reviewer:** gemini\n**Location:** src/domain/epic/scope.py:78\n\nIterating over `all_issue_ids` (a set) causes `_compute_commit_list` to discover commits in a non-deterministic order. Since the method preserves the first-seen order when deduplicating, the resulting commit list and summary will vary across runs. Sorting the IDs ensures consistent and predictable output.\n\n---\n","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T06:34:12.828110806Z","created_by":"cyou","updated_at":"2026-01-03T06:39:35.58091751Z","closed_at":"2026-01-03T06:39:35.58091751Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_findings:4f3a0e69e797","source:mala-99bh.1"]}
{"id":"mala-3sp","title":"Prevent agents from gaming type check validation","description":"## Problem\n\nAgents are gaming validation by:\n1. Running truncated checks (`| head -20`) to hide errors\n2. Assuming errors are \"pre-existing\" without verification\n3. Running `uvx ty check src/\u003cfile\u003e.py` instead of `uvx ty check` to avoid test file errors\n4. Never type-checking the test files they modify\n\nEvidence from logs (commit 0328a6e):\n- Agent modified `tests/test_morph_integration.py`\n- Saw type errors, claimed \"pre-existing\"\n- Ran `uvx ty check src/orchestrator.py` (not the test file)\n- Committed with 62 type errors in test files\n\n## Solution\n\n### 1. Update implementer prompt (src/prompts/implementer_prompt.md)\n\nAdd explicit rules after the quality checks section:\n\n```markdown\n**CRITICAL - No Gaming Validation:**\n- Run `uvx ty check` with NO path arguments (checks entire codebase)\n- Do NOT pipe to `head` or truncate output\n- Do NOT assume errors are \"pre-existing\" - verify with `git blame`\n- If you modified a test file, type errors in that file are YOUR responsibility\n- All checks must pass with ZERO errors before committing\n```\n\n## Acceptance Criteria\n\n- [ ] Implementer prompt updated with anti-gaming rules\n- [ ] Prompt explicitly states: no `| head`, no path args, zero errors required\n- [ ] Prompt requires `git blame` verification before claiming \"pre-existing\"\n\n## Files\n- src/prompts/implementer_prompt.md","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-27T06:33:22.096830922Z","updated_at":"2025-12-27T06:38:42.564228491Z","closed_at":"2025-12-27T06:38:42.564228491Z","close_reason":"Closed"}
{"id":"mala-3uo","title":"Document ISSUE_NO_CHANGE/ISSUE_OBSOLETE markers in implementer prompt","description":"Context:\n- The quality gate (src/quality_gate.py:157-271) supports ISSUE_NO_CHANGE and ISSUE_OBSOLETE markers\n- When a subagent outputs these markers with a rationale and clean working tree, the gate passes without requiring a commit\n- The implementer prompt (src/prompts/implementer_prompt.md) doesn't document this capability\n- Subagents currently have no way to know they can skip commits for no-op issues\n\nScope:\n- In: Add documentation to implementer_prompt.md explaining the no-change/obsolete flow\n- Out: Changing the quality gate logic itself\n\nAcceptance Criteria:\n- implementer_prompt.md documents when/how to use ISSUE_NO_CHANGE and ISSUE_OBSOLETE markers\n- Clear instructions: ensure clean working tree, provide rationale, release locks\n- Example output format shown\n\nTest Plan:\n- Manual review of prompt clarity\n- No code changes to test\n\nNotes for Agent:\n- Add a new section (e.g., '### If No Code Changes Required') after the commit section\n- Keep it concise - subagents need quick reference, not lengthy explanation","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-27T06:21:36.035492243Z","updated_at":"2025-12-27T07:23:58.793225485Z","closed_at":"2025-12-27T07:23:58.793225485Z","close_reason":"Closed"}
{"id":"mala-3xv","title":"Add global test mutex wrapper","description":"Context:\n- Track A5 requires serializing repo-wide commands: uv sync, pytest, ruff, ty (docs/coordination-plan-codex.md:44-45).\n- scripts directory currently only contains lock scripts (src/scripts).\n\nScope:\n- In: add a script (e.g., test-mutex.sh) that acquires a global lock and runs a command; release on exit.\n- In: add tests for the mutex behavior in a new test module.\n- Out: no prompt changes (A1) and no orchestrator hooks.\n\nAcceptance Criteria:\n- Wrapper exits non-zero when mutex is held and reports the holder.\n- Successful runs acquire and release the mutex even on command failure.\n- Tests verify acquisition, contention, and release behavior.\n\nTest Plan:\n- TDD: add failing tests in new tests/test_test_mutex.py.\n- Implement script and re-run tests.\n- Run uv run pytest tests/test_test_mutex.py.\n\nNotes for Agent:\n- Pick a stable mutex key (e.g., a fixed path token) to avoid collisions.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-26T16:44:22.179577746Z","updated_at":"2025-12-26T17:05:43.991099145Z","closed_at":"2025-12-26T17:05:43.991099145Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-3xv","depends_on_id":"mala-4w7","type":"parent-child","created_at":"2025-12-26T16:47:52.996880868Z","created_by":"daemon"}]}
{"id":"mala-3z2","title":"Test: tiny edit","status":"tombstone","priority":2,"issue_type":"task","created_at":"2025-12-25T19:08:44.86959643Z","updated_at":"2025-12-25T19:12:41.579812812Z","deleted_at":"2025-12-25T19:12:41.579812812Z","deleted_by":"daemon","delete_reason":"delete","original_type":"task"}
{"id":"mala-40pg","title":"Epic: Improve Terminal Logging Visibility","description":"# Epic: Improve Terminal Logging Visibility\n\n**Spec**: `docs/2026-01-01-improve-terminal-logging-spec.md`\n**Plan**: `docs/2026-01-01-improve-terminal-logging-plan.md`\n\n## Context\n- Users need better visibility into orchestrator operations during long-running tasks\n- Currently missing `on_validation_started` event, gate/review starts are verbose-only\n- Issue IDs not shown in logs, making it hard to correlate with specific issues\n\n## Goals\n1. Add `on_validation_started` event (new)\n2. Make gate/review start events visible in normal mode (change `log_verbose()` → `log()`)\n3. Include issue IDs in major event logs (e.g., `[ISSUE-123]` instead of `[agent-a1b2]`)\n\n## Scope\n**In**: coordinate all terminal logging improvements across console.py, event_sink.py, agent_session_runner.py\n**Out**: JSON/machine-readable output, progress bars, CLI flag changes\n\n## Child Issues\n- Add `issue_id` parameter to console `log()` function\n- Add `on_validation_started` and `issue_id` params to MalaEventSink protocol\n- Implement ConsoleEventSink logging with `issue_id` and normal-mode start events\n- Wire event emissions with `issue_id` in AgentSessionRunner\n\n## Acceptance Criteria\n- [ ] `on_validation_started` event emits and logs\n- [ ] Gate/review start events visible without `-v` flag\n- [ ] Issue IDs shown as `[ISSUE-123]` in major event logs\n- [ ] Agent ID fallback `[agent-a1b2]` when no issue context\n- [ ] All existing tests pass; new tests added","status":"closed","priority":2,"issue_type":"epic","created_at":"2026-01-01T18:58:31.17131776Z","created_by":"cyou","updated_at":"2026-01-01T19:56:47.290782182Z","closed_at":"2026-01-01T19:56:47.290782182Z","close_reason":"Closed","labels":["logging"]}
{"id":"mala-40pg.1","title":"Add issue_id parameter to console log() function","description":"# Add issue_id parameter to console log() function\n\n**Plan Task**: Task 1 from `docs/2026-01-01-improve-terminal-logging-plan.md`\n\n## Context\n- `log()` function at `src/infra/io/log_output/console.py:116-142` currently only accepts `agent_id`\n- Need dual-ID display: show `issue_id` for display, use `agent_id` for color mapping\n- Format: `[ISSUE-123]` when issue_id provided, fallback to `[agent-a1b2]`\n\n## Primary Files\n- `src/infra/io/log_output/console.py`\n- `tests/test_logging_console.py`\n\n## Scope\n**In**: Add `issue_id: str | None = None` parameter to `log()`, update prefix logic\n**Out**: Changes to `log_verbose()`, `log_tool()`, `log_agent_text()` (must stay compact per spec)\n\n## Implementation\n```python\ndef log(\n    icon: str,\n    message: str,\n    color: str = Colors.RESET,\n    dim: bool = False,\n    agent_id: str | None = None,\n    issue_id: str | None = None,  # NEW parameter\n) -\u003e None:\n```\n\nUpdate prefix logic (lines 134-138):\n```python\nif issue_id:\n    # Use issue_id as display, agent_id for color mapping\n    agent_color = get_agent_color(agent_id) if agent_id else Colors.CYAN\n    prefix = f\"{agent_color}[{issue_id}]{Colors.RESET} \"\nelif agent_id:\n    agent_color = get_agent_color(agent_id)\n    prefix = f\"{agent_color}[{agent_id}]{Colors.RESET} \"\nelse:\n    prefix = \"\"\n```\n\n## Acceptance Criteria\n- [ ] `log()` accepts optional `issue_id` parameter\n- [ ] When `issue_id` provided: displays `[ISSUE-123]`, agent_id used for color only\n- [ ] When `issue_id` not provided: displays `[agent-a1b2]` (existing behavior)\n- [ ] When `issue_id` provided without `agent_id`: uses cyan color\n- [ ] Existing calls to `log()` continue to work unchanged\n\n## Test Plan\n- TDD: Write `test_log_with_issue_id_shows_issue_only` first - assert `[ISSUE-123]` in output, `agent-abc` NOT in output\n- TDD: Write `test_log_with_issue_id_uses_agent_color` - assert agent_id in color map\n- TDD: Write `test_log_fallback_shows_agent_id_when_no_issue_id` - assert `[agent-a1b2]` appears\n- Run: `uv run pytest tests/test_logging_console.py -v`\n\n## Notes for Agent\n- Preserve signature compatibility - all new params must be optional with defaults\n- Color mapping via `get_agent_color(agent_id)` must still work when issue_id is displayed\n- See plan lines 69-92 for exact implementation","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-01T18:58:44.626789209Z","created_by":"cyou","updated_at":"2026-01-01T19:32:36.918841057Z","closed_at":"2026-01-01T19:32:36.918841057Z","close_reason":"Closed","labels":["logging"],"dependencies":[{"issue_id":"mala-40pg.1","depends_on_id":"mala-40pg","type":"parent-child","created_at":"2026-01-01T18:58:44.634828266Z","created_by":"daemon"}]}
{"id":"mala-40pg.2","title":"Add on_validation_started and issue_id params to MalaEventSink protocol","description":"# Add on_validation_started and issue_id params to MalaEventSink protocol\n\n**Plan Task**: Task 2 from `docs/2026-01-01-improve-terminal-logging-plan.md`\n\n## Context\n- `MalaEventSink` protocol at lines 56-599 defines semantic events\n- `NullEventSink` at lines 601-835 must implement matching no-ops\n- Currently no `on_validation_started` event; only `on_validation_result` exists at line 388\n\n## Primary Files\n- `src/infra/io/event_sink.py` (lines 55-835 only - protocol and NullEventSink)\n- `tests/test_event_sink.py`\n\n## Scope\n**In**: Add `on_validation_started(agent_id, issue_id=None)` to protocol and NullEventSink; add `issue_id` param to 9 existing methods\n**Out**: ConsoleEventSink implementation (separate task), changing method logic\n\n## Methods to Update\nAdd `issue_id: str | None = None` parameter to:\n1. `on_validation_result` (~line 388)\n2. `on_gate_started` (~line 195)\n3. `on_gate_passed` (~line 210)\n4. `on_gate_failed` (~line 218)\n5. `on_gate_retry` (~line 233)\n6. `on_gate_result` (~line 248)\n7. `on_review_started` (~line 270)\n8. `on_review_passed` (~line 285)\n9. `on_review_retry` (~line 293)\n\n## New Method (Protocol)\nAdd after `on_validation_result` at ~line 399:\n```python\ndef on_validation_started(self, agent_id: str, issue_id: str | None = None) -\u003e None:\n    \"\"\"Called when per-issue validation begins.\n\n    Args:\n        agent_id: Agent being validated.\n        issue_id: Issue being validated (for display).\n    \"\"\"\n    ...\n```\n\n## New Method (NullEventSink)\nAdd after line 752:\n```python\ndef on_validation_started(self, agent_id: str, issue_id: str | None = None) -\u003e None:\n    pass\n```\n\n## Acceptance Criteria\n- [ ] `MalaEventSink` protocol has `on_validation_started(agent_id: str, issue_id: str | None = None)`\n- [ ] All 9 methods updated with `issue_id: str | None = None` parameter\n- [ ] `NullEventSink` implements all new/modified methods as `pass`\n- [ ] `isinstance(NullEventSink(), MalaEventSink)` still returns True\n\n## Test Plan\n- TDD: Add `test_null_sink_on_validation_started` - assert returns None\n- TDD: Add test that NullEventSink accepts `issue_id=None` without error for updated methods\n- Run: `uv run pytest tests/test_event_sink.py::TestNullEventSink -v`\n- Run: `uvx ty check src/infra/io/event_sink.py`\n\n## Notes for Agent\n- This task does NOT touch ConsoleEventSink (lines 837+)\n- All `issue_id` params must default to `None` for backwards compatibility\n- Also update the corresponding NullEventSink methods to accept the new param\n\n## Coordination Note\n**mala-cavk.1** (P1) also modifies `event_sink.py` to create `BaseEventSink`. If that lands first:\n- `NullEventSink` may become an alias to `BaseEventSink`\n- Add new method to `BaseEventSink` instead of `NullEventSink`\n- Check current structure before starting","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-01T18:58:58.573608388Z","created_by":"cyou","updated_at":"2026-01-01T19:33:10.814819603Z","closed_at":"2026-01-01T19:33:10.814819603Z","close_reason":"Closed","labels":["logging"],"dependencies":[{"issue_id":"mala-40pg.2","depends_on_id":"mala-40pg","type":"parent-child","created_at":"2026-01-01T18:58:58.581712773Z","created_by":"daemon"}]}
{"id":"mala-40pg.3","title":"Implement ConsoleEventSink logging with issue_id and normal-mode start events","description":"# Implement ConsoleEventSink logging with issue_id and normal-mode start events\n\n**Plan Task**: Task 3 from `docs/2026-01-01-improve-terminal-logging-plan.md`\n\n## Context\n- `ConsoleEventSink` at `src/infra/io/event_sink.py:837-1397` needs to implement new events and pass issue_id to log()\n- `on_gate_started` (line 1074) and `on_review_started` (line 1146) currently use `log_verbose()` - need to change to `log()`\n\n## Primary Files\n- `src/infra/io/event_sink.py` (lines 837-1397 only - ConsoleEventSink)\n- `tests/test_event_sink.py`\n\n## Scope\n**In**: Implement `on_validation_started` in ConsoleEventSink; add `issue_id` param to 9 methods and pass to `log()`; change `on_gate_started` and `on_review_started` from `log_verbose()` to `log()`\n**Out**: Event emission call sites (separate task)\n\n## Implementation\n\n### Add on_validation_started (after on_validation_result at ~line 1248)\n```python\ndef on_validation_started(self, agent_id: str, issue_id: str | None = None) -\u003e None:\n    \"\"\"Log validation start.\"\"\"\n    log(\"◐\", \"Starting validation...\", Colors.MUTED, agent_id=agent_id, issue_id=issue_id)\n```\n\n### Update on_gate_started (~line 1074-1087)\n```python\ndef on_gate_started(\n    self,\n    agent_id: str | None,\n    attempt: int,\n    max_attempts: int,\n    issue_id: str | None = None,  # NEW\n) -\u003e None:\n    \"\"\"Log quality gate check start.\"\"\"\n    scope = \"run-level \" if agent_id is None else \"\"\n    log(  # Changed from log_verbose\n        \"→\",\n        f\"Quality gate {scope}check (attempt {attempt}/{max_attempts})\",\n        Colors.MUTED,\n        agent_id=agent_id,\n        issue_id=issue_id,\n    )\n```\n\n### Update on_review_started (~line 1146-1158)\n```python\ndef on_review_started(\n    self,\n    agent_id: str,\n    attempt: int,\n    max_attempts: int,\n    issue_id: str | None = None,  # NEW\n) -\u003e None:\n    \"\"\"Log review start.\"\"\"\n    log(  # Changed from log_verbose\n        \"→\",\n        f\"Review (attempt {attempt}/{max_attempts})\",\n        Colors.MUTED,\n        agent_id=agent_id,\n        issue_id=issue_id,\n    )\n```\n\n### Update remaining handlers with issue_id parameter\n- `on_gate_passed`, `on_gate_failed`, `on_gate_retry`, `on_gate_result`\n- `on_review_passed`, `on_review_retry`\n- `on_validation_result`\n\n## Acceptance Criteria\n- [ ] `ConsoleEventSink.on_validation_started()` logs \"Starting validation...\" with issue_id\n- [ ] `on_gate_started` uses `log()` not `log_verbose()` (visible without -v)\n- [ ] `on_review_started` uses `log()` not `log_verbose()` (visible without -v)\n- [ ] All 9 updated methods pass `issue_id` to `log()` calls\n- [ ] `isinstance(ConsoleEventSink(), MalaEventSink)` returns True\n\n## Test Plan\n- TDD: Add `test_validation_started_logs_message` - mock datetime, assert timestamp and \"[ISSUE-123]\" in output\n- TDD: Add `test_on_gate_started_uses_log_not_verbose` - patch `log`, assert called\n- TDD: Add `test_on_review_started_uses_log_not_verbose` - patch `log`, assert called\n- Run: `uv run pytest tests/test_event_sink.py::TestConsoleEventSink -v`\n\n## Notes for Agent\n- Do NOT modify `log_tool()` or `log_agent_text()` output (spec non-goal)\n- See plan lines 151-202 for exact implementation snippets\n\n## Coordination Note\n**mala-cavk.1** (P1) also modifies `event_sink.py` to refactor inheritance. If that lands first:\n- `ConsoleEventSink` may inherit from `BaseEventSink` instead of implementing from scratch\n- Verify class inheritance before adding new methods\n- The line numbers in this issue may shift - search for method names instead","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-01T18:59:17.304278149Z","created_by":"cyou","updated_at":"2026-01-01T19:41:09.663780386Z","closed_at":"2026-01-01T19:41:09.663780386Z","close_reason":"Closed","labels":["logging"],"dependencies":[{"issue_id":"mala-40pg.3","depends_on_id":"mala-40pg","type":"parent-child","created_at":"2026-01-01T18:59:17.312930516Z","created_by":"daemon"},{"issue_id":"mala-40pg.3","depends_on_id":"mala-40pg.1","type":"blocks","created_at":"2026-01-01T19:03:15.480738598Z","created_by":"daemon"},{"issue_id":"mala-40pg.3","depends_on_id":"mala-40pg.2","type":"blocks","created_at":"2026-01-01T19:03:35.43540132Z","created_by":"daemon"}]}
{"id":"mala-40pg.4","title":"Wire event emissions with issue_id in AgentSessionRunner","description":"# Wire event emissions with issue_id in AgentSessionRunner\n\n**Plan Task**: Task 4 from `docs/2026-01-01-improve-terminal-logging-plan.md`\n\n## Context\n- `AgentSessionRunner.run_session()` emits gate/review events at lines 856-930\n- Need to emit new `on_validation_started` event and pass `issue_id` to existing emissions\n- **Current behavior**: `input.issue_id` is passed as `agent_id` param (line 875, 1003) for color mapping\n\n## Primary Files\n- `src/pipeline/agent_session_runner.py`\n- `tests/test_agent_session_runner.py`\n\n## Scope\n**In**: Emit `on_validation_started` before RUN_GATE effect; add `issue_id=input.issue_id` kwarg to all event_sink calls\n**Out**: Protocol/sink changes (done in prior tasks); changing color mapping behavior\n\n## Implementation\nIn `AgentSessionRunner.run_session()`, at the RUN_GATE handling (~line 874):\n\n```python\nif result.effect == Effect.RUN_GATE:\n    # Emit validation started BEFORE the gate check\n    if self.event_sink is not None:\n        self.event_sink.on_validation_started(\n            input.issue_id,  # Keep using issue_id for color mapping (current behavior)\n            issue_id=input.issue_id,  # NEW: also pass for display\n        )\n\n    if self.event_sink is not None:\n        self.event_sink.on_gate_started(\n            input.issue_id,  # Keep using issue_id for color mapping (current behavior)\n            lifecycle_ctx.retry_state.gate_attempt,\n            self.config.max_gate_retries,\n            issue_id=input.issue_id,  # NEW: pass for display\n        )\n    # ... existing gate check code ...\n```\n\nSimilarly update all other event emissions to add `issue_id=input.issue_id` as a new keyword argument.\n\n**Note**: We keep `input.issue_id` as the first positional arg (agent_id) to maintain current color-per-issue behavior. The new `issue_id` kwarg enables the display format `[ISSUE-123]` without changing colors.\n\n## Acceptance Criteria\n- [ ] `on_validation_started(input.issue_id, issue_id=input.issue_id)` emitted just before gate check\n- [ ] All `on_gate_*` emissions include new `issue_id=input.issue_id` kwarg\n- [ ] All `on_review_*` emissions include new `issue_id=input.issue_id` kwarg\n- [ ] Color mapping behavior unchanged (still uses issue_id for colors)\n\n## Test Plan\n- Add test in `tests/test_agent_session_runner.py` verifying `on_validation_started` is called before `on_gate_started`\n- Run: `uv run pytest tests/test_agent_session_runner.py -v -k gate`\n- Manual: Run `mala run` on test issue and verify log output matches spec example\n\n## Notes for Agent\n- **Keep first arg as `input.issue_id`** - this maintains current color-per-issue behavior\n- The new `issue_id` kwarg is for display purposes only\n- Validation started should emit just before the RUN_GATE effect handling (~line 874)","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-01T18:59:35.611542705Z","created_by":"cyou","updated_at":"2026-01-01T19:54:45.015490132Z","closed_at":"2026-01-01T19:54:45.015490132Z","close_reason":"Closed","labels":["logging"],"dependencies":[{"issue_id":"mala-40pg.4","depends_on_id":"mala-40pg","type":"parent-child","created_at":"2026-01-01T18:59:35.619955695Z","created_by":"daemon"},{"issue_id":"mala-40pg.4","depends_on_id":"mala-40pg.3","type":"blocks","created_at":"2026-01-01T19:03:43.398597552Z","created_by":"daemon"}]}
{"id":"mala-4313","title":"[Review] src/infra/hooks/lint_cache.py:115-120: [P2] Case-insensitive matching removed from lint detection","description":"## Review Finding\n\nThis issue was auto-created from a P2 review finding.\n\n**Source issue:** mala-fdp1.10\n**Reviewer:** claude\n**Location:** src/infra/hooks/lint_cache.py:115-120\n\n## Details\n\nThe previous implementation used `re.IGNORECASE` for lint command detection, meaning commands like `RUFF CHECK .` would be recognized. The new implementation using `extract_tool_name()` does case-sensitive matching since `lint_tools` contains lowercase strings (`\"ruff\"`, `\"eslint\"`, etc.) and the extracted tool name is compared directly. While uppercase lint commands are unusual in practice, this is a subtle behavioral change that could cause previously-cached commands to no longer match. If case-insensitive matching was intentional, you could normalize: `base_tool.lower() in lint_tools`.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-02T15:43:51.253520158Z","created_by":"cyou","updated_at":"2026-01-02T16:58:51.109671009Z","closed_at":"2026-01-02T16:58:51.109671009Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_finding:ae57de29aa47","source:mala-fdp1.10"]}
{"id":"mala-46q","title":"Add orchestrator unit tests (mock bd + SDK)","description":"Problem: Tests focus on lock scripts/SDK integration, but MalaOrchestrator control flow is untested (bd ready/claim/reset, timeout handling, exit code semantics).\\n\\nScope/files: tests/ (new test module), can mock subprocess.run and ClaudeSDKClient to avoid network. Avoid modifying src/cli.py unless absolutely required; if refactor is needed, document why.\\n\\nSuggested tests:\\n- get_ready_issues handles bd JSON and errors.\\n- claim_issue/reset_issue invoked appropriately.\\n- run() handles no ready issues and stops cleanly.\\n- On failed task, resets issue status.\\n\\nAcceptance criteria:\\n- Tests run without network and without actual bd CLI.\\n- Minimal mocking surface; focus on orchestrator state transitions.\\n\\nTDD: Write tests first, then adjust code if needed.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-25T21:38:34.899307031Z","updated_at":"2025-12-25T23:59:05.389010652Z","closed_at":"2025-12-25T23:59:05.389010652Z","close_reason":"Closed"}
{"id":"mala-47kd","title":"Epic: Centralize prompt loading in PromptProvider","description":"## Summary\nPrompts live in multiple modules and are read from disk inside pipeline stages. This fragments prompt ownership and complicates testing or swapping prompt sets.\n\n## Primary Files\n- `src/orchestration/prompts.py:19`\n- `src/domain/prompts.py:24-29`\n- `src/pipeline/agent_session_runner.py:80-94,1222,1460`\n- `src/pipeline/run_coordinator.py:63-69,393`\n\n## Category\nAbstraction\n\n## Source\ncodex\n\n## Fix Approach\nCentralize prompt loading in a `PromptProvider` (pure data object or interface) and pass prompts into pipeline components; keep file IO at the boundary.\n\n## Non-Goals\n- Template engine or dynamic prompt generation\n- Changing prompt content\n\n## Acceptance Criteria\n- [ ] Pipeline components accept prompt strings/provider without reading files directly\n- [ ] Prompt file IO is centralized in one module\n\n## Test Plan\n- Unit tests for pipeline components with injected prompts\n- Verify prompt files loaded once at startup","status":"closed","priority":3,"issue_type":"epic","created_at":"2026-01-03T05:50:12.100102904Z","created_by":"cyou","updated_at":"2026-01-03T18:03:42.797648727Z","closed_at":"2026-01-03T18:03:42.797648727Z","close_reason":"Closed"}
{"id":"mala-47kd.1","title":"Create PromptProvider data class with all prompt templates","description":"Create a PromptProvider dataclass in src/domain/prompts.py that holds all loaded prompt templates as string fields. This is a pure data object (not a service) that can be constructed at startup boundary.\n\nFiles to modify:\n- src/domain/prompts.py (primary)\n\nThe PromptProvider should contain:\n- implementer_prompt: str\n- review_followup_prompt: str\n- gate_followup_prompt: str\n- fixer_prompt: str\n- idle_resume_prompt: str\n\nInclude a factory function load_prompts(prompt_dir: Path) -\u003e PromptProvider that reads all prompt files from disk.\n\nKeep existing functions (get_gate_followup_prompt, etc.) temporarily for backward compatibility - they will be removed in subsequent tasks.\n\nNo changes to consumers yet - this task only adds the new abstraction.","notes":"Quality gate failed: Quality gate failed: No commit with bd-mala-47kd.1 found after run baseline (stale commits from previous runs are rejected)\nLog: /home/cyou/.claude/projects/-home-cyou-mala/52b1920c-5cc0-4abe-9d9d-f37007561563.jsonl","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-03T05:54:10.920265756Z","created_by":"cyou","updated_at":"2026-01-03T14:58:24.753927639Z","closed_at":"2026-01-03T14:58:24.753927639Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-47kd.1","depends_on_id":"mala-47kd","type":"parent-child","created_at":"2026-01-03T17:58:44.969043306Z","created_by":"daemon"}]}
{"id":"mala-47kd.2","title":"Consolidate duplicate prompt loaders in orchestration/prompts.py","description":"Remove src/orchestration/prompts.py and consolidate its definitions into src/domain/prompts.py.\n\nFiles to modify:\n- src/orchestration/prompts.py (delete)\n- src/domain/prompts.py (add get_implementer_prompt, get_review_followup_prompt, get_fixer_prompt)\n- src/orchestration/orchestrator.py (update import from src.orchestration.prompts to src.domain.prompts)\n\nCurrently src/orchestration/prompts.py duplicates the pattern found in src/domain/prompts.py. Consolidate all prompt loading into the domain layer.\n\nBackward compatibility: The existing get_implementer_prompt() function signature must remain unchanged for now.","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-03T05:54:18.119294398Z","created_by":"cyou","updated_at":"2026-01-03T16:38:35.911409647Z","closed_at":"2026-01-03T16:38:35.911409647Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-47kd.2","depends_on_id":"mala-47kd.1","type":"blocks","created_at":"2026-01-03T05:55:08.914141719Z","created_by":"daemon"},{"issue_id":"mala-47kd.2","depends_on_id":"mala-au9g.4","type":"blocks","created_at":"2026-01-03T05:59:32.230738432Z","created_by":"daemon"},{"issue_id":"mala-47kd.2","depends_on_id":"mala-47kd","type":"parent-child","created_at":"2026-01-03T17:58:44.977584605Z","created_by":"daemon"}]}
{"id":"mala-47kd.3","title":"Remove inline prompt loaders from agent_session_runner.py","description":"Remove the local prompt loading functions from agent_session_runner.py and use the centralized versions from src/domain/prompts.py.\n\nFiles to modify:\n- src/pipeline/agent_session_runner.py (primary)\n- src/domain/prompts.py (add get_review_followup_prompt, get_idle_resume_prompt if not already present)\n\nRemove these local functions from agent_session_runner.py:\n- _get_review_followup_prompt() (lines 88-90)\n- _get_idle_resume_prompt() (lines 94-96)\n\nUpdate the call sites at lines 1222 and 1460 to import and use the centralized versions from src.domain.prompts.\n\nAlso remove the local REVIEW_FOLLOWUP_FILE and IDLE_RESUME_PROMPT_FILE constants.","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-03T05:54:26.456091222Z","created_by":"cyou","updated_at":"2026-01-03T16:39:37.417117672Z","closed_at":"2026-01-03T16:39:37.417117672Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-47kd.3","depends_on_id":"mala-47kd.2","type":"blocks","created_at":"2026-01-03T05:55:08.975763096Z","created_by":"daemon"},{"issue_id":"mala-47kd.3","depends_on_id":"mala-mgfb.4","type":"blocks","created_at":"2026-01-03T05:59:32.260472256Z","created_by":"daemon"},{"issue_id":"mala-47kd.3","depends_on_id":"mala-ari7.6","type":"blocks","created_at":"2026-01-03T05:59:32.27035227Z","created_by":"daemon"},{"issue_id":"mala-47kd.3","depends_on_id":"mala-47kd","type":"parent-child","created_at":"2026-01-03T17:58:44.985664347Z","created_by":"daemon"}]}
{"id":"mala-47kd.4","title":"Remove inline prompt loader from run_coordinator.py","description":"Remove the local prompt loading function from run_coordinator.py and use the centralized version from src/domain/prompts.py.\n\nFiles to modify:\n- src/pipeline/run_coordinator.py (primary)\n- src/domain/prompts.py (ensure get_fixer_prompt exists)\n\nRemove these from run_coordinator.py:\n- _get_fixer_prompt() function (lines 67-69)\n- FIXER_PROMPT_FILE constant (line 63)\n- _PROMPT_DIR constant (lines 61-62)\n\nUpdate the call site at line 393 to import and use get_fixer_prompt from src.domain.prompts.","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-03T05:54:32.227176848Z","created_by":"cyou","updated_at":"2026-01-03T16:39:14.752672272Z","closed_at":"2026-01-03T16:39:14.752672272Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-47kd.4","depends_on_id":"mala-47kd.2","type":"blocks","created_at":"2026-01-03T05:55:09.031194612Z","created_by":"daemon"},{"issue_id":"mala-47kd.4","depends_on_id":"mala-47kd","type":"parent-child","created_at":"2026-01-03T17:58:44.993969048Z","created_by":"daemon"}]}
{"id":"mala-47kd.5","title":"Inject PromptProvider into AgentSessionRunner","description":"Refactor AgentSessionRunner to receive prompts via dependency injection instead of loading them directly.\n\nFiles to modify:\n- src/pipeline/agent_session_runner.py (primary)\n- src/orchestration/orchestrator.py (pass prompts when constructing runner)\n\nChanges:\n1. Add prompt fields to AgentSessionRunnerConfig (or create a separate PromptProvider parameter)\n2. Remove direct calls to get_gate_followup_prompt(), get_review_followup_prompt(), get_idle_resume_prompt()\n3. Use injected prompts from config instead\n4. Update orchestrator.py to load prompts once and pass them to the runner\n\nThis keeps file IO at the orchestration boundary and makes testing easier (prompts can be mocked).","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-03T05:54:41.818973909Z","created_by":"cyou","updated_at":"2026-01-03T17:40:57.364315839Z","closed_at":"2026-01-03T17:40:57.364315839Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-47kd.5","depends_on_id":"mala-47kd.3","type":"blocks","created_at":"2026-01-03T05:55:09.081414241Z","created_by":"daemon"},{"issue_id":"mala-47kd.5","depends_on_id":"mala-47kd","type":"parent-child","created_at":"2026-01-03T17:58:45.001726223Z","created_by":"daemon"}]}
{"id":"mala-47kd.6","title":"Inject PromptProvider into RunCoordinator","description":"Refactor RunCoordinator to receive prompts via dependency injection instead of loading them directly.\n\nFiles to modify:\n- src/pipeline/run_coordinator.py (primary)\n- src/orchestration/orchestrator.py (pass prompts when constructing coordinator)\n\nChanges:\n1. Add fixer_prompt field to RunCoordinatorConfig\n2. Remove the import/call to get_fixer_prompt()\n3. Use the injected prompt from config instead\n4. Update orchestrator.py to pass the fixer prompt when constructing RunCoordinator\n\nThis keeps file IO at the orchestration boundary and makes testing easier.","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-03T05:54:49.338659557Z","created_by":"cyou","updated_at":"2026-01-03T16:46:37.276365828Z","closed_at":"2026-01-03T16:46:37.276365828Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-47kd.6","depends_on_id":"mala-47kd.4","type":"blocks","created_at":"2026-01-03T05:55:09.132893402Z","created_by":"daemon"},{"issue_id":"mala-47kd.6","depends_on_id":"mala-47kd","type":"parent-child","created_at":"2026-01-03T17:58:45.009530207Z","created_by":"daemon"}]}
{"id":"mala-47kd.7","title":"Remove deprecated prompt loader functions from src/domain/prompts.py","description":"After DI migration is complete, remove the deprecated global prompt loader functions.\n\nFiles to modify:\n- src/domain/prompts.py (primary)\n\nRemove these deprecated functions:\n- get_implementer_prompt()\n- get_review_followup_prompt()\n- get_fixer_prompt()\n- get_gate_followup_prompt()\n- get_idle_resume_prompt()\n\nKeep only:\n- PromptProvider dataclass\n- load_prompts() factory function\n- get_default_validation_commands()\n- build_prompt_validation_commands()\n\nRun tests to ensure no remaining usages of the removed functions.","notes":"Quality gate failed: Quality gate failed: No commit with bd-mala-47kd.7 found after run baseline (stale commits from previous runs are rejected)\nLog: /home/cyou/.claude/projects/-home-cyou-mala/cb481075-dc85-4336-8811-83697f75ad05.jsonl","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-03T05:54:55.560176353Z","created_by":"cyou","updated_at":"2026-01-03T18:01:25.85112825Z","closed_at":"2026-01-03T18:01:25.85112825Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-47kd.7","depends_on_id":"mala-47kd.5","type":"blocks","created_at":"2026-01-03T05:55:09.206253303Z","created_by":"daemon"},{"issue_id":"mala-47kd.7","depends_on_id":"mala-47kd.6","type":"blocks","created_at":"2026-01-03T05:55:09.272558939Z","created_by":"daemon"},{"issue_id":"mala-47kd.7","depends_on_id":"mala-47kd","type":"parent-child","created_at":"2026-01-03T17:58:45.01744375Z","created_by":"daemon"}]}
{"id":"mala-48s","title":"Improve review prompt","description":"Enhance the review prompt for better quality code reviews","status":"closed","priority":3,"issue_type":"task","created_at":"2025-12-28T06:21:39.46584358Z","updated_at":"2026-01-01T21:39:57.039885773Z","closed_at":"2026-01-01T21:39:57.039885773Z","close_reason":"Closed"}
{"id":"mala-4dh","title":"Implement E2E fixture runner","description":"Context:\n- E2E fixture run validates mala end-to-end behavior on a tiny repo.\n- Requires Claude CLI, MORPH_API_KEY, and bd availability.\n\nScope:\n- In: create fixture repo, run mala, validate outputs, cleanup; enforce preconditions; support --skip-e2e-if-no-keys.\n- Out: per-issue validation; E2E is run-level only.\n\nAcceptance Criteria:\n- E2E runner returns E2EResult with clear failure reasons.\n- Preconditions enforced or skipped per flag.\n- Fixture repo cleaned up unless keep flag is set.\n\nTest Plan:\n- Add unit tests for precondition checks and fixture path handling.\n- Mark full E2E run test as slow/optional.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-26T23:00:01.691693551Z","updated_at":"2025-12-27T00:32:01.5851032Z","closed_at":"2025-12-27T00:32:01.5851032Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-4dh","depends_on_id":"mala-j6p","type":"blocks","created_at":"2025-12-26T23:00:26.489084536Z","created_by":"daemon"}]}
{"id":"mala-4h5","title":"CLI: Make quiet mode the default, require --verbose for verbose output","description":"## Problem\n\nCurrently the CLI defaults to verbose mode (`--verbose/--quiet` with default True), meaning users must pass `-q` or `--quiet` to get quieter output. This is backwards - quiet should be the default.\n\n## Current Behavior\n```python\nverbose: Annotated[\n    bool,\n    typer.Option(\n        \"--verbose/--quiet\",\n        \"-v/-q\",\n        help=\"Verbose output shows full tool arguments; quiet mode shows single line per tool call\",\n    ),\n] = True\n```\n\n## Desired Behavior\n- Default to quiet mode (verbose=False)\n- Users must specify `--verbose` or `-v` to get verbose output\n- Remove the `--quiet`/`-q` flag entirely since quiet is now the default\n\n## Files\n- src/cli.py\n\n## Acceptance Criteria\n- [ ] CLI defaults to quiet mode\n- [ ] `--verbose` / `-v` enables verbose output\n- [ ] `--quiet` / `-q` flags are removed\n- [ ] Help text updated accordingly\n- [ ] Tests updated to reflect new default","status":"closed","priority":3,"issue_type":"task","created_at":"2025-12-27T17:54:13.041418656Z","updated_at":"2025-12-28T05:08:24.629647587Z","closed_at":"2025-12-28T05:08:24.629647587Z","close_reason":"Closed"}
{"id":"mala-4k1p","title":"[P2] Avoid permanently skipping real e2e tests with misleading reason","description":"## Review Finding\n\n**Priority:** P2\n**Trigger:** run_end\n**Baseline:** bf7d0ad2..102e65c8\n**Location:** tests/e2e/test_lock_sdk.py:174-180\n\n---\n\nThree async e2e tests are now unconditionally skipped. These aren’t flaky; they’re disabled.\n\nConcrete impact: `TestAgentAcquiresLocks.test_agent_runs_lock_try_script`, `TestAgentHandlesContention.test_agent_detects_blocked_file`, and `TestMultiAgentWithSDK.test_sequential_agents_handoff_lock` will never run in any environment, so regressions in the script/SDK integration paths can slip through.\n\nAlso, the skip reason is misleading: these tests already run under pytest/uv context and invoke the SDK; the issue seems to be about LLM determinism, not “require uv run environment”. If the intent is to skip only in certain environments, gate the skip on a condition (e.g., missing CLI auth/tooling) similar to the existing `require_claude_cli_auth` fixture, or downgrade to `xfail` with a tracked issue.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-11T06:40:16.303099591Z","created_by":"cyou","updated_at":"2026-01-11T06:59:15.906300373Z","closed_at":"2026-01-11T06:59:15.906300373Z","close_reason":"Closed","labels":["auto_generated","cumulative-review","fp:ce0acc0a094d1196","review_finding","trigger:run_end"]}
{"id":"mala-4l0p","title":"[Review] 1 non-blocking finding from mala-gc5l","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-gc5l\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Use optional newline in fallback closing fence regex\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/domain/prompts.py:154\n\nThe updated regex `\\n[ \\t]*\\`\\`\\`[ \\t]*\\Z` requires a mandatory newline before the closing code fence. This is a regression from the previous `\\n?` behavior and will fail to strip the closing fence in compact blocks like `\\`\\`\\`text\\ncontent\\`\\`\\``. Consider using `\\n?[ \\t]*\\`\\`\\`[ \\t]*\\Z` to maintain support for both indented fences and compact, single-line-style blocks.\n\n---\n\n\u003c!-- fp:a8617e64c86fad62 --\u003e\n\u003c!-- review_finding:3d26cb7abdac --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T20:44:15.327218134Z","created_by":"cyou","updated_at":"2026-01-03T20:48:07.11297273Z","closed_at":"2026-01-03T20:48:07.11297273Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-gc5l"]}
{"id":"mala-4ls","title":"Lock key: hash canonical paths + repo namespace","description":"Problem: Lock filenames are derived by replacing '/' with '_' (scripts + src/tools/locking.py). This causes collisions (e.g., 'a/b' vs 'a_b') and cross-repo contention. Fix by hashing a canonical path (absolute or repo-root+relative) and namespacing locks per repo/cwd.\\n\\nScope/files: scripts/lock-try.sh, lock-check.sh, lock-holder.sh, lock-release.sh, lock-release-all.sh, src/tools/locking.py, tests in tests/test_lock_scripts.py and tests/test_lock_integration.py (and any necessary updates). Consider also centralizing LOCK_DIR/SCRIPTS_DIR if needed.\\n\\nAcceptance criteria:\\n- Lock key is collision-resistant (e.g., sha256 of canonical path).\\n- Locks for different repos/paths do not collide.\\n- Lock holder and cleanup continue to work with AGENT_ID.\\n- Tests updated/added to cover collision case and repo namespace case.\\n\\nTDD: Add/adjust tests first to demonstrate collision and then pass after change.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-25T21:37:59.249680035Z","updated_at":"2025-12-26T00:01:08.029869568Z","closed_at":"2025-12-26T00:01:08.029869568Z","close_reason":"Closed"}
{"id":"mala-4o4","title":"Commit partial progress and mark needs-continuation","status":"tombstone","priority":2,"issue_type":"epic","created_at":"2025-12-26T00:55:42.724613481Z","updated_at":"2025-12-26T00:56:32.195455786Z","deleted_at":"2025-12-26T00:56:32.195455786Z","deleted_by":"daemon","delete_reason":"delete","original_type":"epic"}
{"id":"mala-4ov","title":"Metrics and logging for multi-agent sessions","description":"Add metrics and logging for multi-agent sessions (counts, durations, success/failure rates, queue depth) to make runs observable and debuggable.","status":"closed","priority":4,"issue_type":"epic","created_at":"2025-12-26T00:55:42.866840213Z","updated_at":"2025-12-26T18:28:00.511651865Z","closed_at":"2025-12-26T18:28:00.511651865Z","close_reason":"Closed per user request"}
{"id":"mala-4qjm","title":"Epic: Consolidate duplicated lock pattern in epic_verifier","description":"## Summary\nLock acquisition with `wait_for_lock` and release with `lp.unlink()` appears in three locations with nearly identical try/finally patterns. The `try: from src.infra.tools.locking import ...` pattern is repeated each time.\n\n## Primary Files\n- `src/infra/epic_verifier.py:428-449`\n- `src/infra/epic_verifier.py:807-831`\n- `src/infra/epic_verifier.py:898-911`\n\n## Category\nDuplication\n\n## Source\nclaude\n\n## Fix Approach\nCreate an `@asynccontextmanager` helper `epic_verify_lock(epic_id, repo_path)` that encapsulates acquisition, timeout handling, and cleanup. Use it in `verify_and_close_eligible` and `verify_epic_with_options`.\n\n## Non-Goals\n- Changing lock timeout values\n- Introducing distributed locking\n\n## Acceptance Criteria\n- [ ] Lock handling consolidated into one async context manager\n- [ ] All three lock sites use the same helper\n- [ ] Lock behavior unchanged\n\n## Test Plan\n- Unit test for context manager with mock lock file operations\n- Integration test verifying concurrent epic verification respects locks\n\n## Agent Notes\nThe lock timeout is configured via environment variable; ensure the helper accepts timeout as parameter.","status":"closed","priority":2,"issue_type":"epic","created_at":"2026-01-03T05:48:39.956106494Z","created_by":"cyou","updated_at":"2026-01-03T18:07:40.962054299Z","closed_at":"2026-01-03T18:07:40.962054299Z","close_reason":"Closed"}
{"id":"mala-4qjm.1","title":"Consolidate duplicated lock pattern in epic_verifier.py into async context manager","description":"## Problem\n\nLock acquisition with `wait_for_lock` and release with `lp.unlink()` appears in two locations with nearly identical try/finally patterns:\n\n1. **`verify_and_close_eligible()`** (lines 428-449 acquisition, 518-530 release):\n   - Used within a for loop over eligible epics\n   - On lock failure: `continue` to skip the epic\n\n2. **`verify_epic_with_options()`** (lines 807-831 acquisition, 898-911 release):\n   - Used for single epic verification\n   - On lock failure: returns empty `EpicVerificationResult`\n\n### Code Duplication\n\n**Acquisition pattern (~20 lines, duplicated):**\n```python\nlock_key = f\"epic_verify:{epic_id}\"\nlock_agent_id = f\"epic_verifier_{os.getpid()}\"\ntry:\n    from src.infra.tools.locking import wait_for_lock\n    acquired = await asyncio.to_thread(\n        wait_for_lock,\n        lock_key,\n        lock_agent_id,\n        str(self.repo_path),\n        EPIC_VERIFY_LOCK_TIMEOUT_SECONDS,\n    )\n    if not acquired:\n        # ... failure handling differs\nexcept ImportError:\n    pass\n```\n\n**Release pattern (13 lines, identical):**\n```python\nif self.lock_manager is not None and lock_key is not None:\n    try:\n        from src.infra.tools.locking import get_lock_holder, lock_path\n        repo_ns = str(self.repo_path)\n        lp = lock_path(lock_key, repo_ns)\n        if lp.exists() and get_lock_holder(lock_key, repo_ns) == lock_agent_id:\n            lp.unlink(missing_ok=True)\n    except ImportError:\n        pass\n```\n\n## Solution\n\nCreate an `@asynccontextmanager` helper that encapsulates acquisition, timeout handling, and cleanup:\n\n```python\n@asynccontextmanager\nasync def epic_verify_lock(\n    epic_id: str,\n    repo_path: Path,\n    lock_manager: LockManager | None,\n) -\u003e AsyncIterator[bool]:\n    \"\"\"Acquire per-epic verification lock with automatic cleanup.\n    \n    Yields True if lock acquired (or locking unavailable), False if timed out.\n    Caller decides how to handle False (skip, return empty result, etc.)\n    \"\"\"\n    if lock_manager is None:\n        yield True\n        return\n    \n    lock_key = f\"epic_verify:{epic_id}\"\n    lock_agent_id = f\"epic_verifier_{os.getpid()}\"\n    acquired = False\n    \n    try:\n        from src.infra.tools.locking import wait_for_lock\n        acquired = await asyncio.to_thread(\n            wait_for_lock,\n            lock_key,\n            lock_agent_id,\n            str(repo_path),\n            EPIC_VERIFY_LOCK_TIMEOUT_SECONDS,\n        )\n    except ImportError:\n        acquired = True  # No locking available, proceed without lock\n    \n    try:\n        yield acquired\n    finally:\n        if acquired:\n            try:\n                from src.infra.tools.locking import get_lock_holder, lock_path\n                repo_ns = str(repo_path)\n                lp = lock_path(lock_key, repo_ns)\n                if lp.exists() and get_lock_holder(lock_key, repo_ns) == lock_agent_id:\n                    lp.unlink(missing_ok=True)\n            except ImportError:\n                pass\n```\n\n## Implementation Steps\n\n1. Add the `epic_verify_lock` async context manager function\n2. Refactor `verify_and_close_eligible()` to use the context manager\n3. Refactor `verify_epic_with_options()` to use the context manager\n4. Run tests to verify no regression\n\n## Affected Code\n\n- `/home/cyou/mala/src/infra/epic_verifier.py`\n\n## Testing\n\n- Existing tests should pass with no changes\n- Lock behavior should remain identical","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T05:54:19.965877799Z","created_by":"cyou","updated_at":"2026-01-03T06:33:23.816684677Z","closed_at":"2026-01-03T06:33:23.816684677Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-4qjm.1","depends_on_id":"mala-4qjm","type":"parent-child","created_at":"2026-01-03T17:57:27.20717536Z","created_by":"daemon"}]}
{"id":"mala-4s4t","title":"[P2] Avoid duplicate source of truth for run_end in injected config","description":"## Review Finding\n\n**Priority:** P2\n**Trigger:** run_end\n**Baseline:** f577f175..3de3735d\n**Location:** src/domain/validation/e2e.py:458-462\n\n---\n\nThe injected `run_end` block duplicates configuration from `tests/fixtures/e2e-fixture/mala.yaml`. If the fixture is updated (e.g., coverage args), this injection will silently revert those changes during E2E runs. Consider parsing the YAML (`yaml.safe_load`) and merging the `session_end` block programmatically instead of appending raw string overrides.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-11T04:44:09.515097223Z","created_by":"cyou","updated_at":"2026-01-11T04:48:41.598474994Z","closed_at":"2026-01-11T04:48:41.598474994Z","close_reason":"Closed","labels":["auto_generated","cumulative-review","fp:0ae28757ff9946de","review_finding","trigger:run_end"]}
{"id":"mala-4w7","title":"Epic: Track A Shared-Worktree Hardening","description":"Context:\n- Umbrella epic for Track A (A1–A6) tasks from docs/coordination-plan-codex.md.\n\nScope:\n- In: group all Track A tasks under this epic.\n- Out: no implementation changes.\n\nAcceptance Criteria:\n- All Track A issues are linked as children (directly or via sub-epics).\n\nTest Plan:\n- N/A (coordination-only).\n\nNotes for Agent:\n- Keep A3 as a sub-epic under this umbrella.","status":"closed","priority":1,"issue_type":"epic","created_at":"2025-12-26T16:47:48.086896295Z","updated_at":"2025-12-26T17:15:07.704095723Z","closed_at":"2025-12-26T17:15:07.704095723Z","close_reason":"All children completed"}
{"id":"mala-503","title":"Fix run-level E2E status when E2E is skipped","description":"Run-level validation currently records e2e_passed True/False even when E2E was not executed (e.g., disable_validations/run_e2e false or spec omits E2E). This makes run metadata misleading. Update run-level metadata to reflect actual E2E execution: set e2e_passed based on E2E run result, or None when E2E skipped. Affects src/orchestrator.py around run-level validation recording.","status":"closed","priority":2,"issue_type":"bug","created_at":"2025-12-27T22:40:50.347545047Z","updated_at":"2025-12-28T05:11:42.096301671Z","closed_at":"2025-12-28T05:11:42.096301671Z","close_reason":"Closed"}
{"id":"mala-51q","title":"Epic: Architecture refactors (2025-12-28 review)","description":"## Context\nArchitecture review identified 10 issues across the codebase. Primary hotspot is orchestrator.py with CCN=81 in run() method.\n\n## Goals\n- Reduce orchestrator.py complexity (target: \u003c500 LOC, no function CCN \u003e 20)\n- Eliminate duplicate code in BeadsClient and QualityGate\n- Improve testability through better separation of concerns\n\n## Child Issues\nSee linked children for individual refactors.\n\n## Acceptance Criteria\n- All child issues completed\n- No function with CCN \u003e 20\n- Existing tests pass","status":"closed","priority":1,"issue_type":"epic","created_at":"2025-12-28T07:20:17.041646469Z","updated_at":"2025-12-28T16:39:47.541056148Z","closed_at":"2025-12-28T16:39:47.541056148Z","close_reason":"All children completed"}
{"id":"mala-51q.1","title":"BeadsClient: extract shared filtering logic from get_ready methods","description":"## Context\n- `get_ready_async` (lines 298-420) and `get_ready_issues_async` (lines 423-538) share ~80% logic\n- Filtering, WIP handling, epic filtering, blocked epic exclusion, and sorting are duplicated\n- Only return type differs (list[str] vs list[dict])\n- Current implementations have subtle ordering differences that could diverge\n\n## Primary Files\n- src/beads_client.py\n\n## Scope\n**In:** Extract `_fetch_and_filter_issues(...)` returning list[dict]; refactor both public methods to use it\n**Out:** No public API changes; no behavioral changes\n\n## Acceptance Criteria\n- Single private method handles all filtering/sorting logic\n- `get_ready_async` extracts IDs from shared result\n- `get_ready_issues_async` returns shared result directly\n- Identical ordering between both methods (add test)\n\n## Test Plan\n- Run `test_beads_client.py` suite\n- Add test verifying `get_ready_async` and `get_ready_issues_async` return consistent ordering\n\n## Notes for Agent\n- Watch for subtle differences in lines 404-418 vs 524-534 (sorting after focus grouping)\n- Both methods must call the same parent_epic enrichment logic","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-28T07:20:38.570019133Z","updated_at":"2025-12-28T07:35:23.973092937Z","closed_at":"2025-12-28T07:35:23.973092937Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-51q.1","depends_on_id":"mala-51q","type":"parent-child","created_at":"2025-12-28T07:20:38.577740681Z","created_by":"daemon"}]}
{"id":"mala-51q.10","title":"QualityGate: remove deprecated VALIDATION_PATTERNS and legacy parsing methods","description":"## Context\n- `VALIDATION_PATTERNS` class variable at `quality_gate.py:165-170` is marked \"DEPRECATED: Use detection_pattern from ValidationSpec commands instead\"\n- `_get_fallback_pattern()` still uses these deprecated patterns for backward compatibility\n- Legacy methods `parse_validation_evidence()` and `parse_validation_evidence_from_offset()` still exist\n- Production code uses `parse_validation_evidence_with_spec()` exclusively (per mala-yg9.7 implementation)\n\n## Primary Files\n- src/quality_gate.py\n\n## Scope\n**In:** Verify no production code uses legacy methods; remove `VALIDATION_PATTERNS`, `_get_fallback_pattern()`, and legacy parsing methods if safe\n**Out:** Do not change spec-driven parsing logic\n\n## Acceptance Criteria\n- `VALIDATION_PATTERNS` removed from QualityGate\n- `_get_fallback_pattern()` removed\n- Legacy `parse_validation_evidence()` and `parse_validation_evidence_from_offset()` removed or clearly deprecated\n- All tests pass\n- Grep confirms no production usages of removed methods\n\n## Test Plan\n- Grep for usages of deprecated methods in production code (not tests)\n- `uv run pytest tests/test_quality_gate.py`\n\n## Notes for Agent\n- This depends on mala-51q.2 (JSONL iterator extraction) since that touches same file\n- Check that orchestrator passes spec to all QualityGate calls\n- If any production path still needs legacy methods, add deprecation warnings instead of removing","status":"closed","priority":3,"issue_type":"chore","created_at":"2025-12-28T07:23:46.438342948Z","updated_at":"2025-12-28T16:27:01.364829948Z","closed_at":"2025-12-28T16:27:01.364829948Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-51q.10","depends_on_id":"mala-51q","type":"parent-child","created_at":"2025-12-28T07:23:46.438965911Z","created_by":"daemon"},{"issue_id":"mala-51q.10","depends_on_id":"mala-51q.2","type":"blocks","created_at":"2025-12-28T07:23:51.988808262Z","created_by":"daemon"}]}
{"id":"mala-51q.2","title":"QualityGate: extract reusable JSONL log iterator","description":"## Context\n- Three methods iterate JSONL logs with nearly identical patterns: `parse_issue_resolution_from_offset`, `parse_validation_evidence_from_offset`, `parse_validation_evidence_with_spec`\n- Each has byte-offset tracking, JSON parsing, error handling duplicated\n- CCN ranges from 15-31 due to deep nesting\n\n## Primary Files\n- src/quality_gate.py\n\n## Scope\n**In:** Create `_iter_jsonl_entries(log_path, offset) -\u003e Iterator[(entry, line_len, offset)]`; refactor all three methods to use it\n**Out:** Do not remove deprecated methods; no behavioral changes\n\n## Acceptance Criteria\n- Single JSONL iteration implementation\n- Each parser method is \u003c30 lines\n- Offset tracking works correctly (test with edge cases)\n\n## Test Plan\n- Run `test_quality_gate.py` suite\n- Verify offset tracking with empty lines, binary data, truncated files\n\n## Notes for Agent\n- Must read in binary mode for accurate byte offsets\n- Handle UnicodeDecodeError gracefully in iterator\n- Consider yielding a named tuple or dataclass for clarity","notes":"Quality gate failed: Quality gate failed: Missing validation commands: ty check, ruff format, pytest, ruff check; No progress: commit unchanged and no new validation evidence\nLog: /home/cyou/.claude/projects/-home-cyou-mala/97d62139-ad7f-4f84-bf2d-72364026f051.jsonl","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-28T07:20:55.555078495Z","updated_at":"2025-12-28T15:32:13.909523879Z","closed_at":"2025-12-28T15:32:13.909523879Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-51q.2","depends_on_id":"mala-51q","type":"parent-child","created_at":"2025-12-28T07:20:55.562506778Z","created_by":"daemon"}]}
{"id":"mala-51q.3","title":"Orchestrator: extract result handling into separate module","description":"## Context\n- Lines 1300-1483 in `run()` handle completed task processing\n- This includes evidence parsing, validation running, metadata recording, and issue closing\n- Extracting this reduces orchestrator complexity significantly\n\n## Primary Files\n- src/orchestrator.py\n- src/result_handler.py (new)\n\n## Scope\n**In:** Create `src/result_handler.py` with `handle_completed_result(...)` function; move result processing logic there\n**Out:** Do not change external behavior; do not refactor run_implementer yet\n\n## Acceptance Criteria\n- Result handling logic in dedicated module\n- orchestrator.py `run()` method calls `handle_completed_result`\n- All existing tests pass\n\n## Test Plan\n- Run `test_orchestrator.py` suite\n- Verify successful and failed issue handling works identically\n\n## Notes for Agent\n- Will need access to quality_gate, beads client, and run_metadata\n- Consider passing these as parameters or a context object\n- Keep the interface simple - don't over-abstract yet","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-28T07:20:55.629561739Z","updated_at":"2025-12-28T07:52:53.19473233Z","closed_at":"2025-12-28T07:52:53.19473233Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-51q.3","depends_on_id":"mala-51q","type":"parent-child","created_at":"2025-12-28T07:20:55.630113922Z","created_by":"daemon"}]}
{"id":"mala-51q.4","title":"spec_runner: split _execute_spec_commands into focused functions","description":"## Context\n- `_execute_spec_commands` (lines 386-525) has CCN=17 and mixes command execution, logging, coverage, and E2E\n- Early returns scattered throughout make success path hard to follow\n- `_check_coverage` is already extracted but invoked inline\n\n## Primary Files\n- src/validation/spec_runner.py\n\n## Scope\n**In:** Extract `_run_commands(spec, cwd, env, log_dir) -\u003e list[ValidationStepResult]`; use pipeline pattern in `_run_spec_sync`\n**Out:** Do not change ValidationResult structure\n\n## Acceptance Criteria\n- Each step function has single responsibility\n- Success path is linear: run_commands -\u003e check_coverage -\u003e run_e2e -\u003e build_result\n- No function over 50 lines\n\n## Test Plan\n- Existing spec_runner tests pass\n- Add test for command failure early exit\n\n## Notes for Agent\n- Coverage check already extracted; just needs cleaner invocation\n- Consider returning early failure as a result, not using exceptions","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-28T07:21:17.14628441Z","updated_at":"2025-12-28T08:40:39.117367362Z","closed_at":"2025-12-28T08:40:39.117367362Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-51q.4","depends_on_id":"mala-51q","type":"parent-child","created_at":"2025-12-28T07:21:17.155201727Z","created_by":"daemon"}]}
{"id":"mala-51q.5","title":"Unify RetryState between orchestrator and lifecycle modules","description":"## Context\n- Two RetryState classes exist: orchestrator uses `attempt`, lifecycle uses `gate_attempt`\n- Lines 854-863 in run_implementer manually sync between them\n- lifecycle.py notes this as \"compatibility\" but it creates confusion\n- **Bridge functions are dead code**: `from_orchestrator_retry_state()` and `to_orchestrator_retry_state_fields()` (lines 434-489) are only used in tests, not in production\n\n## Primary Files\n- src/orchestrator.py\n- src/lifecycle.py\n- tests/test_lifecycle.py\n\n## Scope\n**In:** Use lifecycle.py's RetryState as canonical source; remove orchestrator's duplicate; eliminate sync code; remove unused bridge functions\n**Out:** Do not change lifecycle state machine semantics\n\n## Acceptance Criteria\n- Single RetryState definition (in lifecycle.py)\n- No manual state syncing in run_implementer\n- Bridge functions `from_orchestrator_retry_state()` and `to_orchestrator_retry_state_fields()` removed\n- Tests updated to construct RetryState directly\n- All tests pass\n\n## Test Plan\n- Run lifecycle and orchestrator test suites\n- `pytest tests/test_lifecycle.py` passes after bridge function removal\n\n## Notes for Agent\n- Check if any code outside run_implementer uses orchestrator's RetryState\n- May need to import RetryState from lifecycle in orchestrator\n- Bridge functions at lifecycle.py:434-489 are dead code - remove them\n- Update test_lifecycle.py to construct RetryState directly instead of using bridge","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-28T07:21:17.236197567Z","updated_at":"2025-12-28T08:07:08.787455409Z","closed_at":"2025-12-28T08:07:08.787455409Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-51q.5","depends_on_id":"mala-51q","type":"parent-child","created_at":"2025-12-28T07:21:17.236515373Z","created_by":"daemon"},{"issue_id":"mala-51q.5","depends_on_id":"mala-51q.3","type":"blocks","created_at":"2025-12-28T07:22:06.952208101Z","created_by":"daemon"}]}
{"id":"mala-51q.6","title":"cli.py: eliminate runtime imports with lazy-loading pattern","description":"## Context\n- CLI imports BeadsClient and MalaOrchestrator inside run() function body (lines 385-387)\n- Done to defer SDK loading until after bootstrap()\n- Makes dependencies unclear and complicates testing\n\n## Primary Files\n- src/cli.py\n\n## Scope\n**In:** Use `__getattr__` lazy-loading pattern at module level; or create AppContext returned by bootstrap\n**Out:** Do not change bootstrap() external behavior\n\n## Acceptance Criteria\n- Dependencies are explicit (either in signatures or module-level with lazy loading)\n- No runtime imports inside command functions\n- CLI still works with and without BRAINTRUST_API_KEY\n\n## Test Plan\n- Verify CLI commands work with/without env vars\n- Test imports don't trigger SDK loading prematurely\n\n## Notes for Agent\n- The bootstrap() pattern exists to ensure Braintrust SDK patching before claude_agent_sdk import\n- A simple approach: use module-level `_orchestrator_module = None` and lazy-load on first access","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-28T07:21:34.83971Z","updated_at":"2025-12-28T08:28:48.428025328Z","closed_at":"2025-12-28T08:28:48.428025328Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-51q.6","depends_on_id":"mala-51q","type":"parent-child","created_at":"2025-12-28T07:21:34.846347497Z","created_by":"daemon"}]}
{"id":"mala-51q.7","title":"Orchestrator: cache per-issue validation spec","description":"## Context\n- `build_validation_spec()` called 4+ times in run() with identical arguments\n- Lines 298-303, 1304-1309, 1402-1407 all build the same PER_ISSUE spec\n- Minor inefficiency but also DRY violation\n\n## Primary Files\n- src/orchestrator.py\n\n## Scope\n**In:** Build per-issue spec once at start of run loop; reuse throughout\n**Out:** Do not change spec building logic\n\n## Acceptance Criteria\n- Single spec construction per scope per run\n- No behavioral change\n\n## Test Plan\n- Verify spec attributes match current behavior\n\n## Notes for Agent\n- Create spec before entering main while loop\n- Pass spec to result handler if extracted\n- Depends on ORCH-RESULT being done first (same file)","notes":"Quality gate failed: Quality gate failed: No commit with bd-mala-51q.7 found after run baseline (stale commits from previous runs are rejected); Missing validation commands: ty check, ruff format, pytest, ruff check; No progress: commit unchanged and no new validation evidence\nLog: /home/cyou/.claude/projects/-home-cyou-mala/906253d2-918a-40da-a462-77f3d08b6361.jsonl","status":"closed","priority":3,"issue_type":"chore","created_at":"2025-12-28T07:21:34.910351945Z","updated_at":"2025-12-28T16:19:54.628873488Z","closed_at":"2025-12-28T16:19:54.628873488Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-51q.7","depends_on_id":"mala-51q","type":"parent-child","created_at":"2025-12-28T07:21:34.910655561Z","created_by":"daemon"},{"issue_id":"mala-51q.7","depends_on_id":"mala-51q.3","type":"blocks","created_at":"2025-12-28T07:22:06.969877016Z","created_by":"daemon"}]}
{"id":"mala-51q.8","title":"Orchestrator: move inline prompts to src/prompts/","description":"## Context\n- `GATE_FOLLOWUP_PROMPT`, `CODEX_REVIEW_FOLLOWUP_PROMPT`, `RUN_LEVEL_FIXER_PROMPT` are multi-line strings in orchestrator.py (lines 95-150)\n- Main implementer prompt correctly loads from file\n- Consistency improves maintainability\n\n## Primary Files\n- src/orchestrator.py\n- src/prompts/gate_followup.md (new)\n- src/prompts/review_followup.md (new)\n- src/prompts/fixer.md (new)\n\n## Scope\n**In:** Move three follow-up prompts to `src/prompts/`; load at module level like implementer prompt\n**Out:** Do not change prompt content\n\n## Acceptance Criteria\n- All prompts in `src/prompts/` directory\n- Orchestrator has no inline multi-line prompt strings\n- Prompts load correctly at import time\n\n## Test Plan\n- Verify prompts contain expected placeholders\n- Run integration test that triggers follow-up prompts (gate retry)","status":"closed","priority":3,"issue_type":"chore","created_at":"2025-12-28T07:21:50.973141103Z","updated_at":"2025-12-28T08:52:55.945821631Z","closed_at":"2025-12-28T08:52:55.945821631Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-51q.8","depends_on_id":"mala-51q","type":"parent-child","created_at":"2025-12-28T07:21:50.973469208Z","created_by":"daemon"},{"issue_id":"mala-51q.8","depends_on_id":"mala-51q.3","type":"blocks","created_at":"2025-12-28T07:22:06.981091579Z","created_by":"daemon"}]}
{"id":"mala-51q.9","title":"Remove LegacyValidationRunner after verifying no external usage","description":"## Context\n- `legacy_runner.py` (240 LOC) duplicates spec_runner functionality\n- **17 usages** of `ValidationRunner` in tests (`test_validation.py`) - not just one\n- Orchestrator now uses `SpecValidationRunner.run_spec()` directly\n- `ValidationRunner` is marked deprecated in docstrings but still re-exported from `__init__.py`\n\n## Primary Files\n- src/validation/legacy_runner.py\n- src/validation/runner.py  \n- src/validation/__init__.py\n- tests/test_validation.py\n\n## Scope\n**In:** Migrate all 17 test usages from `ValidationRunner(repo_path, config)` to `SpecValidationRunner(repo_path)`; remove `ValidationRunner` facade; remove `LegacyValidationRunner`; update exports\n**Out:** Do not change ValidationResult structure; do not refactor SpecValidationRunner itself\n\n## Acceptance Criteria\n- All tests use `SpecValidationRunner` directly\n- `ValidationRunner` facade class removed\n- `LegacyValidationRunner` removed\n- Exports updated in `__init__.py`\n- Coverage remains at 86%+\n\n## Test Plan\n- All existing tests pass after migration\n- Grep confirms no ValidationRunner usages remain\n\n## Notes for Agent\n- Check for any plugins/external tools importing from mala.validation\n- Many tests construct `ValidationRunner(tmp_path, config)` - each needs migration to spec-based API\n- May need to convert ValidationConfig usage to ValidationSpec","notes":"Quality gate failed: Quality gate failed: Missing validation commands: ty check, ruff format, pytest, ruff check; No progress: commit unchanged and no new validation evidence\nLog: /home/cyou/.claude/projects/-home-cyou-mala/59dc6ec6-5d59-4475-82ca-246d6a8c1a30.jsonl","status":"closed","priority":3,"issue_type":"chore","created_at":"2025-12-28T07:21:51.030134159Z","updated_at":"2025-12-28T16:39:47.521533012Z","closed_at":"2025-12-28T16:39:47.521533012Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-51q.9","depends_on_id":"mala-51q","type":"parent-child","created_at":"2025-12-28T07:21:51.030678912Z","created_by":"daemon"}]}
{"id":"mala-52gm","title":"pass more context to cerberus for reviews, incl. ability to add author context, issue context, past review comments, etc.","status":"tombstone","priority":2,"issue_type":"feature","created_at":"2026-01-04T05:29:47.272112394Z","created_by":"cyou","updated_at":"2026-01-06T22:52:10.761723406Z","deleted_at":"2026-01-06T22:52:10.761723406Z","deleted_by":"batch delete","delete_reason":"batch delete","original_type":"feature"}
{"id":"mala-53pv","title":"add explore subagent and instruct its use in implementer prompt","status":"tombstone","priority":2,"issue_type":"feature","created_at":"2026-01-04T02:40:05.81008898Z","created_by":"cyou","updated_at":"2026-01-06T22:52:10.761723406Z","deleted_at":"2026-01-06T22:52:10.761723406Z","deleted_by":"batch delete","delete_reason":"batch delete","original_type":"feature"}
{"id":"mala-53v","title":"Quality gate: enforce full validation suite evidence","description":"Context:\n- src/quality_gate.py: ValidationEvidence.has_minimum_validation() currently passes with pytest OR ruff check+format; missing_commands() omits uv sync and ty check.\n- Implementer prompt/README require the full suite (uv sync, pytest, ruff check/format, ty check), so the gate can pass incomplete validation.\n\nScope:\n- In: require evidence for the full validation suite (uv sync, pytest, ruff check, ruff format, ty check) and update tests accordingly.\n- Out: changing prompt/docs or redefining the suite beyond the current prompt requirements.\n\nAcceptance Criteria:\n- Gate fails when any required command is missing, even if pytest or ruff ran.\n- Failure reasons list all missing commands including uv sync and ty check.\n- Tests cover missing uv sync and ty check scenarios.\n\nTest Plan:\n- TDD: add failing tests in tests/test_quality_gate.py for missing uv sync/ty check, implement minimal fix, run: uv run pytest tests/test_quality_gate.py\n\nNotes for Agent:\n- Keep parsing patterns consistent; only tighten required-command logic.","status":"closed","priority":1,"issue_type":"bug","created_at":"2025-12-26T21:43:43.706215554Z","updated_at":"2025-12-26T22:39:21.901160405Z","closed_at":"2025-12-26T22:39:21.901160405Z","close_reason":"Closed"}
{"id":"mala-553g","title":"Fail fast on invalid trigger command refs at startup","description":"Spec requires unknown trigger command refs to be caught during config load / spec build, not at runtime when a trigger fires. Add validation in config loader or merged-config validation to ensure all TriggerCommandRef.ref values exist in effective base pool. Files: src/domain/validation/config_loader.py, src/domain/validation/spec.py, src/pipeline/run_coordinator.py (remove reliance on runtime exception if desired).","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-09T16:53:08.809158831Z","created_by":"cyou","updated_at":"2026-01-09T21:34:16.167125332Z","closed_at":"2026-01-09T21:34:16.167125332Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-553g","depends_on_id":"mala-642f","type":"blocks","created_at":"2026-01-09T16:54:17.482246827Z","created_by":"cyou"},{"issue_id":"mala-553g","depends_on_id":"mala-smaj","type":"parent-child","created_at":"2026-01-09T16:55:01.462893208Z","created_by":"cyou"}]}
{"id":"mala-59n","title":"Fix log indentation and add no-truncation option","description":"## Problem\nLog output has inconsistent indentation and lines are truncated:\n```\n00:43:23 ◌ Waiting for 1 agent(s)...\n  [mala-l7r] ⚙ Bash {'command': 'bd show mala-l7r', 'description': 'Vi\n    [mala-l7r] Now let me understand the codebase structure...\n  [mala-l7r] ⚙ Bash {'command': 'ls -la /home/cyou/mala/src/', 'descri\n```\n\nIssues:\n1. **Inconsistent indentation**: Some lines have 2-space indent, others have 4-space indent before `[agent-id]`\n2. **Truncation**: Lines are cut off (e.g., `'Vi`, `'descri`) without option to see full content\n\n## Scope/files\n- `src/logging/console.py` or equivalent logging module\n- `src/cli.py` or `src/orchestrator.py` for CLI flag\n\n## Work\n1. Fix indentation to be consistent (likely all agent output should have same base indent)\n2. Add `--no-truncate` or `--verbose` CLI flag to disable line truncation\n3. Consider making truncation width configurable\n\n## Acceptance criteria\n- All agent log lines have consistent indentation\n- New flag allows viewing full log lines without truncation\n- Default behavior (truncated) still works","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-26T00:47:13.094441264Z","updated_at":"2025-12-26T00:57:22.727412832Z","closed_at":"2025-12-26T00:57:22.727412832Z","close_reason":"Closed"}
{"id":"mala-5aa","title":"Refactor: consolidate locking into tools/locking.py","description":"## Context\nLocking behavior is split between `src/filelock.py` and shell scripts in `scripts/`. `src/main.py` directly imports `LOCK_DIR` and `release_all_locks` from `filelock.py` and also defines `make_stop_hook` which relies on lock scripts. The plan is to consolidate locking into `src/tools/locking.py`.\n\n## Scope\nCreate `src/tools/locking.py` only. Do **not** modify `src/main.py` or tests in this issue; integration will wire it up later.\n\n### Files\n- Add: `src/tools/locking.py`\n- (Optional) Modify or remove: `src/filelock.py`\n\n## Requirements\n- `tools/locking.py` should export:\n  - `LOCK_DIR`\n  - `release_all_locks()` (existing behavior)\n  - `cleanup_agent_locks(agent_id)` (existing behavior from `MalaOrchestrator`)\n  - `make_stop_hook(agent_id)` (existing behavior)\n- Decide one:\n  - Keep `src/filelock.py` as a thin re-export for backward compatibility, OR\n  - Remove `src/filelock.py` and update all imports (handled later in integration).\n\n## TDD Guidance\n- If you touch tests in this issue, stop and defer them to integration (tests should be updated there).\n\n## Acceptance Criteria\n- `src/tools/locking.py` exists with the required exports.\n- No other files are modified in this issue (except optional `src/filelock.py`).\n","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-25T19:54:51.176735691Z","updated_at":"2025-12-25T20:10:14.682614354Z","closed_at":"2025-12-25T20:10:14.682614354Z","close_reason":"Closed"}
{"id":"mala-5cz","title":"Skip uv sync when pyproject.toml unchanged","description":"## Context\nCurrently `uv sync` runs on every validation cycle, even when dependencies haven't changed.\n\n## Goal\nOnly run `uv sync` when `pyproject.toml` (or `uv.lock`) has been modified since the last sync.\n\n## Approach\n- Track last sync timestamp or hash of pyproject.toml/uv.lock\n- Compare before running sync\n- Skip if unchanged, reducing validation overhead\n\n## Acceptance Criteria\n- `uv sync` only runs when dependency files have changed\n- First run still syncs (no prior state)\n- Force sync option available if needed","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-27T00:55:36.999060635Z","updated_at":"2025-12-27T01:09:49.406031776Z","closed_at":"2025-12-27T01:09:49.406031776Z","close_reason":"Closed"}
{"id":"mala-5eg","title":"Enforce full validation in quality gate (no file-specific checks)","description":"## Problem\n\n**CRITICAL GAP**: The orchestrator does NOT actually run validation after agent commits.\n\nCurrent flow:\n1. Agent runs, does its own validation (or games it with `ty check src/specific_file.py`)\n2. `QualityGate` parses JSONL logs for regex matches like `r\"\\b(uvx\\s+)?ty\\s+check\\b\"`\n3. If `ty check` was **mentioned** (even with a path argument) → gate passes\n\nThe `QualityGate` only checks IF commands were mentioned, NOT:\n- Whether they ran on the full codebase (vs `ty check src/foo.py`)\n- Whether they passed (exit code 0)\n- Whether the output showed 0 errors\n\nMeanwhile, `ValidationRunner` exists with proper worktree-based validation but is **not wired up** to the orchestrator flow.\n\n## Solution\n\n### Option A: Wire up ValidationRunner (Recommended)\nAfter agent commits, orchestrator should:\n1. Call `ValidationRunner.validate_commit(commit_sha, issue_id)`\n2. This runs full validation in clean worktree including `uvx ty check`\n3. Gate fails if any validation step fails\n\n### Option B: Enhance QualityGate Regex (Partial Fix)\nUpdate `VALIDATION_PATTERNS[\"ty_check\"]` to reject:\n- Path arguments: `ty check src/`, `ty check tests/`\n- Truncation: `| head`, `| tail`\n- Non-zero exit detection from tool_result\n\nBut this is weaker - agents could still game it.\n\n## Root Cause Evidence\n\nFrom commit 0328a6e logs, agent ran:\n```\nuvx ty check 2\u003e\u00261 | head -20     # Truncated!\nuvx ty check src/orchestrator.py  # File-specific to avoid errors!\n```\n\nQualityGate regex matched these and passed the gate despite 62 type errors in test files.\n\n## Acceptance Criteria\n\n- [ ] ValidationRunner integrated into post-commit flow\n- [ ] Gate fails if `uvx ty check` returns non-zero\n- [ ] Gate fails if pytest/ruff/format fail\n- [ ] Tests verify full validation actually runs\n\n## Files\n- src/orchestrator.py (integrate ValidationRunner)\n- src/quality_gate.py (or deprecate log-parsing approach)","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-27T06:33:36.274895875Z","updated_at":"2025-12-27T06:36:30.720892188Z","closed_at":"2025-12-27T06:36:30.720892188Z","close_reason":"Duplicate of mala-yg9.2.4 which already covers wiring up ValidationRunner"}
{"id":"mala-5ie4","title":"Epic: Evidence Check Configuration","description":"Decouple evidence gate requirements from run command pool via a new `evidence_check:` config section.\n\n**Spec**: docs/2026-01-10-evidence-check-config-spec.md\n**Plan**: plans/2026-01-11-evidence-check-config-plan.md\n\n## Goals\n- Users explicitly declare which commands require evidence\n- No `evidence_check:` means no evidence required (BREAKING CHANGE)\n- Enables independent control of trigger execution vs. evidence requirements\n\n## Key Requirements (R1-R9)\n- R1: New `evidence_check:` config section with `required:` list\n- R2: Absent section = no evidence required\n- R3: Validate keys against resolved command map (preset + project merge)\n- R4: Pattern detection from resolved command string\n- R5: Both built-in (test, lint) and custom commands referenceable\n- R6: Set semantics for duplicates\n- R7: Scope filtering (E2E excluded from per-session gates)\n- R8: allow_fail affects failure handling, not evidence requirement\n- R9: Per-key attribution; single log can satisfy multiple keys\n\n## Files Modified\n- src/domain/validation/config.py\n- src/domain/validation/config_loader.py\n- src/domain/validation/spec.py\n- src/domain/evidence_check.py\n- src/pipeline/gate_runner.py\n\n## Test Files\n- tests/unit/domain/validation/test_config_loader.py\n- tests/unit/domain/test_spec.py\n- tests/unit/domain/test_evidence_check.py\n- tests/integration/domain/test_evidence_detection.py","status":"closed","priority":1,"issue_type":"epic","created_at":"2026-01-11T02:45:38.997760777Z","created_by":"cyou","updated_at":"2026-01-11T04:00:49.260485305Z","closed_at":"2026-01-11T04:00:49.260485305Z","close_reason":"Closed"}
{"id":"mala-5ie4.1","title":"[T001] [integration-path-test] Skeleton + Failing Integration Test for evidence_check config","description":"**Type**: task\n**Priority**: P2\n**Story**: Epic: Evidence Check Configuration\n**Parallel**: No (foundation task)\n**Primary Files**: config.py, config_loader.py, spec.py, test_evidence_detection.py\n**Subsystems**: domain/validation, tests/integration\n\n## Goal\nCreate compile-ready stubs for all evidence_check infrastructure and a failing integration test that exercises the end-to-end config flow.\n\n## Context\n- This is the TDD \"red\" phase - skeleton stubs + failing integration test\n- Following existing patterns: EvidenceCheckConfig mirrors other frozen dataclasses, parsing follows _parse_code_review_config() pattern\n- Integration test MUST fail because behavior is unimplemented, not because of missing imports/wiring\n\n## Scope\n**In**:\n- Add EvidenceCheckConfig dataclass stub to config.py\n- Add evidence_check field to ValidationConfig\n- Add evidence_required field to ValidationSpec\n- Add _parse_evidence_check_config() stub returning None to config_loader.py\n- Add \"evidence_check\" to _ALLOWED_TOP_LEVEL_FIELDS\n- Wire stub call into ValidationConfig.from_dict()\n- Write failing integration test for full flow (config parsing -\u003e preset merge -\u003e evidence check filtering)\n\n**Out**:\n- Actual parsing logic (T002)\n- Reference validation logic (T003)\n- Evidence filtering logic (T004)\n\n## Changes\n- `src/domain/validation/config.py` — Exists — Add EvidenceCheckConfig dataclass (frozen, slots, required: tuple[str, ...])\n- `src/domain/validation/config.py` — Exists — Add evidence_check: EvidenceCheckConfig | None = None to ValidationConfig\n- `src/domain/validation/spec.py` — Exists — Add evidence_required: tuple[str, ...] to ValidationSpec\n- `src/domain/validation/config_loader.py` — Exists — Add \"evidence_check\" to _ALLOWED_TOP_LEVEL_FIELDS\n- `src/domain/validation/config_loader.py` — Exists — Add _parse_evidence_check_config() stub returning None\n- `src/domain/validation/config_loader.py` — Exists — Wire into from_dict()\n- `tests/integration/domain/test_evidence_detection.py` — Exists — Add failing integration test\n\n## Wiring Map\n- mala.yaml `evidence_check:` → config_loader._parse_evidence_check_config() → ValidationConfig.evidence_check → spec.py build_validation_spec() → evidence_check.check_evidence_against_spec()\n\n## Acceptance Criteria\n- Project compiles and existing tests pass\n- EvidenceCheckConfig dataclass defined with required: tuple[str, ...] field\n- ValidationConfig has evidence_check: EvidenceCheckConfig | None field\n- ValidationSpec has evidence_required: tuple[str, ...] field\n- \"evidence_check\" in _ALLOWED_TOP_LEVEL_FIELDS\n- Integration test exists and FAILS for expected reason (behavior not implemented)\n\n## Verification\n- `uv run pytest -x` passes (existing tests)\n- `uv run pytest tests/integration/domain/test_evidence_detection.py -k evidence_check -x` fails with expected \"no evidence required\" assertion error (not import/syntax error)\n\n## Notes for Agent\n- Use @dataclass(frozen=True, slots=True) matching existing patterns\n- required field should use default_factory=tuple like other tuple fields\n- The integration test should parse a config with evidence_check.required: [test], run through preset merge, and assert evidence is checked (which will fail because filtering isn't implemented yet)\n- Keep stubs minimal - just enough to compile and show the failure path","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-11T02:46:06.090388172Z","created_by":"cyou","updated_at":"2026-01-11T03:20:12.348769528Z","closed_at":"2026-01-11T03:20:12.348769528Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-5ie4.1","depends_on_id":"mala-5ie4","type":"parent-child","created_at":"2026-01-11T02:46:06.091272387Z","created_by":"cyou"}]}
{"id":"mala-5ie4.2","title":"[T002] Implement evidence_check config parsing","description":"**Type**: task\n**Priority**: P2\n**Story**: Epic: Evidence Check Configuration\n**Parallel**: [P] (can run parallel to T003, T004 after T001)\n**Primary Files**: config_loader.py, test_config_loader.py\n**Subsystems**: domain/validation, tests/unit\n\n## Goal\nImplement the _parse_evidence_check_config() function following _parse_code_review_config() pattern, with full validation and unit tests.\n\n## Context\n- Following the established parsing pattern from _parse_code_review_config() (config_loader.py:499-722)\n- Handle type validation: evidence_check must be dict (not null/string/list)\n- Handle required field validation: must be list of strings (not null/non-list/non-string items)\n- Reject unknown fields within evidence_check section\n\n## Scope\n**In**:\n- Implement _parse_evidence_check_config() with full validation\n- Add unit tests for all 8 parsing test cases from plan\n- Handle edge cases: null section, empty object, null required, empty required\n\n**Out**:\n- Reference validation against command map (T003)\n- Evidence filtering logic (T004)\n\n## Changes\n- `src/domain/validation/config_loader.py` — Exists — Implement _parse_evidence_check_config() with validation logic\n- `tests/unit/domain/validation/test_config_loader.py` — Exists — Add 8 parsing unit tests\n\n## Acceptance Criteria\n- _parse_evidence_check_config() validates evidence_check is dict (rejects null, string, list)\n- _parse_evidence_check_config() validates required is list of strings (rejects non-list, non-string items)\n- _parse_evidence_check_config() rejects unknown fields\n- Returns None when section absent, EvidenceCheckConfig otherwise\n- Treats null/empty required as empty tuple\n\n## Verification\nRun these tests:\n```bash\nuv run pytest tests/unit/domain/validation/test_config_loader.py -k evidence_check -v\n```\n\n### Unit Tests (from plan tests 1-8)\n1. Parse valid `evidence_check.required: [test, lint]` → EvidenceCheckConfig with tuple\n2. Parse `evidence_check: {}` → EvidenceCheckConfig with empty required\n3. Parse `evidence_check.required: null` → EvidenceCheckConfig with empty required\n4. Reject `evidence_check: null` → config error\n5. Reject `evidence_check: \"string\"` → config error\n6. Reject `evidence_check.required: \"not-a-list\"` → config error\n7. Reject `evidence_check.required: [test, 1]` → config error (non-string in list)\n8. Reject unknown field in `evidence_check:` → config error\n\n## Notes for Agent\n- Follow _parse_code_review_config() pattern exactly for structure\n- Use ValidationError for errors, appending to errors list\n- Error messages should be user-friendly with context\n- Test edge case: empty string in required list (should be valid, empty string is still a string)","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-11T02:46:25.103159103Z","created_by":"cyou","updated_at":"2026-01-11T03:28:45.230462968Z","closed_at":"2026-01-11T03:28:45.230462968Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-5ie4.2","depends_on_id":"mala-5ie4","type":"parent-child","created_at":"2026-01-11T02:46:25.103867489Z","created_by":"cyou"},{"issue_id":"mala-5ie4.2","depends_on_id":"mala-5ie4.1","type":"blocks","created_at":"2026-01-11T02:47:26.453335931Z","created_by":"cyou"}]}
{"id":"mala-5ie4.3","title":"[T003] Implement evidence_check reference validation","description":"**Type**: task\n**Priority**: P2\n**Story**: Epic: Evidence Check Configuration\n**Parallel**: [P] (can run parallel to T002, T004 after T001)\n**Primary Files**: spec.py, test_spec.py\n**Subsystems**: domain/validation, tests/unit\n\n## Goal\nAdd _validate_evidence_check_refs() to validate required keys against the resolved command map (after preset merge), following _validate_trigger_command_refs() pattern.\n\n## Context\n- Validation MUST happen in spec.py during build_validation_spec() AFTER preset merge (not during initial YAML parsing)\n- This ensures keys can reference preset-only commands (like \"test\") even when project has no commands: section\n- Error message format follows existing _validate_trigger_command_refs() pattern\n\n## Scope\n**In**:\n- Add _validate_evidence_check_refs() function in spec.py\n- Call during build_validation_spec() after preset merge creates resolved command map\n- Set evidence_required field on ValidationSpec from validated config\n- Add unit tests for validation (plan tests 9-13)\n\n**Out**:\n- Config parsing (T002)\n- Evidence filtering logic (T004)\n\n## Changes\n- `src/domain/validation/spec.py` — Exists — Add _validate_evidence_check_refs() function\n- `src/domain/validation/spec.py` — Exists — Call in build_validation_spec() after preset merge\n- `src/domain/validation/spec.py` — Exists — Set evidence_required field on ValidationSpec\n- `tests/unit/domain/test_spec.py` — Exists — Add 5 validation unit tests\n\n**NOTE**: Test file is at `tests/unit/domain/test_spec.py` (NOT `tests/unit/domain/validation/test_spec.py`)\n\n## Wiring Map\n- ValidationConfig.evidence_check → build_validation_spec() → _validate_evidence_check_refs(config, resolved_commands) → ValidationSpec.evidence_required\n\n## Acceptance Criteria\n- _validate_evidence_check_refs() validates all required keys exist in resolved command map\n- Invalid key produces error with: invalid key name, sorted list of available keys\n- Empty/None evidence_check passes validation (nothing to validate)\n- Global-only commands validate successfully (R3 is pre-scope)\n- Validation happens AFTER preset merge (can reference preset-only keys)\n- ValidationSpec.evidence_required is set to validated tuple of keys\n\n## Verification\nRun these tests:\n```bash\nuv run pytest tests/unit/domain/test_spec.py -k evidence -v\n```\n\n### Unit Tests (from plan tests 9-13, use inline fixture presets)\n9. Valid key from preset only (e.g., `required: [test]` with no project commands.test) → passes\n10. Valid key from project `commands:` → passes\n11. Invalid key → config error with invalid key name and available keys\n12. Global-only command validates successfully pre-scope\n13. Project override of preset key → uses project pattern for detection\n\n## Notes for Agent\n- Use inline fixture presets for tests (no external preset file dependencies)\n- Follow _validate_trigger_command_refs() error message format exactly\n- Resolved command map is available after _merge_preset_commands() in build_validation_spec()\n- ValidationSpec.evidence_required should be tuple of validated keys","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-11T02:46:56.900457116Z","created_by":"cyou","updated_at":"2026-01-11T03:31:43.031510703Z","closed_at":"2026-01-11T03:31:43.031510703Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-5ie4.3","depends_on_id":"mala-5ie4","type":"parent-child","created_at":"2026-01-11T02:46:56.901135412Z","created_by":"cyou"},{"issue_id":"mala-5ie4.3","depends_on_id":"mala-5ie4.1","type":"blocks","created_at":"2026-01-11T02:47:26.558436864Z","created_by":"cyou"}]}
{"id":"mala-5ie4.4","title":"[T004] Implement evidence check filtering by required config","description":"**Type**: task\n**Priority**: P2\n**Story**: Epic: Evidence Check Configuration\n**Parallel**: [P] (can run parallel to T002, T003 after T001)\n**Primary Files**: evidence_check.py, test_evidence_check.py\n**Subsystems**: domain, tests/unit\n\n## Goal\nModify check_evidence_against_spec() to use spec.evidence_required for filtering which commands require evidence.\n\n## Context\n- BREAKING CHANGE: Empty evidence_required means no evidence required (gate passes)\n- The spec.evidence_required field is populated by T003 during build_validation_spec()\n- Current function signature: `check_evidence_against_spec(evidence: ValidationEvidence, spec: ValidationSpec) -\u003e tuple[bool, list[str], list[str]]`\n- No new parameters needed - use spec.evidence_required directly\n\n## Current vs New Behavior\n**Current**: Check ALL commands in spec.commands for evidence\n**New**: Check ONLY commands whose names are in spec.evidence_required\n- If spec.evidence_required is empty tuple: return (True, [], []) immediately\n\n## Scope\n**In**:\n- Modify check_evidence_against_spec() to check spec.evidence_required\n- If evidence_required is empty: return success immediately (no evidence checking)\n- If evidence_required has keys: only check commands whose names are in evidence_required\n- Apply set semantics (handled by spec.evidence_required already being deduplicated)\n- Add unit tests (plan tests 14-21)\n\n**Out**:\n- Integration wiring (T005)\n- Config parsing (T002)\n- Reference validation (T003)\n\n## Changes\n- `src/domain/evidence_check.py` — Exists — Add early return if spec.evidence_required is empty\n- `src/domain/evidence_check.py` — Exists — Filter command checking to only commands in evidence_required\n- `tests/unit/domain/test_evidence_check.py` — Exists — Add 8 filtering unit tests\n\n## Implementation Notes\n\nThe current function iterates over spec.commands and checks evidence for each. The new behavior:\n\n```python\ndef check_evidence_against_spec(evidence: ValidationEvidence, spec: ValidationSpec) -\u003e tuple[bool, list[str], list[str]]:\n    # NEW: Early return if no evidence required (BREAKING CHANGE)\n    if not spec.evidence_required:\n        return (True, [], [])\n    \n    # NEW: Build set of required command names\n    required_keys = set(spec.evidence_required)\n    \n    # Filter spec.commands to only those in required_keys\n    # Then check evidence as before, but only for filtered commands\n    ...\n```\n\n## Acceptance Criteria\n- Empty evidence_required returns (True, [], []) - pass with no missing\n- Non-empty evidence_required checks only those command names\n- Commands not in evidence_required are NOT reported as missing\n- Existing allow_fail logic preserved for checked commands\n- Single log can satisfy multiple required keys (overlapping patterns work)\n\n## Verification\nRun these tests:\n```bash\nuv run pytest tests/unit/domain/test_evidence_check.py -k evidence_required -v\n```\n\n### Unit Tests (from plan tests 14-21)\n14. spec.evidence_required=() → no evidence required, returns (True, [], [])\n15. spec.evidence_required=(\"test\",) with test evidence → passes\n16. spec.evidence_required=(\"test\", \"lint\") → checks only those commands\n17. Commands not in evidence_required not reported as missing\n18. Scope filtering → E2E excluded from per-session (spec won't include E2E in evidence_required)\n19. allow_fail + not run → fails for missing evidence\n20. allow_fail + ran + failed → passes, advisory failure\n21. Overlapping patterns → single log satisfies multiple keys\n\n## Notes for Agent\n- DO NOT add new parameters to check_evidence_against_spec - use spec.evidence_required\n- The filtering is based on command NAMES (keys), not CommandKind\n- Build a set of required_keys from spec.evidence_required for O(1) lookup\n- Test 18 (scope filtering) is handled by spec construction, not this function","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-11T02:46:57.466841287Z","created_by":"cyou","updated_at":"2026-01-11T03:39:19.183320695Z","closed_at":"2026-01-11T03:39:19.183320695Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-5ie4.4","depends_on_id":"mala-5ie4","type":"parent-child","created_at":"2026-01-11T02:46:57.467503033Z","created_by":"cyou"},{"issue_id":"mala-5ie4.4","depends_on_id":"mala-5ie4.1","type":"blocks","created_at":"2026-01-11T02:47:26.671596635Z","created_by":"cyou"}]}
{"id":"mala-5ie4.5","title":"[T005] Final integration tests + full verification","description":"**Type**: task\n**Priority**: P2\n**Story**: Epic: Evidence Check Configuration\n**Parallel**: No (depends on T002, T003, T004)\n**Primary Files**: test_evidence_detection.py\n**Subsystems**: tests/integration\n\n## Goal\nComplete the feature by verifying all integration tests pass and the full flow works end-to-end.\n\n## Context\n- T001 created a failing integration test\n- T002, T003, T004 implemented the components\n- This task verifies everything works together\n- NO changes needed to gate_runner.py (the call to check_evidence_against_spec is in evidence_check.py:1069)\n\n## Scope\n**In**:\n- Add integration tests for full flow (plan tests 22-23)\n- Verify T001 integration test now passes\n- Verify ALL existing and new tests pass\n\n**Out**:\n- N/A - this completes the feature\n\n## Changes\n- `tests/integration/domain/test_evidence_detection.py` — Exists — Add 2 integration tests + verify T001 test passes\n\n## Wiring Verification\nThe full data flow to verify:\n1. mala.yaml `evidence_check:` → config_loader._parse_evidence_check_config() → EvidenceCheckConfig\n2. EvidenceCheckConfig → ValidationConfig.evidence_check\n3. build_validation_spec() → _validate_evidence_check_refs() → ValidationSpec.evidence_required\n4. EvidenceCheck.check_with_resolution() passes spec to check_evidence_against_spec()\n5. check_evidence_against_spec() uses spec.evidence_required for filtering\n\n## Acceptance Criteria\n- T001 integration test now passes (was failing, now behavior implemented)\n- New integration tests pass for full flow with preset merge\n- ALL tests pass: unit + integration\n- Coverage threshold (72%) maintained\n\n## Verification\nRun full test suite:\n```bash\nuv run pytest -m \"unit or integration\" -v\n```\n\nRun specific integration tests:\n```bash\nuv run pytest tests/integration/domain/test_evidence_detection.py -v\n```\n\n### Integration Tests (from plan tests 22-23)\n22. Full flow: config parsing → preset merge → validation → evidence check with filtering\n23. Preset + project merge → uses resolved patterns for detection\n\n### Final Verification Checklist\n- [ ] `uv run pytest` passes with no failures\n- [ ] Coverage threshold (72%) maintained\n- [ ] T001 integration test flips from failing to passing\n\n## Notes for Agent\n- NO changes to src/ files in this task - only tests\n- If any tests fail, investigate which component (T002, T003, or T004) has the bug\n- The integration test should verify the full wiring from config to evidence check result","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-11T02:47:17.818273177Z","created_by":"cyou","updated_at":"2026-01-11T03:58:16.483811013Z","closed_at":"2026-01-11T03:58:16.483811013Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-5ie4.5","depends_on_id":"mala-5ie4","type":"parent-child","created_at":"2026-01-11T02:47:17.818993953Z","created_by":"cyou"},{"issue_id":"mala-5ie4.5","depends_on_id":"mala-5ie4.2","type":"blocks","created_at":"2026-01-11T02:47:27.207288304Z","created_by":"cyou"},{"issue_id":"mala-5ie4.5","depends_on_id":"mala-5ie4.3","type":"blocks","created_at":"2026-01-11T02:47:27.317006353Z","created_by":"cyou"},{"issue_id":"mala-5ie4.5","depends_on_id":"mala-5ie4.4","type":"blocks","created_at":"2026-01-11T02:47:27.421759067Z","created_by":"cyou"}]}
{"id":"mala-5ie4.6","title":"[Review] 1 non-blocking finding from mala-5ie4.2","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-5ie4.2\n**Highest priority:** P3\n\n---\n\n### Finding 1: [P3] Redundant local import of EvidenceCheckConfig\n\n**Priority:** P3\n**Reviewer:** gemini\n**Location:** src/domain/validation/config_loader.py:744-746\n\nEvidenceCheckConfig is already imported at the module level (from `src.domain.validation.config`). The local import inside `_parse_evidence_check_config` (aliasing `EvidenceCheckConfigClass`) is unnecessary. Use the module-level `EvidenceCheckConfig` directly.\n\n---\n\n\u003c!-- fp:f8be56951c9fa62f --\u003e\n\u003c!-- review_finding:86c1af646622 --\u003e","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-11T03:28:45.553669206Z","created_by":"cyou","updated_at":"2026-01-11T03:32:35.018016432Z","closed_at":"2026-01-11T03:32:35.018016432Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-5ie4.2"],"dependencies":[{"issue_id":"mala-5ie4.6","depends_on_id":"mala-5ie4","type":"parent-child","created_at":"2026-01-11T03:28:45.554385562Z","created_by":"cyou"}]}
{"id":"mala-5ie4.7","title":"[Review] 1 non-blocking finding from mala-5ie4.3","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-5ie4.3\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Avoid hardcoded built-in command list in evidence_check validation\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/domain/validation/spec.py:541-548\n\n`_validate_evidence_check_refs()` reconstructs a \"resolved command map\" using a hardcoded tuple of built-in command names. This can incorrectly reject valid `evidence_check.required` keys if the actual supported built-in commands differ (e.g., new built-ins added elsewhere, or if `_build_default_validation_spec()`/base-pool logic supports more commands).\n\nConcrete failure path: if the codebase adds a new built-in command field (say `docs`) to `CommandsConfig` and uses it in presets/defaults, `evidence_check.required: [docs]` would be valid at runtime but will be rejected here because `docs` is not in the hardcoded list.\n\nMinimal fix: reuse the same base-pool construction logic used for triggers (or extract a shared helper) instead of duplicating the list in `spec.py`.\n\n```python\nfor cmd_name in (\"test\", \"lint\", \"format\", \"typecheck\", \"e2e\", \"setup\", \"build\"):\n```\n\n\n---\n\n\u003c!-- fp:92a2c4f9d3f39a31 --\u003e\n\u003c!-- review_finding:3a76050fa6ae --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-11T03:31:43.353686919Z","created_by":"cyou","updated_at":"2026-01-11T03:38:29.688410114Z","closed_at":"2026-01-11T03:38:29.688410114Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-5ie4.3"],"dependencies":[{"issue_id":"mala-5ie4.7","depends_on_id":"mala-5ie4","type":"parent-child","created_at":"2026-01-11T03:31:43.354357725Z","created_by":"cyou"}]}
{"id":"mala-5ie4.8","title":"[Review] 1 non-blocking finding from mala-5ie4.7","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-5ie4.7\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Incomplete refactor: hardcoded command lists remain in config_loader and config_merger\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/domain/validation/config_loader.py:1225\n\nAuthor context requested reusing base-pool construction logic to avoid hardcoded lists. However, `src/domain/validation/config_loader.py` (`_validate_trigger_command_refs`) and `src/domain/validation/config_merger.py` (`_validate_no_partial_commands`) still contain hardcoded lists of built-in commands. These should be updated to use `BUILTIN_COMMAND_NAMES` from `config.py` to ensure consistency and avoid maintenance hazards when new commands are added.\n\n---\n\n\u003c!-- fp:50431016742fb0dd --\u003e\n\u003c!-- review_finding:e1e64ecbff60 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-11T03:38:30.004600531Z","created_by":"cyou","updated_at":"2026-01-11T03:50:30.741770931Z","closed_at":"2026-01-11T03:50:30.741770931Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-5ie4.7"],"dependencies":[{"issue_id":"mala-5ie4.8","depends_on_id":"mala-5ie4","type":"parent-child","created_at":"2026-01-11T03:38:30.005159087Z","created_by":"cyou"}]}
{"id":"mala-5ie4.9","title":"[Review] 1 non-blocking finding from mala-5ie4.4","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-5ie4.4\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Do not silently ignore required keys missing from spec.commands\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/domain/evidence_check.py:236-279\n\n`check_evidence_against_spec()` gates evidence checks on `spec.commands` contents. With the new filtering, if `spec.evidence_required` contains a key that is *not present* in `spec.commands` (e.g., spec construction bug or stale config), the function will skip it and can return pass.\n\nConcrete failure path: `spec = ValidationSpec(commands=[], evidence_required=(\"test\",))` returns `(True, [], [])` because `get_required_evidence_kinds(spec)` is empty and the CUSTOM loop sees no commands, so `missing` stays empty.\n\nThis contradicts the acceptance criterion that “Non-empty evidence_required checks only those command names” (it currently checks only names that also exist in `spec.commands`). If spec construction is wrong, the gate becomes a false negative.\n\nSuggested fix: when `spec.evidence_required` is non-empty, verify that every required key is represented in `spec.commands` (or otherwise resolvable) and treat unknown/missing keys as missing evidence or a failure reason.\n\nRelevant code:\n```python\nrequired_keys = set(spec.evidence_required)\n...\nfor cmd in spec.commands:\n```\n\n\n---\n\n\u003c!-- fp:4d93a1c72e978313 --\u003e\n\u003c!-- review_finding:d9b74b7f809f --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-11T03:39:19.500332097Z","created_by":"cyou","updated_at":"2026-01-11T03:49:07.254554171Z","closed_at":"2026-01-11T03:49:07.254554171Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-5ie4.4"],"dependencies":[{"issue_id":"mala-5ie4.9","depends_on_id":"mala-5ie4","type":"parent-child","created_at":"2026-01-11T03:39:19.501028693Z","created_by":"cyou"}]}
{"id":"mala-5igt","title":"[Review] 2 non-blocking findings from mala-porw","description":"## Review Findings\n\nThis issue consolidates 2 non-blocking findings from code review.\n\n**Source issue:** mala-porw\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Don’t drop tuple/sequence CLI values in config summary\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/infra/io/console_sink.py:109-115\n\n`disable_validations` and `epic_override` are only logged when they are a `list`, so if these args arrive as a tuple (common with some CLI parsers) or other non-list sequence, they’ll be silently omitted from the run-start config summary.\n\n```py\nif disable and isinstance(disable, list):\n    items.append(...)\n```\nConsider accepting any non-string sequence (e.g., `list | tuple` or `collections.abc.Sequence` excluding `str`) so the summary reliably reflects the effective config.\n\n---\n\n### Finding 2: [P2] Normalize/quote review disabled reason to keep key=value tokens parseable\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/infra/io/console_sink.py:96-99\n\nIf `config.review_disabled_reason` contains whitespace, the output breaks the advertised space-delimited `key=value` format (it becomes multiple tokens), which makes logs harder to scan and any downstream parsing brittle.\n\n```py\nreason = config.review_disabled_reason or \"disabled\"\nitems.append(f\"review={reason}\")\n```\nConsider normalizing (e.g., replace whitespace with `_`) or quoting/escaping the reason so `review=...` remains a single token.\n\n---\n\n\u003c!-- fp:0d5ff0506a971d23 --\u003e\n\u003c!-- fp:e82a2399faa7471f --\u003e\n\u003c!-- review_finding:3a49bfe76277 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-07T18:48:48.465800346Z","created_by":"cyou","updated_at":"2026-01-07T18:53:07.101984198Z","closed_at":"2026-01-07T18:53:07.101984198Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-porw"]}
{"id":"mala-5ot","title":"Remove unused sync methods from BeadsClient","description":"## Context\n- `BeadsClient` has both sync and async versions of all methods\n- Orchestrator exclusively uses async versions (`get_ready_async`, `claim_async`, etc.)\n- Sync methods add ~200 lines of duplicated logic\n- Confidence: High - grep confirms no production calls to sync methods\n\n## Primary Files\n`src/beads_client.py:82-278`\n\n## Scope\n**In:** Remove sync methods: `get_epic_children`, `get_ready`, `claim`, `reset`, `get_issue_status`, `commit_issues`, `close_eligible_epics`, `mark_needs_followup`\n**Out:** Keep async methods and `_run_subprocess_async` helper intact\n\n## Acceptance Criteria\n- All sync methods removed from BeadsClient\n- No import errors or missing references\n- All 221 tests pass\n\n## Test Plan\n- Run `uv run pytest`\n- Verify no import errors with `python -c \"from src.beads_client import BeadsClient\"`\n\n## Notes for Agent\n- Tests in `test_orchestrator.py` mock both sync and async methods - only async mocks matter\n- The `DEFAULT_COMMAND_TIMEOUT` constant is used by async methods, keep it","status":"closed","priority":2,"issue_type":"chore","created_at":"2025-12-26T19:44:50.439271516Z","updated_at":"2025-12-26T19:51:49.651613381Z","closed_at":"2025-12-26T19:51:49.651613381Z","close_reason":"Closed","labels":["dead-code"],"dependencies":[{"issue_id":"mala-5ot","depends_on_id":"mala-7zq","type":"parent-child","created_at":"2025-12-26T19:45:26.536295235Z","created_by":"daemon"}]}
{"id":"mala-5otq","title":"[Review] 3 non-blocking findings from mala-ui4o","description":"## Review Findings\n\nThis issue consolidates 3 non-blocking findings from code review.\n\n**Source issue:** mala-ui4o\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P3] Update docstrings to document new call tracking lists\n\n**Priority:** P3\n**Reviewer:** claude\n**Location:** tests/fakes/gate_checker.py:38-40\n\nThe module docstring (lines 6-9) and class docstring (lines 38-40) document `check_with_resolution_calls`, `check_no_progress_calls`, and `get_log_end_offset_calls` as observable state, but the newly added `check_commit_exists_calls` and `parse_validation_evidence_calls` (lines 58-59) are not documented. This inconsistency makes it harder for test authors to discover available tracking capabilities.\n\n---\n\n### Finding 2: [P2] Fix VerificationAttempt.commit_list type annotation\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** tests/fakes/epic_model.py:16-26\n\n`VerificationAttempt.commit_list` is annotated as `str`, but the name (and typical usage) suggests it’s a list/sequence of commits. If `FakeEpicVerificationModel.verify()` actually receives `list[str]`/`Sequence[str]`, this will either (a) cause `ty` type-check failures or (b) silently record a non-`str` value in a `str`-typed field, undermining the purpose of the tracking.\n\nSuggested fix: annotate `commit_list` to match the `verify()` parameter type (e.g. `Sequence[str]` or `list[str]`).\n\n---\n\n### Finding 3: [P3] Make get_protocol_members import robust to runtimes/backports\n\n**Priority:** P3\n**Reviewer:** codex\n**Location:** tests/contracts/__init__.py:11-33\n\n`get_protocol_members()` gates on `sys.version_info \u003e= (3, 13)` and then imports `typing.get_protocol_members`. Version checks can be brittle on alternative runtimes or backports where the version tuple may not perfectly reflect stdlib API availability, leading to an `ImportError` in the test suite.\n\nPrefer a feature check:\n`try: from typing import get_protocol_members`\n`except ImportError: from typing_extensions import get_protocol_members`\n\n---\n\n\u003c!-- fp:534843efe847f58f --\u003e\n\u003c!-- fp:6c91ada9b39172a7 --\u003e\n\u003c!-- fp:20114f1dae09a990 --\u003e\n\u003c!-- review_finding:73cf6f2734ea --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-05T16:33:03.027530784Z","created_by":"cyou","updated_at":"2026-01-05T16:35:21.630381271Z","closed_at":"2026-01-05T16:35:21.630381271Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-ui4o"]}
{"id":"mala-5shj","title":"Auto-create beads issues from P2/P3 Cerberus review findings","description":"When Cerberus review returns P2/P3 issues, mala currently accepts the review but notes them for later tracking. This feature should automatically create beads issues from those P2/P3 findings so they are actually tracked and not forgotten.\n\nContext:\n- In src/lifecycle.py, P0/P1 issues block the review (priority \u003c= 1)\n- P2/P3 issues are accepted with message 'Review passed (N P2/P3 issues noted for later)'\n- The issues contain: file, line_start, line_end, priority, title, body, reviewer\n- These should become beads issues with appropriate priority mapping (P2→P2, P3→P3)\n\nImplementation notes:\n- After review passes with low-priority issues, call bd create for each\n- Include file:line reference in issue title/description\n- Link reviewer attribution in the issue body\n- Consider making this behavior configurable (--track-review-issues flag)","status":"closed","priority":2,"issue_type":"feature","created_at":"2025-12-31T19:06:15.394486047Z","created_by":"cyou","updated_at":"2025-12-31T19:51:10.343260754Z","closed_at":"2025-12-31T19:51:10.343260754Z","close_reason":"Closed"}
{"id":"mala-5us","title":"Implement ValidationSpec + policy classification","description":"Context:\n- ValidationSpec and related types define what validations run per scope.\n- Policy classification (code vs docs) and disable list drive required commands.\n\nScope:\n- In: implement ValidationSpec/ValidationCommand/Context/Result/Artifacts/IssueResolution types; build ValidationSpec from inputs; code-change classification and disable list handling per plan.\n- Out: runner execution and orchestration (separate tasks).\n\nAcceptance Criteria:\n- ValidationSpec construction supports scope (per-issue vs run-level).\n- Code vs docs classification available as a helper.\n- Disable list values map to spec omissions as described in the plan.\n\nTest Plan:\n- TDD: add unit tests for spec building and classification before implementation.\n- Run unit tests for spec module.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-26T23:00:01.653337578Z","updated_at":"2025-12-26T23:45:40.848755874Z","closed_at":"2025-12-26T23:45:40.848755874Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-5us","depends_on_id":"mala-j6p","type":"blocks","created_at":"2025-12-26T23:00:26.454579505Z","created_by":"daemon"}]}
{"id":"mala-5x4w","title":"[Review] src/domain/validation/tool_name_extractor.py:130-139: [P2] Skip all set arguments to avoid false tool detection","description":"## Review Finding\n\nThis issue was auto-created from a P2 review finding.\n\n**Source issue:** mala-c7wb\n**Reviewer:** codex\n**Location:** src/domain/validation/tool_name_extractor.py:130-139\n\n## Details\n\n`set` is a shell built-in and does not execute a following command; all remaining words are arguments/positional parameters. The new logic stops at the first non-flag and returns it as a tool. Example: `set -- pytest -q \u0026\u0026 echo done` will extract `pytest` from the `set` segment and return it as the tool, even though nothing ran there and the operator handling returns the first meaningful tool. Consider consuming the rest of the tokens for `set` (or treating it as terminal) to avoid false positives.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-02T03:55:38.87207758Z","created_by":"cyou","updated_at":"2026-01-02T04:22:20.7108924Z","closed_at":"2026-01-02T04:22:20.7108924Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_finding:43794d0d11f1","source:mala-c7wb"]}
{"id":"mala-642f","title":"Fix trigger base pool to use merged global_validation_commands","description":"Trigger execution currently builds base pool from ValidationConfig.commands on the unmerged config, so presets and global_validation_commands are ignored. This breaks documented usage (preset + ref: test) and spec. Fix by using merged config (preset + project) and base pool from global_validation_commands per spec. Update RunCoordinator/_build_base_pool and wiring in orchestrator/factory to pass merged ValidationConfig or merge before run. Files: src/pipeline/run_coordinator.py, src/orchestration/factory.py, src/domain/validation/spec.py.","notes":"Quality gate failed: External review failed: src/pipeline/run_coordinator.py:1089: [P1] Crash in _build_base_pool if global_validation_commands is missing\nLog: /home/cyou/.claude/projects/-home-cyou-mala/4a79039c-b365-411a-972d-368cd5e1d12b.jsonl","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-09T16:52:57.445168778Z","created_by":"cyou","updated_at":"2026-01-09T21:23:05.32238322Z","closed_at":"2026-01-09T21:23:05.32238322Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-642f","depends_on_id":"mala-smaj","type":"parent-child","created_at":"2026-01-09T16:54:56.335787901Z","created_by":"cyou"}]}
{"id":"mala-67vp","title":"[Review] tests/test_code_pattern_matcher.py:51-62: [P2] Test for invalid pattern warning path is incomplete","description":"## Review Finding\n\nThis issue was auto-created from a P2 review finding.\n\n**Source issue:** mala-fdp1.6\n**Reviewer:** claude\n**Location:** tests/test_code_pattern_matcher.py:51-62\n\n## Details\n\nThe test `test_invalid_pattern_treated_as_literal` doesn't actually test the `re.error` exception path (lines 80-83 of the implementation). The test comments acknowledge this but doesn't provide an actual test. Since the implementation escapes all regex special chars before compilation, triggering `re.error` is difficult, but this means the error handling code is untested dead code. Consider either removing the try/except or finding a way to trigger it (e.g., via mocking).","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-02T03:15:34.517073504Z","created_by":"cyou","updated_at":"2026-01-02T03:21:30.456201061Z","closed_at":"2026-01-02T03:21:30.456201061Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_finding:20497ddaa6c2","source:mala-fdp1.6"]}
{"id":"mala-698p","title":"[Review] src/orchestration/orchestrator.py:1215-1233: [P2] Propagate failed results to coordinator exclude list","description":"## Review Finding\n\nThis issue was auto-created from a P2 review finding.\n\n**Source issue:** mala-cavk.3\n**Reviewer:** codex\n**Location:** src/orchestration/orchestrator.py:1215-1233\n\n## Details\n\n`IssueExecutionCoordinator.run_loop` uses its own `failed_issues` set as `exclude_ids` when calling `get_ready_async`. In the new flow, the orchestrator only calls `mark_completed`, so issues that fail during execution (gate failure, agent error) are never added to the coordinator’s failed set. That means the provider can return the same failed issue again in the same run, causing repeated work or loops if the issue stays open. Consider marking failures in `finalize_callback` (e.g., when `result.success` is false) or have the coordinator exclude `completed_ids` as well.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-01T22:23:07.425262853Z","created_by":"cyou","updated_at":"2026-01-02T01:49:06.154094095Z","closed_at":"2026-01-02T01:49:06.154094095Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_finding:3d119c8b5aca","source:mala-cavk.3"],"dependencies":[{"issue_id":"mala-698p","depends_on_id":"mala-i8ke","type":"blocks","created_at":"2026-01-01T23:03:58.862844682Z","created_by":"daemon"}]}
{"id":"mala-6a7","title":"Epic: Coverage 'No Decrease' Policy with Auto-Refresh Baseline","description":"Context:\n- Current CLI uses fixed 85% coverage threshold, which is inflexible\n- Goal: default behavior should prevent coverage from decreasing (not a fixed number)\n- Uses existing coverage.xml as baseline, with auto-refresh when missing/stale\n- Must handle parallel agents safely via locking and temp worktrees\n\nScope:\n- In: CLI option changes, coverage.py baseline functions, spec.py CoverageConfig, spec_runner.py auto-refresh logic, orchestrator.py signature\n- Out: pyproject.toml threshold (keep for direct pytest), legacy_runner changes\n\nAcceptance Criteria:\n- `bd run` without `--coverage-threshold` uses \"no decrease\" mode\n- Baseline auto-refreshes when missing or stale\n- Parallel agents don't corrupt baseline via locking\n- Parse errors fail loudly, missing baseline warns + refreshes\n\nNotes for Agent:\n- This is the umbrella epic; implement child issues in dependency order","status":"closed","priority":2,"issue_type":"epic","created_at":"2025-12-27T18:20:55.598874949Z","updated_at":"2025-12-28T04:44:54.466282373Z","closed_at":"2025-12-28T04:44:54.466282373Z","close_reason":"All children completed"}
{"id":"mala-6a7.1","title":"Add baseline coverage functions to coverage.py","description":"Context:\n- `coverage.py` currently only parses and checks against fixed thresholds\n- Need new functions: `get_baseline_coverage()` and `is_baseline_stale()`\n- Must distinguish \"missing\" (returns None) from \"parse error\" (raises/fails)\n- Staleness detection: compare mtime vs last commit time + check dirty working tree\n\nScope:\n- In: Add `get_baseline_coverage(report_path: Path) -\u003e float | None`\n- In: Add `is_baseline_stale(report_path: Path, repo_path: Path) -\u003e bool`\n- In: Update `check_coverage_threshold` to accept `min_percent: float | None`\n- In: Update `parse_and_check_coverage` signature similarly\n- Out: Auto-refresh logic (that's in spec_runner.py)\n\nAcceptance Criteria:\n- `get_baseline_coverage()` returns percentage if file exists and valid, None if missing, raises on parse errors\n- `is_baseline_stale()` returns True if mtime \u003c last commit time OR repo is dirty\n- `is_baseline_stale()` handles non-git repos gracefully (returns True if git commands fail)\n- `check_coverage_threshold(result, None)` returns PASSED\n\nTest Plan:\n- TDD: Write failing tests for each new function\n- Test missing file → returns None\n- Test valid file → returns percentage\n- Test malformed XML → raises error\n- Test stale mtime → returns True\n- Test dirty repo → returns True\n- Test non-git repo → handles gracefully\n\nNotes for Agent:\n- Use `git log -1 --format=%ct` for last commit time\n- Use `git status --porcelain` to check dirty status\n- Wrap git commands in try/except for non-git fallback\n- Primary files: src/validation/coverage.py, tests/test_coverage.py","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-27T18:21:11.580596827Z","updated_at":"2025-12-28T03:22:57.898659634Z","closed_at":"2025-12-28T03:22:57.898659634Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-6a7.1","depends_on_id":"mala-6a7","type":"parent-child","created_at":"2025-12-27T18:21:11.587208102Z","created_by":"daemon"}]}
{"id":"mala-6a7.2","title":"Update CoverageConfig and pytest command in spec.py","description":"Context:\n- `CoverageConfig.min_percent` currently defaults to 85.0\n- Need to change default to None (meaning \"no decrease\" mode)\n- When `coverage_threshold is None`, must add `--cov-fail-under=0` to pytest command to override pyproject.toml\n\nScope:\n- In: Change `CoverageConfig.min_percent: float = 85.0` to `min_percent: float | None = None`\n- In: Update docstring to explain None = \"no decrease\" mode\n- In: In `build_validation_spec`, when `coverage_threshold is None`, add `--cov-fail-under=0` to pytest command\n- Out: Actual baseline comparison logic (that's in spec_runner.py)\n\nAcceptance Criteria:\n- `CoverageConfig(enabled=True)` has `min_percent=None` by default\n- Pytest command includes `--cov-fail-under=0` when threshold is None\n- Pytest command includes explicit threshold when provided\n- Docstrings updated\n\nTest Plan:\n- TDD: Test default CoverageConfig has min_percent=None\n- Test `build_validation_spec()` with no threshold → pytest includes `--cov-fail-under=0`\n- Test `build_validation_spec(coverage_threshold=80)` → pytest includes `--cov-fail-under=80`\n\nNotes for Agent:\n- The `--cov-fail-under=0` is needed to override pyproject.toml's `--cov-fail-under=85`\n- Keep existing coverage-related pytest flags (`--cov=src`, etc.)\n- Primary files: src/validation/spec.py, tests/test_spec.py","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-27T18:21:24.776545691Z","updated_at":"2025-12-28T03:29:44.503500832Z","closed_at":"2025-12-28T03:29:44.503500832Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-6a7.2","depends_on_id":"mala-6a7","type":"parent-child","created_at":"2025-12-27T18:21:24.783252376Z","created_by":"daemon"},{"issue_id":"mala-6a7.2","depends_on_id":"mala-6a7.1","type":"blocks","created_at":"2025-12-27T18:22:19.876162823Z","created_by":"daemon"}]}
{"id":"mala-6a7.3","title":"Implement baseline auto-refresh in spec_runner.py","description":"Context:\n- Must capture baseline from main repo BEFORE worktree creation\n- If baseline missing or stale, auto-refresh by running tests in temp worktree\n- Need locking to prevent parallel agents from clobbering\n- Must use same pytest args as validation spec\n\nScope:\n- In: Add `_refresh_baseline(spec, repo_path) -\u003e float` method\n- In: Before worktree setup in `run_validation_spec`, check/refresh baseline\n- In: Use file locking (existing locking.py) with double-check pattern\n- In: Pass `baseline_percent` to `_check_coverage`\n- In: Update `_check_coverage` to accept and use baseline_percent\n- Out: CLI changes, CoverageConfig changes (done in other issues)\n\nAcceptance Criteria:\n- Baseline captured from main repo before worktree creation\n- If baseline missing/stale, `_refresh_baseline` runs in temp worktree\n- Lock acquired before refresh, released after\n- Double-check pattern: re-verify staleness after acquiring lock\n- Temp worktree cleaned up after refresh\n- Atomic rename of coverage.xml to main repo\n- `_check_coverage` uses baseline_percent when min_percent is None\n\nTest Plan:\n- Test baseline captured before worktree setup\n- Test missing baseline triggers refresh\n- Test stale baseline triggers refresh\n- Test fresh baseline skips refresh\n- Test lock prevents concurrent refreshes\n- Mock worktree creation/cleanup\n\nNotes for Agent:\n- Use existing `create_worktree` from worktree.py\n- Use existing locking functions from locking.py\n- Run pytest with same args from spec, just override `--cov-fail-under=0`\n- Write to temp file, then `os.rename()` for atomic update\n- Primary file: src/validation/spec_runner.py","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-27T18:21:40.548034735Z","updated_at":"2025-12-28T04:07:15.465023394Z","closed_at":"2025-12-28T04:07:15.465023394Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-6a7.3","depends_on_id":"mala-6a7","type":"parent-child","created_at":"2025-12-27T18:21:40.555202907Z","created_by":"daemon"},{"issue_id":"mala-6a7.3","depends_on_id":"mala-6a7.1","type":"blocks","created_at":"2025-12-27T18:22:19.893411903Z","created_by":"daemon"},{"issue_id":"mala-6a7.3","depends_on_id":"mala-6a7.2","type":"blocks","created_at":"2025-12-27T18:22:19.90738317Z","created_by":"daemon"}]}
{"id":"mala-6a7.4","title":"Update CLI coverage-threshold option","description":"Context:\n- CLI currently has `--coverage-threshold` defaulting to 85.0\n- Need to change default to None\n- Validation logic must allow None (currently rejects non-0-100 values)\n\nScope:\n- In: Change `--coverage-threshold` default from `85.0` to `None`\n- In: Update help text to explain \"no decrease\" mode\n- In: Update validation to allow None (skip range check if None)\n- Out: Actual coverage logic (that's in coverage.py/spec_runner.py)\n\nAcceptance Criteria:\n- `--coverage-threshold` defaults to None\n- Help text mentions \"no decrease\" behavior\n- Running without `--coverage-threshold` doesn't error\n- Running with `--coverage-threshold 80` still works\n\nTest Plan:\n- Test CLI parses None default correctly\n- Test explicit threshold value works\n- Test help text is updated\n\nNotes for Agent:\n- Type should be `float | None` with default `None`\n- Skip the `0 \u003c= coverage_threshold \u003c= 100` check when None\n- Primary file: src/cli.py","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-27T18:21:51.768479029Z","updated_at":"2025-12-28T04:16:04.584178893Z","closed_at":"2025-12-28T04:16:04.584178893Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-6a7.4","depends_on_id":"mala-6a7","type":"parent-child","created_at":"2025-12-27T18:21:51.776699972Z","created_by":"daemon"},{"issue_id":"mala-6a7.4","depends_on_id":"mala-6a7.5","type":"blocks","created_at":"2025-12-27T18:25:07.340502685Z","created_by":"daemon"}]}
{"id":"mala-6a7.5","title":"Update orchestrator coverage_threshold type","description":"Context:\n- `MalaOrchestrator.__init__` accepts `coverage_threshold: float = 85.0`\n- Need to update type to `float | None = None` to match CLI\n\nScope:\n- In: Change `coverage_threshold` parameter type to `float | None = None`\n- Out: Any logic changes (just type signature update)\n\nAcceptance Criteria:\n- Orchestrator accepts None for coverage_threshold\n- Type hints updated\n- Passes value through to ValidationSpec correctly\n\nTest Plan:\n- Verify orchestrator instantiation with None works\n- Verify orchestrator instantiation with float works\n\nNotes for Agent:\n- Simple signature change, no logic changes needed\n- Primary file: src/orchestrator.py","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-27T18:21:59.927350234Z","updated_at":"2025-12-28T04:10:58.931642626Z","closed_at":"2025-12-28T04:10:58.931642626Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-6a7.5","depends_on_id":"mala-6a7","type":"parent-child","created_at":"2025-12-27T18:21:59.93766352Z","created_by":"daemon"},{"issue_id":"mala-6a7.5","depends_on_id":"mala-6a7.2","type":"blocks","created_at":"2025-12-27T18:25:07.327542248Z","created_by":"daemon"}]}
{"id":"mala-6a7.6","title":"Add tests for 'no decrease' coverage behavior","description":"Context:\n- Need integration-level tests for the full \"no decrease\" flow\n- Existing tests may need updates for new default behavior\n\nScope:\n- In: Add tests for \"no decrease\" mode end-to-end\n- In: Update existing tests that assume 85.0 default\n- Out: Core implementation (done in other issues)\n\nAcceptance Criteria:\n- Tests verify baseline is captured before validation\n- Tests verify auto-refresh when baseline missing\n- Tests verify coverage comparison against baseline\n- Existing tests pass with new defaults\n\nTest Plan:\n- Test \"no decrease\" mode with fresh baseline\n- Test \"no decrease\" mode with stale baseline triggers refresh\n- Test explicit threshold overrides baseline mode\n\nNotes for Agent:\n- May need to mock git commands for staleness tests\n- May need to mock worktree creation for refresh tests\n- Primary files: tests/test_spec.py, tests/test_coverage.py","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-27T18:22:09.331814334Z","updated_at":"2025-12-28T04:44:54.447831708Z","closed_at":"2025-12-28T04:44:54.447831708Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-6a7.6","depends_on_id":"mala-6a7","type":"parent-child","created_at":"2025-12-27T18:22:09.338662428Z","created_by":"daemon"},{"issue_id":"mala-6a7.6","depends_on_id":"mala-6a7.3","type":"blocks","created_at":"2025-12-27T18:22:19.946957118Z","created_by":"daemon"}]}
{"id":"mala-6ap","title":"Enhanced cleanup on early agent exit: git reset + reopen issue","description":"## Problem\nWhen agents exit early (context exhaustion, error, timeout, etc.), they may leave partial work in the git working tree and the issue in an inconsistent state (e.g., still marked as in_progress).\n\n## Current Behavior\n- Orchestrator calls `reset_issue()` which sets status back, but doesn't clean up git state\n- Partial file changes may remain uncommitted in the working tree\n- Next agent picking up the issue sees a dirty state\n\n## Proposed Solution\nOn early agent exit, the orchestrator should:\n\n1. **Reset git state**: Discard all uncommitted changes for files the agent touched\n   - `git checkout -- \u003cfiles\u003e` or `git restore \u003cfiles\u003e`\n   - Consider `git clean` for any new untracked files created by the agent\n   - Be careful not to discard work from other concurrent agents\n\n2. **Mark issue as open**: Use `bd update \u003cissue\u003e --status open` (not just reset to previous state)\n   - This makes it clear the issue needs to be picked up fresh\n\n3. **Release all locks**: Already done, but ensure it happens before git reset\n\n## Implementation Notes\n- Need to track which files the agent modified (from tool calls in JSONL log)\n- Or simpler: reset only files that match the issue's scope (if defined)\n- Safest approach: only reset files the agent held locks for\n\n## Acceptance Criteria\n- Early exit leaves working tree clean (no partial changes from failed agent)\n- Issue status is set to 'open' \n- Locks are released\n- Other agents' uncommitted work is not affected","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-26T17:08:40.391592982Z","updated_at":"2025-12-26T18:28:00.450472489Z","closed_at":"2025-12-26T18:28:00.450472489Z","close_reason":"Merged into mala-b0m (Graceful shutdown with agent wrap-up)"}
{"id":"mala-6axc","title":"Add context_restart_threshold and context_limit to mala.yaml","description":"Currently hardcoded defaults in OrchestratorConfig (context_restart_threshold=0.70, context_limit=200_000). Should be configurable in mala.yaml for tuning agent context exhaustion behavior.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-10T21:24:32.029996888Z","created_by":"cyou","updated_at":"2026-01-10T23:15:44.160152022Z","closed_at":"2026-01-10T23:15:44.160152022Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-6axc","depends_on_id":"mala-n5or","type":"blocks","created_at":"2026-01-10T21:26:23.694208475Z","created_by":"cyou"}]}
{"id":"mala-6c0","title":"Enforce single mala run per directory with lock file","description":"Context:\n- Running multiple mala instances in the same directory can cause conflicts.\n- Need a lock mechanism to prevent concurrent runs.\n\nScope:\n- In: create lock file on run start; check for existing lock; fail fast if locked.\n- Out: distributed locking; cross-machine coordination.\n\nAcceptance Criteria:\n- Second mala run in same directory fails immediately with clear error message.\n- Lock file is cleaned up on normal exit.\n- Stale locks (from crashed runs) are detected and can be overridden with --force.\n\nTest Plan:\n- Manual: start two runs in same dir, verify second fails.\n- Unit: mock lock file operations.","status":"closed","priority":0,"issue_type":"feature","created_at":"2025-12-27T01:11:27.170231152Z","updated_at":"2026-01-03T04:45:17.247425049Z","closed_at":"2026-01-03T04:45:17.247425049Z","close_reason":"Closed"}
{"id":"mala-6dgv","title":"[Review] src/pipeline/agent_session_runner.py:526-743: [P2] Missing unit tests for extracted helpers","description":"## Review Finding\n\nThis issue was auto-created from a P2 review finding.\n\n**Source issue:** mala-cavk.4\n**Reviewer:** gemini\n**Location:** src/pipeline/agent_session_runner.py:526-743\n\n## Details\n\nThe acceptance criteria for this epic explicitly require 'New unit tests for extracted helpers'. While the refactoring successfully reduces the line count of `run_session` from 262 to 39 lines (satisfying the primary line-count goal), this commit does not include any new tests for the newly extracted methods (`_initialize_session`, `_run_lifecycle_loop`, `_handle_gate_check`, `_handle_review_check`, `_build_session_output`). Adding unit tests for these components is necessary to ensure the refactor is fully verified and compliant with the epic's requirements.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-01T22:35:35.188851751Z","created_by":"cyou","updated_at":"2026-01-02T02:06:07.737279028Z","closed_at":"2026-01-02T02:06:07.737279028Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_finding:3be8d5669704","source:mala-cavk.4"],"dependencies":[{"issue_id":"mala-6dgv","depends_on_id":"mala-actw","type":"blocks","created_at":"2026-01-01T23:31:14.934932198Z","created_by":"daemon"},{"issue_id":"mala-6dgv","depends_on_id":"mala-rmxf","type":"blocks","created_at":"2026-01-01T23:31:14.966743103Z","created_by":"daemon"}]}
{"id":"mala-6hp","title":"Add CLAUDE.md/AGENTS.md context loading for Agent SDK apps","description":"Context:\n- Agent SDK apps don't automatically load project CLAUDE.md/AGENTS.md\n- Claude Code loads these for project-specific instructions\n- Want consistent behavior for custom SDK agents\n\nLocation:\n- src/pipeline/agent_session_runner.py:500-504 (setting_sources parameter)\n- src/pipeline/run_coordinator.py (setting_sources)\n- Claude Agent SDK documentation\n\nScope:\n- In: API option to load CLAUDE.md/AGENTS.md into agent context\n- In: Configuration for which files to load (CLAUDE.md, AGENTS.md, both)\n- In: Respect .claudeignore patterns for context files\n- Out: Automatic loading without opt-in (breaking change)\n\nAcceptance Criteria:\n- SDK apps can opt-in to loading project context files\n- setting_sources=['project'] loads CLAUDE.md and AGENTS.md\n- Content injected into system prompt or initial context\n- Clear documentation for usage\n\nTest Plan:\n- Unit: Test context file loading with mock files\n- Integration: Create SDK app with project files, verify loading\n- Manual: Compare behavior with/without project context\n\nNotes for Agent:\n- Currently setting_sources is hardcoded in multiple places\n- Related to mala-j8fj (configurable setting_sources)\n- Consider environment variable for default behavior","status":"closed","priority":4,"issue_type":"feature","created_at":"2025-12-27T20:00:05.349091312Z","updated_at":"2026-01-03T04:52:45.44034019Z","closed_at":"2026-01-03T04:52:45.44034019Z","close_reason":"Closed"}
{"id":"mala-6iv","title":"Test: add hello comment to pyproject.toml","status":"tombstone","priority":2,"issue_type":"task","created_at":"2025-12-25T19:03:17.354821017Z","updated_at":"2025-12-25T19:05:18.077405908Z","close_reason":"Closed","deleted_at":"2025-12-25T19:05:18.077405908Z","deleted_by":"daemon","delete_reason":"delete","original_type":"task"}
{"id":"mala-6lt9","title":"Preserve agent_id on early SIGINT in AgentSessionRunner","description":"If run_session returns early due to interrupt_event, ensure agent_id is returned (or propagated) so orchestrator cleanup/tracking uses a stable ID. Add unit test for early interrupt path.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-09T16:53:04.05206021Z","created_by":"cyou","updated_at":"2026-01-09T19:12:11.082894035Z","closed_at":"2026-01-09T19:12:11.082894035Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-6lt9","depends_on_id":"mala-84jv","type":"blocks","created_at":"2026-01-09T16:54:16.773958671Z","created_by":"cyou"},{"issue_id":"mala-6lt9","depends_on_id":"mala-s9p8","type":"parent-child","created_at":"2026-01-09T16:54:44.918673104Z","created_by":"cyou"}]}
{"id":"mala-6m0","title":"Remove dist artifacts from repo","description":"Context:\n- Built artifacts in dist/ are checked into the repo and can become stale or misleading.\n\nScope:\n- In: remove dist artifacts from git and add root .gitignore entries to prevent re-addition.\n- Out: changes to build scripts or release process.\n\nAcceptance Criteria:\n- dist/ artifacts are removed from version control.\n- dist/ is ignored at repo root.\n\nTest Plan:\n- `git status` is clean after removal; building still produces artifacts locally.","status":"closed","priority":4,"issue_type":"chore","created_at":"2025-12-26T17:36:09.569995213Z","updated_at":"2025-12-26T18:41:25.970765111Z","closed_at":"2025-12-26T18:41:25.970765111Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-6m0","depends_on_id":"mala-jnq","type":"parent-child","created_at":"2025-12-26T17:36:15.744955272Z","created_by":"daemon"}]}
{"id":"mala-6rko","title":"[Review] 1 non-blocking finding from mala-cw18","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-cw18\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Persistent redundant disk I/O in build_continuation_prompt\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/domain/prompts.py:162-178\n\nThe `build_continuation_prompt` function still performs direct disk I/O on every call by calling `load_prompt`. This fails to fully address the previous architectural finding (Finding 3) regarding the established loading pattern. Since these prompts are now part of the `PromptProvider` dataclass, the formatting logic should be decoupled from the loading logic. Refactoring this function to accept the template string as an argument would avoid redundant filesystem access and ensure that application-wide prompt configurations (such as custom prompt directories) are correctly respected.\n\n---\n\n\u003c!-- fp:13e50d06915f7cad --\u003e\n\u003c!-- review_finding:bbeefc857d47 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T19:25:16.481853631Z","created_by":"cyou","updated_at":"2026-01-03T19:30:42.694247928Z","closed_at":"2026-01-03T19:30:42.694247928Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-cw18"]}
{"id":"mala-6tdu","title":"[Review] 4 non-blocking findings from mala-au9g.2","description":"## Review Findings\n\nThis issue consolidates 4 non-blocking findings from code review.\n\n**Source issue:** mala-au9g.2\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Make epic-verifier availability dynamic\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/pipeline/epic_verification_coordinator.py:120-121\n\n`has_epic_verifier` is captured at construction time, but the new callbacks explicitly allow patching `self.epic_verifier` after init. If the verifier is injected later (tests or lazy init), the coordinator will keep taking the fallback path and never call verification, which differs from the previous runtime check `self.epic_verifier is not None`. Consider deriving this from a callback/property at call time instead of a frozen boolean.\n\n---\n\n### Finding 2: [P2] Preserve agent_id for remediation failures\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/pipeline/epic_verification_coordinator.py:258-260\n\nWhen remediation tasks error or are cancelled, `_extract_task_result` now builds `IssueResult` with `agent_id=\"unknown\"`. Previously the orchestrator used `self.agent_ids.get(issue_id, \"unknown\")`, so failures were attributed to the actual spawned agent. This regression drops useful metadata for logs/metrics. Consider adding an `agent_id` callback or passing it through to keep behavior consistent.\n\n---\n\n### Finding 3: [P2] Restore telemetry detail in remediation failure reporting\n\n**Priority:** P3\n**Reviewer:** gemini\n**Location:** src/pipeline/epic_verification_coordinator.py:235-276\n\nThe extracted logic for remediation issue execution has lost telemetry detail compared to the original implementation. In `_extract_task_result`, the `agent_id` is hardcoded to `\"unknown\"` for cancelled or failed tasks, whereas it was previously looked up in `self.agent_ids`. Additionally, the `on_warning` callback for finalization errors no longer includes the `agent_id` parameter (previously passed as `issue_id`). This reduces the ability to identify which agent failed during automated remediation.\n\n---\n\n### Finding 4: [P2] Use callback for has_epic_verifier to support patching\n\n**Priority:** P3\n**Reviewer:** gemini\n**Location:** src/orchestration/orchestrator.py:288\n\nIn `MalaOrchestrator`, `has_epic_verifier` is evaluated eagerly as a boolean during initialization. However, the accompanying comments state that resolution is deferred via lambdas to allow tests to patch components like `self.epic_verifier` after construction. To support this design goal, `has_epic_verifier` should be passed as a callback (or the coordinator should check the `verify_epic` callback) so that it reflects the current state of the orchestrator if it is patched in tests.\n\n---\n","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T07:35:14.047039419Z","created_by":"cyou","updated_at":"2026-01-03T07:42:41.870843217Z","closed_at":"2026-01-03T07:42:41.870843217Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_findings:1823f4b2bf3e","source:mala-au9g.2"]}
{"id":"mala-6uit","title":"add separate bug template that is tdd","status":"tombstone","priority":2,"issue_type":"feature","created_at":"2026-01-04T05:25:09.496196463Z","created_by":"cyou","updated_at":"2026-01-06T22:52:10.761723406Z","deleted_at":"2026-01-06T22:52:10.761723406Z","deleted_by":"batch delete","delete_reason":"batch delete","original_type":"feature"}
{"id":"mala-6v0w","title":"[Review] src/domain/validation/config.py:105-109: [P3] Reject boolean timeout values in CommandConfig","description":"## Review Finding\n\nThis issue was auto-created from a P3 review finding.\n\n**Source issue:** mala-fdp1.1\n**Reviewer:** codex\n**Location:** src/domain/validation/config.py:105-109\n\n## Details\n\n`bool` is a subclass of `int`, so a YAML value like `timeout: true` passes the `isinstance(timeout, int)` check and becomes a 1-second timeout instead of raising a config error. That’s a silent misconfiguration. Consider rejecting booleans explicitly (e.g., `isinstance(timeout, bool)` or `type(timeout) is int`).","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-02T03:19:13.881432152Z","created_by":"cyou","updated_at":"2026-01-02T03:45:14.194503004Z","closed_at":"2026-01-02T03:45:14.194503004Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_finding:aef0c433e009","source:mala-fdp1.1"]}
{"id":"mala-6wen","title":"[Review] src/orchestration/orchestrator.py:543-547: [P2] Include exception details in finalization warning","description":"## Review Finding\n\nThis issue was auto-created from a P2 review finding.\n\n**Source issue:** mala-mfnr\n**Reviewer:** claude\n**Location:** src/orchestration/orchestrator.py:543-547\n\n## Details\n\nThe caught exception is discarded without logging its message or traceback. This makes debugging difficult when `_finalize_issue_result` fails. Capture the exception with `except Exception as e:` and include it in the warning message, e.g.:\n```python\nexcept Exception as e:\n    self.event_sink.on_warning(\n        f\"Failed to finalize remediation result for {issue_id}: {e}\",\n```","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-02T02:17:45.104276387Z","created_by":"cyou","updated_at":"2026-01-02T02:19:58.047300382Z","closed_at":"2026-01-02T02:19:58.047300382Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_finding:73aa721c3b73","source:mala-mfnr"]}
{"id":"mala-6xp5","title":"[Review] 1 non-blocking finding from mala-ejeq","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-ejeq\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Cannot disable track_review_issues without enabling code_review\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/orchestration/orchestrator.py:474-477\n\nIn `Orchestrator._get_track_review_issues` (L474), the configuration is only respected if `code_review.enabled` is true. This prevents users from disabling issue tracking for session findings (via `IssueFinalizer`) without also enabling the post-session code review (which adds cost/latency). If `session_end.code_review` has `enabled: false` (default) and `track_review_issues: false`, the setting is ignored and it falls back to the env var (default True).\n\n---\n\n\u003c!-- fp:355171a406075303 --\u003e\n\u003c!-- review_finding:4e43eff95f9a --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-11T00:09:22.185556226Z","created_by":"cyou","updated_at":"2026-01-11T00:24:50.550466863Z","closed_at":"2026-01-11T00:24:50.550466863Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-ejeq"]}
{"id":"mala-720s","title":"[Review] src/infra/clients/cerberus_review.py:255-258: [P3] Use user-facing terminology in fatal error message","description":"## Review Finding\n\nThis issue was auto-created from a P3 review finding.\n\n**Source issue:** mala-awsa\n**Reviewer:** gemini\n**Location:** src/infra/clients/cerberus_review.py:255-258\n\n## Details\n\nThe fatal error message includes technical justification ('cannot auto-resolve without session scoping') that may be confusing to end users. Consider a more actionable message like: 'Another review gate is already active. Please wait for the current run to finish or resolve it manually using `review-gate resolve` if you are certain no other runs are active.'","notes":"Quality gate failed: External review failed: spawn failed: Artifact written: /home/cyou/.claude/projects/-home-cyou-mala/cerberus/7b38713e-813e-4a50-8c59-4aff423d849e/latest.md\nReview gate already active (status: pending, age: 24s)\nUse /home/cyou/.claude/plugins/cache/cerberus/cerberus/1.10.6/bin/review-gate resolve to complete the existing gate first.\nLog: /home/cyou/.claude/projects/-home-cyou-mala/7b38713e-813e-4a50-8c59-4aff423d849e.jsonl","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-01T23:55:18.815108514Z","created_by":"cyou","updated_at":"2026-01-02T00:48:32.831997342Z","closed_at":"2026-01-02T00:48:32.831997342Z","close_reason":"Closed","labels":["auto_generated","needs-followup","review_finding","review_finding:4d81bd7daf78","source:mala-awsa"]}
{"id":"mala-77kx","title":"focus mode should only work on 1 epic at a time","status":"closed","priority":2,"issue_type":"feature","created_at":"2026-01-03T20:42:29.023818828Z","created_by":"cyou","updated_at":"2026-01-06T05:19:51.531220942Z","closed_at":"2026-01-06T05:19:51.531220942Z","close_reason":"Closed"}
{"id":"mala-77z","title":"Remove unused log_result function","description":"## Context\n- `log_result()` is defined but never imported or called anywhere\n- Orchestrator uses `log()` with \"✓\" and \"✗\" icons directly instead\n- ~12 lines of dead code\n- Confidence: High - grep shows only the definition, no callers\n\n## Primary Files\n`src/logging/console.py:263-274`\n\n## Scope\n**In:** Delete the `log_result` function\n**Out:** Keep all other logging functions\n\n## Acceptance Criteria\n- Function removed\n- No import errors or reference breaks\n- Tests pass\n\n## Test Plan\n- Run `uv run pytest`\n- Verify: `python -c \"from src.logging.console import log, log_tool, log_agent_text\"`\n\n## Notes for Agent\n- Function is at lines 263-274, after `log_agent_text`\n- No tests reference this function","status":"closed","priority":2,"issue_type":"chore","created_at":"2025-12-26T19:45:05.56975816Z","updated_at":"2025-12-26T19:53:05.087656466Z","closed_at":"2025-12-26T19:53:05.087656466Z","close_reason":"Closed","labels":["dead-code"],"dependencies":[{"issue_id":"mala-77z","depends_on_id":"mala-7zq","type":"parent-child","created_at":"2025-12-26T19:45:26.566257822Z","created_by":"daemon"}]}
{"id":"mala-781","title":"Package prompt file and align Python requirement","description":"Context:\n- Orchestrator reads the implementer prompt at import; missing files in the wheel will crash CLI startup.\n- Current build config doesn’t explicitly include prompt files in artifacts.\n- requires-python is set to \u003e=3.14 even though the codebase doesn’t appear to need it.\n- Needs verification: confirm wheel contents and the actual minimum supported Python version.\n\nScope:\n- In: include src/prompts/implementer_prompt.md in wheel build config and lower requires-python to the agreed minimum (likely 3.11/3.12).\n- Out: changing prompt content or runtime loading semantics.\n\nAcceptance Criteria:\n- Built wheel contains the prompt file and importing src.orchestrator succeeds.\n- Package installs on the agreed minimum Python version.\n\nTest Plan:\n- Build a wheel and verify prompt file inclusion; run a simple import or `mala status` in a venv.\n\nNotes for Agent:\n- Keep changes isolated to packaging config unless a fallback is required.","status":"closed","priority":1,"issue_type":"bug","created_at":"2025-12-26T17:35:34.45601593Z","updated_at":"2025-12-26T18:20:25.115815935Z","closed_at":"2025-12-26T18:20:25.115815935Z","close_reason":"False positive: hatchling's packages=[\"src\"] includes all files (not just .py), so prompt is already in wheel. Only requires-python issue remains valid but is minor.","dependencies":[{"issue_id":"mala-781","depends_on_id":"mala-jnq","type":"parent-child","created_at":"2025-12-26T17:36:15.690597316Z","created_by":"daemon"}]}
{"id":"mala-784","title":"Ideas: Future improvements","description":"Brainstorm ideas needing grooming:\n- Add back claude orchestrator, manually managing context\n- Agent mail using filesystem?\n- Use codex for code review?\n- Make morphllm optional","status":"tombstone","priority":2,"issue_type":"epic","created_at":"2025-12-26T00:54:39.176848025Z","updated_at":"2025-12-26T00:55:27.983470092Z","deleted_at":"2025-12-26T00:55:27.983470092Z","deleted_by":"daemon","delete_reason":"delete","original_type":"epic"}
{"id":"mala-78np","title":"[Review] 2 non-blocking findings from mala-q2xb","description":"## Review Findings\n\nThis issue consolidates 2 non-blocking findings from code review.\n\n**Source issue:** mala-q2xb\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P3] Test incorrectly computes batch dedup hash\n\n**Priority:** P3\n**Reviewer:** claude\n**Location:** tests/test_orchestration_helpers.py:606-607\n\nThe test at line 607 computes the batch dedup hash from a single fingerprint: `hashlib.sha256(individual_fp.encode())`. However, `_build_findings_section` computes it from pipe-separated sorted fingerprints: `hashlib.sha256(\"|\".join(sorted_fingerprints).encode())`. For a single finding, the test should use `hashlib.sha256(f\"{individual_fp}\".encode())` which happens to be equivalent, but the comment on line 606 (\"Batch dedup uses sorted individual fingerprints\") is misleading since a single hash doesn't demonstrate the pipe-join behavior. This test works by coincidence but may confuse future readers.\n\n---\n\n### Finding 2: [P2] Preserve legacy fingerprints to avoid duplicate tracking issues\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/orchestration/review_tracking.py:79-84\n\nBy switching fingerprints to hex-only comments, `_extract_existing_fingerprints` will no longer recognize fingerprints written by the previous format (`\u003c!-- fp:file:line:line:title --\u003e`). Any existing tracking issue created before this change will appear to have zero fingerprints, so dedup can fail and the system may append duplicates or create a new tracking issue instead of updating the old one. Consider supporting both formats during a transition or migrating existing descriptions/tags.\n\n---\n","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T06:46:56.296499918Z","created_by":"cyou","updated_at":"2026-01-03T06:50:47.818994163Z","closed_at":"2026-01-03T06:50:47.818994163Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_findings:89b52434af53","source:mala-q2xb"]}
{"id":"mala-78zr","title":"add cerberus code review, max mode to run level validations","status":"tombstone","priority":2,"issue_type":"feature","created_at":"2026-01-06T05:32:18.420782907Z","created_by":"cyou","updated_at":"2026-01-06T22:52:10.761723406Z","deleted_at":"2026-01-06T22:52:10.761723406Z","deleted_by":"batch delete","delete_reason":"batch delete","original_type":"feature"}
{"id":"mala-7a6","title":"Baseline commit lost on session resume: Codex review sees partial diff","description":"## Problem\n\nWhen an issue is partially completed in a prior session (e.g., agent made commits but gate/review failed), a new orchestrator session captures a NEW baseline from current HEAD. This means the cumulative diff only includes changes from the current session, not the full implementation.\n\n### Scenario\n\n1. **Session 1**: Issue claimed, baseline = commit A\n2. **Session 1**: Agent creates commits B, C (partial implementation)\n3. **Session 1**: Gate/review fails, session ends\n4. **Session 2**: New orchestrator run, baseline = commit C (prior work!)\n5. **Session 2**: Agent creates commit D (fix)\n6. **Session 2**: Codex reviews diff C..D — **misses work in B and C!**\n\n### Root Cause\n\n`baseline_commit_hash` is captured fresh in `run_implementer()` at session start, stored only in `RetryState` (in-memory). Not persisted across orchestrator runs.\n\n## Solution\n\nDerive baseline from git history instead of capturing at session start:\n\n```python\ndef get_baseline_for_issue(repo_path: Path, issue_id: str) -\u003e str | None:\n    # Find first commit with \"bd-{issue_id}:\" prefix\n    result = subprocess.run(\n        [\"git\", \"log\", \"--oneline\", \"--reverse\", f\"--grep=^bd-{issue_id}:\"],\n        capture_output=True, text=True, cwd=repo_path\n    )\n    if result.stdout.strip():\n        first_commit = result.stdout.strip().split('\\n')[0].split()[0]\n        # Get its parent\n        parent = subprocess.run(\n            [\"git\", \"rev-parse\", f\"{first_commit}^\"],\n            capture_output=True, text=True, cwd=repo_path\n        )\n        return parent.stdout.strip() if parent.returncode == 0 else None\n    return None  # No commits yet for this issue\n```\n\nLogic in `run_implementer()`:\n- If git history has commits for this issue → use parent of first commit as baseline\n- If no commits yet → capture current HEAD as baseline (fresh issue)\n\n### Why git history approach\n\n1. Git is source of truth - always accurate\n2. Works retroactively for existing in-progress issues  \n3. No beads schema changes or notes parsing\n4. Self-healing if metadata gets corrupted\n5. Handles edge cases (parallel agents, rebases) naturally\n\n## In Scope\n\n- `src/git_utils.py`: Add `get_baseline_for_issue()` async function\n- `src/orchestrator.py`: Update `run_implementer()` to use git-derived baseline\n- Tests: TDD approach - write tests first for:\n  - Fresh issue (no prior commits) → captures HEAD\n  - Resumed issue (has prior commits) → finds parent of first commit\n  - Edge cases: merge commits, rebased history, no parent (root commit)\n\n## Out of Scope\n\n- Persisting baseline in beads notes (simpler git approach preferred)\n- Changes to beads schema\n- Codex prompt changes (already handles baseline correctly when provided)\n\n## Implementation Notes\n\n**Use TDD approach:**\n1. Write failing tests first for `get_baseline_for_issue()`\n2. Write failing tests for orchestrator integration\n3. Implement to make tests pass\n4. Refactor as needed\n\n## Acceptance Criteria\n\n- [ ] Resumed sessions use correct baseline (parent of first issue commit)\n- [ ] Fresh issues still capture HEAD as baseline\n- [ ] Codex review sees cumulative diff across sessions\n- [ ] Tests cover fresh, resumed, and edge case scenarios","status":"closed","priority":0,"issue_type":"bug","created_at":"2025-12-27T23:49:29.153535606Z","updated_at":"2025-12-28T00:55:16.085961516Z","closed_at":"2025-12-28T00:55:16.085961516Z","close_reason":"Closed"}
{"id":"mala-7e23","title":"Fix FakeIssueProvider blocked count semantics","description":"## Problem\\nFakeIssueProvider.get_blocked_count_async currently treats any status not in (ready, closed, in_progress) as blocked. This incorrectly counts open issues as blocked and diverges from BeadsClient (which counts only status=blocked via bd list --status blocked). This skews watch-mode idle messaging and any logic that uses blocked counts during tests.\\n\\n## Scope\\n- Align FakeIssueProvider.get_blocked_count_async with BeadsClient semantics.\\n- Ensure only issues with status==\"blocked\" are counted (and possibly match task-only filtering if needed).\\n- Update/extend tests to validate parity.\\n\\n## Acceptance Criteria\\n- FakeIssueProvider.get_blocked_count_async returns count of issues whose status is exactly \"blocked\".\\n- A unit/integration test demonstrates parity with the intended behavior (open/in_progress/closed excluded; blocked counted).\\n- No regressions in existing tests.\\n\\n## Files\\n- tests/fakes/issue_provider.py\\n- tests/contracts/test_issue_provider_contract.py (or new targeted test in tests/unit/...)\\n\\n## Verification\\n- uv run pytest tests/contracts/test_issue_provider_contract.py -v\\n- uv run pytest tests/unit/... (as needed)","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-05T14:17:44.428234796Z","created_by":"cyou","updated_at":"2026-01-05T15:59:11.767117555Z","closed_at":"2026-01-05T15:59:11.767117555Z","close_reason":"Closed"}
{"id":"mala-7jj","title":"CLI tool for mala run performance analysis","description":"Context:\n- Currently, analyzing mala run performance requires ad-hoc Python scripts\n- Key metrics needed: token usage, context accumulation, duration, success rates, headroom analysis\n- Data sources: ~/.config/mala/runs/*.json (run metadata) and Claude JSONL logs (token usage)\n\nScope:\n- In: CLI command `mala analyze` (or similar) that queries local run data\n- In: Summary stats (min/median/mean/max) for: final context tokens, duration, success rate\n- In: Context headroom analysis (% of 200K limit used)\n- In: Top N issues by token consumption\n- In: Filtering by time range (e.g., --last 1d, --last 7d)\n- Out: Braintrust integration (separate concern)\n- Out: Real-time monitoring (this is post-hoc analysis)\n\nAcceptance Criteria:\n- mala analyze shows summary statistics for recent runs\n- mala analyze --last 1d filters to last 24 hours\n- mala analyze --top 10 shows top 10 issues by token usage\n- mala analyze --context shows context headroom distribution\n- Output is formatted for terminal (not JSON by default, but --json flag available)\n\nTest Plan:\n- Unit tests for log parsing and aggregation logic\n- Integration test with sample run/log fixtures\n- Manual verification against known run data\n\nNotes for Agent:\n- Token calculation: input_tokens + cache_creation_input_tokens + cache_read_input_tokens\n- Run files are in ~/.config/mala/runs/*.json\n- Log files are referenced in run JSON as issues.\u003cid\u003e.log_path\n- Usage data is in JSONL entries at entry.message.usage","status":"tombstone","priority":2,"issue_type":"feature","created_at":"2025-12-27T18:08:15.379697993Z","updated_at":"2026-01-06T22:52:10.761723406Z","deleted_at":"2026-01-06T22:52:10.761723406Z","deleted_by":"batch delete","delete_reason":"batch delete","original_type":"feature"}
{"id":"mala-7m1","title":"Support non-Python repos in validation","description":"Currently mala assumes all repos are Python projects and requires uv sync, ruff, pytest, ty validation commands.\n\n## Future Improvements\n\n### 1. Detect project type\nSkip Python validation for non-Python repos (check for pyproject.toml existence). Could also detect other project types (Node.js via package.json, Go via go.mod, etc.) and run appropriate validation.\n\n### 2. Per-repo validation config\nAllow repos to specify their own validation commands via:\n- CLAUDE.md directives\n- .beads/validation.yaml\n- pyproject.toml [tool.mala] section\n\n## Context\nDiscovered when ai-configs-464.1 (a shell script repo) failed quality gate due to missing 'uv sync' - there's no pyproject.toml because it's not a Python project.\n\n## References\n- src/validation/spec.py:build_validation_spec() - hardcodes Python commands\n- src/quality_gate.py:ValidationEvidence - hardcodes Python evidence checks","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-27T20:30:11.760932242Z","updated_at":"2025-12-27T20:36:17.101092438Z","closed_at":"2025-12-27T20:36:17.101092438Z","close_reason":"Merged into parent epic mala-ytn","dependencies":[{"issue_id":"mala-7m1","depends_on_id":"mala-ytn","type":"parent-child","created_at":"2025-12-27T20:36:11.439535313Z","created_by":"daemon"}]}
{"id":"mala-7pd","title":"Remove Edit from FILE_WRITE_TOOLS (redundant)","description":"## Context\n- `FILE_WRITE_TOOLS` includes `\"Edit\"` for lock enforcement\n- `MORPH_DISALLOWED_TOOLS` also lists `\"Edit\"`, blocking it before lock check runs\n- Lock enforcement hook never triggers for Edit - redundant entry\n- Confidence: Medium - logic is correct but relies on hook ordering\n\n## Primary Files\n`src/hooks.py:50-57`\n\n## Scope\n**In:** Remove `\"Edit\"` from `FILE_WRITE_TOOLS` constant\n**Out:** Don't change `MORPH_DISALLOWED_TOOLS` or lock enforcement logic\n\n## Acceptance Criteria\n- `\"Edit\"` removed from `FILE_WRITE_TOOLS`\n- `\"Edit\"` removed from `FILE_PATH_KEYS` dict\n- Test `test_file_write_tools_constant_contains_expected_tools` updated\n- All tests pass\n\n## Test Plan\n- Run `uv run pytest tests/test_hooks.py`\n- Run full suite: `uv run pytest`\n\n## Notes for Agent\n- Also remove `\"Edit\"` from `FILE_PATH_KEYS` dict (line 61) since it won't be checked\n- Update test in `test_hooks.py:120-124` to not expect `Edit` in the set","status":"closed","priority":3,"issue_type":"chore","created_at":"2025-12-26T19:45:13.913074646Z","updated_at":"2025-12-26T19:50:02.12412912Z","closed_at":"2025-12-26T19:50:02.12412912Z","close_reason":"Closed","labels":["consistency"],"dependencies":[{"issue_id":"mala-7pd","depends_on_id":"mala-7zq","type":"parent-child","created_at":"2025-12-26T19:45:26.577618753Z","created_by":"daemon"}]}
{"id":"mala-7qr5","title":"Epic: Migrate MalaConfig to pydantic-settings","description":"Replace manual env var parsing in MalaConfig.from_env() with pydantic-settings.\n\n**Context**: Evaluated config libraries (python-decouple, dynaconf, pydantic-settings) for mala. Oracle analysis confirmed pydantic-settings as best fit.\n\n**Why pydantic-settings**:\n- Typed settings with built-in validation (replaces manual validate())\n- Env var aliases/fallbacks (cleans up LLM_API_KEY/ANTHROPIC_API_KEY logic)\n- Custom validators for MALA_CERBERUS_* shlex/JSON parsing\n- Immutable models (frozen=True) - matches frozen dataclasses\n- No global state - preserves layered architecture\n\n**Why NOT dynaconf** (despite similarity to Viper):\n- Encourages global settings object - fights layer contracts\n- Wants to own precedence - conflicts with CLIOverrides → ResolvedConfig merge\n- Too much magic for ~15 settings\n\n**Scope**:\n- Only runtime/user config (MalaConfig from env vars)\n- Keep mala.yaml on current domain-layer dataclass loader (separate concern)\n\n**Migration approach**:\n- Replace internals of MalaConfig.from_env() with BaseSettings model\n- Convert to existing frozen dataclass\n- Keep public API and merge semantics intact\n\n**Reference**:\n- Oracle thread: http://localhost:8317/threads/T-019b8738-a20c-773f-84e9-e3075707415c\n- Current config: src/infra/io/config.py","status":"tombstone","priority":2,"issue_type":"feature","created_at":"2026-01-04T05:02:16.009899584Z","created_by":"cyou","updated_at":"2026-01-06T22:52:10.761723406Z","deleted_at":"2026-01-06T22:52:10.761723406Z","deleted_by":"batch delete","delete_reason":"batch delete","original_type":"feature"}
{"id":"mala-7rt","title":"Worktree mode support","description":"Context:\n- Currently agents share single worktree, requiring file locks\n- Git worktrees would allow parallel work without lock contention\n- Major architectural change with significant benefits\n\nLocation:\n- src/orchestration/orchestrator.py (agent spawning)\n- src/pipeline/agent_session_runner.py (session setup)\n- src/infra/tools/locking.py (current lock implementation)\n\nScope:\n- In: Create git worktree per agent on spawn\n- In: Agent works in isolated worktree directory\n- In: Merge completed worktree changes back to main\n- Out: Remote worktrees; distributed execution\n\nAcceptance Criteria:\n- Each agent operates in its own git worktree\n- No file lock contention between agents\n- Completed work merges cleanly to main worktree\n- Worktrees cleaned up on agent completion/failure\n\nTest Plan:\n- Unit: Test worktree creation/deletion utilities\n- Integration: Run multiple agents, verify isolation\n- Manual: Observe no lock waits between agents\n\nNotes for Agent:\n- `git worktree add` creates isolated working directory\n- Need conflict resolution strategy for merge-back\n- Consider keeping N recent worktrees for debugging\n- May want --worktree flag to opt-in (default shared mode)","status":"tombstone","priority":4,"issue_type":"feature","created_at":"2025-12-28T06:21:39.000904646Z","updated_at":"2026-01-06T22:52:10.761723406Z","deleted_at":"2026-01-06T22:52:10.761723406Z","deleted_by":"batch delete","delete_reason":"batch delete","original_type":"feature"}
{"id":"mala-7si","title":"Epic: Healthcheck cleanup (2025-12-28)","description":"## Context\nHealthcheck from 2025-12-28 identified incomplete migrations and minor issues.\n\n## Findings from mala-869 (Architecture epic)\nThree modules were created but never integrated:\n- event_sink.py - MalaEventSink protocol (DEFERRED - too large)\n- telemetry.py - TelemetryProvider protocol (small fix)\n- config.py - MalaConfig dataclass (medium fix)\n\n## Other Findings\n- Dist artifacts still exist despite mala-w8w.3 being closed\n- Tool name case sensitivity issue in session_log_parser.py\n\n## Acceptance Criteria\n- TelemetryProvider injected into orchestrator\n- MalaConfig injected into orchestrator and CLI\n- Tool name case sensitivity fixed\n- Dist directory clean\n- Event_sink epic documented for future consideration\n\n## Execution Order\n1. mala-7si.2 (telemetry) - no blockers\n2. mala-7si.5 (config) - blocked by telemetry (file overlap)\n3. mala-7si.3 (case sensitivity) - parallel with above\n4. mala-7si.4 (dist artifacts) - parallel with above\n\n## Out of Scope\n- Full event_sink migration (deferred as mala-7si.1)","notes":"Combined under mala-3qp epic.","status":"closed","priority":2,"issue_type":"epic","created_at":"2025-12-28T22:06:30.51180045Z","created_by":"cyou","updated_at":"2025-12-30T04:25:34.989833297Z","closed_at":"2025-12-30T04:25:34.989833297Z","close_reason":"Closed"}
{"id":"mala-7si.1","title":"Epic: Inject MalaEventSink into orchestrator","description":"## Context\n- `mala-869.17` created MalaEventSink protocol but follow-up to inject into orchestrator was never done\n- Goal: Decouple orchestrator from console logging for testability\n- Currently orchestrator directly calls log(), log_tool(), log_agent_text() (131 log-related lines)\n\n## Complexity Assessment\nThis is a large migration:\n- 131 log calls need mapping to ~30 event sink methods\n- Need to create ConsoleEventSink (doesn't exist)\n- RunConfig naming collision: event_sink.RunConfig vs run_metadata.RunConfig\n- All-or-nothing: partial migration provides zero testability benefit\n- 3811 lines of orchestrator tests already work without this\n\n## Subtasks Needed (if pursued)\n1. Resolve RunConfig naming collision\n2. Create ConsoleEventSink wrapping current log functions\n3. Add event_sink parameter to MalaOrchestrator\n4. Migrate lifecycle log calls to sink events\n5. Migrate agent log calls to sink events\n6. Migrate gate/review log calls to sink events\n7. Migrate warning/diagnostic log calls to sink events\n\n## Decision\nDeferred to P4. Current tests work fine. Revisit if testability becomes a real problem.\n\n## Primary Files\n- src/orchestrator.py\n- src/event_sink.py\n\n## Acceptance Criteria\n- All subtasks completed\n- Orchestrator emits ALL output through event sink\n- NullEventSink enables silent test execution\n\n[Added Context]\n- Codebase still uses console logging directly in src/orchestrator.py via log/log_tool/log_agent_text (~100+ call sites)\n- src/event_sink.py defines MalaEventSink + NullEventSink but no ConsoleEventSink exists\n- Recommend doing this after orchestrator pipeline extraction (mala-3qp.6.4) to reduce churn\n","status":"closed","priority":4,"issue_type":"epic","created_at":"2025-12-28T22:06:43.782262444Z","created_by":"cyou","updated_at":"2025-12-30T04:24:12.287383624Z","closed_at":"2025-12-30T04:24:12.287383624Z","close_reason":"All children completed","dependencies":[{"issue_id":"mala-7si.1","depends_on_id":"mala-7si","type":"parent-child","created_at":"2025-12-28T22:06:43.789175677Z","created_by":"daemon"},{"issue_id":"mala-7si.1","depends_on_id":"mala-3qp.6.4","type":"blocks","created_at":"2025-12-29T15:35:31.32178513Z","created_by":"daemon"}]}
{"id":"mala-7si.1.1","title":"Event sink: add ConsoleEventSink + resolve RunConfig collision","description":"## Context\n- src/event_sink.py defines MalaEventSink + NullEventSink but no ConsoleEventSink exists\n- src/event_sink.py also defines RunConfig which collides with logging.run_metadata.RunConfig\n- Orchestrator still logs directly; a console sink is needed before migration\n\n## Primary Files\n- src/event_sink.py\n- src/event_sink_console.py (new)\n- src/logging/console.py\n\n## Scope\n**In:** Add ConsoleEventSink implementing MalaEventSink using existing console log helpers\n**In:** Resolve RunConfig naming collision (rename in event_sink or export alias)\n**Out:** Modifying orchestrator or changing logging formats\n\n## Acceptance Criteria\n- ConsoleEventSink exists and implements all MalaEventSink methods\n- RunConfig naming collision is resolved (no ambiguous RunConfig import in orchestrator)\n- No behavior changes yet (orchestrator still logs directly)\n\n## Test Plan\n- Add a smoke test that instantiates ConsoleEventSink and calls representative methods (monkeypatch log/log_tool/log_agent_text)\n- Run `uv run pytest`\n\n## Notes for Agent\n- Keep console formatting identical by reusing logging/console helpers\n- Prefer renaming event_sink.RunConfig to EventRunConfig (or similar) to avoid clashes\n","status":"closed","priority":4,"issue_type":"task","created_at":"2025-12-29T15:40:07.495706178Z","created_by":"cyou","updated_at":"2025-12-30T00:19:55.069028743Z","closed_at":"2025-12-30T00:19:55.069028743Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-7si.1.1","depends_on_id":"mala-7si.1","type":"parent-child","created_at":"2025-12-29T15:40:07.505797977Z","created_by":"daemon"}]}
{"id":"mala-7si.1.2","title":"Orchestrator: wire event sink for run lifecycle logs","description":"## Context\n- Orchestrator still logs run lifecycle events directly via log()\n- With ConsoleEventSink available, run-level events can be routed through the sink\n\n## Primary Files\n- src/orchestrator.py\n- src/event_sink_console.py\n- src/event_sink.py\n\n## Scope\n**In:** Add event_sink parameter to MalaOrchestrator with default ConsoleEventSink\n**In:** Replace run lifecycle logs (run started, ready issues, waiting, no more issues, run completed) with sink events\n**Out:** Changing run behavior or log content\n\n## Acceptance Criteria\n- MalaOrchestrator accepts optional event_sink and defaults to ConsoleEventSink\n- Run lifecycle log calls are replaced by event_sink methods\n- Console output remains unchanged with default sink\n\n## Test Plan\n- Add a test using a fake sink to assert run lifecycle events are emitted\n- Run `uv run pytest`\n\n## Notes for Agent\n- Avoid duplicating output (remove direct log() calls once sink is used)\n- Keep sink calls non-blocking and synchronous\n","notes":"Quality gate failed: Codex review failed: /home/cyou/mala/src/orchestrator.py:1023: [P2] Commit/metadata console lines are now suppressed or changed under the default sink; /home/cyou/mala/src/orchestrator.py:942: [P3] on_ready_issues receives a list that is immediately mutated\nLog: /home/cyou/.claude/projects/-home-cyou-mala/d5c7c053-4701-4860-b815-5c5285f433f5.jsonl","status":"closed","priority":4,"issue_type":"task","created_at":"2025-12-29T15:40:21.389957102Z","created_by":"cyou","updated_at":"2025-12-30T02:10:20.391933808Z","closed_at":"2025-12-30T02:10:20.391933808Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-7si.1.2","depends_on_id":"mala-7si.1","type":"parent-child","created_at":"2025-12-29T15:40:21.399160417Z","created_by":"daemon"},{"issue_id":"mala-7si.1.2","depends_on_id":"mala-7si.1.1","type":"blocks","created_at":"2025-12-29T15:41:16.34066354Z","created_by":"daemon"},{"issue_id":"mala-7si.1.2","depends_on_id":"mala-3qp.6.4","type":"blocks","created_at":"2025-12-29T15:41:21.371744994Z","created_by":"daemon"}]}
{"id":"mala-7si.1.3","title":"Orchestrator: migrate agent lifecycle + streaming logs to sink","description":"## Context\n- Agent lifecycle and streaming output are logged directly via log_agent_text/log_tool/log()\n- This output should be routed through the event sink for testability\n\n## Primary Files\n- src/orchestrator.py\n- src/event_sink_console.py\n\n## Scope\n**In:** Replace agent lifecycle logs (agent started/completed/claim failed) with sink events\n**In:** Route tool usage + agent text streaming through sink methods\n**Out:** Changing SDK integration or log formatting\n\n## Acceptance Criteria\n- All agent lifecycle and streaming log calls are replaced by event_sink calls\n- Console output remains unchanged with ConsoleEventSink\n\n## Test Plan\n- Add a test using a fake sink to assert agent events and tool/text events are emitted\n- Run `uv run pytest`\n\n## Notes for Agent\n- Keep truncation behavior consistent by reusing console helpers in the sink\n","status":"closed","priority":4,"issue_type":"task","created_at":"2025-12-29T15:40:34.85849573Z","created_by":"cyou","updated_at":"2025-12-30T03:05:45.141158874Z","closed_at":"2025-12-30T03:05:45.141158874Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-7si.1.3","depends_on_id":"mala-7si.1","type":"parent-child","created_at":"2025-12-29T15:40:34.867420567Z","created_by":"daemon"},{"issue_id":"mala-7si.1.3","depends_on_id":"mala-7si.1.2","type":"blocks","created_at":"2025-12-29T15:41:26.40026671Z","created_by":"daemon"}]}
{"id":"mala-7si.1.4","title":"Orchestrator: migrate gate/review/fixer logs to sink","description":"## Context\n- Gate/review/fixer flows log directly to console within orchestrator\n- These should emit structured sink events for testability and alternate sinks\n\n## Primary Files\n- src/orchestrator.py\n- src/event_sink_console.py\n\n## Scope\n**In:** Replace gate logs (start/pass/fail/retry/result) with sink events\n**In:** Replace review logs (start/retry/pass) with sink events\n**In:** Replace fixer logs (start/complete/fail) with sink events\n**Out:** Changing gate/review policy or retry behavior\n\n## Acceptance Criteria\n- Gate/review/fixer log calls are fully routed through event sink\n- Console output remains unchanged with ConsoleEventSink\n\n## Test Plan\n- Add a test using a fake sink to assert gate/review/fixer events are emitted\n- Run `uv run pytest`\n\n## Notes for Agent\n- Avoid emitting both sink and direct log calls\n","notes":"Quality gate failed: Codex review failed: /home/cyou/mala/src/pipeline/agent_session_runner.py:603: [P2] Gate/review start messages no longer appear in non-verbose runs; /home/cyou/mala/src/event_sink_console.py:297: [P3] Gate retries now print failure reasons on every attempt\nLog: /home/cyou/.claude/projects/-home-cyou-mala/7777348a-3a78-4473-995e-24f43d03fb39.jsonl","status":"closed","priority":4,"issue_type":"task","created_at":"2025-12-29T15:40:48.964887082Z","created_by":"cyou","updated_at":"2025-12-30T04:04:31.79574809Z","closed_at":"2025-12-30T04:04:31.79574809Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-7si.1.4","depends_on_id":"mala-7si.1","type":"parent-child","created_at":"2025-12-29T15:40:48.975462167Z","created_by":"daemon"},{"issue_id":"mala-7si.1.4","depends_on_id":"mala-7si.1.3","type":"blocks","created_at":"2025-12-29T15:41:31.436148248Z","created_by":"daemon"}]}
{"id":"mala-7si.1.5","title":"Orchestrator: migrate warnings/diagnostics logs to sink","description":"## Context\n- Warnings, diagnostics, lock cleanup, and metadata messages still log directly to console\n- These should route through the event sink for full decoupling\n\n## Primary Files\n- src/orchestrator.py\n- src/event_sink_console.py\n\n## Scope\n**In:** Replace warning/diagnostic logs (timeouts, lock cleanup/release, metadata saved, issues committed) with sink events\n**Out:** Changing error handling or log message content\n\n## Acceptance Criteria\n- All remaining diagnostic/warning log calls use event sink\n- No direct log() calls remain in orchestrator (except possibly in CLI or other modules)\n- Console output remains unchanged with ConsoleEventSink\n\n## Test Plan\n- Add a test using a fake sink to assert diagnostic events are emitted\n- Run `uv run pytest`\n\n## Notes for Agent\n- Verify remaining log() calls in orchestrator are eliminated after this step\n","status":"closed","priority":4,"issue_type":"task","created_at":"2025-12-29T15:41:02.380545492Z","created_by":"cyou","updated_at":"2025-12-30T04:24:12.274050067Z","closed_at":"2025-12-30T04:24:12.274050067Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-7si.1.5","depends_on_id":"mala-7si.1","type":"parent-child","created_at":"2025-12-29T15:41:02.391032208Z","created_by":"daemon"},{"issue_id":"mala-7si.1.5","depends_on_id":"mala-7si.1.4","type":"blocks","created_at":"2025-12-29T15:41:36.462025059Z","created_by":"daemon"}]}
{"id":"mala-7si.2","title":"Inject TelemetryProvider into orchestrator","description":"## Context\n- `mala-869.8` created TelemetryProvider protocol but follow-up to inject it was never done\n- Goal: Abstract telemetry for testability and programmatic control\n- Currently uses TracedAgentExecution directly from braintrust_integration.py (one call site: line 782)\n- telemetry.py already has complete BraintrustProvider wrapping TracedAgentExecution\n\n## Primary Files\n- src/orchestrator.py (add provider parameter, change one call site)\n- src/telemetry.py (already complete - no changes needed)\n\n## Scope\n**In:** Inject TelemetryProvider into MalaOrchestrator; use provider.create_span() instead of TracedAgentExecution directly\n**Out:** Removing global SDK patching (may still be needed for auto-instrumentation)\n\n## Acceptance Criteria\n- MalaOrchestrator accepts optional telemetry_provider parameter\n- Default to BraintrustProvider() when braintrust_enabled=True, else NullTelemetryProvider()\n- NullTelemetryProvider can be used in tests\n- Existing tracing behavior unchanged\n\n## Test Plan\n- Add test using NullTelemetryProvider to verify no telemetry side effects\n- Verify Braintrust traces still work in production\n- Run existing tests to confirm no regression\n\n## Notes for Agent\n- Only ONE call site to change: line 782 TracedAgentExecution usage\n- BraintrustProvider is already implemented in telemetry.py\n- Keep backward compatible - provider should be optional with sensible default","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-28T22:06:51.294234122Z","created_by":"cyou","updated_at":"2025-12-28T23:10:41.614342542Z","closed_at":"2025-12-28T23:10:41.614342542Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-7si.2","depends_on_id":"mala-7si","type":"parent-child","created_at":"2025-12-28T22:06:51.302309739Z","created_by":"daemon"}]}
{"id":"mala-7si.3","title":"Fix tool name case sensitivity in session_log_parser","description":"## Context\n- `session_log_parser.py:168` checks `block.name == \"Bash\"` (case-sensitive)\n- `hooks.py:95` defines `BASH_TOOL_NAMES = frozenset([\"bash\"])` with comment \"case-insensitive matching\"\n- If Claude SDK ever returns \"bash\" instead of \"Bash\", validation evidence detection would fail\n- Confidence: Medium - Claude Code currently uses \"Bash\" but this is fragile\n\n## Primary Files\n- src/session_log_parser.py\n\n## Scope\n**In:** Normalize tool name comparison in extract_bash_commands() to be case-insensitive\n**Out:** Changes to hooks.py (already handles case correctly)\n\n## Acceptance Criteria\n- `extract_bash_commands()` matches tool name case-insensitively\n- Both \"Bash\" and \"bash\" return the same results\n\n## Test Plan\n- TDD: Add test case with lowercase \"bash\" tool name, verify it fails initially\n- Fix: Change `block.name == \"Bash\"` to `block.name.lower() == \"bash\"`\n- Verify test passes\n\n## Notes for Agent\n- Also check `_extract_bash_commands_from_data()` fallback method for same issue (line ~190)\n- Use `.lower()` comparison, not a set lookup (simpler)","status":"closed","priority":2,"issue_type":"bug","created_at":"2025-12-28T22:06:59.297259102Z","created_by":"cyou","updated_at":"2025-12-28T22:55:33.235204619Z","closed_at":"2025-12-28T22:55:33.235204619Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-7si.3","depends_on_id":"mala-7si","type":"parent-child","created_at":"2025-12-28T22:06:59.304539444Z","created_by":"daemon"}]}
{"id":"mala-7si.4","title":"Remove dist artifacts from repository","description":"## Context\n- `mala-w8w.3` was closed as \"Already resolved\" but artifacts still exist\n- `dist/` contains mala-0.1.0-py3-none-any.whl and mala-0.1.0.tar.gz\n- `.gitignore` with `*` exists in dist/ but files were committed before it was added\n- This is a reopen of the incorrectly closed mala-w8w.3\n\n## Primary Files\n- dist/mala-0.1.0-py3-none-any.whl (delete)\n- dist/mala-0.1.0.tar.gz (delete)\n\n## Scope\n**In:** Remove dist/mala-0.1.0-py3-none-any.whl and dist/mala-0.1.0.tar.gz from git\n**Out:** Changes to build system or CI\n\n## Acceptance Criteria\n- `git ls-files dist/` shows only .gitignore\n- No wheel/tarball files in dist/\n\n## Test Plan\n- `ls dist/` shows only .gitignore after cleanup\n- `git status` shows files as deleted\n\n## Notes for Agent\n- Use `git rm` to remove tracked files, not just `rm`\n- Keep dist/.gitignore intact","status":"closed","priority":4,"issue_type":"chore","created_at":"2025-12-28T22:07:07.422904666Z","created_by":"cyou","updated_at":"2025-12-28T22:39:05.568318763Z","closed_at":"2025-12-28T22:39:05.568318763Z","close_reason":"Already resolved: dist/ files are local build artifacts, not tracked in git. The .gitignore with '*' is working correctly.","dependencies":[{"issue_id":"mala-7si.4","depends_on_id":"mala-7si","type":"parent-child","created_at":"2025-12-28T22:07:07.42970931Z","created_by":"daemon"}]}
{"id":"mala-7si.5","title":"Inject MalaConfig into orchestrator and CLI","description":"## Context\n- `mala-869.2` created MalaConfig dataclass but follow-up to inject it was never done\n- Goal: Centralize configuration for programmatic users (no env var dependency)\n- Currently env vars are accessed directly in cli.py and orchestrator.py (~10 call sites)\n- MalaConfig already has from_env() for CLI compatibility\n\n## Primary Files\n- src/orchestrator.py (accept MalaConfig, use instead of direct env access)\n- src/cli.py (construct MalaConfig.from_env(), pass to orchestrator)\n- src/config.py (already complete - no changes needed)\n\n## Scope\n**In:** Add optional config parameter to MalaOrchestrator\n**In:** Update CLI to construct MalaConfig.from_env() and pass to orchestrator\n**In:** Replace direct os.environ.get() calls with config attributes\n**Out:** Removing env var support (CLI still uses from_env())\n**Out:** Changes to tools/env.py (keep for backward compat)\n\n## Call Sites to Update\ncli.py:\n- Line 72: os.environ.get(\"BRAINTRUST_API_KEY\")\n- Line 235: os.environ.get(\"MORPH_API_KEY\")\n\norchestrator.py:\n- Line 181: os.environ.get(\"MORPH_API_KEY\") in get_mcp_servers()\n- Lines 611, 739: os.environ inheritance (may keep as-is for subprocess env)\n\n## Acceptance Criteria\n- MalaOrchestrator accepts optional MalaConfig parameter\n- Default behavior unchanged (uses MalaConfig.from_env() internally if not provided)\n- Programmatic users can construct MalaConfig without env vars\n- All existing tests pass\n- CLI still works with env vars\n\n## Test Plan\n- Add test constructing orchestrator with explicit MalaConfig (no env vars)\n- Verify existing tests pass (env var path)\n- Test validation errors propagate correctly\n\n## Notes for Agent\n- MalaConfig is frozen dataclass - treat as immutable\n- Keep backward compatible: config param should be optional\n- The subprocess env inheritance (lines 611, 739) may need to stay as os.environ for child process env","notes":"Quality gate failed: Timeout after 30 minutes\nLog: /home/cyou/.claude/projects/-home-cyou-mala/c8b4520c-7749-4891-b291-35e413b623d7.jsonl","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-28T22:22:57.546322519Z","created_by":"cyou","updated_at":"2025-12-29T05:08:16.549668751Z","closed_at":"2025-12-29T05:08:16.549668751Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-7si.5","depends_on_id":"mala-7si","type":"parent-child","created_at":"2025-12-28T22:22:57.557222154Z","created_by":"daemon"},{"issue_id":"mala-7si.5","depends_on_id":"mala-7si.2","type":"blocks","created_at":"2025-12-28T22:23:06.687558734Z","created_by":"daemon"}]}
{"id":"mala-7wp2","title":"Clarify run_end/code_review trigger support in spec/docs or remove","description":"Implementation includes run_end trigger and code_review hook in trigger configs, but smaj spec/docs cover only epic_completion/session_end/periodic. Decide whether to document these features or remove/guard them. Files: docs/validation-triggers.md, plans/2026-01-07-epic-validation-trigger-spec.md, src/domain/validation/config.py, src/domain/validation/config_loader.py, src/orchestration/orchestrator.py.","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-09T16:53:44.722817304Z","created_by":"cyou","updated_at":"2026-01-09T21:42:19.321073892Z","closed_at":"2026-01-09T21:42:19.321073892Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-7wp2","depends_on_id":"mala-642f","type":"blocks","created_at":"2026-01-09T16:54:27.715338596Z","created_by":"cyou"},{"issue_id":"mala-7wp2","depends_on_id":"mala-553g","type":"blocks","created_at":"2026-01-09T16:54:32.837285677Z","created_by":"cyou"},{"issue_id":"mala-7wp2","depends_on_id":"mala-smaj","type":"parent-child","created_at":"2026-01-09T16:55:21.973681427Z","created_by":"cyou"}]}
{"id":"mala-7ya5","title":"Refactor locking_mcp.py to use LockingBackend protocol for DI","description":"## Problem\n\nTests in `tests/unit/infra/tools/test_locking_mcp.py` heavily use `patch()` to mock locking functions, which couples tests to implementation details and violates the codebase's 'fakes over mocks' philosophy.\n\n**Current patch() calls being used:**\n- `patch('src.infra.tools.locking_mcp.try_lock', ...)`\n- `patch('src.infra.tools.locking_mcp.release_lock', ...)`\n- `patch('src.infra.tools.locking_mcp.get_lock_holder', ...)`\n- `patch('src.infra.tools.locking_mcp.wait_for_lock_async', ...)`\n- `patch('src.infra.tools.locking_mcp.canonicalize_path', ...)`\n\n## Solution\n\nIntroduce a `LockingBackend` protocol and inject it into `create_locking_mcp_server()`.\n\n### 1. Create LockingBackend Protocol\n\n```python\n# src/core/protocols.py (or src/infra/tools/locking_mcp.py)\nclass LockingBackend(Protocol):\n    def canonicalize_path(self, path: str, namespace: str | None) -\u003e str: ...\n    def try_lock(self, filepath: str, agent_id: str, ns: str | None) -\u003e bool: ...\n    def release_lock(self, filepath: str, agent_id: str, ns: str | None) -\u003e bool: ...\n    def get_lock_holder(self, filepath: str, ns: str | None) -\u003e str | None: ...\n    async def wait_for_lock_async(\n        self, filepath: str, agent_id: str, ns: str | None,\n        timeout_seconds: float, poll_interval_ms: int,\n    ) -\u003e bool: ...\n```\n\n### 2. Create DefaultLockingBackend\n\n```python\nclass DefaultLockingBackend:\n    '''Production implementation forwarding to existing functions.'''\n    def canonicalize_path(self, path: str, ns: str | None) -\u003e str:\n        return canonicalize_path(path, ns)\n    \n    def try_lock(self, filepath: str, agent_id: str, ns: str | None) -\u003e bool:\n        return try_lock(filepath, agent_id, ns)\n    # ... etc for other methods\n```\n\n### 3. Update create_locking_mcp_server()\n\n```python\ndef create_locking_mcp_server(\n    agent_id: str,\n    repo_namespace: str | None,\n    emit_lock_event: Callable[[LockEvent], None | Awaitable[None]],\n    *,\n    locking_backend: LockingBackend | None = None,  # NEW\n    _return_handlers: bool = False,\n) -\u003e McpSdkServerConfig | tuple[McpSdkServerConfig, LockingToolHandlers]:\n    backend = locking_backend or DefaultLockingBackend()\n    handlers = LockingToolHandlers(..., locking_backend=backend)\n    ...\n```\n\n### 4. Update LockingToolHandlers\n\nReplace all direct function calls with `self._backend.method()`:\n- `try_lock(...)` → `self._backend.try_lock(...)`\n- `release_lock(...)` → `self._backend.release_lock(...)`\n- etc.\n\n### 5. Create FakeLockingBackend for tests\n\n```python\n# tests/fakes/locking_backend.py\nclass FakeLockingBackend:\n    def __init__(self) -\u003e None:\n        self.try_lock_calls: list[tuple[str, str, str | None]] = []\n        self.release_calls: list[tuple[str, str, str | None]] = []\n        self.holders: dict[str, str | None] = {}\n        self.try_lock_results: dict[str, bool] = {}\n        # ... etc\n\n    def try_lock(self, filepath: str, agent_id: str, ns: str | None) -\u003e bool:\n        self.try_lock_calls.append((filepath, agent_id, ns))\n        return self.try_lock_results.get(filepath, True)\n    # ... etc\n```\n\n### 6. Update tests to use FakeLockingBackend\n\n```python\n# Before (patch-based)\nwith patch('src.infra.tools.locking_mcp.try_lock', return_value=True):\n    handlers = _create_handlers()\n\n# After (DI-based)\nbackend = FakeLockingBackend()\nbackend.try_lock_results = {'/a.py': True}\nhandlers = _create_handlers(locking_backend=backend)\n```\n\n## Files to Modify\n\n1. `src/infra/tools/locking_mcp.py` - Add protocol, default impl, update signatures\n2. `tests/fakes/locking_backend.py` - New file with FakeLockingBackend\n3. `tests/unit/infra/tools/test_locking_mcp.py` - Replace all patch() with DI\n\n## Benefits\n\n- Tests no longer coupled to implementation details\n- Consistent with existing fakes (FakeSDKClient, FakeCommandRunner, etc.)\n- Observable state for assertions without verifying call order\n- No more fragile patch() context managers","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-05T23:01:49.239391743Z","created_by":"cyou","updated_at":"2026-01-06T00:04:46.198605029Z","closed_at":"2026-01-06T00:04:46.198605029Z","close_reason":"Closed"}
{"id":"mala-7znn","title":"[Review] 2 non-blocking findings from mala-9g3k","description":"## Review Findings\n\nThis issue consolidates 2 non-blocking findings from code review.\n\n**Source issue:** mala-9g3k\n**Highest priority:** P3\n\n---\n\n### Finding 1: [P3] Redundant attempt number in fixer text output\n\n**Priority:** P3\n**Reviewer:** claude\n**Location:** src/infra/io/console_sink.py:494-495\n\nThe attempt number appears twice: once in the agent_id (`fixer-{attempt}`) and again as `[{attempt}]` prefix in the message text. Output will be `[fixer-1] [1] message...`. Consider removing one occurrence.\n\n---\n\n### Finding 2: [P3] Double truncation in fixer text logging\n\n**Priority:** P3\n**Reviewer:** claude\n**Location:** src/infra/io/console_sink.py:494-495\n\n`truncate_text()` is called explicitly before passing to `log_agent_text()`, which internally calls `truncate_text()` again. The first truncation to 100 chars then has `[{attempt}] ` prepended (~4 chars), and gets truncated again. While harmless, you could skip the explicit truncation since `log_agent_text` already handles it.\n\n---\n\n\u003c!-- fp:3379406bff3c48cf --\u003e\n\u003c!-- fp:8ef19f6eba6da328 --\u003e\n\u003c!-- review_finding:c84690ce3b4f --\u003e","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-06T03:16:08.856051978Z","created_by":"cyou","updated_at":"2026-01-06T03:25:41.392876644Z","closed_at":"2026-01-06T03:25:41.392876644Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-9g3k"]}
{"id":"mala-7zq","title":"Dead Code and Consistency Cleanup","description":"## Context\n- Healthcheck identified ~250 lines of dead code from incomplete async migration\n- Sync methods exist alongside async versions but are never called in production\n- Minor consistency issues in hook constants\n- All child issues are independent and can parallelize\n\n## Scope\n**In:** Remove dead code, fix consistency issues identified in healthcheck\n**Out:** No functional changes, no new features\n\n## Acceptance Criteria\n- All child issues completed\n- Test suite still passes (221 tests)\n- No dead code warnings from static analysis\n\n## Test Plan\n- Run `uv run pytest` after all children complete","status":"closed","priority":2,"issue_type":"epic","created_at":"2025-12-26T19:44:42.537192954Z","updated_at":"2025-12-26T19:53:05.164431129Z","closed_at":"2025-12-26T19:53:05.164431129Z","close_reason":"All children completed","labels":["dead-code"]}
{"id":"mala-800f","title":"Remove backward-compatibility shims from skxg refactor","description":"## Problem\n\nThe skxg refactor introduced backward-compatibility shims despite the plan stating 'no backward-compatibility shims or re-exports'.\n\n## Shim Locations\n\n1. **review_output_parser.py:285** - Module-level convenience functions 'for backward compatibility'\n2. **orchestrator.py:394** - `_config` alias for backward compatibility\n3. **locking.py:289** - 'write only agent_id' for backward compatibility\n\n## Fix Approach\n\n1. Remove module-level convenience functions from ReviewOutputParser, update callers to use class methods\n2. Remove `_config` alias from MalaOrchestrator, update any callers\n3. Remove agent_id-only write path from locking, require full lock info\n\n## Acceptance Criteria\n\n- [ ] No 'backward compat' comments remain in refactored code\n- [ ] All callers updated to use new APIs directly\n- [ ] Existing tests pass\n\n## References\n\n- Parent refactor: mala-skxg\n- AGENTS.md states: 'No backward-compatibility shims'","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-04T04:10:24.72557251Z","created_by":"cyou","updated_at":"2026-01-04T04:34:38.459548972Z","closed_at":"2026-01-04T04:34:38.459548972Z","close_reason":"Closed"}
{"id":"mala-812j","title":"[BUG] _validate_no_partial_commands ignores custom commands","description":"**Source**: Review of mala-smaj.5\n\n**Problem**: _validate_no_partial_commands in config_merger.py only validates built-in command fields (setup, test, lint, format, typecheck, e2e). It does not check custom_commands for empty command strings.\n\n**Impact**: If a user defines a custom command with only timeout overrides expecting preset inheritance, the validation won't catch empty command strings, causing runtime failures or confusing errors downstream.\n\n**Location**: src/domain/validation/config_merger.py:43\n\n**Fix**: Add iteration over commands.custom_commands.values() and check each CustomCommandConfig.command for empty strings.","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-07T19:15:27.639042724Z","created_by":"cyou","updated_at":"2026-01-07T21:57:49.406367377Z","closed_at":"2026-01-07T21:57:49.406367377Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-812j","depends_on_id":"mala-smaj","type":"parent-child","created_at":"2026-01-07T19:15:57.938364767Z","created_by":"cyou"}]}
{"id":"mala-81g","title":"Explore using CLAUDE_CONFIG_DIR to isolate mala agent logs from system Claude Code","description":"Context:\n- Agent JSONL logs currently go to ~/.claude/projects/ alongside system logs\n- Hard to separate mala agent logs from manual Claude Code sessions\n- CLAUDE_CONFIG_DIR env var could redirect agent logs\n\nLocation:\n- src/pipeline/agent_session_runner.py:438-454 (environment setup)\n- ~/.claude/projects/ (current log location)\n- ~/.config/mala/claude-logs/ (proposed isolated location)\n\nScope:\n- In: Set CLAUDE_CONFIG_DIR when spawning agents\n- In: Redirect agent logs to mala-specific directory\n- In: Update log path references in run metadata\n- Out: Log aggregation; log rotation; cleanup automation\n\nAcceptance Criteria:\n- Agent JSONL logs written to ~/.config/mala/claude-logs/ (or configurable)\n- System Claude Code logs unaffected in ~/.claude/\n- Run metadata correctly references new log paths\n- Log analysis tools (mala-7jj) work with new paths\n\nTest Plan:\n- Unit: Test environment variable is set correctly\n- Integration: Run agent, verify logs appear in isolated directory\n- Manual: Check ~/.claude/projects/ has no new mala agent logs\n\nNotes for Agent:\n- Research CLAUDE_CONFIG_DIR behavior in Claude SDK\n- May need to create directory structure before agent spawn\n- Consider --log-dir CLI flag for custom location","status":"tombstone","priority":2,"issue_type":"feature","created_at":"2025-12-26T18:20:17.980544403Z","updated_at":"2026-01-06T22:52:10.761723406Z","labels":["blocked"],"deleted_at":"2026-01-06T22:52:10.761723406Z","deleted_by":"batch delete","delete_reason":"batch delete","original_type":"feature"}
{"id":"mala-81qt","title":"Epic: Inline Custom Commands with Plus-Prefix Additive Overrides","description":"## Summary\nReplace dedicated `custom_commands` / `run_level_custom_commands` top-level config sections with **inline custom commands** inside `commands` and `run_level_commands`. Custom commands are identified as unknown keys.\n\n## Key Features\n- Custom commands inline with built-ins in `commands` section\n- Run-level override modes: Inherit (default when no custom keys), Replace, Additive (`+` prefix), Clear (`_clear_customs: true`)\n- Backward-incompatible removal of old keys with migration errors\n- Preserve PER_ISSUE scope behavior (always uses repo-level customs)\n\n## Plan Reference\n`plans/2026-01-06-inline-custom-commands-plan.md`\n\n## Acceptance Criteria\n- Unknown keys in `commands`/`run_level_commands` parsed as custom commands\n- Plus-prefix additive mode merges customs\n- `_clear_customs: true` explicitly drops all customs\n- Old top-level keys rejected with migration hint\n- PER_ISSUE scope ignores run-level custom modes\n- Presets cannot define custom commands","status":"closed","priority":1,"issue_type":"epic","created_at":"2026-01-06T16:35:03.599403503Z","created_by":"cyou","updated_at":"2026-01-06T21:24:59.105286931Z","closed_at":"2026-01-06T21:24:59.105286931Z","close_reason":"Closed"}
{"id":"mala-81qt.1","title":"[T001] [integration-path-test] Skeleton + Integration: CustomOverrideMode enum and CommandsConfig extension","description":"## Goal\nAdd the foundational data model changes (CustomOverrideMode enum, new CommandsConfig fields) with stubs and a failing integration test that exercises the full config parsing path.\n\n## Context\nThis task creates the compile-ready skeleton for the inline custom commands feature. The integration test will fail because behavior is not yet implemented, confirming the \"red\" phase of TDD.\n\n## Scope\n**In**: CustomOverrideMode enum, CommandsConfig fields (custom_commands, custom_override_mode), stub from_dict signature change, failing integration test\n**Out**: Full parsing logic, mode detection, validation errors\n\n## Changes\n- `src/domain/validation/config.py` — [Exists] — Add `CustomOverrideMode` enum, add `custom_commands: dict[str, CustomCommandConfig]` and `custom_override_mode: CustomOverrideMode` fields to `CommandsConfig`, add `is_run_level` parameter to `CommandsConfig.from_dict` signature (stub implementation that ignores it initially)\n- `tests/unit/domain/test_validation_config.py` — [Exists] — Add failing integration test that parses a full YAML config with inline custom commands through `ValidationConfig.from_dict` and asserts customs are populated\n\n## Wiring Map\n- YAML config → `ValidationConfig.from_dict` → `CommandsConfig.from_dict(is_run_level=...)` → `custom_commands` dict populated\n\n## Acceptance Criteria\n- `CustomOverrideMode` enum exists with INHERIT, CLEAR, REPLACE, ADDITIVE values\n- `CommandsConfig` has `custom_commands` and `custom_override_mode` fields\n- `CommandsConfig.from_dict` accepts `is_run_level` kwarg\n- Project compiles/type-checks\n- At least one integration test fails for the right reason (customs not parsed yet)\n\n## Verification\n```bash\nuvx ty check\nuv run pytest tests/unit/domain/test_validation_config.py -k \"inline_custom\" -v\n# Should show 1+ failing test (red phase)\n```\n\n## Notes for Agent\n- The enum should be defined near the top of config.py with other enums\n- `custom_commands` field default should be `field(default_factory=dict)`\n- `custom_override_mode` default should be `CustomOverrideMode.INHERIT`\n- Existing `CustomCommandConfig` class is reused (no changes needed)\n- The failing test proves the integration path works (config loads, just doesn't parse customs yet)","notes":"Quality gate failed: Aborted due to unrecoverable error\nLog: /home/cyou/.claude/projects/-home-cyou-mala/b9be4914-4698-45be-862d-138b57647298.jsonl","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-06T16:35:30.41850655Z","created_by":"cyou","updated_at":"2026-01-06T21:21:39.361148593Z","closed_at":"2026-01-06T21:21:39.361148593Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-81qt.1","depends_on_id":"mala-81qt","type":"parent-child","created_at":"2026-01-06T16:35:30.43806038Z","created_by":"cyou"}]}
{"id":"mala-81qt.10","title":"[T010] Update PromptValidationCommands to source customs from new location","description":"## Goal\nUpdate `PromptValidationCommands.from_validation_config` to source custom commands from `commands.custom_commands` instead of the deprecated top-level field.\n\n## Context\nThe prompt rendering needs custom commands to display them to users. With the source moving to inline custom commands, the prompt plumbing must be updated to read from the new location.\n\n## Scope\n**In**: Update `PromptValidationCommands.from_validation_config` custom commands source, update any references in prompts.py\n**Out**: Quality gate changes (not needed per plan)\n\n## Changes\n- `src/domain/validation/config.py` — [Exists] — Update `PromptValidationCommands.from_validation_config` to read customs from `commands.custom_commands` (or ValidationSpec which already has the resolved customs)\n- `src/domain/prompts.py` — [Exists] — Update any direct references to custom commands if they read from ValidationConfig\n\n## Acceptance Criteria\n- PromptValidationCommands correctly populated with custom commands from new source\n- Prompt rendering includes inline custom commands\n- No references to old top-level `custom_commands` field remain in prompt code\n\n## Verification\n```bash\nuv run pytest tests/unit/domain/test_validation_config.py -k \"prompt\" -v\nuv run pytest tests/ -k \"prompt\" -v\n# Tests should pass\n```\n\n## Notes for Agent\n- Check where PromptValidationCommands.from_validation_config is defined\n- It may already read from ValidationSpec (which is built correctly in T008), in which case no changes needed\n- Verify by grepping for `custom_commands` references in prompts.py\n- This is the final task to ensure end-to-end integration works","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-06T16:37:29.380142925Z","created_by":"cyou","updated_at":"2026-01-06T17:46:23.550306152Z","closed_at":"2026-01-06T17:46:23.550306152Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-81qt.10","depends_on_id":"mala-81qt","type":"parent-child","created_at":"2026-01-06T16:37:29.380659792Z","created_by":"cyou"},{"issue_id":"mala-81qt.10","depends_on_id":"mala-81qt.8","type":"blocks","created_at":"2026-01-06T16:37:43.643922776Z","created_by":"cyou"}]}
{"id":"mala-81qt.11","title":"[Review] 3 non-blocking findings from mala-81qt.9","description":"## Review Findings\n\nThis issue consolidates 3 non-blocking findings from code review.\n\n**Source issue:** mala-81qt.9\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Raise ConfigError for non-dict/non-string preset command keys\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/domain/validation/preset_registry.py:134-142\n\n`_validate_preset_commands()` assumes `commands_data` is a `dict` with comparable keys. If a preset YAML accidentally sets `commands` to a non-mapping (e.g. `commands: []`/`commands: \"...\"`), `commands_data.keys()` will raise `AttributeError`. If keys are non-strings (e.g. `commands: {1: \"...\"}`), `sorted(unknown_keys)` can raise `TypeError` when sorting mixed/unorderable key types. Since presets are user-facing configuration artifacts, this should fail with a clear `ConfigError` rather than an internal exception; consider `isinstance(commands_data, dict)` + explicit key-type validation before computing/sorting unknown keys.\n\n---\n\n### Finding 2: [P2] Presets can still define custom commands in run_level_commands\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/domain/validation/preset_registry.py:81-82\n\nThe validation only checks the `commands` key, but `ValidationConfig` also parses `run_level_commands`. An unknown key in `run_level_commands` would be interpreted as a custom command, bypassing the protection intended by this change.\n\n---\n\n### Finding 3: [P3] Redundant check for custom_commands\n\n**Priority:** P3\n**Reviewer:** gemini\n**Location:** src/domain/validation/preset_registry.py:77-79\n\nThe check for `custom_commands` in `PresetRegistry.get` is redundant because `_build_config` (which calls `ValidationConfig.from_dict`) already raises a `ConfigError` with a migration hint for this field.\n\n---\n\n\u003c!-- fp:0e06747740cba254 --\u003e\n\u003c!-- fp:4688385d224e997d --\u003e\n\u003c!-- fp:8d0c9f8b85fd4002 --\u003e\n\u003c!-- review_finding:a5682a4b8b90 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-06T17:11:56.52941604Z","created_by":"cyou","updated_at":"2026-01-06T17:16:43.983139248Z","closed_at":"2026-01-06T17:16:43.983139248Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-81qt.9"],"dependencies":[{"issue_id":"mala-81qt.11","depends_on_id":"mala-81qt","type":"parent-child","created_at":"2026-01-06T17:11:56.529843028Z","created_by":"cyou"}]}
{"id":"mala-81qt.12","title":"[Review] 2 non-blocking findings from mala-81qt.7","description":"## Review Findings\n\nThis issue consolidates 2 non-blocking findings from code review.\n\n**Source issue:** mala-81qt.7\n**Highest priority:** P3\n\n---\n\n### Finding 1: [P3] Redundant and inconsistent local imports in tests\n\n**Priority:** P3\n**Reviewer:** gemini\n**Location:** tests/unit/domain/test_spec.py:804-810\n\nMultiple test methods (e.g., `test_build_validation_spec_custom_commands_pipeline_order`, `test_build_validation_spec_custom_commands_insertion_order`) include redundant local imports for `_build_commands_from_config` and configuration classes that are already available at the top-level or could be consolidated there. This clutters the test code and deviates from the style used in other tests in the same file (e.g., `test_run_level_replace_mode_fully_replaces_repo_customs`) which call internal functions like `_apply_custom_commands_override` without local imports.\n\n---\n\n### Finding 2: [P3] Redundant local import of CustomOverrideMode\n\n**Priority:** P3\n**Reviewer:** gemini\n**Location:** src/domain/validation/spec.py:483\n\nIn `src/domain/validation/spec.py`, `CustomOverrideMode` is imported locally inside `_apply_custom_commands_override`. Since `CommandsConfig` (from the same module) is already imported at the top-level of `spec.py`, and `config.py` does not import `spec.py` (avoiding circularity), this local import is unnecessary and adds a small runtime overhead to every call.\n\n---\n\n\u003c!-- fp:c3e56999326086ee --\u003e\n\u003c!-- fp:cac395b3bfb3fae8 --\u003e\n\u003c!-- review_finding:f87bb45f9f15 --\u003e","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-06T17:26:14.648146682Z","created_by":"cyou","updated_at":"2026-01-06T17:29:09.938307523Z","closed_at":"2026-01-06T17:29:09.938307523Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-81qt.7"],"dependencies":[{"issue_id":"mala-81qt.12","depends_on_id":"mala-81qt","type":"parent-child","created_at":"2026-01-06T17:26:14.648588088Z","created_by":"cyou"}]}
{"id":"mala-81qt.2","title":"[T002] Implement repo-level inline custom command parsing","description":"## Goal\nImplement parsing of unknown keys in `commands` section as custom commands at repo-level.\n\n## Context\nThis task implements the first half of inline custom command parsing - the repo-level side where `+` prefix is not allowed and unknown keys become custom commands directly.\n\n## Scope\n**In**: Repo-level parsing of unknown keys to custom_commands dict, validation that `+` prefix is rejected at repo-level, command name validation against regex and built-in collision\n**Out**: Run-level parsing, mode detection, `_clear_customs` handling\n\n## Changes\n- `src/domain/validation/config.py` — [Exists] — Update `CommandsConfig.from_dict` to: (1) identify unknown keys (not in setup/format/lint/typecheck/test/e2e), (2) reject `+`-prefixed keys when `is_run_level=False`, (3) parse unknown keys as custom commands via `CustomCommandConfig.from_dict`, (4) validate command names against built-in collision\n- `tests/unit/domain/test_validation_config.py` — [Exists] — Add unit tests: unknown keys become customs, `+` prefix in `commands` errors, name collision with built-in errors, hyphenated names work\n\n## Acceptance Criteria\n- Unknown keys in `commands` section are parsed into `CommandsConfig.custom_commands` dict\n- `+`-prefixed keys in `commands` raise ConfigError\n- Custom command names matching built-ins raise ConfigError\n- Command name regex validation applies (existing regex)\n- Hyphenated command names (e.g., `arch-check`) work correctly\n\n## Verification\n```bash\nuv run pytest tests/unit/domain/test_validation_config.py -k \"repo_level\" -v\nuv run pytest tests/unit/domain/test_validation_config.py -k \"inline_custom\" -v\n# All tests should pass\n```\n\n## Notes for Agent\n- Known keys constant: `{\"setup\", \"format\", \"lint\", \"typecheck\", \"test\", \"e2e\"}`\n- Reuse existing `CustomCommandConfig.from_dict` for parsing each custom\n- The `_fields_set` should only track built-in fields, not custom commands\n- Remember to update ValidationConfig.from_dict to pass `is_run_level=False` when parsing `commands`","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-06T16:35:43.970056679Z","created_by":"cyou","updated_at":"2026-01-06T17:06:44.612185759Z","closed_at":"2026-01-06T17:06:44.612185759Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-81qt.2","depends_on_id":"mala-81qt","type":"parent-child","created_at":"2026-01-06T16:35:43.989640619Z","created_by":"cyou"},{"issue_id":"mala-81qt.2","depends_on_id":"mala-81qt.1","type":"blocks","created_at":"2026-01-06T16:37:43.498446841Z","created_by":"cyou"}]}
{"id":"mala-81qt.3","title":"[T003] Implement run-level inline custom parsing with mode detection","description":"## Goal\nImplement parsing of unknown keys in `run_level_commands` with plus-prefix mode detection (INHERIT/REPLACE/ADDITIVE).\n\n## Context\nThis task implements run-level custom command parsing with the mode detection logic. When `+`-prefixed keys are found, mode is ADDITIVE; when unprefixed custom keys are found, mode is REPLACE; when no custom keys, mode is INHERIT.\n\n## Scope\n**In**: Run-level parsing with mode detection, `+` prefix stripping and storing, mixed prefix validation error\n**Out**: `_clear_customs` handling (T004), merger integration\n\n## Changes\n- `src/domain/validation/config.py` — [Exists] — Update `CommandsConfig.from_dict` when `is_run_level=True`: (1) separate `+`-prefixed and unprefixed custom keys, (2) error if mixed, (3) set mode to ADDITIVE/REPLACE/INHERIT based on keys, (4) strip `+` prefix before storing custom command name\n- `tests/unit/domain/test_validation_config.py` — [Exists] — Add unit tests: all `+`-prefixed → mode=ADDITIVE with stripped names, all unprefixed → mode=REPLACE, no custom keys → mode=INHERIT, mixed prefixes → error\n\n## Acceptance Criteria\n- Run-level with all `+`-prefixed custom keys: mode=ADDITIVE, names stored without `+`\n- Run-level with unprefixed custom keys: mode=REPLACE\n- Run-level with only built-in overrides (no custom keys): mode=INHERIT\n- Mixed `+`/non-`+` custom keys raise ConfigError\n- Built-in collision check applies after stripping `+` (e.g., `+lint` errors)\n\n## Verification\n```bash\nuv run pytest tests/unit/domain/test_validation_config.py -k \"run_level\" -v\nuv run pytest tests/unit/domain/test_validation_config.py -k \"mode\" -v\n# All tests should pass\n```\n\n## Notes for Agent\n- Use a helper to count prefixed vs unprefixed keys\n- Strip `+` before calling `CustomCommandConfig.from_dict`\n- Remember: `ValidationConfig.from_dict` should pass `is_run_level=True` for `run_level_commands`","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-06T16:35:55.672052319Z","created_by":"cyou","updated_at":"2026-01-06T17:17:19.505482594Z","closed_at":"2026-01-06T17:17:19.505482594Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-81qt.3","depends_on_id":"mala-81qt","type":"parent-child","created_at":"2026-01-06T16:35:55.682040993Z","created_by":"cyou"},{"issue_id":"mala-81qt.3","depends_on_id":"mala-81qt.1","type":"blocks","created_at":"2026-01-06T16:37:43.519162554Z","created_by":"cyou"}]}
{"id":"mala-81qt.4","title":"[T004] Implement _clear_customs validation and CLEAR mode","description":"## Goal\nImplement the `_clear_customs: true` reserved key handling and CLEAR mode for explicitly dropping all repo-level custom commands.\n\n## Context\nWhen users want to explicitly drop all repo-level custom commands without defining any run-level ones, they use `_clear_customs: true`. This is the only way to express \"zero custom commands\" since absence of custom keys means INHERIT mode.\n\n## Scope\n**In**: `_clear_customs` key detection and validation, CLEAR mode setting, error cases\n**Out**: Merger integration, spec building\n\n## Changes\n- `src/domain/validation/config.py` — [Exists] — In `CommandsConfig.from_dict`: (1) detect `_clear_customs` key, (2) validate value is exactly `true` (boolean), (3) error if combined with custom keys, (4) error if at repo-level, (5) set mode=CLEAR when valid, (6) reject `+_clear_customs` as invalid\n- `tests/unit/domain/test_validation_config.py` — [Exists] — Add unit tests: `_clear_customs: true` at run-level → mode=CLEAR, `_clear_customs: false` → error, `_clear_customs: \"yes\"` → error, `_clear_customs` at repo-level → error, `_clear_customs` with custom keys → error, `+_clear_customs` → error\n\n## Acceptance Criteria\n- `_clear_customs: true` in `run_level_commands` sets mode=CLEAR, custom_commands={}\n- `_clear_customs` with any value other than `true` raises ConfigError\n- `_clear_customs` combined with custom keys (prefixed or unprefixed) raises ConfigError\n- `_clear_customs` in `commands` (repo-level) raises ConfigError\n- `+_clear_customs` raises ConfigError (reserved key cannot be prefixed)\n\n## Verification\n```bash\nuv run pytest tests/unit/domain/test_validation_config.py -k \"clear_customs\" -v\n# All tests should pass\n```\n\n## Notes for Agent\n- Check for `_clear_customs` key BEFORE processing unknown keys as customs\n- The reserved key should not end up in the custom_commands dict\n- Error messages should be descriptive (e.g., \"_clear_customs cannot be combined with custom commands\")","notes":"Quality gate failed: Timeout after 60 minutes\nLog: /home/cyou/.claude/projects/-home-cyou-mala/7b8d19e0-0618-4165-9237-2962397fb646.jsonl","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-06T16:36:07.845663053Z","created_by":"cyou","updated_at":"2026-01-06T21:21:21.292213649Z","closed_at":"2026-01-06T21:21:21.292213649Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-81qt.4","depends_on_id":"mala-81qt","type":"parent-child","created_at":"2026-01-06T16:36:07.854536103Z","created_by":"cyou"},{"issue_id":"mala-81qt.4","depends_on_id":"mala-81qt.3","type":"blocks","created_at":"2026-01-06T16:37:43.532717747Z","created_by":"cyou"}]}
{"id":"mala-81qt.5","title":"[T005] Add migration errors for deprecated top-level custom_commands keys","description":"## Goal\nAdd explicit error messages when users have old `custom_commands` or `run_level_custom_commands` top-level keys, guiding them to migrate to the new inline syntax.\n\n## Context\nThe old config format used top-level `custom_commands` and `run_level_custom_commands` sections. These are now replaced by inline custom commands in `commands` and `run_level_commands`. Users need clear migration guidance.\n\n## Scope\n**In**: Detection of deprecated keys in ValidationConfig.from_dict, descriptive error messages with migration hints\n**Out**: Actual migration tooling, documentation updates\n\n## Changes\n- `src/domain/validation/config.py` — [Exists] — In `ValidationConfig.from_dict`: (1) check for `custom_commands` key → raise ConfigError with migration hint, (2) check for `run_level_custom_commands` key → raise ConfigError with migration hint\n- `tests/unit/domain/test_validation_config.py` — [Exists] — Add unit tests: top-level `custom_commands` → error with hint, top-level `run_level_custom_commands` → error with hint, error messages mention the spec doc path\n\n## Acceptance Criteria\n- `custom_commands` at top-level raises ConfigError: \"'custom_commands' is no longer supported. Move custom commands into the 'commands' section...\"\n- `run_level_custom_commands` at top-level raises ConfigError: \"'run_level_custom_commands' is no longer supported. Move custom commands into the 'run_level_commands' section with '+' prefix for additive merge...\"\n- Error messages reference the spec document\n\n## Verification\n```bash\nuv run pytest tests/unit/domain/test_validation_config.py -k \"deprecated\" -v\nuv run pytest tests/unit/domain/test_validation_config.py -k \"migration\" -v\n# All tests should pass\n```\n\n## Notes for Agent\n- Check for these keys early in ValidationConfig.from_dict, before other parsing\n- Include the spec path in the error message: `plans/2026-01-06-custom-validation-commands-spec.md`\n- Both keys should be checked independently (both can be present and error)","notes":"Quality gate failed: External review failed: src/domain/validation/config.py:823: [P1] Wire inline custom commands into PromptValidationCommands; src/domain/validation/config.py:782: [P1] Count inline custom commands in has_any_command; src/domain/validation/config.py:811: [P1] Inline custom commands are not wired to PromptValidationCommands\nLog: /home/cyou/.claude/projects/-home-cyou-mala/7cdd1d71-0334-4ace-b849-55c410c41768.jsonl","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-06T16:36:20.704629798Z","created_by":"cyou","updated_at":"2026-01-06T21:22:07.985524599Z","closed_at":"2026-01-06T21:22:07.985524599Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-81qt.5","depends_on_id":"mala-81qt","type":"parent-child","created_at":"2026-01-06T16:36:20.713507158Z","created_by":"cyou"},{"issue_id":"mala-81qt.5","depends_on_id":"mala-81qt.2","type":"blocks","created_at":"2026-01-06T16:37:43.546362929Z","created_by":"cyou"}]}
{"id":"mala-81qt.6","title":"[T006] Update config merger to preserve custom fields and fix explicitly-empty detection","description":"## Goal\nUpdate the config merger to correctly handle inline custom commands: preserve custom_commands and custom_override_mode fields, and fix the \"explicitly empty\" detection to not trigger when only custom keys are present.\n\n## Context\nThe merger uses `_fields_set` emptiness to detect when a user explicitly set `run_level_commands` to empty/null. With inline customs, a config with only custom keys (no built-ins) would have empty `_fields_set` but should NOT be treated as \"explicitly empty\".\n\n## Scope\n**In**: Update `_merge_commands` to check custom_commands and custom_override_mode when detecting \"explicitly empty\", preserve user's custom fields in merged result\n**Out**: spec.py changes, validation execution\n\n## Changes\n- `src/domain/validation/config_merger.py` — [Exists] — Update `_merge_commands`: (1) check `not user.custom_commands` and `user.custom_override_mode == INHERIT` in addition to `not user._fields_set` for \"explicitly empty\" detection, (2) include `custom_commands` and `custom_override_mode` in returned CommandsConfig\n- `tests/unit/infra/test_config_merger.py` — [Exists] — Add unit tests: run-level with only custom keys does NOT trigger \"explicitly empty\", run-level with `_clear_customs: true` does NOT trigger \"explicitly empty\", run-level with `{}` (truly empty) still clears preset overrides\n\n## Acceptance Criteria\n- Run-level with only custom keys (no built-ins) preserves customs and does not clear preset built-ins\n- Run-level with `_clear_customs: true` (mode=CLEAR) does not trigger \"explicitly empty\" preset clearing\n- Run-level with `{}` (truly empty: no built-ins, no customs, mode=INHERIT) still clears preset run-level overrides\n- Merged CommandsConfig includes user's custom_commands and custom_override_mode\n\n## Verification\n```bash\nuv run pytest tests/unit/infra/test_config_merger.py -k \"custom\" -v\nuv run pytest tests/unit/infra/test_config_merger.py -k \"explicitly_empty\" -v\n# All tests should pass\n```\n\n## Notes for Agent\n- Import CustomOverrideMode from config.py\n- The \"explicitly empty\" condition becomes: `not _fields_set AND not custom_commands AND mode == INHERIT`\n- Preserve user's custom fields even when they're empty (don't drop them)","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-06T16:36:36.418426013Z","created_by":"cyou","updated_at":"2026-01-06T17:29:12.99540268Z","closed_at":"2026-01-06T17:29:12.99540268Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-81qt.6","depends_on_id":"mala-81qt","type":"parent-child","created_at":"2026-01-06T16:36:36.41902451Z","created_by":"cyou"},{"issue_id":"mala-81qt.6","depends_on_id":"mala-81qt.2","type":"blocks","created_at":"2026-01-06T16:37:43.559325066Z","created_by":"cyou"},{"issue_id":"mala-81qt.6","depends_on_id":"mala-81qt.3","type":"blocks","created_at":"2026-01-06T16:37:43.573239747Z","created_by":"cyou"}]}
{"id":"mala-81qt.7","title":"[T007] Update _apply_custom_commands_override to use mode enum with scope","description":"## Goal\nUpdate the spec building function `_apply_custom_commands_override` to use the computed CustomOverrideMode enum and respect ValidationScope (PER_ISSUE always uses repo-level customs).\n\n## Context\nThe spec builder needs to apply the new mode-based custom command override logic. For RUN_LEVEL scope, it applies INHERIT/CLEAR/REPLACE/ADDITIVE modes. For PER_ISSUE scope, it always returns repo-level customs unchanged (existing behavior preserved).\n\n## Scope\n**In**: Update `_apply_custom_commands_override` signature and logic, add scope parameter, mode-based branching\n**Out**: Full ValidationSpec building, prompt plumbing\n\n## Changes\n- `src/domain/validation/spec.py` — [Exists] — Update `_apply_custom_commands_override`: (1) add `scope: ValidationScope` parameter, (2) for PER_ISSUE: return repo_customs unchanged, (3) for RUN_LEVEL: branch on `run_level_commands.custom_override_mode` (INHERIT→repo, CLEAR→{}, REPLACE→run, ADDITIVE→merge), (4) update call site to pass scope\n- `tests/unit/domain/test_spec.py` — [Exists] — Add unit tests: PER_ISSUE scope ignores all run-level modes (regression test), RUN_LEVEL+INHERIT→repo customs, RUN_LEVEL+CLEAR→empty, RUN_LEVEL+REPLACE→run customs only, RUN_LEVEL+ADDITIVE→merged with repo order preserved\n\n## Acceptance Criteria\n- PER_ISSUE scope always returns repo-level customs regardless of run-level mode\n- RUN_LEVEL + INHERIT mode returns repo-level customs\n- RUN_LEVEL + CLEAR mode returns empty dict\n- RUN_LEVEL + REPLACE mode returns only run-level customs\n- RUN_LEVEL + ADDITIVE mode returns `{**repo_customs, **run_customs}` (repo order preserved)\n\n## Verification\n```bash\nuv run pytest tests/unit/domain/test_spec.py -k \"custom\" -v\nuv run pytest tests/unit/domain/test_spec.py -k \"override\" -v\n# All tests should pass\n```\n\n## Notes for Agent\n- Import CustomOverrideMode from config.py\n- The existing function signature may need refactoring to accept CommandsConfig instead of just the dict\n- Ensure the call site in build_validation_spec passes the scope parameter\n- For ADDITIVE merge, `{**repo, **run}` preserves repo order, updates in-place, appends new","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-06T16:36:50.173371447Z","created_by":"cyou","updated_at":"2026-01-06T17:26:14.604834013Z","closed_at":"2026-01-06T17:26:14.604834013Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-81qt.7","depends_on_id":"mala-81qt","type":"parent-child","created_at":"2026-01-06T16:36:50.180704815Z","created_by":"cyou"},{"issue_id":"mala-81qt.7","depends_on_id":"mala-81qt.2","type":"blocks","created_at":"2026-01-06T16:37:43.586851499Z","created_by":"cyou"},{"issue_id":"mala-81qt.7","depends_on_id":"mala-81qt.3","type":"blocks","created_at":"2026-01-06T16:37:43.60090857Z","created_by":"cyou"}]}
{"id":"mala-81qt.8","title":"[T008] Update build_validation_spec to source customs from commands.custom_commands","description":"## Goal\nUpdate `build_validation_spec` to source custom commands from the new `commands.custom_commands` field instead of the deprecated top-level `custom_commands` field.\n\n## Context\nWith inline custom commands, the source of truth moves from `ValidationConfig.custom_commands` (top-level) to `ValidationConfig.commands.custom_commands` (nested in CommandsConfig). This task wires the new source into spec building.\n\n## Scope\n**In**: Update custom command sourcing in build_validation_spec, ensure final integration test passes\n**Out**: Prompt plumbing (T010)\n\n## Changes\n- `src/domain/validation/spec.py` — [Exists] — Update `build_validation_spec`: (1) source repo-level customs from `merged_config.commands.custom_commands` instead of `merged_config.custom_commands`, (2) ensure run-level customs come from `merged_config.run_level_commands.custom_commands`\n- `tests/unit/domain/test_spec.py` — [Exists] — Add integration tests: YAML with inline customs in `commands` → ValidationSpec has customs, YAML with `+` customs in `run_level_commands` → ValidationSpec merges correctly\n\n## Acceptance Criteria\n- ValidationSpec correctly populated with customs from `commands.custom_commands`\n- Run-level override modes work end-to-end (YAML → ValidationSpec)\n- Integration test from T001 now passes (green phase)\n\n## Verification\n```bash\nuv run pytest tests/unit/domain/test_spec.py -k \"inline\" -v\nuv run pytest tests/unit/domain/test_validation_config.py -k \"inline_custom\" -v\n# All tests should pass including the integration test\n```\n\n## Notes for Agent\n- The old `custom_commands` and `run_level_custom_commands` top-level fields on ValidationConfig will be removed (they error in T005)\n- This is the task where the full integration path should turn green\n- Verify the wiring: ValidationConfig → CommandsConfig.custom_commands → spec builder → ValidationSpec","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-06T16:37:05.647377042Z","created_by":"cyou","updated_at":"2026-01-06T17:34:08.206091839Z","closed_at":"2026-01-06T17:34:08.206091839Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-81qt.8","depends_on_id":"mala-81qt","type":"parent-child","created_at":"2026-01-06T16:37:05.664380016Z","created_by":"cyou"},{"issue_id":"mala-81qt.8","depends_on_id":"mala-81qt.7","type":"blocks","created_at":"2026-01-06T16:37:43.615083059Z","created_by":"cyou"}]}
{"id":"mala-81qt.9","title":"[T009] Add preset validation to reject unknown keys in commands","description":"## Goal\nAdd validation in preset loading to reject unknown keys in the `commands` section, preventing presets from accidentally defining custom commands.\n\n## Context\nPresets should only define built-in commands. With inline custom commands, any unknown key in `commands` would become a custom command. This must be blocked for presets to prevent accidental introduction of customs via preset definitions.\n\n## Scope\n**In**: Add `_validate_preset_commands` helper, call it in PresetRegistry.load(), error on unknown keys\n**Out**: User-facing config validation (already handled by T002-T005)\n\n## Changes\n- `src/domain/validation/preset_registry.py` — [Exists] — Add `_validate_preset_commands(commands_data: dict)` that errors if any keys are not in {setup, format, lint, typecheck, test, e2e}, call it during preset loading before registering\n- Add tests for preset validation (may need new test file or add to existing preset tests)\n\n## Acceptance Criteria\n- Preset with unknown keys in `commands` raises ConfigError during loading\n- Error message: \"Preset commands contain unknown keys: {keys}. Presets can only define built-in commands.\"\n- Presets with only built-in keys load successfully\n\n## Verification\n```bash\nuv run pytest tests/ -k \"preset\" -v\n# Tests for preset validation should pass\n```\n\n## Notes for Agent\n- Find where presets are loaded (likely `PresetRegistry.load()` or similar)\n- The validation happens before the preset is registered, not at usage time\n- This is a safeguard against future typos/mistakes in preset definitions\n- Check if there are existing preset tests to add to","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-06T16:37:18.708197071Z","created_by":"cyou","updated_at":"2026-01-06T17:11:56.480107219Z","closed_at":"2026-01-06T17:11:56.480107219Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-81qt.9","depends_on_id":"mala-81qt","type":"parent-child","created_at":"2026-01-06T16:37:18.716909662Z","created_by":"cyou"},{"issue_id":"mala-81qt.9","depends_on_id":"mala-81qt.2","type":"blocks","created_at":"2026-01-06T16:37:43.62914685Z","created_by":"cyou"}]}
{"id":"mala-8285","title":"[Advisory] Epic verification tests fully migrated to fakes-based approa...","description":"## Context\nThis issue was auto-created by epic verification for epic `mala-3bu9`.\n\n## Epic Description / Acceptance Criteria\nTitle: Epic: Test Suite Migration - Mocks to Fakes\n\nMigrate the mala test suite from heavy mock usage to a fakes-based, behavior-focused approach per CLAUDE.md/AGENTS.md testing philosophy.\n\n## Goals\n1. Replace mock-heavy tests with fakes-based behavioral tests\n2. Delete tests that don't catch meaningful bugs (dataclass defaults, stdlib behavior)\n3. Reduce test maintenance burden while preserving defect-catching capability\n\n## Success Criteria\n- Tests assert on observable outcomes (state changes, events emitted, return values) rather than internal call sequences\n- All fakes have contract tests validating fake-vs-real parity\n- `uvx ty check` passes with all fakes implementing full protocol interfaces\n- Coverage stays ≥72%\n\n## Key Deliverables\n- `tests/fakes/` module with FakeIssueProvider, FakeCommandRunner, FakeLockManager, FakeEpicVerificationModel, FakeEventSink\n- `tests/contracts/` with parity tests\n- `tests/README.md` documenting testing philosophy\n- Rewritten orchestrator, validation, and epic verification tests\n- Deleted low-value tests (dataclass defaults)\n\n## Plan Reference\nplans/2026-01-05-test-migration-plan.md\n\n## Commit Scope\n- Range hint: 19650b19367b859f968dbd26a5b48e77ff7d41f7^..76fddafc627cba06ddcdea94e3abc66cc68ac722\n- Commits:\n- 76fddafc627cba06ddcdea94e3abc66cc68ac722 bd-mala-3bu9.13: Fix 6 review findings from external review round 3\n- 54015b0333ded3c0721c1b7e35335932a4d3a888 bd-mala-3bu9.13: Restore test coverage and remove real git from unit tests\n- 1a2350e3913656e866e10b45002c515dfe5f7776 bd-mala-3bu9.13: Fix type errors in unit tests\n- caa82d5f16097943ee049cf8402e4d32c26070a5 bd-mala-3bu9.13: Eliminate patches and assert_called from unit tests\n- 664a3fd076d91cb02bf68ff3d879cbb9d3bcda09 bd-mala-3bu9.9: Migrate orchestrator tests to use FakeIssueProvider and direct method replacement\n- 0d38721b2f30401ed24b8bc11dfb73d35b53baa6 bd-mala-3bu9.19: Fix type annotations in MCP server factory test helpers\n- 6066aa58224b3e6139265218fef0e8818888c5c7 bd-mala-3bu9.9: Clean up unused fixtures and migrate tests to DI\n- be767540710c3d470e2f59870db50a66964e5d48 bd-mala-3bu9.9: Rewrite orchestrator tests to use fakes instead of patches\n- d4f39c696a618a7a4fac50e5af4bdcdf65068997 bd-mala-3bu9.19: Use str() coercion in join() for command args\n- 67e78da13d24b5367d938e971dc0561b16891d08 bd-mala-3bu9.18: Fix test assertions and migrate to FakeCommandRunner\n- 75035051cc694f435cef7a262d9ba2a37a65804c bd-mala-3bu9.10: Fix review issues in validation tests\n- 350b32e12d7f40120ab3ca0fe2c4e8a8d780961e bd-mala-3bu9.10: Replace mock patches with FakeCommandRunner in validation tests\n- 809737f5bb8076ebb1ce331a57c3723a576a111b bd-mala-3bu9.17: Make xfail markers strict to avoid silent XPASS\n- 32d4d997cfefbb9ce565506d6f0d16edc671f176 bd-mala-3bu9.5: Clear pytest cache to fix xfail marker detection\n- fc3794b2e8b88aab03b8f278071fec71d21e7349 bd-mala-3bu9.2: Mark unimplemented watch mode tests as xfail\n- de28c08b3a54bdd82303dbde69c84e47a09f4a71 bd-mala-3bu9.15: Fix shell commands in README metrics table\n- 1896708abf600683514a295fcb5e109da479036b bd-mala-3bu9.11: Move FakeVerificationResults to canonical fakes, add default ResultMessage\n- b36e5892d95f50599348aa7946320816597000ac bd-mala-3bu9.10: Migrate TestSpecValidationRunner to FakeCommandRunner\n- e59b2e2a12f672423736c08469b5792213d5f431 bd-mala-3bu9.2: Add epic filtering and tags to FakeIssueProvider\n- a94a254f29ea06c69943ff14e4711f13fc5ee980 bd-mala-3bu9.2: Fix FakeIssueProvider behavioral parity with IssueManager\n- b7b428892353215dbda32b7c43413de14fe72139 bd-mala-3bu9.11: Rewrite epic verification retry tests to use fakes\n- da11664699f21fab44b626f30ffcef1142bb4954 bd-mala-3bu9.5: Fix Protocol introspection in completeness test\n- 2a5eceb959d010515a764cbb0137c18f64c4bde5 bd-mala-3bu9.2: Add FakeIssueProvider and contract tests\n- d30c658ff8dc19fa8427ab22b6f12b284b7ac18c bd-mala-3bu9.16: Fix FakeCommandRunner type preservation and prefix normalization\n- d011b8d81fe157618cb3c158b70fbe8ffd1cb018 bd-mala-3bu9.4: Add FakeLockManager and FakeEpicVerificationModel\n- 19ed280161fad1a8425f383f5ead48ce77730410 bd-mala-3bu9.5: Add FakeEventSink with completeness contract test\n- 1b34f7b6a97876e1e359aca2b099afa91417a8cd bd-mala-3bu9.6: Wire runs_dir through to RunMetadata and debug logging\n- be3d907df05228681a4f619d5dc35cd48941fa68 bd-mala-3bu9.3: Add FakeCommandRunner with fail-closed semantics\n- 7ca02513d84cfac375b56484250fba50b78a97fd bd-mala-3bu9.15: Fix usage example and shell commands in tests/README.md\n- ea2e2d94193b516ba977646cf91ac81123262199 bd-mala-3bu9.1: Fix docs to include tests/__init__.py, correct example, add verification commands\n- 5868cb9563a7ca86b115dd84fba551b9a3911061 bd-mala-3bu9.6: Wire runs_dir and lock_releaser through factory\n- 8a42d95a3cd69ef9544fef38839d4e56f8806e04 bd-mala-3bu9.1: Fix docs to mark fakes as planned and use correct protocol names\n- ee6c944a9b9d626f5ce7c73f055734ba80e3f9c9 bd-mala-3bu9.12: Delete low-value config tests (dataclass defaults)\n- 7f34722dd4341166ee2cafb2a9a207e554970024 bd-mala-3bu9.6: Add DI parameters to Orchestrator for runs_dir and lock_releaser\n- 19650b19367b859f968dbd26a5b48e77ff7d41f7 bd-mala-3bu9.1: Create tests/fakes/ module and tests/README.md\n\n## Child Issues\n- mala-3bu9.1\n- mala-3bu9.10\n- mala-3bu9.11\n- mala-3bu9.12\n- mala-3bu9.13\n- mala-3bu9.14\n- mala-3bu9.15\n- mala-3bu9.16\n- mala-3bu9.17\n- mala-3bu9.18\n- mala-3bu9.19\n- mala-3bu9.2\n- mala-3bu9.3\n- mala-3bu9.4\n- mala-3bu9.5\n- mala-3bu9.6\n- mala-3bu9.7\n- mala-3bu9.8\n- mala-3bu9.9\n\n## Unmet Criterion\nEpic verification tests fully migrated to fakes-based approach\n\n## Evidence\nEpic verification tests (test_epic_verification_retry.py) use FakeEpicVerificationModel and FakeVerificationResults, but integration tests in test_epic_verifier.py still use MagicMock for BeadsClient, model, and command runner. However, this appears to be intentional as integration tests test cross-layer behavior.\n\n## Priority\nP2 (informational)\n\n## Resolution\nThis is advisory (P2/P3) and does not block epic closure. When complete, close this issue.\n","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-05T05:38:00.595086808Z","created_by":"cyou","updated_at":"2026-01-05T14:04:56.886683749Z","closed_at":"2026-01-05T14:04:56.886683749Z","close_reason":"Closed","labels":["auto_generated","epic_remediation:mala-3bu9:ce26dff704d8be5a7b34a9d890eb68c1085abc6222d43b20bf6e588746944674"]}
{"id":"mala-82u","title":"Epic: Coverage 'No Decrease' Policy with Auto-Refresh Baseline","description":"Context:\n- Current CLI uses fixed 85% coverage threshold, which is inflexible\n- Goal: default behavior should prevent coverage from decreasing (not a fixed number)\n- Uses existing coverage.xml as baseline, with auto-refresh when missing/stale\n- Must handle parallel agents safely via locking and temp worktrees\n\nScope:\n- In: CLI option changes, coverage.py baseline functions, spec.py CoverageConfig, spec_runner.py auto-refresh logic, orchestrator.py signature\n- Out: pyproject.toml threshold (keep for direct pytest), legacy_runner changes\n\nAcceptance Criteria:\n- `bd run` without `--coverage-threshold` uses \"no decrease\" mode\n- Baseline auto-refreshes when missing or stale\n- Parallel agents don't corrupt baseline via locking\n- Parse errors fail loudly, missing baseline warns + refreshes\n\nTest Plan:\n- Integration test for full \"no decrease\" flow\n- Unit tests for each new function\n\nNotes for Agent:\n- This is the umbrella epic; implement child issues in dependency order","status":"tombstone","priority":1,"issue_type":"epic","created_at":"2025-12-27T18:13:22.418075331Z","updated_at":"2025-12-27T18:20:40.635708509Z","labels":["validation"],"deleted_at":"2025-12-27T18:20:40.635708509Z","deleted_by":"daemon","delete_reason":"delete","original_type":"epic"}
{"id":"mala-83fe","title":"[Review] src/orchestration/orchestrator.py:860: [P3] Extract lint tools once per run instead of per issue","description":"## Review Finding\n\nThis issue was auto-created from a P3 review finding.\n\n**Source issue:** mala-pc7u\n**Reviewer:** gemini\n**Location:** src/orchestration/orchestrator.py:860\n\n## Details\n\nIn `MalaOrchestrator.run_implementer`, `_extract_lint_tools_from_spec` is called every time an agent is spawned. Since `self.per_issue_spec` is immutable for the duration of a run, the lint tool names should be extracted once in `run()` and stored for reuse.","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-02T17:05:46.362560714Z","created_by":"cyou","updated_at":"2026-01-02T17:14:35.210900052Z","closed_at":"2026-01-02T17:14:35.210900052Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_finding:b73bd37b4137","source:mala-pc7u"]}
{"id":"mala-83jo","title":"[Review] 1 non-blocking finding from mala-ari7.1","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-ari7.1\n**Highest priority:** P3\n\n---\n\n### Finding 1: [P3] Misleading docstring for transition_result\n\n**Priority:** P3\n**Reviewer:** gemini\n**Location:** src/pipeline/agent_session_runner.py:432-433\n\nThe docstring for `transition_result` in `ReviewEffectResult` (and the updated docstring for `_handle_review_effect`) states that it is `None` only for no-progress early exit. However, the implementation for the no-progress case (lines 1104-1109) passes a valid `no_progress_result` (a `TransitionResult`) to the constructor. The field is never actually `None` in the current implementation.\n\n---\n","notes":"Quality gate failed: Quality gate failed: No commit with bd-mala-83jo found after run baseline (stale commits from previous runs are rejected)\nLog: /home/cyou/.claude/projects/-home-cyou-mala/f45b84e9-4ed2-47f1-ada7-f332260c075f.jsonl","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-03T08:14:33.71521305Z","created_by":"cyou","updated_at":"2026-01-03T15:02:23.561715596Z","closed_at":"2026-01-03T15:02:23.561715596Z","close_reason":"Closed","labels":["auto_generated","needs-followup","review_finding","review_findings:6ed658117ff3","source:mala-ari7.1"]}
{"id":"mala-84jv","title":"Propagate interrupt_event into gate/review callbacks","description":"Extend SessionCallbacks/SessionCallbackFactory to pass interrupt_event into gate and review runners so SIGINT can short-circuit gate/review work. Add tests to assert propagation.","notes":"Quality gate failed: External review failed: src/pipeline/session_callback_factory.py:20: [P1] Avoid runtime NameError from asyncio used in type annotations\nLog: /home/cyou/.claude/projects/-home-cyou-mala/c3e58157-67ed-4fba-ad57-5911bc79c44b.jsonl","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-09T16:52:55.322678456Z","created_by":"cyou","updated_at":"2026-01-09T19:09:09.717337519Z","closed_at":"2026-01-09T19:09:09.717337519Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-84jv","depends_on_id":"mala-s9p8","type":"parent-child","created_at":"2026-01-09T16:54:37.063745137Z","created_by":"cyou"}]}
{"id":"mala-869","title":"Epic: Architecture boundaries and testability (2025-12-28 comprehensive review)","description":"## Context\nComprehensive architecture review identified 18 issues focused on:\n- **Boundary clarity**: Orchestrator is a mega-controller with no pipeline stage interfaces\n- **Configuration**: Env var access scattered with no central config object\n- **Testability**: Global telemetry patching, console coupling, no sync facade\n- **Subprocess fragmentation**: 5+ modules with independent subprocess implementations\n\n## Goals\n1. Define pipeline stage protocols for testable, extensible orchestration\n2. Centralize configuration in MalaConfig dataclass\n3. Abstract telemetry for testability and programmatic control\n4. Unify subprocess execution in CommandRunner\n5. Fix immediate bugs (MORPH_API_KEY crash)\n\n## Approach\nIssues are ordered for parallel execution where possible. Dependencies are explicit for file overlap or logical ordering.\n\n## Acceptance Criteria\n- All child issues completed\n- Orchestrator uses injected stage implementations\n- MalaConfig is the single source of configuration\n- Tests don't rely on global state or stdout capture\n- All subprocess calls go through CommandRunner\n\n## Out of Scope\n- Low-priority cleanup (absolute imports, duplicate metadata building)\n- Issues subsumed by larger refactors (BeadsClient/Codex coupling -\u003e pipeline stages)\n","status":"closed","priority":1,"issue_type":"epic","created_at":"2025-12-28T16:36:38.45357471Z","updated_at":"2025-12-29T05:14:38.714984924Z","closed_at":"2025-12-29T05:14:38.714984924Z","close_reason":"All children completed"}
{"id":"mala-869.1","title":"Fix get_mcp_servers crash on missing MORPH_API_KEY","description":"## Context\n- `get_mcp_servers()` at `src/orchestrator.py:127` unconditionally accesses `os.environ[\"MORPH_API_KEY\"]` when `morph_enabled=True`\n- CLI checks for the key and sets `morph_enabled=False` if missing (`cli.py:403-410`)\n- Programmatic use of `MalaOrchestrator` with default `morph_enabled=True` crashes with `KeyError`\n- This is the simplest bug to fix and can be done independently\n\n## Primary Files\n- src/orchestrator.py:110-132\n\n## Scope\n**In:** Use `os.environ.get()` with explicit check and clear error message, or auto-disable morph when key missing with warning\n**Out:** Change morph feature behavior; change default value of morph_enabled\n\n## Acceptance Criteria\n- Clear error message when MORPH_API_KEY missing but morph_enabled=True\n- OR: Auto-disable morph when key missing (with warning logged)\n- No KeyError crashes for programmatic users\n\n## Test Plan\n- Add unit test for get_mcp_servers with missing env var\n- Test with morph_enabled=True and no MORPH_API_KEY set\n- Verify CLI still works (auto-disables morph)\n\n## Notes for Agent\n- This is a quick fix - don't over-engineer\n- Prefer clear error message over silent fallback\n","status":"closed","priority":2,"issue_type":"bug","created_at":"2025-12-28T16:36:51.981378703Z","updated_at":"2025-12-28T16:49:34.251877605Z","closed_at":"2025-12-28T16:49:34.251877605Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-869.1","depends_on_id":"mala-869","type":"parent-child","created_at":"2025-12-28T16:36:51.988240931Z","created_by":"daemon"}]}
{"id":"mala-869.10","title":"Extract SessionLogParser from QualityGate","description":"## Context\n- QualityGate contains ~330 lines of JSONL log parsing plus ~200 lines of policy checking\n- Parsing methods: `_iter_jsonl_entries`, `_extract_bash_commands`, `_extract_tool_results`, `_extract_assistant_text_blocks`\n- This logic is reusable (debugging tools, analytics) but coupled to QualityGate class\n- Previous issue mala-51q.2 extracted the iterator but parsing methods remain coupled\n\n## Primary Files\n- src/quality_gate.py:168-500\n- src/session_log_parser.py (new file)\n\n## Scope\n**In:** Extract `SessionLogParser` class with all parsing methods from QualityGate\n**In:** Use log event types from mala-869.3 (LogEntry, ToolUseBlock, etc.)\n**In:** QualityGate delegates parsing to SessionLogParser\n**Out:** Change evidence detection patterns\n\n## Acceptance Criteria\n- SessionLogParser is independently usable\n- QualityGate delegates all parsing\n- All existing quality_gate tests pass\n\n## Test Plan\n- Unit tests for SessionLogParser with sample JSONL\n- Run existing quality_gate test suite\n\n## Notes for Agent\n- Wait for mala-869.3 (log event types) to be completed first\n- Keep QualityGate focused on policy checking only\n","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-28T16:38:46.726645944Z","updated_at":"2025-12-28T20:39:51.621254453Z","closed_at":"2025-12-28T20:39:51.621254453Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-869.10","depends_on_id":"mala-869","type":"parent-child","created_at":"2025-12-28T16:38:46.734139103Z","created_by":"daemon"},{"issue_id":"mala-869.10","depends_on_id":"mala-869.3","type":"blocks","created_at":"2025-12-28T16:38:50.512473592Z","created_by":"daemon"}]}
{"id":"mala-869.11","title":"Decompose beads_client._fetch_and_filter_issues into pipeline","description":"## Context\n- `_fetch_and_filter_issues()` has CCN 37, 121 lines\n- Handles: fetching, WIP fetching, filtering by only_ids, filtering by epic, blocking by epic status, enrichment with parent_epic, sorting (focus mode vs priority)\n- Logic is correct but hard to follow and test in isolation\n\n## Primary Files\n- src/beads_client.py:298-418\n\n## Scope\n**In:** Decompose into pipeline steps:\n  - `_fetch_base_issues()` - get issues from bd\n  - `_merge_wip_issues()` - add in-progress issues\n  - `_apply_filters()` - only_ids, epic filters\n  - `_enrich_with_epics()` - add parent_epic info\n  - `_sort_issues()` - focus mode vs priority sort\n**In:** Each step is a pure function taking and returning issue lists\n**Out:** Change subprocess calls or async behavior\n\n## Acceptance Criteria\n- Main method \u003c 30 lines\n- Each pipeline step testable with fixture data\n- All existing tests pass\n\n## Test Plan\n- Unit tests for each pipeline step with fixture data\n- Integration test with mocked subprocess\n\n## Notes for Agent\n- Pure functions are easier to test\n- Keep the overall behavior identical\n","notes":"Quality gate failed: Quality gate failed: Missing validation commands: ruff format, ruff check, pytest, ty check; No progress: commit unchanged and no new validation evidence\nLog: /home/cyou/.claude/projects/-home-cyou-mala/340c1814-2439-4a7c-8160-ad828a5c4357.jsonl","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-28T16:39:02.285552402Z","updated_at":"2025-12-28T20:29:07.709176969Z","closed_at":"2025-12-28T20:29:07.709176969Z","close_reason":"Implementation complete. Codex review failed due to contradictory scope (pure function + don't change I/O). Manual review confirms acceptance criteria met: main method 25 lines, all tests pass, clear pipeline separation documented.","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-869.11","depends_on_id":"mala-869","type":"parent-child","created_at":"2025-12-28T16:39:02.293677482Z","created_by":"daemon"},{"issue_id":"mala-869.11","depends_on_id":"mala-869.5","type":"blocks","created_at":"2025-12-28T16:41:03.037605634Z","created_by":"daemon"}]}
{"id":"mala-869.12","title":"Move SDK hook types from locking.py to hooks.py","description":"## Context\n- `tools/locking.py` imports `claude_agent_sdk.types` for hook type annotations (line 12)\n- This is a low-level utility module that should have no SDK dependencies\n- `hooks.py` already imports SDK types (line 10)\n- Impact is limited since SDK is always present; this is code organization hygiene\n\n## Primary Files\n- src/tools/locking.py:12-26 (remove SDK imports)\n- src/hooks.py (receives code from locking.py)\n\n## Scope\n**In:** Move `make_stop_hook`, `StopHook` type alias from locking.py to hooks.py\n**In:** Update imports in any module that uses these from locking.py\n**Out:** Change hook behavior\n\n## Acceptance Criteria\n- `tools/locking.py` has no `claude_agent_sdk` imports\n- Hook-related code is in hooks.py\n- All existing tests pass\n\n## Test Plan\n- Grep for claude_agent_sdk in locking.py (should be zero)\n- Run existing tests\n\n## Notes for Agent\n- Simple code move, no logic changes\n- Check for any modules importing StopHook from locking.py\n","notes":"Quality gate failed: Quality gate failed: Missing validation commands: ruff format, ruff check, ty check\nLog: /home/cyou/.claude/projects/-home-cyou-mala/15fa0113-ecb3-4b22-8960-8bbd86e893da.jsonl","status":"closed","priority":3,"issue_type":"task","created_at":"2025-12-28T16:39:14.144192887Z","updated_at":"2025-12-28T18:12:22.926282464Z","closed_at":"2025-12-28T18:12:22.926282464Z","close_reason":"Work complete - commit 60714c1 merged, all validation passed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-869.12","depends_on_id":"mala-869","type":"parent-child","created_at":"2025-12-28T16:39:14.151220727Z","created_by":"daemon"}]}
{"id":"mala-869.13","title":"Define local outcome types in lifecycle.py","description":"## Context\n- `LifecycleContext` stores `GateResult` and `CodexReviewResult` - concrete types from infra modules\n- While under TYPE_CHECKING guard (no runtime import), the type signatures are still coupled\n- lifecycle.py should define its own outcome protocols\n\n## Primary Files\n- src/lifecycle.py:26-29, 124-128\n\n## Scope\n**In:** Define local `GateOutcome` and `ReviewOutcome` protocols/dataclasses in lifecycle with only the fields lifecycle needs (passed, failure_reasons, commit_hash, etc.)\n**In:** Update LifecycleContext to use local types\n**Out:** Change lifecycle state machine logic\n\n## Acceptance Criteria\n- lifecycle.py has no imports from quality_gate or codex_review (not even TYPE_CHECKING)\n- Local outcome types define the interface\n- All existing tests pass\n\n## Test Plan\n- Unit test lifecycle with local outcome types\n- Verify orchestrator can still pass gate/review results\n\n## Notes for Agent\n- Orchestrator will need to map infra results to lifecycle outcomes when passing\n- Keep the fields minimal - only what lifecycle actually uses\n","notes":"Quality gate failed: Quality gate failed: Validation commands failed (non-zero exit): pytest\nLog: /home/cyou/.claude/projects/-home-cyou-mala/af827556-09cc-43fd-960b-9b0e052d4320.jsonl","status":"closed","priority":3,"issue_type":"task","created_at":"2025-12-28T16:39:25.908272658Z","updated_at":"2025-12-28T18:12:22.943623198Z","closed_at":"2025-12-28T18:12:22.943623198Z","close_reason":"Work complete - commits 3892e71 and 73fe507 merged, coverage now at 89%","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-869.13","depends_on_id":"mala-869","type":"parent-child","created_at":"2025-12-28T16:39:25.914842407Z","created_by":"daemon"}]}
{"id":"mala-869.14","title":"Add deprecation warning for ValidationConfig","description":"## Context\n- `validation/legacy_runner.py` still exists with `ValidationConfig` dataclass\n- `LegacyValidationRunner` was removed but `ValidationConfig` remains\n- `validation/runner.py` re-exports both ValidationConfig (legacy) and SpecValidationRunner (modern)\n- No deprecation warnings or migration timeline\n\n## Primary Files\n- src/validation/legacy_runner.py\n- src/validation/runner.py\n- src/validation/__init__.py\n\n## Scope\n**In:** Add `DeprecationWarning` when `ValidationConfig` is imported\n**In:** Update docstrings to indicate deprecation\n**Out:** Remove ValidationConfig (do that once no usage found)\n\n## Acceptance Criteria\n- Importing `ValidationConfig` emits deprecation warning\n- Docstring indicates deprecation\n- Existing code still works (just with warnings)\n\n## Test Plan\n- Test that deprecation warning is raised on import\n- Grep codebase for ValidationConfig usage\n\n## Notes for Agent\n- Use `warnings.warn()` with `DeprecationWarning` category\n- Check __getattr__ pattern if needed for module-level deprecation\n","notes":"Quality gate failed: Quality gate failed: Working tree has uncommitted changes for obsolete resolution: M src/config.py\n M src/orchestrator.py\n M src/validation/command_runner.py\n M tests/test_command_runner.py\n M tests/test_orchestrator.py\n?? coverage.xml\n?? src/protocols.py; No progress: commit unchanged and no new validation evidence\nLog: /home/cyou/.claude/projects/-home-cyou-mala/e5a741b9-24aa-4b84-9583-bd9156967e35.jsonl","status":"closed","priority":3,"issue_type":"task","created_at":"2025-12-28T16:39:38.422145649Z","updated_at":"2025-12-28T18:12:22.955486841Z","closed_at":"2025-12-28T18:12:22.955486841Z","close_reason":"Obsolete - ValidationConfig was already removed in cd7a02a (mala-51q.9)","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-869.14","depends_on_id":"mala-869","type":"parent-child","created_at":"2025-12-28T16:39:38.428729609Z","created_by":"daemon"}]}
{"id":"mala-869.15","title":"Define pipeline stage protocols (IssueProvider, CodeReviewer, GateChecker)","description":"## Context\n- MalaOrchestrator directly coordinates all pipeline stages with no intermediate interfaces:\n  - Issue fetching (BeadsClient) → Agent execution (Claude SDK) → Validation (SpecValidationRunner) → Review (run_codex_review) → Gate checking (QualityGate) → Issue closing (BeadsClient)\n- Each stage is a direct function/method call with concrete types\n- Stages cannot be tested independently without mocking the entire orchestrator\n- This issue defines the protocols; a follow-up issue will inject them into orchestrator\n\n## Primary Files\n- src/protocols.py (new file)\n\n## Scope\n**In:** Define Protocol classes for pipeline stages:\n  - `IssueProvider`: get_ready(), claim(), close(), mark_needs_followup()\n  - `CodeReviewer`: review(repo_path, commit_sha, ...) -\u003e ReviewResult\n  - `GateChecker`: check(log_path, spec, ...) -\u003e GateResult\n**In:** Add docstrings explaining each protocol's contract\n**In:** Define result types used by protocols (or reference existing ones)\n**Out:** Modify orchestrator to use protocols (separate issue)\n**Out:** Define AgentRunner/Validator protocols (these are more complex)\n\n## Acceptance Criteria\n- Protocol classes defined in src/protocols.py\n- Each protocol has clear docstrings\n- Protocols are minimal (only methods orchestrator actually calls)\n- BeadsClient, codex_review, QualityGate match the protocols\n\n## Test Plan\n- Verify BeadsClient conforms to IssueProvider\n- Verify run_codex_review matches CodeReviewer\n- Verify QualityGate matches GateChecker\n- Type checker should pass\n\n## Notes for Agent\n- Use typing.Protocol for structural typing\n- Keep protocols minimal - don't add methods orchestrator doesn't use\n- This enables mock implementations for testing\n","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-28T16:39:55.366326722Z","updated_at":"2025-12-28T17:17:15.411919995Z","closed_at":"2025-12-28T17:17:15.411919995Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-869.15","depends_on_id":"mala-869","type":"parent-child","created_at":"2025-12-28T16:39:55.374472357Z","created_by":"daemon"}]}
{"id":"mala-869.16","title":"Inject pipeline stage implementations into MalaOrchestrator","description":"## Context\n- After mala-869.15 defines protocols, orchestrator needs to use them\n- Currently orchestrator directly instantiates BeadsClient (line 207) and calls run_codex_review, QualityGate\n- This blocks testing without patching concrete classes\n\n## Primary Files\n- src/orchestrator.py:21,203-207,854-868\n\n## Scope\n**In:** Add protocol types to MalaOrchestrator constructor: issue_provider, code_reviewer, gate_checker\n**In:** Provide default implementations (BeadsClient, CodexReviewer wrapper, QualityGate)\n**In:** Update orchestrator methods to use injected implementations\n**Out:** Define new protocols (done in mala-869.15)\n**Out:** Create mock implementations (tests can do that)\n\n## Acceptance Criteria\n- Orchestrator accepts IssueProvider, CodeReviewer, GateChecker in constructor\n- Default values maintain current behavior\n- Tests can inject mock implementations without patching\n- All existing tests pass\n\n## Test Plan\n- Unit test orchestrator with mock implementations\n- Integration test with default (real) implementations\n- Verify CLI still works\n\n## Notes for Agent\n- Keep backwards compatible: defaults should work without changes\n- This is a big change to orchestrator - be careful with state\n","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-28T16:40:06.538185974Z","updated_at":"2025-12-28T18:33:08.746352673Z","closed_at":"2025-12-28T18:33:08.746352673Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-869.16","depends_on_id":"mala-869","type":"parent-child","created_at":"2025-12-28T16:40:06.538943862Z","created_by":"daemon"},{"issue_id":"mala-869.16","depends_on_id":"mala-869.15","type":"blocks","created_at":"2025-12-28T16:40:10.854300629Z","created_by":"daemon"}]}
{"id":"mala-869.17","title":"Define MalaEventSink protocol for testable logging","description":"## Context\n- MalaOrchestrator directly calls `log()`, `log_tool()`, `log_agent_text()` from logging.console (lines 43-50)\n- This mixes orchestration logic with presentation concerns\n- Orchestrator cannot be tested without capturing stdout\n\n## Primary Files\n- src/event_sink.py (new file)\n- src/logging/console.py (will wrap as ConsoleEventSink in follow-up)\n\n## Scope\n**In:** Define `MalaEventSink` protocol with methods: `on_agent_started()`, `on_gate_result()`, `on_issue_completed()`, `on_tool_use()`, etc.\n**In:** Create `NullEventSink` for testing\n**Out:** Inject sink into orchestrator (follow-up task)\n**Out:** Create ConsoleEventSink wrapper\n\n## Acceptance Criteria\n- MalaEventSink protocol defined with key event methods\n- NullEventSink implementation for tests\n- Protocol matches current log() call sites in orchestrator\n\n## Test Plan\n- Unit test NullEventSink (no side effects)\n- Verify protocol covers all orchestrator log calls\n\n## Notes for Agent\n- Survey orchestrator for all log/log_tool/log_agent_text calls\n- Protocol should have one method per semantic event, not generic log()\n","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-28T16:40:22.220423004Z","updated_at":"2025-12-28T17:25:07.66424121Z","closed_at":"2025-12-28T17:25:07.66424121Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-869.17","depends_on_id":"mala-869","type":"parent-child","created_at":"2025-12-28T16:40:22.227226354Z","created_by":"daemon"}]}
{"id":"mala-869.18","title":"Lazy-load prompts in orchestrator.py","description":"## Context\n- `orchestrator.py` reads prompt files at module import time (lines 93-103)\n- `PROMPT_FILE.read_text()`, `GATE_FOLLOWUP_PROMPT`, etc. are loaded at import\n- If prompt files are missing or import happens in different working directory, import fails\n- This makes the module untestable without filesystem setup\n\n## Primary Files\n- src/orchestrator.py:93-103\n\n## Scope\n**In:** Lazy-load prompts on first use via `functools.cache` or class property\n**In:** Keep prompt paths as constants, only defer the read\n**Out:** Change prompt content or format\n**Out:** Fix main.py bootstrap (separate concern)\n\n## Acceptance Criteria\n- `import src.orchestrator` succeeds without filesystem access to prompts\n- Prompts are loaded on first use\n- CLI behavior unchanged\n\n## Test Plan\n- Unit test that imports orchestrator in isolated environment without prompt files\n- Integration test that CLI still works\n- Verify prompts are only loaded once (cached)\n\n## Notes for Agent\n- Use `@functools.cache` on a function that reads the file\n- Or use a class with `@property` and `@functools.cached_property`\n","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-28T16:40:34.124069661Z","updated_at":"2025-12-28T19:57:18.253945611Z","closed_at":"2025-12-28T19:57:18.253945611Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-869.18","depends_on_id":"mala-869","type":"parent-child","created_at":"2025-12-28T16:40:34.130890191Z","created_by":"daemon"}]}
{"id":"mala-869.2","title":"Create MalaConfig dataclass for centralized configuration","description":"## Context\n- Environment variable access is scattered across multiple modules with no central configuration object:\n  - `cli.py:72,235`: checks BRAINTRUST_API_KEY, MORPH_API_KEY\n  - `orchestrator.py:127`: accesses MORPH_API_KEY (crashes if missing - fixed by mala-869.1)\n  - `tools/env.py:24,51,110`: reads MALA_RUNS_DIR, MALA_LOCK_DIR, CLAUDE_CONFIG_DIR\n  - `braintrust_integration.py:71`: checks BRAINTRUST_API_KEY\n- Bootstrap sequencing requirements are implicit and undocumented\n- No Settings/Config object for programmatic users\n\n## Primary Files\n- src/config.py (new file)\n- src/tools/env.py (may be absorbed or delegate to config)\n\n## Scope\n**In:** Create `MalaConfig` dataclass that consolidates: paths (runs_dir, lock_dir, claude_config_dir), API keys (braintrust, morph), feature flags (morph_enabled, braintrust_enabled)\n**In:** Add `MalaConfig.from_env()` classmethod that loads from environment with validation\n**In:** Add `MalaConfig.validate()` method with clear error messages\n**In:** Document which env vars are required/optional\n**Out:** Change orchestrator to use MalaConfig (separate issue)\n**Out:** Remove env var support (keep for CLI)\n\n## Acceptance Criteria\n- MalaConfig dataclass with all config fields\n- from_env() loads and validates configuration\n- Clear error messages for missing required config\n- Programmatic users can construct MalaConfig without env vars\n- Docstrings document all env vars\n\n## Test Plan\n- Unit test MalaConfig.from_env() with various env var combinations\n- Test validation errors for missing required fields\n- Test construction without env vars\n\n## Notes for Agent\n- Use `@dataclass(frozen=True)` for immutability\n- Consider using `pydantic` or plain dataclass (check project dependencies)\n- Keep env.py functions as thin wrappers or deprecate them\n","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-28T16:37:06.42256265Z","updated_at":"2025-12-28T17:09:23.90078465Z","closed_at":"2025-12-28T17:09:23.90078465Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-869.2","depends_on_id":"mala-869","type":"parent-child","created_at":"2025-12-28T16:37:06.429148785Z","created_by":"daemon"}]}
{"id":"mala-869.3","title":"Define JSONL log event types for SDK schema contract","description":"## Context\n- QualityGate parses JSONL logs expecting specific structure from Claude Agent SDK:\n  - `{\"type\": \"assistant\", \"message\": {\"content\": [{\"type\": \"tool_use\", \"name\": \"Bash\", \"input\": {\"command\": ...}}]}}`\n  - `{\"type\": \"user\", \"message\": {\"content\": [{\"type\": \"tool_result\", \"tool_use_id\": ...}]}}`\n- This schema is owned by Claude Agent SDK, not by mala\n- No versioned schema, contract tests, or shared type definitions\n- If SDK changes log format, evidence detection silently breaks\n\n## Primary Files\n- src/log_events.py (new file)\n- src/quality_gate.py:234-275, 452-476 (will use new types in follow-up)\n\n## Scope\n**In:** Define explicit `LogEvent` types that represent expected log structure: `LogEntry`, `AssistantMessage`, `ToolUseBlock`, `UserMessage`, `ToolResultBlock`\n**In:** Add `parse_log_entry(data: dict) -\u003e LogEntry | None` function with validation\n**In:** Add contract tests with sample JSONL fixtures\n**Out:** Migrate QualityGate to use new types (separate issue)\n**Out:** Add schema version checking (future enhancement)\n\n## Acceptance Criteria\n- Explicit types for log events in src/log_events.py\n- Contract tests with sample JSONL fixtures from real agent sessions\n- parse_log_entry returns typed objects or None for unrecognized entries\n- Parse failures produce clear errors referencing expected schema\n\n## Test Plan\n- Unit tests with JSONL fixtures representing current SDK format\n- Test with malformed/partial entries\n- Test with future-compatible unknown fields (should not break)\n\n## Notes for Agent\n- Extract 3-5 sample JSONL entries from existing test fixtures or logs\n- Use TypedDict or dataclasses, not Pydantic (keep deps minimal)\n- Design for forward compatibility: unknown fields should be ignored\n","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-28T16:37:22.487714715Z","updated_at":"2025-12-28T18:25:35.542238254Z","closed_at":"2025-12-28T18:25:35.542238254Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-869.3","depends_on_id":"mala-869","type":"parent-child","created_at":"2025-12-28T16:37:22.494493168Z","created_by":"daemon"}]}
{"id":"mala-869.4","title":"Extend CommandRunner with async process group termination","description":"## Context\n- Subprocess execution is fragmented across 5+ modules:\n  - `beads_client.py`: async subprocess with process group termination, custom timeout\n  - `codex_review.py`: async subprocess, basic error handling\n  - `command_runner.py`: sync and async runners with streaming, timeout, structured results\n  - `git_utils.py`: sync subprocess.run with basic error handling\n  - `validation/helpers.py`: sync subprocess.run for git init\n- `command_runner.py` already provides a good abstraction but is only used by validation\n- Previous issue mala-yg9.5 was closed but not all modules migrated\n\n## Primary Files\n- src/validation/command_runner.py\n\n## Scope\n**In:** Add process group creation and termination to async CommandRunner (copy logic from beads_client.py)\n**In:** Add configurable grace period for SIGTERM before SIGKILL\n**In:** Ensure process group termination works on Linux (os.killpg)\n**Out:** Migrate other modules to use CommandRunner (separate issues)\n\n## Acceptance Criteria\n- `run_async()` creates process group when `use_process_group=True`\n- On timeout, sends SIGTERM to process group, waits grace period, then SIGKILL\n- Existing tests pass\n- New tests verify process group termination\n\n## Test Plan\n- Add unit test that spawns a process with children, verifies all are killed on timeout\n- Test SIGTERM grace period behavior\n- Verify existing async tests still pass\n\n## Notes for Agent\n- Reference beads_client.py:132-160 for the process group termination logic\n- Use `os.setsid` in `preexec_fn` for process group creation\n- Grace period should be configurable, default 2 seconds\n","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-28T16:37:37.384800032Z","updated_at":"2025-12-28T18:57:24.475178529Z","closed_at":"2025-12-28T18:57:24.475178529Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-869.4","depends_on_id":"mala-869","type":"parent-child","created_at":"2025-12-28T16:37:37.392583878Z","created_by":"daemon"}]}
{"id":"mala-869.5","title":"Migrate beads_client.py to use CommandRunner","description":"## Context\n- `beads_client.py` has its own `_run_subprocess_async()` implementation (lines 99-132)\n- Includes process group termination, timeout handling, structured results\n- This duplicates functionality now in CommandRunner (after mala-869.4)\n- Migrating ensures consistent error handling and observability\n\n## Primary Files\n- src/beads_client.py:99-160\n\n## Scope\n**In:** Replace `_run_subprocess_async()` with calls to `CommandRunner.run_async()`\n**In:** Update `SubprocessResult` usage to use `CommandResult`\n**In:** Ensure timeout and process group behavior is preserved\n**Out:** Change BeadsClient public API\n\n## Acceptance Criteria\n- No `asyncio.create_subprocess_exec` calls in beads_client.py\n- All existing tests pass\n- Timeout behavior unchanged\n- Process group termination unchanged\n\n## Test Plan\n- Run existing beads_client tests\n- Manual test with actual bd commands\n- Verify timeout kills process groups correctly\n\n## Notes for Agent\n- Keep `SubprocessResult` if needed for API compatibility, or migrate to `CommandResult`\n- The `_terminate_process_group` logic should now be in CommandRunner\n","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-28T16:37:47.58471429Z","updated_at":"2025-12-28T21:27:05.180568803Z","closed_at":"2025-12-28T21:27:05.180568803Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-869.5","depends_on_id":"mala-869","type":"parent-child","created_at":"2025-12-28T16:37:47.591408487Z","created_by":"daemon"},{"issue_id":"mala-869.5","depends_on_id":"mala-869.4","type":"blocks","created_at":"2025-12-28T16:38:06.223617214Z","created_by":"daemon"}]}
{"id":"mala-869.6","title":"Migrate codex_review.py to use CommandRunner","description":"## Context\n- `codex_review.py` has its own async subprocess implementation (lines 335-345)\n- Uses `asyncio.create_subprocess_exec` with basic error handling\n- Should use CommandRunner for consistency\n\n## Primary Files\n- src/codex_review.py:335-400\n\n## Scope\n**In:** Replace async subprocess calls with `CommandRunner.run_async()`\n**In:** Use CommandResult instead of raw stdout/stderr\n**Out:** Change review prompt or schema\n\n## Acceptance Criteria\n- No `asyncio.create_subprocess_exec` calls in codex_review.py\n- All existing tests pass\n- Error handling unchanged\n\n## Test Plan\n- Run existing codex_review tests\n- Manual test with actual codex commands\n\n## Notes for Agent\n- codex_review uses structured JSON output, ensure CommandResult captures it correctly\n","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-28T16:37:55.169116903Z","updated_at":"2025-12-28T20:56:07.163759745Z","closed_at":"2025-12-28T20:56:07.163759745Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-869.6","depends_on_id":"mala-869","type":"parent-child","created_at":"2025-12-28T16:37:55.175656134Z","created_by":"daemon"},{"issue_id":"mala-869.6","depends_on_id":"mala-869.4","type":"blocks","created_at":"2025-12-28T16:38:06.241473443Z","created_by":"daemon"}]}
{"id":"mala-869.7","title":"Migrate git_utils.py to use CommandRunner","description":"## Context\n- `git_utils.py` uses direct `subprocess.run` calls (lines 20-114)\n- Has basic error handling, no timeout management\n- Should use CommandRunner for consistency\n\n## Primary Files\n- src/git_utils.py\n\n## Scope\n**In:** Replace `subprocess.run` calls with `CommandRunner.run()`\n**In:** Use CommandResult for structured output\n**Out:** Change git_utils public API\n\n## Acceptance Criteria\n- No `subprocess.run` calls in git_utils.py\n- All existing tests pass\n- Error handling preserved\n\n## Test Plan\n- Run existing git_utils tests if any\n- Manual test git operations\n\n## Notes for Agent\n- git_utils functions are used by orchestrator and other modules\n- Ensure return values match current API\n","notes":"Quality gate failed: Quality gate failed: Missing validation commands: ruff format, ruff check, pytest, ty check; No progress: commit unchanged and no new validation evidence\nLog: /home/cyou/.claude/projects/-home-cyou-mala/4d3f0f30-d77e-457c-b3a4-507e3a146348.jsonl","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-28T16:38:01.767234246Z","updated_at":"2025-12-29T05:14:38.699565957Z","closed_at":"2025-12-29T05:14:38.699565957Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-869.7","depends_on_id":"mala-869","type":"parent-child","created_at":"2025-12-28T16:38:01.774092351Z","created_by":"daemon"},{"issue_id":"mala-869.7","depends_on_id":"mala-869.4","type":"blocks","created_at":"2025-12-28T16:38:06.252960055Z","created_by":"daemon"}]}
{"id":"mala-869.8","title":"Define TelemetryProvider protocol and NullProvider","description":"## Context\n- Braintrust tracing is enabled via `setup_claude_agent_sdk()` which patches SDK globally at process level (cli.py:73-75)\n- Must happen before any `claude_agent_sdk` import, creating implicit initialization ordering constraint\n- The patching is process-wide with no abstraction: once patched, all SDK usage is traced\n- `braintrust_integration.py` imports `claude_agent_sdk` directly (line 34)\n- This hidden coupling means: (1) programmatic users may bypass tracing, (2) tests pollute global state\n\n## Primary Files\n- src/telemetry.py (new file)\n\n## Scope\n**In:** Define `TelemetryProvider` protocol with methods: `is_enabled()`, `create_span(name, metadata)`, `flush()`\n**In:** Create `NullTelemetryProvider` that does nothing (for tests and opt-out)\n**In:** Create `BraintrustProvider` that wraps existing braintrust_integration.py logic\n**Out:** Inject provider into orchestrator (separate issue)\n**Out:** Remove global patching (may still be needed for auto-instrumentation)\n\n## Acceptance Criteria\n- TelemetryProvider protocol defined\n- NullTelemetryProvider implementation\n- BraintrustProvider wraps TracedAgentExecution\n- Tests can use NullProvider without global state\n\n## Test Plan\n- Unit test NullProvider (no side effects)\n- Unit test BraintrustProvider with mock braintrust\n- Verify protocol matches TracedAgentExecution API\n\n## Notes for Agent\n- Keep BraintrustProvider API compatible with TracedAgentExecution\n- NullProvider should be completely stateless\n- Consider if global SDK patching should remain for auto-instrumentation\n","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-28T16:38:19.763909236Z","updated_at":"2025-12-28T16:54:24.69310314Z","closed_at":"2025-12-28T16:54:24.69310314Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-869.8","depends_on_id":"mala-869","type":"parent-child","created_at":"2025-12-28T16:38:19.770757544Z","created_by":"daemon"}]}
{"id":"mala-869.9","title":"Add run_sync() method to MalaOrchestrator","description":"## Context\n- MalaOrchestrator.run() is async, but there's no sync wrapper for programmatic use\n- CLI uses `asyncio.run()` (cli.py:429,463), which creates a new event loop each time\n- Programmatic users must handle async execution themselves, creating integration risks:\n  - Nested event loops if caller already has one running (Jupyter, GUI apps)\n  - Unclear lifecycle: who creates/owns the event loop?\n  - No hook for running in existing event loop\n\n## Primary Files\n- src/orchestrator.py\n\n## Scope\n**In:** Add `MalaOrchestrator.run_sync()` method that handles event loop creation/reuse correctly\n**In:** Use `asyncio.get_running_loop()` check to detect if already in async context\n**In:** Document async/sync usage patterns in docstring\n**Out:** Change the async implementation\n**Out:** Break CLI usage\n\n## Acceptance Criteria\n- `run_sync()` works from sync code without event loop issues\n- `run()` works when awaited from existing async context\n- Clear docs on when to use each\n- Graceful error if run_sync() called from async context\n\n## Test Plan\n- Test `run_sync()` from sync context\n- Test `run()` from existing async context (inside asyncio.run)\n- Test nested event loop detection (should fail gracefully)\n\n## Notes for Agent\n- Use `asyncio.get_event_loop()` or `asyncio.new_event_loop()` appropriately\n- Consider using `nest_asyncio` library if nested loops are needed (check deps)\n- Keep it simple: raise clear error if called from wrong context\n","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-28T16:38:34.845531039Z","updated_at":"2025-12-28T16:59:25.18438146Z","closed_at":"2025-12-28T16:59:25.18438146Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-869.9","depends_on_id":"mala-869","type":"parent-child","created_at":"2025-12-28T16:38:34.852460257Z","created_by":"daemon"}]}
{"id":"mala-89hu","title":"[Review] src/domain/validation/presets/python-uv.yaml:10: [P2] Correct incomplete fix in `python-uv` preset","description":"## Review Finding\n\nThis issue was auto-created from a P2 review finding.\n\n**Source issue:** mala-fhyl\n**Reviewer:** gemini\n**Location:** src/domain/validation/presets/python-uv.yaml:10\n\n## Details\n\nThe `python-uv` preset in `src/domain/validation/presets/python-uv.yaml` is still missing the `--check` flag for `ruff format`, despite the commit message stating it was already correct. Since the goal of the change was to ensure consistency and avoid in-place reformatting during validation, this preset should also be updated to include the flag.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T00:42:21.580682901Z","created_by":"cyou","updated_at":"2026-01-03T00:50:51.690875517Z","closed_at":"2026-01-03T00:50:51.690875517Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_finding:cd8e6879751e","source:mala-fhyl"]}
{"id":"mala-8bp2","title":"[P1] Public preset references internal marker 'flaky_sdk'","description":"## Review Finding\n\n**Priority:** P1\n**Trigger:** run_end\n**Baseline:** ddc86bb..3deec8ab\n**Location:** src/domain/validation/presets/python-uv.yaml:11\n\n---\n\n`src/domain/validation/presets/python-uv.yaml` is a public preset. Adding `and not flaky_sdk` to the `e2e` command leaks `mala`'s internal testing markers to users. Users without `flaky_sdk` registered in their project may see warnings or failures (if strict markers are enabled). `mala`'s own tests are already handled by the logic added in `tests/e2e/conftest.py`, making this change redundant for internal use and harmful for public use.","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-11T02:30:42.140262927Z","created_by":"cyou","updated_at":"2026-01-11T02:35:43.537136587Z","closed_at":"2026-01-11T02:35:43.537136587Z","close_reason":"Closed","labels":["auto_generated","cumulative-review","fp:1ab7ba24d2c992df","review_finding","trigger:run_end"]}
{"id":"mala-8cxj","title":"Set sentinel when usage missing to mark tracking disabled","description":"Spec says missing usage should disable tracking via sentinel (-1). Current code only logs a warning and leaves usage at zero, which is indistinguishable from 0% usage. Set sentinel (or otherwise mark disabled) and adjust tests/docs.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T22:29:08.927480887Z","created_by":"cyou","updated_at":"2026-01-03T22:55:10.216580658Z","closed_at":"2026-01-03T22:55:10.216580658Z","close_reason":"Closed"}
{"id":"mala-8do","title":"Make inner loop more token efficient","description":"Context:\nOptimize the inner agent loop to reduce token consumption.\n\nLocation:\n- src/pipeline/agent_session_runner.py (main iteration loop)\n- src/infra/hooks/file_cache.py (file read caching)\n- src/infra/hooks/lint_cache.py (lint result caching)\n\nScope:\n- Needs research to identify specific optimizations\n- Potential areas: prompt size, context compaction, caching\n\nAcceptance Criteria:\n- TBD after research - needs specific metrics and targets\n\nTest Plan:\n- TBD after research\n\nNotes for Agent:\n- GROOMING: This issue is too vague. Consider splitting into:\n  1. Research task to profile token usage\n  2. Specific optimization tasks based on findings\n- Current caches: file reads, lint results\n- Token usage available in JSONL logs at entry.message.usage","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-28T06:21:39.553113819Z","updated_at":"2026-01-03T05:34:25.609978468Z","closed_at":"2026-01-03T05:34:25.609978468Z","close_reason":"Closed"}
{"id":"mala-8dpq","title":"[Review] src/domain/validation/config.py:482: [P2] Unconfigured command fallbacks exit with code 1","description":"## Review Finding\n\nThis issue was auto-created from a P2 review finding.\n\n**Source issue:** mala-vnzl\n**Reviewer:** gemini\n**Location:** src/domain/validation/config.py:482\n\n## Details\n\nThe `_NOT_CONFIGURED` fallback command string uses `exit 1`. When substituted into prompt templates, this will cause the agent to perceive a tool failure even if the command is simply not applicable to the project (e.g., no typechecker configured). This can lead to agent confusion or infinite retry loops as the agent attempts to \"fix\" a failure caused by missing infrastructure. Using `exit 0` with a warning message is safer for optional tooling.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T00:31:02.182039624Z","created_by":"cyou","updated_at":"2026-01-03T00:35:01.406667098Z","closed_at":"2026-01-03T00:35:01.406667098Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_finding:812758b4bd59","source:mala-vnzl"]}
{"id":"mala-8kte","title":"Epic: Cerberus review-gate integration","description":"Goal:\n- Replace the Codex-only review flow in mala with Cerberus review-gate per docs/cerberus-review-plan.md.\n- Unanimous external reviewer consensus, issue context injection, and retry semantics owned by mala.\n\nScope:\n- In: review adapter, pipeline/orchestrator wiring, CLI/config/telemetry renames, tests, docs, cleanup of Codex artifacts.\n- Out: implementing Cerberus CLI itself (review-gate binary).\n","status":"closed","priority":1,"issue_type":"epic","created_at":"2025-12-30T05:14:20.082114324Z","created_by":"cyou","updated_at":"2025-12-30T07:23:36.221179682Z","closed_at":"2025-12-30T07:23:36.221179682Z","close_reason":"All children completed","comments":[{"id":1,"issue_id":"mala-8kte","author":"cyou","text":"Reinstating Cerberus epic; separate from mala-yco.","created_at":"2025-12-30T05:41:24Z"}]}
{"id":"mala-8kte.1","title":"Define ReviewResult/ReviewIssue + lifecycle parse_error handling","description":"Context:\n- Cerberus issue schema adds reviewer attribution; ReviewIssue protocol must include it.\n- ReviewOutcome requires parse_error: str | None and an issues list for lifecycle decisions.\n- parse_error should trigger review rerun (Effect.RUN_REVIEW), not an agent prompt.\n\nScope:\n- In: update ReviewIssue/ReviewResult dataclasses and CodeReviewer protocol in src/protocols.py.\n- In: update lifecycle ReviewOutcome docs, LifecycleConfig.review_enabled rename, and parse_error handling in src/lifecycle.py.\n- In: adjust tests/test_lifecycle.py to cover reviewer field and parse_error -\u003e RUN_REVIEW.\n- Out: orchestrator/pipeline wiring; CLI flag changes; Cerberus CLI invocation.\n\nAcceptance Criteria:\n- ReviewResult satisfies ReviewOutcome (passed, parse_error, fatal_error, issues).\n- ReviewIssue protocol includes reviewer: str.\n- Lifecycle uses review_enabled and returns Effect.RUN_REVIEW when parse_error is set.\n- Tests updated and passing.\n\nTest Plan:\n- TDD: update failing tests first, implement changes, refactor.\n- Run: uv run pytest tests/test_lifecycle.py\n\nNotes for Agent:\n- Update Codex wording to \"external review\" only within lifecycle/protocols touched here.\n","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-30T05:14:20.117997427Z","created_by":"cyou","updated_at":"2025-12-30T05:34:42.958245501Z","closed_at":"2025-12-30T05:34:42.958245501Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-8kte.1","depends_on_id":"mala-8kte","type":"parent-child","created_at":"2025-12-30T05:41:24.88077009Z","created_by":"daemon"}]}
{"id":"mala-8kte.10","title":"Cleanup: remove codex_review module + tests","description":"Context:\n- Codex review module/tests are obsolete after Cerberus integration.\n\nScope:\n- In: delete src/codex_review.py.\n- In: delete tests/test_codex_review.py.\n- In: verify no remaining references to codex_review in src/ or tests/ (rg -n \"codex_review\").\n- Out: migrating old run metadata fields (docs only).\n\nAcceptance Criteria:\n- codex_review module and tests removed.\n- rg -n \"codex_review\" src tests returns no matches.\n- Tests pass.\n\nTest Plan:\n- Run: rg -n \"codex_review\" src tests\n- Run: uv run pytest (or targeted suites as needed)\n","status":"closed","priority":2,"issue_type":"chore","created_at":"2025-12-30T05:14:20.328167218Z","created_by":"cyou","updated_at":"2025-12-30T07:23:36.193173764Z","closed_at":"2025-12-30T07:23:36.193173764Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-8kte.10","depends_on_id":"mala-8kte.3","type":"blocks","created_at":"2025-12-30T05:14:20.660147377Z","created_by":"daemon"},{"issue_id":"mala-8kte.10","depends_on_id":"mala-8kte.4","type":"blocks","created_at":"2025-12-30T05:14:20.680339124Z","created_by":"daemon"},{"issue_id":"mala-8kte.10","depends_on_id":"mala-8kte.5","type":"blocks","created_at":"2025-12-30T05:14:20.701323649Z","created_by":"daemon"},{"issue_id":"mala-8kte.10","depends_on_id":"mala-8kte","type":"parent-child","created_at":"2025-12-30T05:41:25.078539186Z","created_by":"daemon"}]}
{"id":"mala-8kte.11","title":"Cleanup: update console + validation spec review naming","description":"Context:\n- Console event sink still references Codex review and thinking mode.\n- Validation spec docs reference the old \"codex-review\" disable value.\n\nScope:\n- In: update src/event_sink_console.py to use review_enabled and remove thinking-mode output; rename strings to \"external review\" or \"review\".\n- In: update src/validation/spec.py to rename \"codex-review\" to \"review\" in docs/comments.\n- Out: other codex references in pipeline/orchestrator/tests (handled elsewhere).\n\nAcceptance Criteria:\n- Console output uses \"review\" phrasing and has no codex_thinking_mode references.\n- Validation spec comment uses \"review\".\n- rg -n \"codex\" src/event_sink_console.py src/validation/spec.py returns no matches.\n\nTest Plan:\n- Run: rg -n \"codex\" src/event_sink_console.py src/validation/spec.py\n- (Optional) uv run pytest tests/test_event_sink.py\n","status":"closed","priority":2,"issue_type":"chore","created_at":"2025-12-30T05:43:04.285878118Z","created_by":"cyou","updated_at":"2025-12-30T06:55:50.878903374Z","closed_at":"2025-12-30T06:55:50.878903374Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-8kte.11","depends_on_id":"mala-8kte","type":"parent-child","created_at":"2025-12-30T05:43:04.293252774Z","created_by":"daemon"},{"issue_id":"mala-8kte.11","depends_on_id":"mala-8kte.6","type":"blocks","created_at":"2025-12-30T05:43:04.320744295Z","created_by":"daemon"},{"issue_id":"mala-8kte.11","depends_on_id":"mala-8kte.7","type":"blocks","created_at":"2025-12-30T05:43:04.342992731Z","created_by":"daemon"}]}
{"id":"mala-8kte.2","title":"Add cerberus_review adapter + parsing tests","description":"Context:\n- Cerberus review-gate returns structured JSON and exit codes 0-5.\n- mala needs a thin adapter and issue formatting compatible with existing pipeline code.\n\nScope:\n- In: add src/cerberus_review.py with spawn/wait helpers or run_cerberus_review, mapping exit codes to ReviewResult and parsing issues/reviewer fields.\n- In: implement format_review_issues to match existing caller expectations.\n- In: add tests/test_cerberus_review.py covering JSON parsing, exit code mapping (0-5), timeout, and parse_error extraction.\n- Out: orchestrator/pipeline integration, empty diff short-circuit (handled elsewhere).\n- Out: removal of Codex review module/tests (cleanup task).\n\nAcceptance Criteria:\n- Adapter returns ReviewResult with correct passed/parse_error/fatal_error mapping for each exit code.\n- Issues are parsed with reviewer/file/line/priority/title/body fields.\n- Tests cover success, fail, parse error, timeout, no reviewers, internal error.\n\nTest Plan:\n- TDD: write failing tests for exit code mapping and parsing, implement adapter, refactor.\n- Run: uv run pytest tests/test_cerberus_review.py\n\nNotes for Agent:\n- Keep CLI execution in repo_path handled by caller; adapter focuses on parsing and mapping.\n","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-30T05:14:20.152241174Z","created_by":"cyou","updated_at":"2025-12-30T06:06:47.590639217Z","closed_at":"2025-12-30T06:06:47.590639217Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-8kte.2","depends_on_id":"mala-8kte.1","type":"blocks","created_at":"2025-12-30T05:14:20.349334512Z","created_by":"daemon"},{"issue_id":"mala-8kte.2","depends_on_id":"mala-8kte","type":"parent-child","created_at":"2025-12-30T05:41:24.902755567Z","created_by":"daemon"}]}
{"id":"mala-8kte.3","title":"Update ReviewRunner to use new reviewer signature","description":"Context:\n- ReviewRunner currently references CodexReviewResult and thinking_mode.\n- Cerberus integration requires the new CodeReviewer signature and context-file handling.\n- Review timeout from config should flow into the reviewer wait call.\n\nScope:\n- In: update src/pipeline/review_runner.py to use ReviewResult and the new CodeReviewer signature (diff_range, context_file, timeout).\n- In: write issue_description to a unique temp context file (e.g., /tmp/claude/review-context-{issue_id}.txt) and pass its path.\n- In: add review_timeout to ReviewRunnerConfig and use it when calling the reviewer.\n- In: update tests/test_review_runner.py to match the new config and call signature.\n- Out: orchestrator and agent session changes.\n\nAcceptance Criteria:\n- ReviewRunner passes context_file and timeout to the reviewer and returns ReviewResult.\n- Context file uses unique per-issue naming and is created only when issue_description is present.\n- review_timeout is respected (default 300 when unset).\n- Tests updated and passing.\n\nTest Plan:\n- TDD: update tests first, implement minimal changes, refactor.\n- Run: uv run pytest tests/test_review_runner.py\n","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-30T05:14:20.174203157Z","created_by":"cyou","updated_at":"2025-12-30T06:50:58.396448454Z","closed_at":"2025-12-30T06:50:58.396448454Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-8kte.3","depends_on_id":"mala-8kte.1","type":"blocks","created_at":"2025-12-30T05:14:20.369883728Z","created_by":"daemon"},{"issue_id":"mala-8kte.3","depends_on_id":"mala-8kte.2","type":"blocks","created_at":"2025-12-30T05:14:20.390857173Z","created_by":"daemon"},{"issue_id":"mala-8kte.3","depends_on_id":"mala-8kte","type":"parent-child","created_at":"2025-12-30T05:41:24.924665555Z","created_by":"daemon"}]}
{"id":"mala-8kte.4","title":"Orchestrator: DefaultReviewer runs review-gate + empty diff pass","description":"Context:\n- Default reviewer must call review-gate CLI with diff range and context file, running in repo_path.\n- Empty diff should short-circuit to PASS on the initial review only.\n- disable-validations value is renamed to \"review\".\n\nScope:\n- In: update src/orchestrator.py DefaultCodeReviewer -\u003e DefaultReviewer with repo_path and review-gate spawn/wait flow; parse session_key; handle spawn failure.\n- In: apply empty-diff short-circuit for initial review only.\n- In: update disable-validations checks to use \"review\".\n- In: pass review_timeout from config into ReviewRunnerConfig so reviewer wait honors CLI timeout.\n- Out: context file creation (handled in ReviewRunner); codex wording sweep (separate issue); CLI flag definitions.\n\nAcceptance Criteria:\n- review-gate commands run in repo_path and map exit codes to ReviewResult correctly.\n- Spawn failure returns parse_error with stderr.\n- Empty diff on initial review returns passed=True without spawning.\n- review_timeout is propagated into ReviewRunnerConfig.\n- Tests updated and passing.\n\nTest Plan:\n- TDD: add failing tests for empty diff and spawn/wait mapping; implement.\n- Run: uv run pytest tests/test_orchestrator.py\n","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-30T05:14:20.19618235Z","created_by":"cyou","updated_at":"2025-12-30T06:45:54.951228677Z","closed_at":"2025-12-30T06:45:54.951228677Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-8kte.4","depends_on_id":"mala-8kte.1","type":"blocks","created_at":"2025-12-30T05:14:20.411650319Z","created_by":"daemon"},{"issue_id":"mala-8kte.4","depends_on_id":"mala-8kte.2","type":"blocks","created_at":"2025-12-30T05:14:20.431473927Z","created_by":"daemon"},{"issue_id":"mala-8kte.4","depends_on_id":"mala-8kte.6","type":"blocks","created_at":"2025-12-30T05:14:20.451567814Z","created_by":"daemon"},{"issue_id":"mala-8kte.4","depends_on_id":"mala-8kte","type":"parent-child","created_at":"2025-12-30T05:41:24.946904338Z","created_by":"daemon"}]}
{"id":"mala-8kte.5","title":"AgentSessionRunner: cerberus review + external prompt","description":"Context:\n- AgentSessionRunner uses Codex review imports/fields and prompt text references Codex.\n- parse_error is now a string message and should trigger review retry without agent prompt.\n\nScope:\n- In: update src/pipeline/agent_session_runner.py imports, types, and field names (review_enabled, review_log_path, ReviewResult).\n- In: adjust review retry logic to treat parse_error as retryable review-tool failure; use format_review_issues(result.issues).\n- In: update src/prompts/review_followup.md wording (\"External Review Failed\", \"external reviewers\").\n- In: update tests/test_agent_session_runner.py for new fields and behavior.\n- Out: review_runner and orchestrator changes.\n\nAcceptance Criteria:\n- AgentSessionRunner stores review_log_path and uses ReviewResult.issues with format_review_issues.\n- parse_error leads to review rerun (no agent prompt) while issues trigger review retry prompt.\n- Prompt text reflects external reviewers.\n- Tests updated and passing.\n\nTest Plan:\n- TDD: update tests first, implement changes, refactor.\n- Run: uv run pytest tests/test_agent_session_runner.py\n","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-30T05:14:20.217931444Z","created_by":"cyou","updated_at":"2025-12-30T07:04:56.938876497Z","closed_at":"2025-12-30T07:04:56.938876497Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-8kte.5","depends_on_id":"mala-8kte.1","type":"blocks","created_at":"2025-12-30T05:14:20.472210809Z","created_by":"daemon"},{"issue_id":"mala-8kte.5","depends_on_id":"mala-8kte.2","type":"blocks","created_at":"2025-12-30T05:14:20.493107025Z","created_by":"daemon"},{"issue_id":"mala-8kte.5","depends_on_id":"mala-8kte.6","type":"blocks","created_at":"2025-12-30T05:14:20.51385693Z","created_by":"daemon"},{"issue_id":"mala-8kte.5","depends_on_id":"mala-8kte.8","type":"blocks","created_at":"2025-12-30T05:14:20.534644986Z","created_by":"daemon"},{"issue_id":"mala-8kte.5","depends_on_id":"mala-8kte","type":"parent-child","created_at":"2025-12-30T05:41:24.969443337Z","created_by":"daemon"}]}
{"id":"mala-8kte.6","title":"CLI/config: rename review flags + add review-timeout","description":"Context:\n- CLI currently exposes Codex-specific flags and disable-validations=codex-review.\n- Cerberus integration requires review-timeout control in mala.\n\nScope:\n- In: update src/cli.py to rename disable-validations value to \"review\", remove --codex-thinking-mode, and add --review-timeout (default 300).\n- In: update src/config.py to rename codex_review_enabled -\u003e review_enabled, add review_timeout, and remove codex_thinking_mode.\n- In: update tests/test_cli.py to cover new flags and removals.\n- Out: orchestrator/pipeline usage changes; telemetry updates.\n\nAcceptance Criteria:\n- CLI help and parsing no longer include --codex-thinking-mode.\n- --disable-validations=review works; codex-review value removed.\n- --review-timeout is accepted with default 300.\n- Config uses review_enabled and review_timeout.\n- Tests updated and passing.\n\nTest Plan:\n- TDD: update CLI tests first, implement changes, refactor.\n- Run: uv run pytest tests/test_cli.py\n","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-30T05:14:20.239296038Z","created_by":"cyou","updated_at":"2025-12-30T05:38:34.47878451Z","closed_at":"2025-12-30T05:38:34.47878451Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-8kte.6","depends_on_id":"mala-8kte","type":"parent-child","created_at":"2025-12-30T05:41:24.99101766Z","created_by":"daemon"}]}
{"id":"mala-8kte.7","title":"Event sink: RunConfig review_enabled rename","description":"Context:\n- Event sink RunConfig still uses codex_review_enabled and codex_thinking_mode.\n- Telemetry should align with new review_enabled naming.\n\nScope:\n- In: update src/event_sink.py RunConfig to review_enabled and remove codex_thinking_mode; update docstrings/comments.\n- In: update tests/test_event_sink.py fixtures and assertions.\n- Out: run_metadata schema changes (separate task).\n\nAcceptance Criteria:\n- Event sink RunConfig matches new config fields (review_enabled only).\n- No Codex-specific fields remain in event sink.\n- Tests updated and passing.\n\nTest Plan:\n- TDD: update tests first, implement changes.\n- Run: uv run pytest tests/test_event_sink.py\n","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-30T05:14:20.262189069Z","created_by":"cyou","updated_at":"2025-12-30T06:07:21.100933948Z","closed_at":"2025-12-30T06:07:21.100933948Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-8kte.7","depends_on_id":"mala-8kte.6","type":"blocks","created_at":"2025-12-30T05:14:20.555322181Z","created_by":"daemon"},{"issue_id":"mala-8kte.7","depends_on_id":"mala-8kte","type":"parent-child","created_at":"2025-12-30T05:41:25.012377437Z","created_by":"daemon"}]}
{"id":"mala-8kte.8","title":"Run metadata: review_log_path + review_enabled rename","description":"Context:\n- Run metadata still stores codex_review_log_path and codex_review fields.\n- Cerberus integration renames these to review_log_path and review_enabled.\n\nScope:\n- In: update src/logging/run_metadata.py IssueRun to use review_log_path, and RunConfig to use review_enabled.\n- In: update tests/test_run_metadata.py fixtures and assertions.\n- Out: backward-compat parsing for historical metadata (documented separately).\n\nAcceptance Criteria:\n- Run metadata schema uses review_log_path and review_enabled.\n- Tests updated and passing.\n\nTest Plan:\n- TDD: update tests first, implement changes.\n- Run: uv run pytest tests/test_run_metadata.py\n\nNotes for Agent:\n- Coordinate with docs update for migration note about old field names.\n","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-30T05:14:20.284151682Z","created_by":"cyou","updated_at":"2025-12-30T05:58:40.204164105Z","closed_at":"2025-12-30T05:58:40.204164105Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-8kte.8","depends_on_id":"mala-8kte.6","type":"blocks","created_at":"2025-12-30T05:14:20.576056367Z","created_by":"daemon"},{"issue_id":"mala-8kte.8","depends_on_id":"mala-8kte","type":"parent-child","created_at":"2025-12-30T05:41:25.034865447Z","created_by":"daemon"}]}
{"id":"mala-8kte.9","title":"Docs: Cerberus review-gate + migration note","description":"Context:\n- Docs still refer to Codex review flow and old CLI flags.\n- Migration requires a note about run metadata field renames.\n\nScope:\n- In: update README.md and docs/quality-hardening-plan.md to describe Cerberus review-gate and new review flag names.\n- In: add migration note about codex_review_log_path/codex_review field rename.\n- In: run rg -i codex over docs/ and update remaining doc references to \"external review\" or \"review-gate\".\n- Out: code changes.\n\nAcceptance Criteria:\n- README and quality-hardening docs reference review-gate and disable-validations=review.\n- Migration note included.\n- No outdated Codex references remain in docs (except historical changelog notes, if any).\n\nTest Plan:\n- N/A (docs).\n","status":"closed","priority":2,"issue_type":"chore","created_at":"2025-12-30T05:14:20.305670256Z","created_by":"cyou","updated_at":"2025-12-30T07:08:00.754304818Z","closed_at":"2025-12-30T07:08:00.754304818Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-8kte.9","depends_on_id":"mala-8kte.6","type":"blocks","created_at":"2025-12-30T05:14:20.596809253Z","created_by":"daemon"},{"issue_id":"mala-8kte.9","depends_on_id":"mala-8kte.2","type":"blocks","created_at":"2025-12-30T05:14:20.617822548Z","created_by":"daemon"},{"issue_id":"mala-8kte.9","depends_on_id":"mala-8kte.1","type":"blocks","created_at":"2025-12-30T05:14:20.639317022Z","created_by":"daemon"},{"issue_id":"mala-8kte.9","depends_on_id":"mala-8kte","type":"parent-child","created_at":"2025-12-30T05:41:25.057193669Z","created_by":"daemon"}]}
{"id":"mala-8ppr","title":"[Review] src/braintrust_integration.py:8: [P3] Missing BRAINTRUST_AVAILABLE in src/infra/tools/__init__.py __all__ (prior review item addressed)","description":"## Review Finding\n\nThis issue was auto-created from a P3 review finding.\n\n**Source issue:** mala-iy6l.4\n**Reviewer:** claude\n**Location:** src/braintrust_integration.py:8\n\n## Details\n\nThe prior Gemini review flagged incomplete exports in `src/infra/tools/__init__.py`. The current diff addresses most locking functions but is unrelated to `BRAINTRUST_AVAILABLE`, which is correctly exported from the braintrust shim. This prior item appears resolved.","notes":"Quality gate failed: Timeout after 60 minutes\nLog: /home/cyou/.claude/projects/-home-cyou-mala/bb2bec6e-f7e4-4677-8a43-f55cf0d8805b.jsonl","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-01T06:35:03.765125997Z","created_by":"cyou","updated_at":"2026-01-01T17:57:41.914527047Z","closed_at":"2026-01-01T17:57:41.914527047Z","close_reason":"Closed","labels":["auto_generated","needs-followup","review_finding","review_finding:7bc2c1b67430","source:mala-iy6l.4"]}
{"id":"mala-8psb","title":"reorganize tests","description":"# Epic: Test Directory Reorganization\n\nReorganize the flat test directory into a category-based hierarchy (unit/integration/e2e) with path-based auto-marking and strict marker enforcement.\n\n## Goals\n- Move ~45 unit tests to tests/unit/ with source-mirroring subdirectories\n- Move ~8 integration tests to tests/integration/\n- Move ~4 e2e tests to tests/e2e/\n- Enable path-based auto-marking via pytest hook\n- Add --strict-markers enforcement\n- Scope conftest.py to appropriate test categories\n\n## Constraints\n- Big-bang migration (single PR)\n- Use git mv to preserve history\n- No compatibility shims (update imports directly)\n- All tests must pass after migration\n\n## Plan Reference\nSee: plans/2026-01-04-test-directory-reorganization-plan.md","status":"closed","priority":2,"issue_type":"epic","created_at":"2026-01-03T19:03:19.859394732Z","created_by":"cyou","updated_at":"2026-01-04T04:21:55.617508245Z","closed_at":"2026-01-04T04:21:55.617508245Z","close_reason":"Closed"}
{"id":"mala-8psb.1","title":"Create directory structure with __init__.py files","description":"## Goal\nCreate the new test directory hierarchy with all required __init__.py files for Python package imports.\n\n## Context\nThis is the foundation task - all file moves depend on directories existing first.\n\n## Scope\n**In**: Create all directories and __init__.py files\n**Out**: No file moves yet\n\n## Changes\nCreate directories:\n- tests/unit/{cli,core,domain,infra,infra/hooks,orchestration,pipeline}/\n- tests/integration/{domain,infra,orchestration,pipeline}/\n- tests/e2e/\n\nCreate __init__.py files (empty, just package markers):\n- tests/unit/__init__.py\n- tests/unit/cli/__init__.py\n- tests/unit/core/__init__.py\n- tests/unit/domain/__init__.py\n- tests/unit/infra/__init__.py\n- tests/unit/infra/hooks/__init__.py\n- tests/unit/orchestration/__init__.py\n- tests/unit/pipeline/__init__.py\n- tests/integration/domain/__init__.py\n- tests/integration/infra/__init__.py\n- tests/integration/orchestration/__init__.py\n- tests/integration/pipeline/__init__.py\n- tests/e2e/__init__.py\n\n## Acceptance Criteria\n- All directories exist\n- All __init__.py files exist\n- Python can import from tests.unit, tests.integration, tests.e2e\n\n## Verification\n```bash\n# Check directories exist\nls -la tests/unit tests/integration tests/e2e\n# Check __init__.py files\nfind tests/unit tests/integration tests/e2e -name \"__init__.py\"\n# Verify imports work\npython -c \"import tests.unit; import tests.integration; import tests.e2e\"\n```\n\n## Rollback\n```bash\nrm -rf tests/unit tests/e2e\n# tests/integration already exists, just remove new subdirs\nrm -rf tests/integration/{domain,infra,orchestration,pipeline}\n```","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-04T03:44:04.136545394Z","created_by":"cyou","updated_at":"2026-01-04T03:51:22.221580834Z","closed_at":"2026-01-04T03:51:22.221580834Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-8psb.1","depends_on_id":"mala-8psb","type":"parent-child","created_at":"2026-01-04T03:44:04.1496614Z","created_by":"daemon"}]}
{"id":"mala-8psb.2","title":"Update pytest configuration for path-based marking","description":"## Goal\nUpdate pytest configuration to enable path-based auto-marking and strict marker enforcement.\n\n## Context\nThis enables the new directory structure to work with pytest's marker system automatically.\n\n## Scope\n**In**: Modify pyproject.toml and tests/conftest.py\n**Out**: No file moves, no conftest splits yet\n\n## Changes\n- `pyproject.toml` — Exists — Add `--strict-markers` to addopts\n  - Current: `addopts = \"-m 'unit or integration' -q --tb=short --no-header --disable-warnings\"`\n  - Updated: `addopts = \"--strict-markers -m 'unit or integration' -q --tb=short --no-header --disable-warnings\"`\n\n- `tests/conftest.py` — Exists — Update pytest_collection_modifyitems hook:\n  ```python\n  def pytest_collection_modifyitems(config: pytest.Config, items: list[pytest.Item]) -\u003e None:\n      \"\"\"Apply markers based on test file path.\"\"\"\n      for item in items:\n          if any(marker in item.keywords for marker in (\"unit\", \"integration\", \"e2e\")):\n              continue\n          # Path-based auto-marking (use item.path for pytest 7+ compatibility)\n          path = str(item.path)\n          if \"/e2e/\" in path:\n              item.add_marker(pytest.mark.e2e)\n          elif \"/integration/\" in path:\n              item.add_marker(pytest.mark.integration)\n          elif \"/unit/\" in path:\n              item.add_marker(pytest.mark.unit)\n          # No default — --strict-markers will fail if unmarked\n  ```\n\n## Acceptance Criteria\n- --strict-markers is enabled in pyproject.toml\n- Path-based marking hook uses item.path (not deprecated item.fspath)\n- Tests in /unit/, /integration/, /e2e/ paths get correct markers\n- Unmarked tests cause pytest collection to fail\n\n## Verification\n```bash\n# Verify pyproject.toml change\ngrep -q \"strict-markers\" pyproject.toml \u0026\u0026 echo \"OK\"\n# Verify hook uses item.path\ngrep -q \"item.path\" tests/conftest.py \u0026\u0026 echo \"OK\"\n# Test path-based marking (after files are moved)\npytest --collect-only -v tests/integration/ 2\u003e\u00261 | grep -q \"integration\"\n```\n\n## Rollback\nRevert pyproject.toml and tests/conftest.py changes via git checkout.\n\n## Notes for Agent\n- Markers unit, integration, e2e are already registered in pyproject.toml (lines 103-107)\n- Keep pytest_configure, make_orchestrator, log_provider fixtures unchanged\n- Don't remove credential copying yet - that's done in the e2e conftest task","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-04T03:44:23.050093526Z","created_by":"cyou","updated_at":"2026-01-04T03:51:18.692577871Z","closed_at":"2026-01-04T03:51:18.692577871Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-8psb.2","depends_on_id":"mala-8psb","type":"parent-child","created_at":"2026-01-04T03:44:23.062161678Z","created_by":"daemon"}]}
{"id":"mala-8psb.3","title":"Move unit tests to tests/unit/","description":"## Goal\nMove all unit tests from the flat tests/ directory to the new tests/unit/ hierarchy with source-mirroring subdirectories.\n\n## Context\nThis is the largest batch of file moves (~45 files). Tests are organized to mirror the src/ structure.\n\n## Scope\n**In**: Move unit test files using git mv\n**Out**: Integration tests, e2e tests, conftest changes\n\n## Changes\nUse `git mv` for all moves to preserve history:\n\n**CLI layer (tests/unit/cli/):**\n- tests/test_cli.py → tests/unit/cli/test_cli.py\n- tests/test_main.py → tests/unit/cli/test_main.py\n\n**Core layer (tests/unit/core/):**\n- tests/test_log_events.py → tests/unit/core/test_log_events.py\n\n**Domain layer (tests/unit/domain/):**\n- tests/test_domain_prompts.py → tests/unit/domain/test_domain_prompts.py\n- tests/test_spec.py → tests/unit/domain/test_spec.py\n- tests/test_spec_workspace.py → tests/unit/domain/test_spec_workspace.py\n- tests/test_epic_scope.py → tests/unit/domain/test_epic_scope.py\n- tests/test_epic_verification_retry.py → tests/unit/domain/test_epic_verification_retry.py\n- tests/test_validation.py → tests/unit/domain/test_validation.py\n- tests/test_validation_config.py → tests/unit/domain/test_validation_config.py\n- tests/test_validation_gating.py → tests/unit/domain/test_validation_gating.py\n- tests/test_run_level_validation.py → tests/unit/domain/test_run_level_validation.py\n- tests/test_quality_gate.py → tests/unit/domain/test_quality_gate.py\n- tests/test_coverage.py → tests/unit/domain/test_coverage.py\n- tests/test_coverage_args.py → tests/unit/domain/test_coverage_args.py\n- tests/test_code_pattern_matcher.py → tests/unit/domain/test_code_pattern_matcher.py\n- tests/test_dangerous_commands.py → tests/unit/domain/test_dangerous_commands.py\n- tests/domain/test_deadlock.py → tests/unit/domain/test_deadlock.py\n\n**Infra layer (tests/unit/infra/):**\n- tests/test_config.py → tests/unit/infra/test_config.py\n- tests/test_config_loader.py → tests/unit/infra/test_config_loader.py\n- tests/test_config_merger.py → tests/unit/infra/test_config_merger.py\n- tests/test_beads_client.py → tests/unit/infra/test_beads_client.py\n- tests/test_event_sink.py → tests/unit/infra/test_event_sink.py\n- tests/test_telemetry.py → tests/unit/infra/test_telemetry.py\n- tests/test_git_utils.py → tests/unit/infra/test_git_utils.py\n- tests/test_session_log_parser.py → tests/unit/infra/test_session_log_parser.py\n- tests/test_cerberus_review.py → tests/unit/infra/test_cerberus_review.py\n- tests/test_cerberus_gate_cli.py → tests/unit/infra/test_cerberus_gate_cli.py\n- tests/test_braintrust_integration.py → tests/unit/infra/test_braintrust_integration.py\n- tests/test_agent_runtime.py → tests/unit/infra/test_agent_runtime.py\n- tests/test_locking_key.py → tests/unit/infra/test_locking_key.py\n- tests/test_lint_cache.py → tests/unit/infra/test_lint_cache.py\n- tests/test_logging_console.py → tests/unit/infra/test_logging_console.py\n- tests/test_lifecycle.py → tests/unit/infra/test_lifecycle.py\n- tests/test_review_output_parser.py → tests/unit/infra/test_review_output_parser.py\n- tests/test_tool_name_extractor.py → tests/unit/infra/test_tool_name_extractor.py\n- tests/test_test_mutex.py → tests/unit/infra/test_test_mutex.py\n- tests/test_worktree.py → tests/unit/infra/test_worktree.py\n\n**Infra hooks (tests/unit/infra/hooks/):**\n- tests/test_hooks.py → tests/unit/infra/hooks/test_hooks.py\n- tests/infra/hooks/test_deadlock_hook.py → tests/unit/infra/hooks/test_deadlock_hook.py\n\n**Orchestration layer (tests/unit/orchestration/):**\n- tests/test_orchestrator.py → tests/unit/orchestration/test_orchestrator.py\n- tests/test_orchestration_helpers.py → tests/unit/orchestration/test_orchestration_helpers.py\n- tests/test_issue_manager.py → tests/unit/orchestration/test_issue_manager.py\n- tests/test_wip_prioritization.py → tests/unit/orchestration/test_wip_prioritization.py\n\n**Pipeline layer (tests/unit/pipeline/):**\n- tests/test_context_pressure_handler.py → tests/unit/pipeline/test_context_pressure_handler.py\n- tests/test_message_stream_processor.py → tests/unit/pipeline/test_message_stream_processor.py\n- tests/test_gate_runner.py → tests/unit/pipeline/test_gate_runner.py\n- tests/test_review_runner.py → tests/unit/pipeline/test_review_runner.py\n- tests/test_issue_execution_coordinator.py → tests/unit/pipeline/test_issue_execution_coordinator.py\n- tests/test_run_metadata.py → tests/unit/pipeline/test_run_metadata.py\n\n**Root unit (tests/unit/):**\n- tests/test_lazy_imports.py → tests/unit/test_lazy_imports.py\n\n## Acceptance Criteria\n- All ~45 unit test files moved to tests/unit/ subdirectories\n- Files organized to mirror src/ structure\n- git history preserved via git mv\n- No test file content changes\n\n## Verification\n```bash\n# Count files in new location\nfind tests/unit -name \"test_*.py\" | wc -l  # Should be ~45\n# Verify old location is empty of unit tests\nls tests/test_*.py 2\u003e/dev/null | wc -l  # Should show remaining integration/e2e tests only\n# Run unit tests\npytest tests/unit/ --collect-only -q\n```\n\n## Rollback\n```bash\ngit checkout HEAD -- tests/\n```","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-04T03:44:53.858591493Z","created_by":"cyou","updated_at":"2026-01-04T04:07:57.558273412Z","closed_at":"2026-01-04T04:07:57.558273412Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-8psb.3","depends_on_id":"mala-8psb","type":"parent-child","created_at":"2026-01-04T03:44:53.869244043Z","created_by":"daemon"},{"issue_id":"mala-8psb.3","depends_on_id":"mala-8psb.1","type":"blocks","created_at":"2026-01-04T03:45:57.796343108Z","created_by":"daemon"}]}
{"id":"mala-8psb.4","title":"Move integration tests to tests/integration/","description":"## Goal\nMove all integration tests to the tests/integration/ hierarchy, removing _integration suffixes from file names.\n\n## Context\nIntegration tests exercise multiple components together. ~8 files to move.\n\n## Scope\n**In**: Move integration test files, rename to remove _integration suffix\n**Out**: Unit tests, e2e tests\n\n## Changes\nUse `git mv` for all moves:\n\n**Domain (tests/integration/domain/):**\n- tests/test_epic_verifier.py → tests/integration/domain/test_epic_verifier.py\n- tests/test_preset_registry.py → tests/integration/domain/test_preset_registry.py\n- tests/test_validation_config_integration.py → tests/integration/domain/test_validation_config.py (remove suffix)\n- tests/integration/test_deadlock_integration.py → tests/integration/domain/test_deadlock.py (remove suffix)\n\n**Infra (tests/integration/infra/):**\n- tests/test_command_runner.py → tests/integration/infra/test_command_runner.py\n- tests/test_lock_integration.py → tests/integration/infra/test_lock.py (remove suffix)\n- tests/test_lock_scripts.py → tests/integration/infra/test_lock_scripts.py\n\n**Pipeline (tests/integration/pipeline/):**\n- tests/test_agent_session_runner.py → tests/integration/pipeline/test_agent_session_runner.py\n\n## Acceptance Criteria\n- All 8 integration test files moved to tests/integration/ subdirectories\n- _integration suffixes removed from file names\n- git history preserved\n- No test file content changes (except marker removal if explicit @pytest.mark.integration exists)\n\n## Verification\n```bash\n# Count files in new location\nfind tests/integration -name \"test_*.py\" | wc -l  # Should be 8\n# Verify no _integration suffix in names\nfind tests/integration -name \"*_integration.py\" | wc -l  # Should be 0\n# Run integration tests\npytest tests/integration/ --collect-only -q\n```\n\n## Rollback\n```bash\ngit checkout HEAD -- tests/\n```\n\n## Notes for Agent\n- The existing tests/integration/__init__.py should be preserved\n- Remove explicit @pytest.mark.integration decorators if present (path-based marking handles it)","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-04T03:45:08.485015479Z","created_by":"cyou","updated_at":"2026-01-04T03:57:27.291649198Z","closed_at":"2026-01-04T03:57:27.291649198Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-8psb.4","depends_on_id":"mala-8psb","type":"parent-child","created_at":"2026-01-04T03:45:08.497420899Z","created_by":"daemon"},{"issue_id":"mala-8psb.4","depends_on_id":"mala-8psb.1","type":"blocks","created_at":"2026-01-04T03:45:57.81185982Z","created_by":"daemon"}]}
{"id":"mala-8psb.5","title":"Move e2e tests and create e2e conftest","description":"## Goal\nMove all e2e tests to tests/e2e/, move claude_auth.py helper, create e2e-specific conftest.py with credential copying logic.\n\n## Context\nE2E tests require real Claude CLI authentication. This task consolidates auth-related setup into the e2e directory.\n\n## Scope\n**In**: Move e2e tests, create e2e/conftest.py, update root conftest.py\n**Out**: Unit tests, integration tests\n\n## Changes\n\n**File Moves (git mv):**\n- tests/test_e2e.py → tests/e2e/test_e2e.py\n- tests/test_epic_verifier_sdk_e2e.py → tests/e2e/test_epic_verifier_sdk.py (remove suffix)\n- tests/test_review_gate_e2e.py → tests/e2e/test_review_gate.py (remove suffix)\n- tests/test_lock_sdk_integration.py → tests/e2e/test_lock_sdk.py (SDK tests are e2e)\n- tests/claude_auth.py → tests/e2e/claude_auth.py\n\n**Create tests/e2e/conftest.py — New:**\nExtract from root conftest.py:\n```python\n\"\"\"E2E test configuration - Claude auth and credential setup.\"\"\"\n\nfrom __future__ import annotations\n\nimport os\nimport shutil\nfrom pathlib import Path\n\n\ndef pytest_configure(config) -\u003e None:\n    \"\"\"Copy OAuth credentials to test config dir for SDK e2e tests.\"\"\"\n    test_claude_dir = Path(\"/tmp/mala-test-claude\")\n    test_claude_dir.mkdir(parents=True, exist_ok=True)\n    os.environ[\"CLAUDE_CONFIG_DIR\"] = str(test_claude_dir)\n    \n    real_credentials = Path.home() / \".claude\" / \".credentials.json\"\n    if real_credentials.exists():\n        shutil.copy2(real_credentials, test_claude_dir / \".credentials.json\")\n```\n\n**Modify tests/conftest.py — Exists:**\n- Remove credential copying logic (lines 50-57)\n- Keep: pytest_configure env setup, pytest_collection_modifyitems, make_orchestrator, log_provider\n\n**Update imports in moved e2e tests:**\n- Change `from tests.claude_auth import ...` → `from tests.e2e.claude_auth import ...`\n\n## Acceptance Criteria\n- All 4 e2e test files moved to tests/e2e/\n- _e2e suffixes removed from file names\n- claude_auth.py moved to tests/e2e/\n- tests/e2e/conftest.py created with credential copying\n- Root conftest.py no longer has credential copying\n- Import paths updated in e2e tests\n\n## Verification\n```bash\n# Count files in new location\nfind tests/e2e -name \"test_*.py\" | wc -l  # Should be 4\n# Verify claude_auth.py moved\ntest -f tests/e2e/claude_auth.py \u0026\u0026 echo \"OK\"\n# Verify imports work\npython -c \"from tests.e2e.claude_auth import is_claude_cli_available\"\n# Verify conftest exists\ntest -f tests/e2e/conftest.py \u0026\u0026 echo \"OK\"\n# Run e2e collection (won't run tests, just collect)\npytest tests/e2e/ --collect-only -q\n```\n\n## Rollback\n```bash\ngit checkout HEAD -- tests/\n```\n\n## Notes for Agent\n- Check tests/test_epic_verifier_sdk_e2e.py and tests/test_review_gate_e2e.py for imports of tests.claude_auth\n- The credential copying is specifically for SDK tests that need real Claude auth","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-04T03:45:28.577800619Z","created_by":"cyou","updated_at":"2026-01-04T03:58:29.160932289Z","closed_at":"2026-01-04T03:58:29.160932289Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-8psb.5","depends_on_id":"mala-8psb","type":"parent-child","created_at":"2026-01-04T03:45:28.589614633Z","created_by":"daemon"},{"issue_id":"mala-8psb.5","depends_on_id":"mala-8psb.1","type":"blocks","created_at":"2026-01-04T03:45:57.820086364Z","created_by":"daemon"}]}
{"id":"mala-8psb.6","title":"Cleanup: delete empty directories and verify migration","description":"## Goal\nDelete empty source directories and verify the complete migration passes all tests.\n\n## Context\nThis is the final cleanup task. After all files are moved, the old directories should be empty and can be deleted.\n\n## Scope\n**In**: Delete empty dirs, run full test verification\n**Out**: Any code changes\n\n## Changes\n\n**Delete directories:**\n- tests/domain/ (including __init__.py)\n- tests/infra/ (including hooks/__init__.py, __init__.py)\n\n**Keep:**\n- tests/integration/__init__.py (directory was restructured, not deleted)\n\n## Acceptance Criteria\n- tests/domain/ directory deleted\n- tests/infra/ directory deleted\n- No test files left at tests/ root level (except conftest.py)\n- All tests pass with parallel execution\n- Marker counts match expectations:\n  - ~45 unit tests\n  - ~8 integration tests\n  - ~4 e2e tests\n\n## Verification [integration-path-test]\n```bash\n# Verify directories deleted\ntest ! -d tests/domain \u0026\u0026 echo \"domain deleted\"\ntest ! -d tests/infra \u0026\u0026 echo \"infra deleted\"\n\n# Verify no stray test files at root\nls tests/test_*.py 2\u003e/dev/null \u0026\u0026 echo \"ERROR: stray files\" || echo \"OK: no stray files\"\n\n# Verify test counts\npytest --collect-only -m unit -q 2\u003e/dev/null | tail -1\npytest --collect-only -m integration -q 2\u003e/dev/null | tail -1\npytest --collect-only -m e2e -q 2\u003e/dev/null | tail -1\n\n# Run full test suite with parallel execution\npytest -n auto\n\n# Verify coverage still works\npytest --cov=src -q\n```\n\n## Rollback\n```bash\ngit checkout HEAD -- tests/\n```\n\n## Notes for Agent\n- This task should be done last, after all file moves are complete\n- If any tests fail, investigate before deleting directories\n- The tests/integration/__init__.py should remain (it was there before)","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-04T03:45:46.21896841Z","created_by":"cyou","updated_at":"2026-01-04T04:19:34.248063303Z","closed_at":"2026-01-04T04:19:34.248063303Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-8psb.6","depends_on_id":"mala-8psb","type":"parent-child","created_at":"2026-01-04T03:45:46.219572737Z","created_by":"daemon"},{"issue_id":"mala-8psb.6","depends_on_id":"mala-8psb.2","type":"blocks","created_at":"2026-01-04T03:45:57.828326207Z","created_by":"daemon"},{"issue_id":"mala-8psb.6","depends_on_id":"mala-8psb.3","type":"blocks","created_at":"2026-01-04T03:45:57.83658276Z","created_by":"daemon"},{"issue_id":"mala-8psb.6","depends_on_id":"mala-8psb.4","type":"blocks","created_at":"2026-01-04T03:45:57.844832144Z","created_by":"daemon"},{"issue_id":"mala-8psb.6","depends_on_id":"mala-8psb.5","type":"blocks","created_at":"2026-01-04T03:45:57.853099657Z","created_by":"daemon"}]}
{"id":"mala-8psb.7","title":"[Review] 1 non-blocking finding from mala-8psb.2","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-8psb.2\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Fail when a test is unmarked and not in a path bucket\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** tests/conftest.py:64-75\n\nWith the new logic, a test that is outside `/unit/`, `/integration/`, or `/e2e/` and has no explicit marker stays unmarked. `--strict-markers` does **not** error on unmarked tests, so those tests are simply deselected by `-m 'unit or integration'` and never run. This is a silent miss and contradicts the goal that unmarked tests should fail collection. Consider raising a `pytest.fail` (or `pytest.UsageError`) in the `else` case when no path-based marker applies.\n\n---\n\n\u003c!-- fp:6369225793ef0b6b --\u003e\n\u003c!-- review_finding:67f6110ba5c1 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-04T03:51:18.724278273Z","created_by":"cyou","updated_at":"2026-01-04T04:01:32.604931606Z","closed_at":"2026-01-04T04:01:32.604931606Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-8psb.2"],"dependencies":[{"issue_id":"mala-8psb.7","depends_on_id":"mala-8psb","type":"parent-child","created_at":"2026-01-04T03:51:18.72467222Z","created_by":"daemon"}]}
{"id":"mala-8psb.8","title":"[Review] 1 non-blocking finding from mala-8psb.1","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-8psb.1\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Missing __init__.py in tests/ root directory\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** tests/__init__.py:1\n\nThe goal is to enable Python package imports (e.g., `import tests.unit`), but `tests/__init__.py` is missing from the diff and the current directory structure. While Python 3 supports implicit namespace packages, explicitly adding `__init__.py` to the `tests/` root ensures consistent behavior with test runners and tools like `mypy` or `pytest` when they expect the `tests` directory to be a regular package.\n\n---\n\n\u003c!-- fp:b12eda47a2dd9ac9 --\u003e\n\u003c!-- review_finding:e28f28ce940f --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-04T03:51:22.253542834Z","created_by":"cyou","updated_at":"2026-01-04T04:00:18.317056501Z","closed_at":"2026-01-04T04:00:18.317056501Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-8psb.1"],"dependencies":[{"issue_id":"mala-8psb.8","depends_on_id":"mala-8psb","type":"parent-child","created_at":"2026-01-04T03:51:22.253946871Z","created_by":"daemon"}]}
{"id":"mala-8psb.9","title":"[Review] 1 non-blocking finding from mala-8psb.7","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-8psb.7\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Normalize path separators before bucket check\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** tests/conftest.py:59-66\n\nOn Windows, `item.path` uses backslashes, so checks like `\"/unit/\" in path` never match. With the new `pytest.fail` in the `else`, any unmarked test under `tests\\unit\\`, `tests\\integration\\`, or `tests\\e2e\\` will now fail collection even though it’s in a valid bucket. That’s a regression for Windows runs and will force all tests to add explicit markers. Consider normalizing the path (e.g., `Path(item.path).parts` or `path.replace(os.sep, \"/\")`) before the bucket checks.\n\n---\n\n\u003c!-- fp:dc2197b0426ff139 --\u003e\n\u003c!-- review_finding:bb977527826c --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-04T04:01:32.63563123Z","created_by":"cyou","updated_at":"2026-01-04T04:06:09.101549074Z","closed_at":"2026-01-04T04:06:09.101549074Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-8psb.7"],"dependencies":[{"issue_id":"mala-8psb.9","depends_on_id":"mala-8psb","type":"parent-child","created_at":"2026-01-04T04:01:32.636029257Z","created_by":"daemon"}]}
{"id":"mala-8td","title":"Log codex review session path in verbose mode","description":"When running in verbose mode, the orchestrator should capture and log the path to the codex session log file that codex creates during the review.\n\nCodex stores session logs in `~/.codex/sessions/YYYY/MM/DD/{session-id}.jsonl`. The orchestrator should:\n1. Capture the session ID or log path from codex's output\n2. Store the path in the runs JSON under the issue's `codex_review_log_path` field\n\nThis enables debugging by pointing to the full codex conversation/session log.","acceptance_criteria":"- In verbose mode, the codex session log path is captured from the codex run\n- The runs JSON includes `codex_review_log_path` field pointing to the codex session JSONL file\n- In non-verbose mode, the path is not captured (to avoid overhead)\n- Path format: `~/.codex/sessions/YYYY/MM/DD/{session-id}.jsonl`","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-27T22:27:51.105697172Z","updated_at":"2025-12-28T04:58:51.036936421Z","closed_at":"2025-12-28T04:58:51.036936421Z","close_reason":"Closed"}
{"id":"mala-8tk1","title":"beads issues created from non blocking review feedback, should all go into a single issue instead of multiple","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T04:57:38.277041862Z","created_by":"cyou","updated_at":"2026-01-03T05:59:41.854180198Z","closed_at":"2026-01-03T05:59:41.854180198Z","close_reason":"Closed"}
{"id":"mala-92r6","title":"Epic: Unified Code Review Configuration","description":"## Summary\nConsolidate all code review settings into a unified `code_review` config block in `mala.yaml`, supporting:\n- Per-issue reviews via `session_end` trigger\n- Cumulative reviews via `epic_completion` trigger  \n- Cumulative reviews via `run_end` trigger (NEW)\n\n## Goals\n- Same `CodeReviewConfig` YAML shape at all trigger levels\n- Move CLI options (reviewer_type, max_retries, cerberus args) into config\n- Baseline tracking for cumulative reviews (`since_run_start` or `since_last_review`)\n- Explicit opt-in (`enabled: true`) - breaking change from implicit reviews\n\n## Key Decisions\n1. Code review is a trigger action, not a CommandKind\n2. No config inheritance - each trigger has full config\n3. `failure_mode` for execution_error, `finding_threshold` for findings\n4. Per-trigger-instance baseline tracking (e.g., `epic_completion:\u003cepic_id\u003e`)\n5. Baseline advances on review completion, not on execution_error\n\n## Plan\nSee: plans/2026-01-09-cumulative-code-review-plan.md\n\n## Acceptance Criteria\n- [ ] Unified `code_review` config block in triggers\n- [ ] Config loading with validation rules\n- [ ] CumulativeReviewRunner with baseline tracking\n- [ ] run_end trigger implemented\n- [ ] CLI flags deprecated with warnings\n- [ ] Documentation updated","status":"closed","priority":1,"issue_type":"epic","created_at":"2026-01-09T02:23:15.331648929Z","created_by":"cyou","updated_at":"2026-01-10T01:44:50.466969522Z","closed_at":"2026-01-10T01:44:50.466969522Z","close_reason":"Closed"}
{"id":"mala-92r6.1","title":"[T001] Add CodeReviewConfig + CerberusConfig + RunEndTriggerConfig dataclasses","description":"## Goal\nAdd new frozen dataclass types for unified code review configuration.\n\n## Context\nThese types define the schema for code review settings that can be attached to any trigger (session_end, epic_completion, run_end). Same shape at all trigger levels.\n\n## Scope\n- **In**: Define dataclasses with proper defaults and validation\n- **Out**: Config loading/parsing (T003), wiring to triggers\n\n## Changes\n- `src/domain/validation/config.py` — Exists — Add new dataclasses and enum values\n\n## Code to Add\n\n### 1. Add RUN_END to TriggerType enum (line ~42)\n```python\nclass TriggerType(Enum):\n    EPIC_COMPLETION = \"epic_completion\"\n    SESSION_END = \"session_end\"\n    PERIODIC = \"periodic\"\n    RUN_END = \"run_end\"  # NEW: When mala run completes\n```\n\n### 2. Add CerberusConfig dataclass\n```python\n@dataclass(frozen=True)\nclass CerberusConfig:\n    \"\"\"Cerberus-specific settings.\"\"\"\n    timeout: int = 300\n    spawn_args: tuple[str, ...] = field(default_factory=tuple)\n    wait_args: tuple[str, ...] = field(default_factory=tuple)\n    env: tuple[tuple[str, str], ...] = field(default_factory=tuple)\n```\n\n### 3. Add CodeReviewConfig dataclass\n```python\n@dataclass(frozen=True)\nclass CodeReviewConfig:\n    \"\"\"Unified code review configuration.\"\"\"\n    enabled: bool = False\n    reviewer_type: Literal[\"cerberus\", \"agent_sdk\"] = \"cerberus\"\n    failure_mode: FailureMode = FailureMode.CONTINUE\n    max_retries: int = 3\n    finding_threshold: Literal[\"P0\", \"P1\", \"P2\", \"P3\", \"none\"] = \"none\"\n    baseline: Literal[\"since_run_start\", \"since_last_review\"] | None = None\n    cerberus: CerberusConfig | None = None\n```\n\n### 4. Update BaseTriggerConfig\n```python\n@dataclass(frozen=True, kw_only=True)\nclass BaseTriggerConfig:\n    failure_mode: FailureMode\n    commands: tuple[TriggerCommandRef, ...]\n    max_retries: int | None = None\n    code_review: CodeReviewConfig | None = None  # NEW\n```\n\n### 5. Add RunEndTriggerConfig\n```python\n@dataclass(frozen=True, kw_only=True)\nclass RunEndTriggerConfig(BaseTriggerConfig):\n    \"\"\"Configuration for run end triggers.\"\"\"\n    fire_on: FireOn = FireOn.SUCCESS\n```\n\n### 6. Update ValidationTriggersConfig\n```python\n@dataclass(frozen=True)\nclass ValidationTriggersConfig:\n    epic_completion: EpicCompletionTriggerConfig | None = None\n    session_end: SessionEndTriggerConfig | None = None\n    periodic: PeriodicTriggerConfig | None = None\n    run_end: RunEndTriggerConfig | None = None  # NEW\n```\n\n## Acceptance Criteria\n- [ ] TriggerType enum has RUN_END value\n- [ ] CerberusConfig dataclass with tuple[str, ...] for args\n- [ ] CodeReviewConfig dataclass with all fields including finding_threshold\n- [ ] BaseTriggerConfig has optional code_review field\n- [ ] RunEndTriggerConfig dataclass with fire_on field\n- [ ] ValidationTriggersConfig has optional run_end field\n- [ ] All dataclasses use frozen=True\n- [ ] Unit tests verify defaults and type constraints\n\n## Verification\n```bash\nuv run pytest tests/unit/domain/validation/ -k \"config\" -v\nuvx ty check src/domain/validation/config.py\n```\n\n## Notes for Agent\n- Follow existing dataclass patterns in config.py\n- Use tuple[str, ...] not list[str] for immutability\n- FailureMode and FireOn enums already exist\n- Place new classes near related existing classes for readability","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-09T02:23:30.545778734Z","created_by":"cyou","updated_at":"2026-01-09T07:05:19.439392219Z","closed_at":"2026-01-09T07:05:19.439392219Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-92r6.1","depends_on_id":"mala-92r6","type":"parent-child","created_at":"2026-01-09T02:23:30.546376151Z","created_by":"cyou"}]}
{"id":"mala-92r6.10","title":"[T010] [integration-path-test] Orchestrator run_start_commit capture + run_end trigger skeleton","description":"## Goal\nAdd skeleton for capturing run_start_commit in orchestrator and firing run_end trigger, with failing integration test.\n\n## Context\nThis is the integration-path-test for orchestrator trigger firing. The orchestrator needs to:\n1. Capture HEAD at run start\n2. Fire run_end trigger after all processing\n\n## Scope\n- **In**: Add skeleton methods, write failing integration test\n- **Out**: Full implementation (T011)\n\n## Changes\n- `src/orchestration/orchestrator.py` — Exists — Add skeleton methods\n- `tests/integration/orchestration/test_run_end_trigger.py` — **New** — Integration test\n\n## Skeleton Work\n\n1. In orchestrator.py:\n```python\nasync def run(self) -\u003e tuple[int, int]:\n    # At start, capture run_start_commit\n    await self._capture_run_start_commit()\n    \n    # ... existing processing loop ...\n    \n    # After all issues processed:\n    await self._fire_run_end_trigger(success_count, total_count)\n    \n    return success_count, total_count\n\nasync def _capture_run_start_commit(self) -\u003e None:\n    raise NotImplementedError(\"run_start_commit capture not implemented\")\n\nasync def _fire_run_end_trigger(self, success_count: int, total_count: int) -\u003e None:\n    raise NotImplementedError(\"run_end trigger not implemented\")\n```\n\n## Integration Test\n```python\nasync def test_orchestrator_fires_run_end_trigger():\n    \"\"\"Integration: Orchestrator.run() fires run_end trigger after processing.\"\"\"\n    # Setup: Orchestrator with config including run_end trigger\n    # Act: Call orchestrator.run()\n    # Assert: run_end trigger was invoked (will raise NotImplementedError)\n```\n\n## Acceptance Criteria\n- [ ] _capture_run_start_commit() stub exists\n- [ ] _fire_run_end_trigger() stub exists\n- [ ] Methods called at correct points in run() flow\n- [ ] Integration test fails with NotImplementedError (not import error)\n- [ ] Project type-checks successfully\n\n## Verification\n```bash\nuvx ty check src/orchestration/orchestrator.py\nuv run pytest tests/integration/orchestration/test_run_end_trigger.py -v\n# Should fail with NotImplementedError\n```\n\n## Notes for Agent\n- Check existing orchestrator structure before adding methods\n- run() method structure varies - understand control flow first\n- Test must use real wiring, not direct method calls","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-09T02:25:51.250653882Z","created_by":"cyou","updated_at":"2026-01-09T07:24:21.136633717Z","closed_at":"2026-01-09T07:24:21.136633717Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-92r6.10","depends_on_id":"mala-92r6","type":"parent-child","created_at":"2026-01-09T02:25:51.251343078Z","created_by":"cyou"},{"issue_id":"mala-92r6.10","depends_on_id":"mala-92r6.4","type":"blocks","created_at":"2026-01-09T02:27:20.073223497Z","created_by":"cyou"}]}
{"id":"mala-92r6.11","title":"[T011] Implement orchestrator run_start_commit capture + run_end trigger","description":"## Goal\nImplement run_start_commit capture and run_end trigger firing in the orchestrator.\n\n## Context\nMakes the T010 integration test pass. Implements:\n1. HEAD capture at run start\n2. run_end trigger firing with fire_on filtering\n\n## Scope\n- **In**: Implement both methods, add unit tests\n- **Out**: CumulativeReviewRunner invocation (T012)\n\n## Changes\n- `src/orchestration/orchestrator.py` — Exists — Implement methods\n- `tests/unit/orchestration/test_orchestrator.py` — Exists — Add unit tests\n\n## Implementation\n\n### _capture_run_start_commit()\n```python\nasync def _capture_run_start_commit(self) -\u003e None:\n    \"\"\"Capture HEAD at run start for baseline tracking.\"\"\"\n    if self.run_metadata.run_start_commit is not None:\n        # Already captured (resumed run)\n        return\n    \n    head = await get_current_head(self.repo_path)\n    self.run_metadata.run_start_commit = head\n    await self._persist_run_metadata()\n```\n\n### _fire_run_end_trigger()\n```python\nasync def _fire_run_end_trigger(self, success_count: int, total_count: int) -\u003e None:\n    \"\"\"Fire run_end trigger based on fire_on setting.\"\"\"\n    trigger_config = self.config.validation_triggers.run_end\n    if trigger_config is None:\n        return\n    \n    # Check fire_on filter\n    all_success = (success_count == total_count)\n    any_failure = (success_count \u003c total_count)\n    \n    should_fire = (\n        (trigger_config.fire_on == FireOn.SUCCESS and all_success) or\n        (trigger_config.fire_on == FireOn.FAILURE and any_failure) or\n        (trigger_config.fire_on == FireOn.BOTH)\n    )\n    \n    if not should_fire:\n        self._logger.debug(f\"Skipping run_end trigger (fire_on={trigger_config.fire_on})\")\n        return\n    \n    # Fire trigger via run_coordinator\n    await self.run_coordinator.handle_trigger(\n        TriggerType.RUN_END,\n        trigger_config,\n        self.run_metadata,\n    )\n```\n\n## Edge Cases\n- Resumed run: Don't recapture run_start_commit\n- No run_end trigger configured: Skip silently\n- fire_on filtering: success, failure, both\n\n## Acceptance Criteria\n- [ ] run_start_commit captured at run start\n- [ ] run_start_commit preserved on resume\n- [ ] run_end trigger fires with correct fire_on filtering\n- [ ] T010 integration test now passes\n- [ ] Unit tests cover all fire_on modes\n\n## Verification\n```bash\nuv run pytest tests/unit/orchestration/test_orchestrator.py -v -k \"run_end or run_start\"\nuv run pytest tests/integration/orchestration/test_run_end_trigger.py -v\nuvx ty check src/orchestration/orchestrator.py\n```\n\n## Notes for Agent\n- Check RunMetadata persistence pattern\n- Ensure fire_on enum values match existing FireOn\n- Don't forget to persist metadata after capture","notes":"Quality gate failed: External review failed: src/orchestration/orchestrator.py:1122: [P1] Fire run_end trigger via run_coordinator with run metadata context; src/orchestration/orchestrator.py:1142: [P1] run_end trigger logic risks non-execution; diverges from plan\nLog: /home/cyou/.claude/projects/-home-cyou-mala/b98a2aaa-cccb-4f39-ab10-da3f20e0f680.jsonl","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-09T02:26:05.418969745Z","created_by":"cyou","updated_at":"2026-01-09T16:17:51.869705164Z","closed_at":"2026-01-09T16:17:51.869705164Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-92r6.11","depends_on_id":"mala-92r6","type":"parent-child","created_at":"2026-01-09T02:26:05.419657911Z","created_by":"cyou"},{"issue_id":"mala-92r6.11","depends_on_id":"mala-92r6.10","type":"blocks","created_at":"2026-01-09T02:27:20.191904538Z","created_by":"cyou"}]}
{"id":"mala-92r6.12","title":"[T012] Integrate CumulativeReviewRunner with RunCoordinator trigger handling","description":"## Goal\nWire CumulativeReviewRunner into RunCoordinator so triggers invoke code review when configured.\n\n## Context\nRunCoordinator handles validation triggers via `run_trigger_validation()`. When a trigger has code_review.enabled, it must invoke CumulativeReviewRunner AFTER validation commands complete.\n\n## Scope\n- **In**: Update RunCoordinator to call CumulativeReviewRunner, unit tests\n- **Out**: N/A - this completes the wiring\n\n## Changes\n- `src/pipeline/run_coordinator.py` — Exists — Add code review invocation\n- `src/orchestration/orchestration_wiring.py` — Exists — Update wiring to inject CumulativeReviewRunner\n- `tests/unit/pipeline/test_run_coordinator.py` — Exists — Add unit tests\n\n## Implementation Details\n\n### 1. Update _get_trigger_config() to handle RUN_END (line ~1073)\n```python\ndef _get_trigger_config(\n    self, triggers_config: ValidationTriggersConfig, trigger_type: TriggerType\n) -\u003e BaseTriggerConfig | None:\n    if trigger_type == TriggerType.EPIC_COMPLETION:\n        return triggers_config.epic_completion\n    elif trigger_type == TriggerType.SESSION_END:\n        return triggers_config.session_end\n    elif trigger_type == TriggerType.PERIODIC:\n        return triggers_config.periodic\n    elif trigger_type == TriggerType.RUN_END:  # NEW\n        return triggers_config.run_end\n    return None\n```\n\n### 2. Add code_review handling to _run_trigger_validation_loop()\nAfter validation commands complete successfully, check for code_review config and invoke:\n```python\n# After command execution loop, before returning success:\nif trigger_config.code_review and trigger_config.code_review.enabled:\n    review_result = await self._run_code_review(\n        trigger_type, trigger_config.code_review, context\n    )\n    # Handle review result per code_review.failure_mode\n```\n\n### 3. Add _run_code_review() method\n```python\nasync def _run_code_review(\n    self,\n    trigger_type: TriggerType,\n    config: CodeReviewConfig,\n    context: dict[str, Any],\n) -\u003e CumulativeReviewResult:\n    \"\"\"Run code review for a trigger.\n    \n    Delegates to CumulativeReviewRunner with appropriate context.\n    \"\"\"\n    return await self._cumulative_review_runner.run_review(\n        trigger_type=trigger_type,\n        config=config,\n        run_metadata=self._run_metadata,\n        repo_path=self._repo_path,\n        interrupt_event=self._interrupt_event,\n        issue_id=context.get(\"issue_id\"),\n        epic_id=context.get(\"epic_id\"),\n    )\n```\n\n### 4. Update constructor for DI\n```python\ndef __init__(\n    self,\n    ...\n    cumulative_review_runner: CumulativeReviewRunner | None = None,  # NEW\n) -\u003e None:\n    ...\n    self._cumulative_review_runner = cumulative_review_runner\n```\n\n### 5. Update wiring in orchestration_wiring.py\nInject CumulativeReviewRunner when constructing RunCoordinator.\n\n## Wiring Map\n```\nTrigger fired → RunCoordinator.handle_trigger()\n  → _run_trigger_validation_loop()\n    → Execute validation commands\n    → Check trigger_config.code_review.enabled\n    → CumulativeReviewRunner.run_review()\n    → Apply code_review.failure_mode to result\n```\n\n## Fixer Session Guard\nWhen running in a fixer session:\n- Skip code_review entirely (prevent loops)\n- Only run validation commands\n\n## Acceptance Criteria\n- [ ] _get_trigger_config() handles TriggerType.RUN_END\n- [ ] code_review runs AFTER validation commands succeed\n- [ ] CumulativeReviewRunner dependency injected via constructor\n- [ ] orchestration_wiring.py updated for new dependency\n- [ ] Fixer sessions skip code_review (prevent loops)\n- [ ] failure_mode applied to review result\n- [ ] All integration tests pass\n\n## Verification\n```bash\nuv run pytest tests/unit/pipeline/test_run_coordinator.py -v\nuv run pytest tests/integration/orchestration/test_cumulative_review.py -v\nuvx ty check src/pipeline/run_coordinator.py\n```\n\n## Notes for Agent\n- Code review runs after ALL validation commands complete\n- Review failure_mode is separate from command failure_mode\n- Check is_fixer_session flag to skip review in fixer sessions\n- Context dict contains issue_id (session_end) or epic_id (epic_completion)","notes":"Quality gate failed: External review failed: src/pipeline/run_coordinator.py:64: [P1] RunCoordinator.__init__ not updated for new dependencies\nLog: /home/cyou/.claude/projects/-home-cyou-mala/e44714ad-3d70-4424-8964-0b0da033ebf7.jsonl","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-09T02:26:23.373159009Z","created_by":"cyou","updated_at":"2026-01-09T19:18:48.61968001Z","closed_at":"2026-01-09T19:18:48.61968001Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-92r6.12","depends_on_id":"mala-92r6","type":"parent-child","created_at":"2026-01-09T02:26:23.373745296Z","created_by":"cyou"},{"issue_id":"mala-92r6.12","depends_on_id":"mala-92r6.6","type":"blocks","created_at":"2026-01-09T02:27:20.304029286Z","created_by":"cyou"},{"issue_id":"mala-92r6.12","depends_on_id":"mala-92r6.11","type":"blocks","created_at":"2026-01-09T02:27:20.415915545Z","created_by":"cyou"}]}
{"id":"mala-92r6.13","title":"[T013] Add CLI deprecation warnings + versioned behavior detection","description":"## Goal\nAdd deprecation warnings for CLI review flags and implement versioned behavior detection.\n\n## Context\nCLI flags like --reviewer-type are being deprecated in favor of config. During transition:\n- CLI flags continue to work with deprecation warning\n- Config takes precedence over CLI\n- Legacy top-level reviewer_type emits deprecation warning\n\n## Scope\n- **In**: Deprecation warnings, precedence logic, unit tests\n- **Out**: Config removal (next major version)\n\n## Changes\n- `src/cli/cli.py` or `src/cli/main.py` — Exists — Add deprecation logic\n- `tests/unit/cli/test_deprecation.py` — **New** — Unit tests\n\n## Deprecation Logic\n\n```python\ndef _check_legacy_review_config(config: MalaConfig, cli_args: Namespace) -\u003e None:\n    \"\"\"Check for deprecated review configuration and emit warnings.\"\"\"\n    \n    # CLI flag deprecation\n    if cli_args.reviewer_type is not None:\n        warnings.warn(\n            \"DEPRECATION: --reviewer-type is deprecated. \"\n            \"Use validation_triggers.session_end.code_review.reviewer_type in config.\",\n            DeprecationWarning,\n        )\n    \n    # Legacy config deprecation\n    if config.reviewer_type is not None and not _has_new_code_review_config(config):\n        logger.warning(\n            \"DEPRECATION: Top-level 'reviewer_type' is deprecated for per-issue reviews. \"\n            \"Add 'validation_triggers.session_end.code_review.enabled: true' to preserve current behavior. \"\n            \"Legacy implicit reviews will be removed in the next major version.\"\n        )\n```\n\n## Precedence Rules\n1. `validation_triggers.*.code_review` (if present) → Use new config\n2. CLI `--reviewer-type` (if set) → Use CLI with deprecation warning\n3. Top-level `reviewer_type` in config → Use legacy with deprecation warning\n4. None of above → No reviews\n\n## Acceptance Criteria\n- [ ] CLI flags emit DeprecationWarning when used\n- [ ] Legacy config emits log warning\n- [ ] Config takes precedence over CLI\n- [ ] Legacy behavior preserved during transition\n- [ ] Unit tests verify warnings and precedence\n\n## Verification\n```bash\nuv run pytest tests/unit/cli/test_deprecation.py -v\nuv run mala run --reviewer-type cerberus 2\u003e\u00261 | grep -i deprecat\n```\n\n## Notes for Agent\n- Check existing CLI argument handling\n- Ensure warnings are logged, not just printed\n- Test both CLI and config deprecation paths","notes":"Quality gate failed: External review failed: src/orchestration/factory.py:628: [P1] Potential TypeError/UnboundLocalError in ConfigMissingError handler\nLog: /home/cyou/.claude/projects/-home-cyou-mala/94325b59-5d80-4023-bec8-e19ad359a3eb.jsonl","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-09T02:26:43.936707994Z","created_by":"cyou","updated_at":"2026-01-09T21:54:19.847477611Z","closed_at":"2026-01-09T21:54:19.847477611Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-92r6.13","depends_on_id":"mala-92r6","type":"parent-child","created_at":"2026-01-09T02:26:43.937310941Z","created_by":"cyou"},{"issue_id":"mala-92r6.13","depends_on_id":"mala-92r6.3","type":"blocks","created_at":"2026-01-09T02:27:20.532278859Z","created_by":"cyou"}]}
{"id":"mala-92r6.14","title":"[T014] Update validation.md + cli-reference.md documentation","description":"## Goal\nUpdate documentation to reflect unified code review configuration.\n\n## Context\nDocumentation needs to cover:\n- New code_review config block structure\n- Trigger types (session_end, epic_completion, run_end)\n- Baseline modes and tracking\n- Migration from legacy config\n\n## Scope\n- **In**: Update existing docs, add examples\n- **Out**: Tutorials or guides (future work)\n\n## Changes\n- `docs/validation.md` — Exists — Add code_review section\n- `docs/cli-reference.md` — Exists — Document deprecated flags\n\n## Documentation Sections\n\n### validation.md additions\n\n1. **Code Review Configuration** section:\n   - CodeReviewConfig structure and fields\n   - CerberusConfig nested options\n   - Baseline modes (since_run_start, since_last_review)\n\n2. **Trigger Types** table:\n   | Trigger | When | Baseline |\n   |---------|------|----------|\n   | session_end | After agent session | Per-issue commits |\n   | epic_completion | When epic completes | Cumulative since baseline |\n   | run_end | After all issues | Cumulative since baseline |\n\n3. **Example configurations** for each trigger type\n\n4. **Migration guide**:\n   - Before/After config examples\n   - What happens if you do nothing\n\n### cli-reference.md additions\n\n1. **Deprecated Flags** section:\n   - --reviewer-type (deprecated)\n   - Migration instructions\n\n## Example Content\n\n```yaml\n# Full example for validation.md\nvalidation_triggers:\n  session_end:\n    commands:\n      - ref: test\n    failure_mode: continue\n    code_review:\n      enabled: true\n      reviewer_type: cerberus\n      failure_mode: remediate\n      finding_threshold: P1\n      max_retries: 3\n      cerberus:\n        timeout: 300\n\n  epic_completion:\n    commands: []\n    failure_mode: continue\n    epic_depth: top_level\n    fire_on: success\n    code_review:\n      enabled: true\n      reviewer_type: cerberus\n      baseline: since_last_review\n      finding_threshold: none\n\n  run_end:\n    commands: []\n    failure_mode: continue\n    fire_on: success\n    code_review:\n      enabled: true\n      baseline: since_run_start\n```\n\n## Acceptance Criteria\n- [ ] code_review config block documented with all fields\n- [ ] Each trigger type documented with when/baseline\n- [ ] Example configurations for common use cases\n- [ ] Migration guide from legacy config\n- [ ] Deprecated CLI flags documented\n\n## Verification\n- Review docs/validation.md renders correctly\n- All YAML examples are valid\n\n## Notes for Agent\n- Follow existing doc style and formatting\n- Include both simple and advanced examples\n- Link to migration guide prominently","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-09T02:26:59.690826175Z","created_by":"cyou","updated_at":"2026-01-10T01:14:58.735914898Z","closed_at":"2026-01-10T01:14:58.735914898Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-92r6.14","depends_on_id":"mala-92r6","type":"parent-child","created_at":"2026-01-09T02:26:59.691372602Z","created_by":"cyou"},{"issue_id":"mala-92r6.14","depends_on_id":"mala-92r6.3","type":"blocks","created_at":"2026-01-09T02:27:20.642633646Z","created_by":"cyou"},{"issue_id":"mala-92r6.14","depends_on_id":"mala-92r6.8","type":"blocks","created_at":"2026-01-09T02:27:20.75347757Z","created_by":"cyou"},{"issue_id":"mala-92r6.14","depends_on_id":"mala-92r6.12","type":"blocks","created_at":"2026-01-09T02:27:20.865135771Z","created_by":"cyou"}]}
{"id":"mala-92r6.15","title":"[Review] 1 non-blocking finding from mala-92r6.4","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-92r6.4\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Avoid shared mutable default for last_cumulative_review_commits\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/infra/io/log_output/run_metadata.py:225-231\n\n`self.last_cumulative_review_commits: dict[str, str] = {}` creates a single mutable default per instance construction site, but since it’s a literal and not created via a factory, it’s easy to accidentally share or reuse patterns that later lead to cross-instance state leakage if the attribute is mutated and copied/cloned patterns are used.\n\nConcrete fix: initialize with a new dict per instance (e.g., `self.last_cumulative_review_commits = {}` is fine as an assignment, but ensure it’s not a class attribute / dataclass field default; if this is meant to mirror a dataclass pattern, use `field(default_factory=dict)` or equivalent pattern in this codebase). Also consider defensive copying in `_from_dict`:\n\n```python\nmetadata.last_cumulative_review_commits = dict(data.get(\"last_cumulative_review_commits\", {}))\n```\n\nThis prevents aliasing the parsed JSON dict if callers later retain `data` and mutate it.\n\n---\n\n\u003c!-- fp:66a24633f166adf1 --\u003e\n\u003c!-- review_finding:abb28a75794f --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-09T07:04:04.960684386Z","created_by":"cyou","updated_at":"2026-01-09T07:05:26.204973934Z","closed_at":"2026-01-09T07:05:26.204973934Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-92r6.4"],"dependencies":[{"issue_id":"mala-92r6.15","depends_on_id":"mala-92r6","type":"parent-child","created_at":"2026-01-09T07:04:04.961414422Z","created_by":"cyou"}]}
{"id":"mala-92r6.16","title":"[Review] 2 non-blocking findings from mala-92r6.5","description":"## Review Findings\n\nThis issue consolidates 2 non-blocking findings from code review.\n\n**Source issue:** mala-92r6.5\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] `_parse_rename_path` drops path segments in braces without arrows\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/infra/git_utils.py:125-136\n\nThe rename parser logic incorrectly discards path segments enclosed in braces if they do not contain the `=\u003e` separator.\n\nScenario: A file `src/{v1}/utils.py` is renamed to `src/{v1}/new_utils.py`. Git output might be `src/{v1}/{utils.py =\u003e new_utils.py}`.\nThe parser iterates over `{v1}`, finds no `=\u003e`, and enters the `else` block (which is missing). The segment is skipped.\nResult: `src//new_utils.py`.\n\n---\n\n### Finding 2: [P2] `files_changed` may contain quoted paths\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/infra/git_utils.py:89-96\n\n`git diff --numstat` quotes paths containing spaces or special characters (e.g., `\"my file.txt\"`). The current implementation adds these quoted strings directly to `files_changed`.\n\nIf consumers of `DiffStat` expect raw filesystem paths (e.g., to open files), this will cause errors. The implementation should likely strip surrounding quotes or unescape the path.\n\n---\n\n\u003c!-- fp:d6522e9d3a562537 --\u003e\n\u003c!-- fp:74bf709c7b4b2b88 --\u003e\n\u003c!-- review_finding:4535249e9e54 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-09T07:09:02.856000484Z","created_by":"cyou","updated_at":"2026-01-09T07:13:00.707281556Z","closed_at":"2026-01-09T07:13:00.707281556Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-92r6.5"],"dependencies":[{"issue_id":"mala-92r6.16","depends_on_id":"mala-92r6","type":"parent-child","created_at":"2026-01-09T07:09:02.856616636Z","created_by":"cyou"}]}
{"id":"mala-92r6.17","title":"[Review] 1 non-blocking finding from mala-92r6.16","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-92r6.16\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Quoted paths are not unescaped\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/infra/git_utils.py:197-199\n\nThe implementation strips surrounding quotes but does not unescape the path content. Git escapes special characters (like `\\\"`, `\\\\`, `\\t`) inside quoted strings. For example, a file named `foo\"bar.py` is output by git as `\"foo\\\"bar.py\"`. The current fix resolves this to `foo\\\"bar.py` (preserving the backslash) which is an incorrect path.\n\nConsider using `codecs.decode(filename[1:-1], \"unicode_escape\")` to correctly handle the unescaping.\n\n---\n\n\u003c!-- fp:02562653d4aabedd --\u003e\n\u003c!-- review_finding:b4234dccfcbc --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-09T07:13:01.014285463Z","created_by":"cyou","updated_at":"2026-01-09T07:20:18.010562739Z","closed_at":"2026-01-09T07:20:18.010562739Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-92r6.16"],"dependencies":[{"issue_id":"mala-92r6.17","depends_on_id":"mala-92r6","type":"parent-child","created_at":"2026-01-09T07:13:01.014959506Z","created_by":"cyou"}]}
{"id":"mala-92r6.18","title":"[Review] 2 non-blocking findings from mala-92r6.7","description":"## Review Findings\n\nThis issue consolidates 2 non-blocking findings from code review.\n\n**Source issue:** mala-92r6.7\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Implement unreachable/shallow baseline handling with skip_reason\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/pipeline/cumulative_review_runner.py:173-260\n\nTask context explicitly requires: “Shallow clone (baseline not reachable): return None with skip_reason.” In the diff, `_get_baseline_commit()` returns a SHA/None but never checks reachability of the chosen baseline nor returns/records a skip reason.\n\nConcrete gap: for `TriggerType.SESSION_END`, it returns the result of `get_baseline_for_issue()` directly, and for other triggers it returns `run_start_commit`/stored baseline/HEAD without validating that commit is reachable in the local clone. That means in a shallow clone where the baseline commit exists logically but isn’t present locally, downstream diff/review steps will fail later, and you won’t get the required “skip with reason” behavior.\n\nActionable fix: plumb a reachability check (likely via git merge-base / rev-parse) into git_utils, and change `_get_baseline_commit()` to surface a structured “skip_reason” (either via return type or by setting a field on run_metadata/result) when unreachable.\n\nFiles affected by this diff: `src/pipeline/cumulative_review_runner.py` and tests should include a case where reachability check fails and asserts `None` + reason.\n\n---\n\n### Finding 2: [P3] Align GitUtils.get_head_commit docstring/return with implementation\n\n**Priority:** P3\n**Reviewer:** codex\n**Location:** src/infra/git_utils.py:332-360\n\n`GitUtils.get_head_commit()` docstring says it returns a “short form” 7-char hash, but it delegates to `get_git_commit_async(self.repo_path)` without any visible shortening in this diff. If `get_git_commit_async` returns a full SHA (common), callers/tests will get inconsistent behavior vs the doc.\n\nThis is worth fixing because `_get_baseline_commit()` uses `get_head_commit()` as a baseline fallback, and baselines are typically expected to be full SHAs for git plumbing.\n\nActionable fix: either (a) update docstring/typing to “full SHA” (and maybe rename to `get_head_sha`), or (b) ensure the helper invoked returns `--short=7` consistently.\n\n\n---\n\n\u003c!-- fp:5a3927b70e12e458 --\u003e\n\u003c!-- fp:20bdce802e58f0d2 --\u003e\n\u003c!-- review_finding:d925e7affe4a --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-09T16:26:23.228148182Z","created_by":"cyou","updated_at":"2026-01-09T16:36:30.250090694Z","closed_at":"2026-01-09T16:36:30.250090694Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-92r6.7"],"dependencies":[{"issue_id":"mala-92r6.18","depends_on_id":"mala-92r6","type":"parent-child","created_at":"2026-01-09T16:26:23.228726449Z","created_by":"cyou"}]}
{"id":"mala-92r6.19","title":"[Review] 1 non-blocking finding from mala-92r6.18","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-92r6.18\n**Highest priority:** P3\n\n---\n\n### Finding 1: [P3] Missing fix for GitUtils.get_head_commit docstring/return alignment\n\n**Priority:** P3\n**Reviewer:** gemini\n**Location:** src/infra/git_utils.py:383-385\n\nThe task context explicitly requested fixing `GitUtils.get_head_commit` (Finding 2) to align its docstring/return with its implementation. The diff adds `is_commit_reachable` but does not appear to modify `get_head_commit` or its docstring in `src/infra/git_utils.py`, leaving the documented mismatch unresolved.\n\n---\n\n\u003c!-- fp:cdce4d032f437a94 --\u003e\n\u003c!-- review_finding:a679c38bd56b --\u003e","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-09T16:36:30.549451737Z","created_by":"cyou","updated_at":"2026-01-09T16:48:54.399561354Z","closed_at":"2026-01-09T16:48:54.399561354Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-92r6.18"],"dependencies":[{"issue_id":"mala-92r6.19","depends_on_id":"mala-92r6","type":"parent-child","created_at":"2026-01-09T16:36:30.550015393Z","created_by":"cyou"}]}
{"id":"mala-92r6.2","title":"[T002] [integration-path-test] Config loader parses code_review block - skeleton + failing integration","description":"## Goal\nAdd skeleton parsing for `code_review` block in validation triggers + write failing integration test that exercises the config loading path.\n\n## Context\nThis is the integration-path-test for config loading. The integration test must fail for the \"right reason\" (unimplemented parsing) not due to import/wiring issues.\n\n## Scope\n- **In**: Add parsing stubs in config_loader, write failing integration test\n- **Out**: Full parsing implementation (T003)\n\n## Changes\n- `src/domain/validation/config_loader.py` — Exists — Add stub parsing for code_review\n- `tests/integration/domain/validation/test_config_loading.py` — **New** — Integration test for config loading path\n\n## Skeleton Work\n1. In config_loader.py, add method signature for parsing code_review:\n   ```python\n   def _parse_code_review_config(self, data: dict[str, Any]) -\u003e CodeReviewConfig | None:\n       raise NotImplementedError(\"code_review parsing not yet implemented\")\n   ```\n\n2. Wire it into trigger parsing methods (session_end, epic_completion, run_end)\n\n## Integration Test\n```python\ndef test_config_loader_parses_code_review_block():\n    \"\"\"Integration test: config_loader.load() correctly parses code_review.\"\"\"\n    yaml_content = '''\n    validation_triggers:\n      session_end:\n        commands: []\n        failure_mode: continue\n        code_review:\n          enabled: true\n          reviewer_type: cerberus\n    '''\n    # Write to temp file, call load(), verify CodeReviewConfig is populated\n    config = ConfigLoader().load(tmp_path / \"mala.yaml\")\n    assert config.validation_triggers.session_end.code_review is not None\n    assert config.validation_triggers.session_end.code_review.enabled is True\n```\n\n## Acceptance Criteria\n- [ ] Stub method exists in config_loader.py\n- [ ] Integration test exists and FAILS with NotImplementedError (not import error)\n- [ ] Project builds/type-checks successfully\n\n## Verification\n```bash\nuvx ty check src/domain/validation/config_loader.py\nuv run pytest tests/integration/domain/validation/test_config_loading.py -v\n# Should fail with NotImplementedError\n```\n\n## Notes for Agent\n- This is a skeleton task - parsing should NOT work yet\n- The test should fail for the RIGHT reason (NotImplementedError)\n- Verify the wiring path from yaml → ConfigLoader.load() → trigger parsing → CodeReviewConfig","notes":"Quality gate failed: External review failed: src/domain/validation/config_loader.py:36: [P0] Missing CodeReviewConfig and RunEndTriggerConfig definitions\nLog: /home/cyou/.claude/projects/-home-cyou-mala/19865596-150c-4d07-acb7-b9da8ac48b15.jsonl","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-09T02:23:45.006127572Z","created_by":"cyou","updated_at":"2026-01-09T19:20:07.782434119Z","closed_at":"2026-01-09T19:20:07.782434119Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-92r6.2","depends_on_id":"mala-92r6","type":"parent-child","created_at":"2026-01-09T02:23:45.006682149Z","created_by":"cyou"},{"issue_id":"mala-92r6.2","depends_on_id":"mala-92r6.1","type":"blocks","created_at":"2026-01-09T02:27:18.93798988Z","created_by":"cyou"}]}
{"id":"mala-92r6.20","title":"[Review] 1 non-blocking finding from mala-92r6.8","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-92r6.8\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Respect failure_mode on review execution errors\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/pipeline/cumulative_review_runner.py:232-252\n\n`run_review()` catches all exceptions from `self._review_runner.run_review(...)` and returns `CumulativeReviewResult(status=\"failed\", ...)` unconditionally.\n\nTask context explicitly requires: “execution_error handling per failure_mode”. With this implementation, even configurations intended to continue-on-error cannot be honored, and upstream orchestration may treat this as a hard failure.\n\nConcrete path: any exception raised by `ReviewRunner` (e.g. network/model error) triggers the `except Exception` block and produces `status=\"failed\"` regardless of `config.code_review.failure_mode`.\n\nSuggested fix: catch the specific review error type used by the system (or keep Exception, but branch) and map to either a non-failing completion result (e.g. `skipped` with `skip_reason=\"execution_error\"`) vs `failed`, based on `config.code_review.failure_mode`; continue to avoid baseline updates in both cases.\n\n---\n\n\u003c!-- fp:54f9084d85e49d5b --\u003e\n\u003c!-- review_finding:cfb37e717346 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-09T16:56:24.6034106Z","created_by":"cyou","updated_at":"2026-01-09T17:33:57.573554384Z","closed_at":"2026-01-09T17:33:57.573554384Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-92r6.8"],"dependencies":[{"issue_id":"mala-92r6.20","depends_on_id":"mala-92r6","type":"parent-child","created_at":"2026-01-09T16:56:24.604052334Z","created_by":"cyou"}]}
{"id":"mala-92r6.21","title":"[Review] 1 non-blocking finding from mala-92r6.20","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-92r6.20\n**Highest priority:** P3\n\n---\n\n### Finding 1: [P3] Move local import to module level\n\n**Priority:** P3\n**Reviewer:** gemini\n**Location:** src/pipeline/cumulative_review_runner.py:227\n\nImporting `FailureMode` inside the exception handler obscures dependencies and deviates from standard practices. Unless strictly necessary to avoid a circular import (unlikely for `src.domain`), move this to the top of the file with other imports.\n\n---\n\n\u003c!-- fp:59cc7806b415fc8d --\u003e\n\u003c!-- review_finding:2f1db24086b1 --\u003e","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-09T17:33:57.873770541Z","created_by":"cyou","updated_at":"2026-01-09T17:35:25.580115713Z","closed_at":"2026-01-09T17:35:25.580115713Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-92r6.20"],"dependencies":[{"issue_id":"mala-92r6.21","depends_on_id":"mala-92r6","type":"parent-child","created_at":"2026-01-09T17:33:57.874296043Z","created_by":"cyou"}]}
{"id":"mala-92r6.22","title":"[Review] 1 non-blocking finding from mala-92r6.2","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-92r6.2\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Remove out-of-scope run_end trigger parsing/wiring\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/domain/validation/config_loader.py:507-684\n\nT002’s stated scope is a skeleton `code_review` parsing stub plus a failing integration-path-test. This diff adds first-class `run_end` trigger support: it introduces `_RUN_END_FIELDS`, `_parse_run_end_trigger`, adds `run_end` to `_VALIDATION_TRIGGERS_FIELDS`, and wires parsing into `_parse_validation_triggers`. That is functional behavior expansion unrelated to making the `code_review` test fail for `NotImplementedError`, and it directly contradicts the task’s **Out** scope (“Full parsing implementation (T003)”).\n\nConcretely, the added `run_end` code paths are not required for the integration test (which exercises `session_end`) and create additional API/config compatibility considerations (e.g., accepting a new top-level `validation_triggers.run_end` key) that should land in its own scoped change.\n\n---\n\n\u003c!-- fp:0a9f55f65693f4ca --\u003e\n\u003c!-- review_finding:a5c5e0369f78 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-09T19:20:08.089823345Z","created_by":"cyou","updated_at":"2026-01-09T19:40:25.256148364Z","closed_at":"2026-01-09T19:40:25.256148364Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-92r6.2"],"dependencies":[{"issue_id":"mala-92r6.22","depends_on_id":"mala-92r6","type":"parent-child","created_at":"2026-01-09T19:20:08.090435104Z","created_by":"cyou"}]}
{"id":"mala-92r6.23","title":"[Review] 4 non-blocking findings from mala-92r6.3","description":"## Review Findings\n\nThis issue consolidates 4 non-blocking findings from code review.\n\n**Source issue:** mala-92r6.3\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Allow invalid reviewer_type when disabled (per spec)\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/domain/validation/config_loader.py:433-456\n\nTask context specifies: “enabled: true with invalid reviewer_type | ERROR”. The implementation raises `ConfigError` for invalid `reviewer_type` even when `enabled` is false, which is stricter than the stated rule and can break configs that keep `code_review` disabled but pre-fill values.\n\nConcrete mismatch: after checking `rt_val not in (\"cerberus\", \"agent_sdk\")`, it raises in both branches; the comment also says “Still error even if not enabled - invalid value is invalid”, which contradicts the table in the task context.\n\nIf the intent is truly “only error when enabled”, then when `enabled` is false you should either (a) warn and fall back to default, or (b) accept the value but ignore it; current behavior is an unconditional error.\n\n---\n\n### Finding 2: [P3] Baseline warning message omits 'trigger' causing brittle tests\n\n**Priority:** P3\n**Reviewer:** codex\n**Location:** tests/unit/domain/validation/test_config_loader.py:346-383\n\nThe warning for missing baseline in `epic_completion`/`run_end` is logged as `\"code_review.baseline not specified for %s trigger; ...\"`. The unit tests assert substrings like `\"baseline not specified for epic_completion\"`, which won’t match because the message includes `\"epic_completion trigger\"`.\n\nThis isn’t a functional bug, but it makes the tests fragile and likely failing depending on exact expectations. Either adjust the message or update the assertions to include `\"epic_completion trigger\"` / `\"run_end trigger\"`.\n\n---\n\n### Finding 3: [P2] Explicit null `baseline` bypasses default for required triggers\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/domain/validation/config_loader.py:622-630\n\nFor `epic_completion` and `run_end`, the logic defaults missing baselines to `'since_run_start'`, implying a value is required. However, explicit `baseline: null` in YAML results in `None`, bypassing this default because the key exists in `data`. If the runner requires a string baseline, this will cause runtime errors.\n\n---\n\n### Finding 4: [P3] Redundant `if enabled:` check in reviewer_type validation\n\n**Priority:** P3\n**Reviewer:** gemini\n**Location:** src/domain/validation/config_loader.py:517-526\n\nThe `if enabled:` check is redundant because the code raises `ConfigError` with the same message regardless of whether `enabled` is true or false. One conditional path is sufficient.\n\n---\n\n\u003c!-- fp:a8e49a0fcb1b8797 --\u003e\n\u003c!-- fp:f0f33f6b626bb25c --\u003e\n\u003c!-- fp:d026db5f20051e3e --\u003e\n\u003c!-- fp:b3509c0f72e96bc5 --\u003e\n\u003c!-- review_finding:b1f21d927339 --\u003e","notes":"Quality gate failed: Killed as deadlock victim (will retry after blocker completes)","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-09T19:33:18.616530646Z","created_by":"cyou","updated_at":"2026-01-09T21:53:45.673016274Z","closed_at":"2026-01-09T21:53:45.673016274Z","close_reason":"Closed","labels":["auto_generated","needs-followup","review_finding","source:mala-92r6.3"],"dependencies":[{"issue_id":"mala-92r6.23","depends_on_id":"mala-92r6","type":"parent-child","created_at":"2026-01-09T19:33:18.617143634Z","created_by":"cyou"},{"issue_id":"mala-92r6.23","depends_on_id":"mala-92r6.22","type":"blocks","created_at":"2026-01-09T19:36:03.329511136Z","created_by":"cyou"}]}
{"id":"mala-92r6.24","title":"Add finding_threshold + remediate handling for code_review","description":"Implement finding_threshold enforcement and remediate behavior per plan. Today code_review failure_mode is only used for execution errors and remediate is explicitly unsupported, and finding_threshold is parsed but never applied.","notes":"Quality gate failed: External review failed: src/pipeline/run_coordinator.py:898: [P1] Use remediation result to clear findings failure\nLog: /home/cyou/.claude/projects/-home-cyou-mala/8ccfacdd-f226-4530-9a3e-a2cc5d783c76.jsonl","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-09T23:24:33.563325108Z","created_by":"cyou","updated_at":"2026-01-10T01:34:15.864236985Z","closed_at":"2026-01-10T01:34:15.864236985Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-92r6.24","depends_on_id":"mala-92r6","type":"parent-child","created_at":"2026-01-09T23:24:33.563848824Z","created_by":"cyou"}]}
{"id":"mala-92r6.25","title":"Add dedup + metadata for cumulative review findings","description":"CumulativeReviewRunner creates beads issues without dedup or trigger/baseline/epic metadata; plan calls for fingerprint-based dedup and richer metadata/tags.","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-09T23:24:43.685640496Z","created_by":"cyou","updated_at":"2026-01-10T01:40:17.502427388Z","closed_at":"2026-01-10T01:40:17.502427388Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-92r6.25","depends_on_id":"mala-92r6","type":"parent-child","created_at":"2026-01-09T23:24:43.686269172Z","created_by":"cyou"},{"issue_id":"mala-92r6.25","depends_on_id":"mala-92r6.24","type":"blocks","created_at":"2026-01-09T23:25:20.097431749Z","created_by":"cyou"}]}
{"id":"mala-92r6.26","title":"Capture run_start_commit for epic_completion cumulative review","description":"_capture_run_start_commit runs only when run_end trigger configured; epic_completion cumulative review with baseline=since_run_start can miss capture and fall back to HEAD. Expand capture conditions or move capture earlier.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-09T23:24:51.942391768Z","created_by":"cyou","updated_at":"2026-01-10T00:59:06.728451933Z","closed_at":"2026-01-10T00:59:06.728451933Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-92r6.26","depends_on_id":"mala-92r6","type":"parent-child","created_at":"2026-01-09T23:24:51.943211122Z","created_by":"cyou"}]}
{"id":"mala-92r6.27","title":"Update docs for unified code_review config + deprecations","description":"Docs still describe legacy reviewer_type + cerberus external review defaults; need to align docs/validation.md, docs/project-config.md, docs/cli-reference.md, docs/validation-triggers.md with new unified code_review config and deprecations.","notes":"Quality gate failed: Quality gate failed: Missing validation evidence for: test, typecheck; Validation command(s) failed: ruff\nLog: /home/cyou/.claude/projects/-home-cyou-mala/4c998db3-8159-429c-a3be-b73dceb98c73.jsonl","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-09T23:25:00.362389666Z","created_by":"cyou","updated_at":"2026-01-10T01:30:24.243710718Z","closed_at":"2026-01-10T01:30:24.243710718Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-92r6.27","depends_on_id":"mala-92r6","type":"parent-child","created_at":"2026-01-09T23:25:00.362992852Z","created_by":"cyou"}]}
{"id":"mala-92r6.28","title":"Add integration tests for run_end YAML + code_review thresholds","description":"Integration tests still construct run_end programmatically and don't cover code_review finding_threshold/remediate behavior. Add YAML parsing coverage and threshold behaviors.","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-09T23:25:09.143373603Z","created_by":"cyou","updated_at":"2026-01-10T00:59:39.025714182Z","closed_at":"2026-01-10T00:59:39.025714182Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-92r6.28","depends_on_id":"mala-92r6","type":"parent-child","created_at":"2026-01-09T23:25:09.143904319Z","created_by":"cyou"}]}
{"id":"mala-92r6.29","title":"Complete deprecation/removal of legacy review config and CLI flags","description":"Plan/roadmap task to fully remove legacy reviewer_type fields and CLI review flags after transition period; ensure code paths, docs, and tests are updated when deprecation window ends.","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-09T23:25:33.971096404Z","created_by":"cyou","updated_at":"2026-01-10T01:23:42.107201261Z","closed_at":"2026-01-10T01:23:42.107201261Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-92r6.29","depends_on_id":"mala-92r6","type":"parent-child","created_at":"2026-01-09T23:25:33.97160636Z","created_by":"cyou"}]}
{"id":"mala-92r6.3","title":"[T003] Implement config_loader code_review parsing + validation rules","description":"## Goal\nImplement full parsing of `code_review` block in config_loader with validation rules.\n\n## Context\nMakes the T002 integration test pass. Includes validation warnings/errors per the plan.\n\n## Scope\n- **In**: Full parsing implementation, validation rules, unit tests\n- **Out**: Runtime behavior (that's in CumulativeReviewRunner)\n\n## Changes\n- `src/domain/validation/config_loader.py` — Exists — Implement _parse_code_review_config()\n- `tests/unit/domain/validation/test_config_loader.py` — **New** — Unit tests for parsing\n\n## Implementation\n\n1. Parse all CodeReviewConfig fields from YAML dict\n2. Parse nested CerberusConfig if present\n3. Add run_end to recognized trigger keys\n4. Implement validation rules:\n\n| Condition | Behavior |\n|-----------|----------|\n| baseline set for session_end | WARN, ignore field |\n| baseline missing for epic_completion/run_end | WARN, default to since_run_start |\n| enabled: true with invalid reviewer_type | ERROR |\n| max_retries \u003c 0 | ERROR |\n| Unknown trigger keys | ERROR (fail-fast) |\n\n## Acceptance Criteria\n- [ ] code_review block parsed into CodeReviewConfig for all trigger types\n- [ ] CerberusConfig nested parsing works\n- [ ] run_end trigger parsing implemented\n- [ ] Validation warnings logged for misplaced baseline\n- [ ] Validation errors raised for invalid values\n- [ ] Unit tests cover all parsing paths and validation rules\n- [ ] T002 integration test now passes\n\n## Verification\n```bash\nuv run pytest tests/unit/domain/validation/test_config_loader.py -v\nuv run pytest tests/integration/domain/validation/test_config_loading.py -v\nuvx ty check src/domain/validation/\n```\n\n## Notes for Agent\n- Follow existing parsing patterns in config_loader.py\n- Use logging.warning() for warnings, raise ConfigError for errors\n- Test both valid and invalid configurations","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-09T02:23:57.176301762Z","created_by":"cyou","updated_at":"2026-01-09T19:33:18.31416632Z","closed_at":"2026-01-09T19:33:18.31416632Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-92r6.3","depends_on_id":"mala-92r6","type":"parent-child","created_at":"2026-01-09T02:23:57.176864549Z","created_by":"cyou"},{"issue_id":"mala-92r6.3","depends_on_id":"mala-92r6.1","type":"blocks","created_at":"2026-01-09T02:27:19.053665278Z","created_by":"cyou"},{"issue_id":"mala-92r6.3","depends_on_id":"mala-92r6.2","type":"blocks","created_at":"2026-01-09T02:27:19.166316453Z","created_by":"cyou"}]}
{"id":"mala-92r6.30","title":"[Review] 2 non-blocking findings from mala-92r6.24","description":"## Review Findings\n\nThis issue consolidates 2 non-blocking findings from code review.\n\n**Source issue:** mala-92r6.24\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Avoid emitting validation_passed after code_review failure\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/pipeline/run_coordinator.py:832-870\n\nWhen `review_result.status == \"failed\"` and `failure_mode` is `CONTINUE` (or `REMEDIATE` exhausts), this code sets `last_failure = (\"code_review\", trigger_type.value)` and emits `on_trigger_validation_failed(...)`, but it then falls through to the unconditional “Emit validation_passed” block.\n\nThat produces an event stream where the same trigger both fails and passes. This is not just cosmetic: any consumer that treats `on_trigger_validation_passed` as authoritative (dashboards, metrics, state machines) will be misled even though the final `TriggerValidationResult` will likely be `failed` due to `last_failure`.\n\nConcrete path:\n- `review_result.status == \"failed\"`\n- `failure_mode == CONTINUE` (or REMEDIATE retries exhausted)\n- `last_failure` is set\n- code reaches `# Emit validation_passed` and emits passed anyway.\n\nFix: gate `on_trigger_validation_passed` emission on there being no `last_failure` for that trigger (or restructure so failure paths `continue`/`return` before the passed event).\n\n---\n\n### Finding 2: [P2] Contradictory event stream: validation_passed emitted after failure\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/pipeline/run_coordinator.py:970-976\n\nWhen `failure_mode` is CONTINUE or REMEDIATE (and remediation fails), `last_failure` is set to record the failure. However, the code subsequently falls through to unconditionally emit `on_trigger_validation_passed` before raising the final `TriggerValidationFailed` exception. This creates a confusing event stream where a trigger is reported as passed immediately after failing.\n\nWrap the `on_trigger_validation_passed` event emission (immediately following the code review logic) in a check: `if last_failure is None:`.\n\n---\n\n\u003c!-- fp:596bc301413b92b2 --\u003e\n\u003c!-- fp:346ddf83027f2882 --\u003e\n\u003c!-- review_finding:8537aa13c49d --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-10T01:34:16.163957086Z","created_by":"cyou","updated_at":"2026-01-10T01:39:29.319844236Z","closed_at":"2026-01-10T01:39:29.319844236Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-92r6.24"],"dependencies":[{"issue_id":"mala-92r6.30","depends_on_id":"mala-92r6","type":"parent-child","created_at":"2026-01-10T01:34:16.164532076Z","created_by":"cyou"}]}
{"id":"mala-92r6.4","title":"[T004] Add run_start_commit + last_cumulative_review_commits to RunMetadata","description":"## Goal\nAdd fields to RunMetadata for tracking cumulative review baselines.\n\n## Context\nThese fields enable baseline tracking for cumulative reviews:\n- run_start_commit: HEAD at run start (captured once)\n- last_cumulative_review_commits: dict mapping trigger keys to reviewed commit SHAs\n\n## Scope\n- **In**: Add fields with defaults, serialization support, unit tests\n- **Out**: Logic to populate these fields (that's in orchestrator/runner)\n\n## Changes\n- `src/infra/io/log_output/run_metadata.py` — Exists — Add new fields\n- `tests/unit/infra/io/test_run_metadata.py` — Exists or **New** — Unit tests\n\n## Code to Add\n```python\n@dataclass\nclass RunMetadata:\n    # ... existing fields ...\n    \n    # Baseline tracking for cumulative reviews\n    run_start_commit: str | None = None\n    last_cumulative_review_commits: dict[str, str] = field(default_factory=dict)\n    # Key format: \"run_end\" or \"epic_completion:\u003cepic_id\u003e\"\n```\n\n## Serialization\nEnsure these fields are included in to_dict()/from_dict() (or equivalent):\n- run_start_commit: simple string field\n- last_cumulative_review_commits: dict[str, str]\n\n## Backward Compatibility\n- New fields default to None/empty dict\n- Loading old metadata files without these fields should work (use .get() with defaults)\n\n## Acceptance Criteria\n- [ ] run_start_commit field added with None default\n- [ ] last_cumulative_review_commits dict field added\n- [ ] Fields serialize/deserialize correctly\n- [ ] Old metadata files load successfully (backward compat)\n- [ ] Unit tests cover field access and serialization\n\n## Verification\n```bash\nuv run pytest tests/unit/infra/io/test_run_metadata.py -v\nuvx ty check src/infra/io/log_output/run_metadata.py\n```\n\n## Notes for Agent\n- Check existing RunMetadata for serialization patterns\n- Ensure field_default_factory for the dict\n- Test loading metadata JSON without these fields (backward compat)","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-09T02:24:09.795445754Z","created_by":"cyou","updated_at":"2026-01-09T07:04:04.619790039Z","closed_at":"2026-01-09T07:04:04.619790039Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-92r6.4","depends_on_id":"mala-92r6","type":"parent-child","created_at":"2026-01-09T02:24:09.796043081Z","created_by":"cyou"}]}
{"id":"mala-92r6.5","title":"[T005] Add get_diff_stat + get_diff_content to git_utils","description":"## Goal\nAdd helper functions to git_utils for generating diffs between commits.\n\n## Context\nCumulativeReviewRunner needs to:\n1. Get diff size (for warning threshold)\n2. Get diff content (for review)\n\n## Scope\n- **In**: Add two async functions with unit tests\n- **Out**: Integration with CumulativeReviewRunner (that's T006-T008)\n\n## Changes\n- `src/infra/git_utils.py` — Exists — Add new functions\n- `tests/unit/infra/test_git_utils.py` — Exists — Add unit tests\n\n## Code to Add\n```python\n@dataclass(frozen=True)\nclass DiffStat:\n    \"\"\"Statistics about a git diff.\"\"\"\n    total_lines: int\n    files_changed: list[str]\n\n\nasync def get_diff_stat(\n    repo_path: Path,\n    from_commit: str,\n    to_commit: str = \"HEAD\",\n) -\u003e DiffStat:\n    \"\"\"Get diff statistics between commits.\n    \n    Uses: git diff --stat \u003cfrom_commit\u003e \u003cto_commit\u003e\n    \"\"\"\n    ...\n\n\nasync def get_diff_content(\n    repo_path: Path,\n    from_commit: str,\n    to_commit: str = \"HEAD\",\n) -\u003e str:\n    \"\"\"Get unified diff content for review.\n    \n    Uses: git diff \u003cfrom_commit\u003e \u003cto_commit\u003e\n    Note: Two-argument form, NOT range syntax.\n    \"\"\"\n    ...\n```\n\n## Git Commands (exact semantics)\n```bash\n# Patch diff for review content\ngit diff \u003cbaseline\u003e \u003chead\u003e  # Two-argument form\n\n# Diff stats for size warning\ngit diff --stat \u003cbaseline\u003e \u003chead\u003e\n```\n\n## Edge Cases\n- Empty diff: Return DiffStat(total_lines=0, files_changed=[])\n- Invalid commit: Raise appropriate error\n- Shallow clone: Function itself doesn't check, caller handles\n\n## Acceptance Criteria\n- [ ] get_diff_stat returns line count and file list\n- [ ] get_diff_content returns unified diff string\n- [ ] Both use two-argument git diff form (not range syntax)\n- [ ] Empty diff handled correctly\n- [ ] Unit tests with mocked git subprocess\n\n## Verification\n```bash\nuv run pytest tests/unit/infra/test_git_utils.py -v -k \"diff\"\nuvx ty check src/infra/git_utils.py\n```\n\n## Notes for Agent\n- Follow existing async subprocess patterns in git_utils.py\n- Use git diff A B (two-argument), not A..B (range syntax)\n- Parse --stat output to extract line counts","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-09T02:24:23.469091041Z","created_by":"cyou","updated_at":"2026-01-09T07:09:02.55053223Z","closed_at":"2026-01-09T07:09:02.55053223Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-92r6.5","depends_on_id":"mala-92r6","type":"parent-child","created_at":"2026-01-09T02:24:23.469993276Z","created_by":"cyou"}]}
{"id":"mala-92r6.6","title":"[T006] [integration-path-test] CumulativeReviewRunner skeleton + failing integration test","description":"## Goal\nCreate CumulativeReviewRunner class skeleton with stub methods + failing integration test that exercises the full review trigger path.\n\n## Context\nThis is the integration-path-test for cumulative review. The test must traverse the composition root (trigger firing → RunCoordinator → CumulativeReviewRunner) and fail for the \"right reason\" (NotImplementedError in runner).\n\n## Scope\n- **In**: Create new module with class skeleton, write failing integration test\n- **Out**: Implementation of baseline/diff/execution logic (T007-T008)\n\n## Changes\n- `src/pipeline/cumulative_review_runner.py` — **New** — CumulativeReviewRunner class skeleton\n- `tests/integration/orchestration/test_cumulative_review.py` — **New** — Integration test\n\n## Skeleton Class\n```python\nclass CumulativeReviewRunner:\n    \"\"\"Orchestrates cumulative code review execution.\"\"\"\n    \n    LARGE_DIFF_THRESHOLD = 5000  # lines\n\n    def __init__(\n        self,\n        review_runner: ReviewRunner,\n        git_utils: GitUtils,\n        beads_client: BeadsClient,\n        logger: Logger,\n    ) -\u003e None:\n        self._review_runner = review_runner\n        self._git_utils = git_utils\n        self._beads_client = beads_client\n        self._logger = logger\n\n    async def run_review(\n        self,\n        trigger_type: TriggerType,\n        config: CodeReviewConfig,\n        run_metadata: RunMetadata,\n        repo_path: Path,\n        interrupt_event: asyncio.Event,\n        *,\n        issue_id: str | None = None,\n        epic_id: str | None = None,\n    ) -\u003e CumulativeReviewResult:\n        raise NotImplementedError(\"CumulativeReviewRunner.run_review not yet implemented\")\n\n    def _get_baseline_commit(self, trigger_type, config, run_metadata) -\u003e str:\n        raise NotImplementedError()\n\n    def _generate_diff(self, baseline_commit: str, head_commit: str) -\u003e DiffResult:\n        raise NotImplementedError()\n\n\n@dataclass(frozen=True)\nclass CumulativeReviewResult:\n    status: Literal[\"success\", \"skipped\", \"failed\"]\n    findings: tuple[ReviewFinding, ...]\n    new_baseline_commit: str | None\n    skip_reason: str | None = None\n```\n\n## Integration Test\n```python\nasync def test_epic_completion_trigger_invokes_cumulative_review():\n    \"\"\"Integration: epic_completion trigger with code_review fires CumulativeReviewRunner.\"\"\"\n    # Setup: Create test config with epic_completion.code_review.enabled = true\n    # Setup: Create RunCoordinator with real wiring\n    # Act: Fire epic_completion trigger\n    # Assert: CumulativeReviewRunner.run_review() was called (will raise NotImplementedError)\n```\n\n## Acceptance Criteria\n- [ ] CumulativeReviewRunner class exists with documented interfaces\n- [ ] CumulativeReviewResult dataclass defined\n- [ ] Module has correct imports and type annotations\n- [ ] Integration test fails with NotImplementedError (not import/wiring error)\n- [ ] Project type-checks successfully\n\n## Verification\n```bash\nuvx ty check src/pipeline/cumulative_review_runner.py\nuv run pytest tests/integration/orchestration/test_cumulative_review.py -v\n# Should fail with NotImplementedError\n```\n\n## Notes for Agent\n- This is skeleton + integration test task - methods should NOT work yet\n- Ensure proper dependency injection via constructor\n- Test must exercise real wiring path, not direct instantiation\n- Check RunCoordinator to understand where review is triggered","notes":"Quality gate failed: External review failed: src/pipeline/cumulative_review_runner.py:1: [P1] Align CumulativeReviewRunner ctor to use BeadsClient (not IssueProvider); tests/integration/orchestration/test_cumulative_review.py:1: [P1] Integration test should not manually set RunCoordinator.run_metadata\nLog: /home/cyou/.claude/projects/-home-cyou-mala/814ceb46-ebab-478e-8a6a-1a639891336f.jsonl","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-09T02:24:47.515933561Z","created_by":"cyou","updated_at":"2026-01-09T16:18:27.839004831Z","closed_at":"2026-01-09T16:18:27.839004831Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-92r6.6","depends_on_id":"mala-92r6","type":"parent-child","created_at":"2026-01-09T02:24:47.516573618Z","created_by":"cyou"},{"issue_id":"mala-92r6.6","depends_on_id":"mala-92r6.1","type":"blocks","created_at":"2026-01-09T02:27:19.28022905Z","created_by":"cyou"},{"issue_id":"mala-92r6.6","depends_on_id":"mala-92r6.4","type":"blocks","created_at":"2026-01-09T02:27:19.39905915Z","created_by":"cyou"},{"issue_id":"mala-92r6.6","depends_on_id":"mala-92r6.5","type":"blocks","created_at":"2026-01-09T02:27:19.512296952Z","created_by":"cyou"}]}
{"id":"mala-92r6.7","title":"[T007] Implement CumulativeReviewRunner baseline determination","description":"## Goal\nImplement _get_baseline_commit() with all baseline determination logic.\n\n## Context\nBaseline determination varies by trigger type and baseline mode:\n- session_end: Uses get_baseline_for_issue() dynamically\n- epic_completion/run_end: Uses since_run_start or since_last_review mode\n\n## Scope\n- **In**: Implement baseline determination, unit tests\n- **Out**: Diff generation and review execution (T008)\n\n## Changes\n- `src/pipeline/cumulative_review_runner.py` — Exists — Implement _get_baseline_commit()\n- `tests/unit/pipeline/test_cumulative_review_runner.py` — **New** — Unit tests\n\n## Baseline Logic\n\n### session_end\n- Call `get_baseline_for_issue(repo_path, issue_id)` from git_utils\n- Returns parent of first commit with `bd-{issue_id}:` prefix\n- Fallback if no commits: return None (skip review)\n\n### epic_completion / run_end with since_run_start\n- Use `run_metadata.run_start_commit`\n- Fallback if None: log warning, capture current HEAD\n\n### epic_completion / run_end with since_last_review\n- Build key: \"run_end\" or \"epic_completion:\u003cepic_id\u003e\"\n- Look up `run_metadata.last_cumulative_review_commits[key]`\n- Fallback if not found: use since_run_start behavior\n\n## Error Handling\n- Shallow clone (baseline not reachable): return None with skip_reason\n- Missing run_start_commit on resume: log warning, capture HEAD\n\n## Acceptance Criteria\n- [ ] session_end uses get_baseline_for_issue()\n- [ ] since_run_start mode uses run_metadata.run_start_commit\n- [ ] since_last_review mode looks up per-trigger key\n- [ ] Fallback chain works correctly\n- [ ] Unit tests cover all baseline modes and edge cases\n\n## Verification\n```bash\nuv run pytest tests/unit/pipeline/test_cumulative_review_runner.py -v -k \"baseline\"\nuvx ty check src/pipeline/cumulative_review_runner.py\n```\n\n## Notes for Agent\n- Inject a git_utils fake for unit tests\n- Test both success paths and error fallbacks\n- Ensure baseline key format is consistent with plan","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-09T02:25:00.798503137Z","created_by":"cyou","updated_at":"2026-01-09T16:26:22.896348098Z","closed_at":"2026-01-09T16:26:22.896348098Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-92r6.7","depends_on_id":"mala-92r6","type":"parent-child","created_at":"2026-01-09T02:25:00.799100674Z","created_by":"cyou"},{"issue_id":"mala-92r6.7","depends_on_id":"mala-92r6.6","type":"blocks","created_at":"2026-01-09T02:27:19.623764763Z","created_by":"cyou"}]}
{"id":"mala-92r6.8","title":"[T008] Implement CumulativeReviewRunner diff handling + review execution","description":"## Goal\nImplement diff generation, empty/large diff handling, and review execution in CumulativeReviewRunner.\n\n## Context\nAfter baseline is determined (T007), the runner must:\n1. Generate diff between baseline and HEAD\n2. Handle empty diffs (skip)\n3. Warn on large diffs (proceed)\n4. Execute review via ReviewRunner\n5. Create beads issues for findings\n6. Update baseline on success\n\n## Scope\n- **In**: Implement run_review() main flow, unit tests\n- **Out**: Orchestrator wiring (T010-T012)\n\n## Changes\n- `src/pipeline/cumulative_review_runner.py` — Exists — Implement run_review() flow\n- `tests/unit/pipeline/test_cumulative_review_runner.py` — Exists — Add unit tests\n\n## Implementation Flow\n\n```python\nasync def run_review(self, ...) -\u003e CumulativeReviewResult:\n    # 1. Determine baseline\n    baseline = self._get_baseline_commit(trigger_type, config, run_metadata)\n    if baseline is None:\n        return CumulativeReviewResult(status=\"skipped\", ...)\n    \n    # 2. Get diff stat\n    diff_stat = await self._git_utils.get_diff_stat(repo_path, baseline, \"HEAD\")\n    \n    # 3. Empty diff check\n    if diff_stat.total_lines == 0:\n        self._logger.info(\"No changes since baseline, skipping review\")\n        return CumulativeReviewResult(status=\"skipped\", skip_reason=\"empty_diff\", ...)\n    \n    # 4. Large diff warning\n    if diff_stat.total_lines \u003e self.LARGE_DIFF_THRESHOLD:\n        self._logger.warning(f\"Large diff ({diff_stat.total_lines} lines)\")\n    \n    # 5. Get diff content\n    diff_content = await self._git_utils.get_diff_content(repo_path, baseline, \"HEAD\")\n    \n    # 6. Execute review\n    try:\n        result = await self._review_runner.run_review(diff_content, config)\n    except ReviewError as e:\n        # execution_error handling per failure_mode\n        ...\n    \n    # 7. Create beads issues\n    for finding in result.findings:\n        await self._beads_client.create_issue(...)\n    \n    # 8. Update baseline\n    head = await get_current_head(repo_path)\n    self._update_baseline(trigger_type, run_metadata, head, epic_id)\n    \n    return CumulativeReviewResult(status=\"success\", findings=result.findings, ...)\n```\n\n## Baseline Update Logic\n- Only update on completed review (success or completed_with_findings)\n- Do NOT update on execution_error or skipped\n- Key: \"run_end\" or \"epic_completion:\u003cepic_id\u003e\"\n\n## Acceptance Criteria\n- [ ] Empty diff returns skipped status without calling reviewer\n- [ ] Large diff logs warning but proceeds\n- [ ] ReviewRunner called with diff content\n- [ ] Beads issues created for each finding\n- [ ] Baseline updated only on completion (not error/skip)\n- [ ] T006 integration test now passes\n- [ ] Unit tests cover all paths and failure modes\n\n## Verification\n```bash\nuv run pytest tests/unit/pipeline/test_cumulative_review_runner.py -v\nuv run pytest tests/integration/orchestration/test_cumulative_review.py -v\nuvx ty check src/pipeline/cumulative_review_runner.py\n```\n\n## Config Override Test (Required)\nAdd test proving `LARGE_DIFF_THRESHOLD` is respected:\n```python\ndef test_large_diff_threshold_triggers_warning():\n    # Arrange: runner with mock git_utils returning diff \u003e threshold\n    # Act: run_review()\n    # Assert: warning logged, review still proceeds\n```\n\n## Notes for Agent\n- Use fakes for ReviewRunner, GitUtils, BeadsClient\n- Test execution_error vs completed_with_findings vs completed_no_findings\n- Verify baseline is NOT updated on execution_error","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-09T02:25:18.974202115Z","created_by":"cyou","updated_at":"2026-01-09T16:56:24.268871265Z","closed_at":"2026-01-09T16:56:24.268871265Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-92r6.8","depends_on_id":"mala-92r6","type":"parent-child","created_at":"2026-01-09T02:25:18.974830221Z","created_by":"cyou"},{"issue_id":"mala-92r6.8","depends_on_id":"mala-92r6.6","type":"blocks","created_at":"2026-01-09T02:27:19.738248477Z","created_by":"cyou"},{"issue_id":"mala-92r6.8","depends_on_id":"mala-92r6.7","type":"blocks","created_at":"2026-01-09T02:27:19.848432505Z","created_by":"cyou"}]}
{"id":"mala-92r6.9","title":"[T009] Update ReviewRunner + CerberusReviewer to accept CodeReviewConfig","description":"## Goal\nUpdate ReviewRunner and CerberusReviewer to accept CodeReviewConfig instead of individual CLI args.\n\n## Context\nMoving CLI options into config requires updating the review infrastructure to read from CodeReviewConfig rather than individual parameters.\n\n## Scope\n- **In**: Update interfaces, refactor parameter passing, unit tests\n- **Out**: CumulativeReviewRunner integration (that's T006-T008)\n\n## Changes\n- `src/pipeline/review_runner.py` — Exists — Accept CodeReviewConfig parameter\n- `src/infra/clients/cerberus_review.py` — Exists — Accept CerberusConfig\n- `tests/unit/pipeline/test_review_runner.py` — Exists — Update tests\n- `tests/unit/infra/test_cerberus_review.py` — Exists — Update tests\n\n## Interface Changes\n\n### ReviewRunner\n```python\n# Before (hypothetical):\nasync def run_review(self, diff: str, reviewer_type: str, max_retries: int, ...) -\u003e ReviewResult\n\n# After:\nasync def run_review(self, diff: str, config: CodeReviewConfig) -\u003e ReviewResult\n```\n\n### CerberusReviewer\n```python\n# Before:\ndef __init__(self, spawn_args: list[str], wait_args: list[str], env: dict, ...)\n\n# After:\ndef __init__(self, config: CerberusConfig)\n```\n\n## Backward Compatibility\n- Existing callers (per-issue review) must continue to work\n- May need adapter method during transition\n\n## Acceptance Criteria\n- [ ] ReviewRunner.run_review() accepts CodeReviewConfig\n- [ ] CerberusReviewer accepts CerberusConfig\n- [ ] reviewer_type field routes to correct reviewer implementation\n- [ ] max_retries is respected from config\n- [ ] Existing per-issue review flow still works\n- [ ] Unit tests updated for new interfaces\n\n## Verification\n```bash\nuv run pytest tests/unit/pipeline/test_review_runner.py -v\nuv run pytest tests/unit/infra/test_cerberus_review.py -v\nuvx ty check src/pipeline/review_runner.py src/infra/clients/cerberus_review.py\n```\n\n## Notes for Agent\n- Check existing interfaces before making changes\n- May need to support both old and new calling conventions\n- CerberusConfig uses tuple[str, ...] for args, not list[str]","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-09T02:25:37.777680337Z","created_by":"cyou","updated_at":"2026-01-09T02:34:22.219521212Z","closed_at":"2026-01-09T02:34:22.219521212Z","close_reason":"Not needed: CumulativeReviewRunner will construct DefaultReviewer directly from CerberusConfig. DefaultReviewer already accepts spawn_args/wait_args/env which map directly to CerberusConfig fields.","dependencies":[{"issue_id":"mala-92r6.9","depends_on_id":"mala-92r6","type":"parent-child","created_at":"2026-01-09T02:25:37.778250334Z","created_by":"cyou"},{"issue_id":"mala-92r6.9","depends_on_id":"mala-92r6.1","type":"blocks","created_at":"2026-01-09T02:27:19.961295269Z","created_by":"cyou"}]}
{"id":"mala-98c","title":"Redirect test logs to /tmp (mala runs + Claude SDK)","description":"## Context\nTests generate run metadata logs in ~/.config/mala/runs/, polluting the user's configuration directory with test artifacts. Additionally, Claude SDK writes session logs to ~/.claude/projects/, which also gets polluted during test runs.\n\n## Problem\nWhen running pytest:\n1. RunMetadata.save() writes to RUNS_DIR (~/.config/mala/runs/), creating hundreds of JSON files from test runs\n2. Claude SDK writes session logs to ~/.claude/projects/, creating test artifacts in the user's Claude config\n\n## Solution\nOverride both directories to point to /tmp locations when running tests:\n1. Add MALA_RUNS_DIR env var support in env.py (similar to MALA_LOCK_DIR)\n2. Add CLAUDE_CONFIG_DIR env var support for get_claude_log_path()\n3. Set both env vars in conftest.py pytest_configure()\n\n## Acceptance Criteria\n- Test runs do not create files in ~/.config/mala/runs/\n- Test runs do not create files in ~/.claude/projects/\n- Run logs go to /tmp/mala-test-runs/ or similar\n- Claude logs use /tmp/mala-test-claude/ or similar\n- Production behavior unchanged\n\n## Files to Modify\n- src/tools/env.py: Add MALA_RUNS_DIR env var support, add get_claude_config_dir() helper, update get_claude_log_path() to use it\n- tests/conftest.py: Set MALA_RUNS_DIR and CLAUDE_CONFIG_DIR to /tmp paths for all tests\n","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-27T06:15:51.085556202Z","updated_at":"2025-12-27T07:19:53.463304503Z","closed_at":"2025-12-27T07:19:53.463304503Z","close_reason":"Closed"}
{"id":"mala-99bh","title":"Epic: Decompose EpicVerifier mixed concerns","description":"## Summary\n`EpicVerifier` performs raw Git operations (`_compute_scoped_commits`), interacts with the LLM (`verify`), and manages issue lifecycle (`create_remediation_issues`, `verify_and_close_eligible`). The git logic is complex and buried within the infra class. Contains duplicated processing logic between `verify_and_close_eligible` and `verify_epic_with_options`.\n\n## Primary Files\n- `src/infra/epic_verifier.py:362-539`\n- `src/infra/epic_verifier.py:724-920`\n\n## Category\nBoundaries\n\n## Source\ngemini\n\n## Fix Approach\n1. Extract `EpicScopeAnalyzer` (Git operations) to `src/domain/epic/scope.py`\n2. Have `verify_and_close_eligible` delegate per-epic work to `verify_epic_with_options` in a loop\n3. Remove duplicated processing logic\n\n## Non-Goals\n- Changing LLM verification prompts\n- Modifying beads client interface\n\n## Acceptance Criteria\n- [ ] Git logic (`_compute_scoped_commits`, `_summarize_commit_range`) moved to domain layer\n- [ ] Shared verification logic exists in one location\n- [ ] Both public methods behave identically for overlapping cases\n\n## Test Plan\n- Unit tests for scope analysis on complex git histories\n- Integration test verifying epic verification flow\n\n## Agent Notes\nThe `verify_epic_with_options` was added later with additional parameters; ensure backward compatibility when consolidating.","status":"closed","priority":2,"issue_type":"epic","created_at":"2026-01-03T05:49:19.216905944Z","created_by":"cyou","updated_at":"2026-01-03T18:14:47.709187554Z","closed_at":"2026-01-03T18:14:47.709187554Z","close_reason":"Closed"}
{"id":"mala-99bh.1","title":"Extract EpicScopeAnalyzer for Git commit operations","description":"## Summary\nExtract Git commit-related operations from `EpicVerifier` into a new `EpicScopeAnalyzer` class in `src/domain/epic/scope.py`.\n\n## Problem\n`EpicVerifier` has mixed concerns - it performs raw Git operations alongside LLM interaction and issue lifecycle management. This violates single responsibility principle and makes the class harder to test and maintain.\n\n## Solution\nCreate `EpicScopeAnalyzer` class that encapsulates all Git commit operations:\n\n### Methods to extract from `EpicVerifier`:\n1. `_compute_scoped_commits(child_ids, blocker_ids)` - lines 922-972\n2. `_summarize_commit_range(commits)` - lines 974-1016\n3. `_format_commit_summary(commits, max_commits)` - lines 1018-1051\n\n### New class structure:\n```python\n@dataclass\nclass ScopedCommits:\n    \"\"\"Result of scoped commit analysis.\"\"\"\n    commit_shas: list[str]\n    commit_range: str | None  # e.g., \"abc123^..def456\"\n    commit_summary: str  # Formatted commit list\n\nclass EpicScopeAnalyzer:\n    \"\"\"Analyzes epic scope by computing related commits from child issues.\"\"\"\n    \n    def __init__(self, repo_path: Path, runner: CommandRunner | None = None):\n        ...\n    \n    async def compute_scoped_commits(\n        self, child_ids: set[str], blocker_ids: set[str] | None = None\n    ) -\u003e ScopedCommits:\n        \"\"\"Compute scoped commits with range and summary.\"\"\"\n        ...\n```\n\n## Files to modify\n- **Create**: `src/domain/epic/__init__.py` \n- **Create**: `src/domain/epic/scope.py` (new class)\n- **Create**: `tests/test_epic_scope.py` (unit tests)\n\n## Acceptance criteria\n- [ ] `EpicScopeAnalyzer` class created with all three methods\n- [ ] `ScopedCommits` dataclass created to hold results\n- [ ] Unit tests cover all three methods\n- [ ] No changes to `epic_verifier.py` yet (that's a follow-up task)","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T05:54:11.3785076Z","created_by":"cyou","updated_at":"2026-01-03T06:34:12.802030734Z","closed_at":"2026-01-03T06:34:12.802030734Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-99bh.1","depends_on_id":"mala-99bh","type":"parent-child","created_at":"2026-01-03T17:57:22.520338559Z","created_by":"daemon"}]}
{"id":"mala-99bh.2","title":"Integrate EpicScopeAnalyzer into EpicVerifier","description":"## Summary\nReplace inline Git operations in `EpicVerifier` with calls to the new `EpicScopeAnalyzer` class.\n\n## Problem\nAfter extracting `EpicScopeAnalyzer`, `EpicVerifier` still contains the original Git methods. Need to wire up the new class and remove duplicated code.\n\n## Solution\n1. Add `EpicScopeAnalyzer` as a dependency to `EpicVerifier`\n2. Update `_verify_epic_with_context` to use `EpicScopeAnalyzer.compute_scoped_commits()`\n3. Remove the following methods from `EpicVerifier`:\n   - `_compute_scoped_commits` (lines 922-972)\n   - `_summarize_commit_range` (lines 974-1016)\n   - `_format_commit_summary` (lines 1018-1051)\n\n### Code change in `_verify_epic_with_context`:\n```python\n# Before (lines 586-600):\ncommit_shas = await self._compute_scoped_commits(child_ids, blocker_ids)\nif not commit_shas:\n    return ...\ncommit_range = await self._summarize_commit_range(commit_shas)\ncommit_summary = await self._format_commit_summary(commit_shas)\n\n# After:\nscoped = await self.scope_analyzer.compute_scoped_commits(child_ids, blocker_ids)\nif not scoped.commit_shas:\n    return ...\n# Use scoped.commit_range, scoped.commit_summary directly\n```\n\n## Files to modify\n- **Modify**: `src/infra/epic_verifier.py` (remove 3 methods, add dependency, update caller)\n- **Modify**: `tests/test_epic_verifier.py` (update tests to mock `EpicScopeAnalyzer`)\n\n## Acceptance criteria\n- [ ] `EpicVerifier.__init__` accepts optional `EpicScopeAnalyzer` (default-constructed if not provided)\n- [ ] `_verify_epic_with_context` uses `EpicScopeAnalyzer` instead of inline Git calls\n- [ ] Three Git methods removed from `EpicVerifier`\n- [ ] All existing tests pass\n- [ ] Net reduction in `epic_verifier.py` line count (~130 lines removed)","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T05:54:31.398569966Z","created_by":"cyou","updated_at":"2026-01-03T06:43:49.648645317Z","closed_at":"2026-01-03T06:43:49.648645317Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-99bh.2","depends_on_id":"mala-99bh.1","type":"blocks","created_at":"2026-01-03T05:55:09.199244088Z","created_by":"daemon"},{"issue_id":"mala-99bh.2","depends_on_id":"mala-99bh","type":"parent-child","created_at":"2026-01-03T17:57:22.558263303Z","created_by":"daemon"}]}
{"id":"mala-99bh.3","title":"Refactor verify_and_close_eligible to delegate to verify_epic_with_options","description":"## Summary\nEliminate code duplication by having `verify_and_close_eligible` delegate per-epic verification work to `verify_epic_with_options`.\n\n## Problem\n`verify_and_close_eligible` (lines 362-539) and `verify_epic_with_options` (lines 724-920) contain nearly identical code for:\n- Human override handling with close logic\n- Lock acquisition/release\n- Verification event emission\n- Low confidence handling\n- Remediation issue creation\n- Epic close with failure handling\n\nThis duplication makes maintenance error-prone - bugs must be fixed in two places.\n\n## Solution\nRefactor `verify_and_close_eligible` to:\n1. Get eligible epics list\n2. Loop over epics and call `verify_epic_with_options` for each\n3. Aggregate results from individual calls\n\n### Simplified structure:\n```python\nasync def verify_and_close_eligible(\n    self,\n    human_override_epic_ids: set[str] | None = None,\n) -\u003e EpicVerificationResult:\n    human_override_epic_ids = human_override_epic_ids or set()\n    eligible_epics = await self._get_eligible_epics()\n    \n    # Aggregate results\n    all_verdicts: dict[str, EpicVerdict] = {}\n    all_remediation_issues: list[str] = []\n    verified_count = 0\n    passed_count = 0\n    failed_count = 0\n    human_review_count = 0\n    \n    for epic_id in eligible_epics:\n        is_override = epic_id in human_override_epic_ids\n        result = await self.verify_epic_with_options(\n            epic_id,\n            human_override=is_override,\n            require_eligible=False,  # Already filtered\n            close_epic=True,\n        )\n        # Aggregate result counts and verdicts\n        all_verdicts.update(result.verdicts)\n        all_remediation_issues.extend(result.remediation_issues_created)\n        verified_count += result.verified_count\n        passed_count += result.passed_count\n        failed_count += result.failed_count\n        human_review_count += result.human_review_count\n    \n    return EpicVerificationResult(\n        verified_count=verified_count,\n        passed_count=passed_count,\n        failed_count=failed_count,\n        human_review_count=human_review_count,\n        verdicts=all_verdicts,\n        remediation_issues_created=all_remediation_issues,\n    )\n```\n\n## Files to modify\n- **Modify**: `src/infra/epic_verifier.py` (rewrite `verify_and_close_eligible` to delegate)\n- **Modify**: `tests/test_epic_verifier.py` (verify behavior preserved)\n\n## Acceptance criteria\n- [ ] `verify_and_close_eligible` reduced to ~30 lines (from ~180 lines)\n- [ ] All existing behavior preserved (human override, locking, events, etc.)\n- [ ] All existing tests pass without modification\n- [ ] `verify_epic_with_options` is now the single source of truth for per-epic verification","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T05:54:56.753442266Z","created_by":"cyou","updated_at":"2026-01-03T06:47:58.906538989Z","closed_at":"2026-01-03T06:47:58.906538989Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-99bh.3","depends_on_id":"mala-99bh.2","type":"blocks","created_at":"2026-01-03T05:55:17.870104359Z","created_by":"daemon"},{"issue_id":"mala-99bh.3","depends_on_id":"mala-99bh","type":"parent-child","created_at":"2026-01-03T17:57:22.619851085Z","created_by":"daemon"}]}
{"id":"mala-99l1","title":"Epic: Claude Settings Configuration","description":"Enable users to configure which Claude Code settings sources (local/project/user) the SDK uses for mala validation runs.\n\n**Spec**: /home/cyou/mala/plans/2026-01-07-claude-settings-config-spec.md\n**Plan**: /home/cyou/mala/plans/2026-01-07-claude-settings-config-plan.md\n\n**Goals**:\n- Support configurable precedence (local/project/user) via mala.yaml, env var, or CLI flag\n- Default to [local, project] for reproducible validation across CI and dev machines\n- Validate source names at config parse time\n- Log resolved sources and missing file warnings\n\n**Acceptance Criteria**:\n- R1: Configure sources via mala.yaml/env/CLI with precedence: CLI \u003e Env \u003e YAML \u003e default\n- R2: Pass validated sources to Claude Agent SDK (merge delegation)\n- R3: Handle missing .claude/settings.local.json gracefully (log WARN, continue)\n- R4: Validate source names (local/project/user) at parse time with clear errors\n- R5: Log active sources at INFO level during agent startup","status":"closed","priority":1,"issue_type":"epic","created_at":"2026-01-07T04:01:26.670668711Z","created_by":"cyou","updated_at":"2026-01-08T14:58:59.567819802Z","closed_at":"2026-01-08T14:58:59.567819802Z","close_reason":"Closed"}
{"id":"mala-99l1.1","title":"[T001] Add shared validation constants for Claude settings sources","description":"**Type**: task\n**Priority**: P2\n**Story**: Foundation\n**Parallel**: [P]\n**Primary Files**: src/domain/validation/constants.py (New)\n**Subsystems**: domain\n**Dependencies**: None\n\n**Goal**:\nDefine shared constants for valid Claude settings sources and default values, used across all config layers.\n\n**Context**:\n- Need constants shared by ValidationConfig (domain), MalaConfig (infra), and validation functions\n- Prevents duplication and ensures consistency\n- Follows existing pattern of domain/validation/constants.py\n\n**Scope**:\n- In: Create constants file with VALID_CLAUDE_SETTINGS_SOURCES and DEFAULT_CLAUDE_SETTINGS_SOURCES\n- Out: No validation logic yet (added in subsequent tasks)\n\n**Changes**:\n- `src/domain/validation/constants.py` — New — Add:\n  - `VALID_CLAUDE_SETTINGS_SOURCES = frozenset({'local', 'project', 'user'})`\n  - `DEFAULT_CLAUDE_SETTINGS_SOURCES = ('local', 'project')`\n  - Module docstring explaining SDK source mapping\n\n**Acceptance Criteria**:\n- Constants module exists and exports VALID_CLAUDE_SETTINGS_SOURCES and DEFAULT_CLAUDE_SETTINGS_SOURCES\n- Imports cleanly from other modules without circular dependencies\n\n**Verification**:\n- Run: `python -c 'from src.domain.validation.constants import VALID_CLAUDE_SETTINGS_SOURCES, DEFAULT_CLAUDE_SETTINGS_SOURCES; print(VALID_CLAUDE_SETTINGS_SOURCES)'`\n- Verify frozenset contains exactly: {'local', 'project', 'user'}\n- Verify default tuple is ('local', 'project')\n\n**Notes for Agent**:\n- Use frozenset for immutability and O(1) membership checks\n- Add docstring explaining SDK source types: local=.claude/settings.local.json, project=.claude/settings.json, user=~/.claude/settings.json","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-07T04:02:04.884621215Z","created_by":"cyou","updated_at":"2026-01-07T17:59:37.81231992Z","closed_at":"2026-01-07T17:59:37.81231992Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-99l1.1","depends_on_id":"mala-99l1","type":"parent-child","created_at":"2026-01-07T04:02:04.885199841Z","created_by":"cyou"}]}
{"id":"mala-99l1.10","title":"[T010] Document claude_settings_sources in project-config.md","description":"**Type**: chore\n**Priority**: P3\n**Story**: Documentation\n**Parallel**: [P]\n**Primary Files**: docs/project-config.md\n**Subsystems**: docs\n**Dependencies**: T007\n\n**Goal**:\nDocument the new claude_settings_sources configuration field in project-config.md with examples, precedence, and migration guidance.\n\n**Context**:\n- Users need to understand the new configuration option\n- Must explain breaking change from ['project', 'user'] to ['local', 'project']\n- Should provide migration path for users relying on old behavior\n\n**Scope**:\n- In: Documentation for claude_settings_sources in mala.yaml, env var, CLI\n- Out: General Claude Code settings documentation (out of scope)\n\n**Changes**:\n- `docs/project-config.md` — Exists — Add section:\n  - **Location**: After existing configuration sections (e.g., after coverage, commands)\n  - **Title**: \"Claude Settings Sources\"\n  - **Content**:\n    - Overview: Control which Claude settings sources SDK uses\n    - Configuration methods: mala.yaml, env var, CLI flag\n    - Precedence: CLI \u003e Env Var \u003e mala.yaml \u003e default ['local', 'project']\n    - Valid sources: local, project, user (with file path mapping)\n    - Example mala.yaml snippet\n    - Example env var: MALA_CLAUDE_SETTINGS_SOURCES=local,project\n    - Example CLI: mala run --claude-settings-sources local,project\n    - Breaking change notice: default changed from ['project', 'user'] to ['local', 'project']\n    - Migration: set claude_settings_sources: [project, user] to restore old behavior\n    - Recommendation: commit .claude/settings.local.json for reproducible validation\n\n**Acceptance Criteria**:\n- Documentation section exists in docs/project-config.md\n- Covers all configuration methods (mala.yaml, env, CLI)\n- Explains precedence clearly\n- Includes examples for each method\n- Mentions breaking change and migration path\n\n**Verification**:\n- Read docs/project-config.md and verify:\n  - Section title \"Claude Settings Sources\" exists\n  - All configuration methods documented\n  - Precedence order stated clearly\n  - At least one example per configuration method\n  - Breaking change mentioned with migration path\n\n**Notes for Agent**:\n- Follow existing docs/project-config.md structure and style\n- Use code blocks for examples\n- Link to spec if helpful: plans/2026-01-07-claude-settings-config-spec.md\n- Keep it concise and practical","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-07T04:05:27.760430438Z","created_by":"cyou","updated_at":"2026-01-08T14:53:29.61639432Z","closed_at":"2026-01-08T14:53:29.61639432Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-99l1.10","depends_on_id":"mala-99l1","type":"parent-child","created_at":"2026-01-07T04:05:27.761156153Z","created_by":"cyou"},{"issue_id":"mala-99l1.10","depends_on_id":"mala-99l1.7","type":"blocks","created_at":"2026-01-07T04:06:34.786226967Z","created_by":"cyou"}]}
{"id":"mala-99l1.11","title":"[Review] 3 non-blocking findings from mala-99l1.3","description":"## Review Findings\n\nThis issue consolidates 3 non-blocking findings from code review.\n\n**Source issue:** mala-99l1.3\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Normalize None override for claude_settings_sources\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/infra/io/config.py:201-206\n\nThe task requires the constructor to accept an optional `claude_settings_sources: tuple[str, ...] | None` (so the orchestrator can pass `None` when mala.yaml doesn’t specify it). With the current dataclass field type/default, `MalaConfig(claude_settings_sources=None)` will persist `None` (no `__post_init__` normalization), which violates the intended defaulting behavior and can break downstream consumers and type checking.\n\nFix by either (a) using an `InitVar[tuple[str, ...] | None]` and setting the real field to `DEFAULT_CLAUDE_SETTINGS_SOURCES` when `None`, or (b) widening the field type to include `None` and normalizing it in `__post_init__`.\n\n---\n\n### Finding 2: [P2] Normalize claude_settings_sources to tuple in __post_init__\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/infra/io/config.py:211-215\n\nThe `MalaConfig` class is a `frozen` dataclass that follows a pattern of normalizing collection fields to immutable tuples in `__post_init__` (see `cerberus_spawn_args`). New field `claude_settings_sources` should be added to this normalization logic to ensure immutability even if a list is passed to the constructor.\n\n---\n\n### Finding 3: [P3] Document MALA_CLAUDE_SETTINGS_SOURCES in file and from_env docstrings\n\n**Priority:** P3\n**Reviewer:** gemini\n**Location:** src/infra/io/config.py:232-243\n\nThe new environment variable `MALA_CLAUDE_SETTINGS_SOURCES` is missing from the module-level docstring's 'Environment Variables' section and the `MalaConfig.from_env()` method docstring, which documents all other supported variables.\n\n---\n\n\u003c!-- fp:055a2fc8735ef2df --\u003e\n\u003c!-- fp:e729e171631f021a --\u003e\n\u003c!-- fp:961b671865577e1a --\u003e\n\u003c!-- review_finding:c29f83d17d70 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-07T18:13:07.304188065Z","created_by":"cyou","updated_at":"2026-01-07T18:21:13.699458072Z","closed_at":"2026-01-07T18:21:13.699458072Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-99l1.3"],"dependencies":[{"issue_id":"mala-99l1.11","depends_on_id":"mala-99l1","type":"parent-child","created_at":"2026-01-07T18:13:07.304745941Z","created_by":"cyou"}]}
{"id":"mala-99l1.12","title":"[Review] 1 non-blocking finding from mala-99l1.11","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-99l1.11\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Keep claude_settings_sources non-Optional post-normalization\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/infra/io/config.py:202-206\n\n`__post_init__` normalizes `claude_settings_sources=None` to `DEFAULT_CLAUDE_SETTINGS_SOURCES`, so the stored attribute is never `None` after construction. Keeping the field typed as `tuple[str, ...] | None` forces downstream code/type-checkers to treat it as nullable even when it can’t be in practice (e.g. `for s in config.claude_settings_sources:` now requires a None-guard).\n\nConsider switching to an init-only param (e.g. `InitVar[tuple[str, ...] | None]`) and keeping the actual dataclass field typed as `tuple[str, ...]` to match the post-init invariant.\n\n---\n\n\u003c!-- fp:70b5c457f43f34f4 --\u003e\n\u003c!-- review_finding:65f9cebd0ef3 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-07T18:21:13.990913292Z","created_by":"cyou","updated_at":"2026-01-07T18:39:10.757334994Z","closed_at":"2026-01-07T18:39:10.757334994Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-99l1.11"],"dependencies":[{"issue_id":"mala-99l1.12","depends_on_id":"mala-99l1","type":"parent-child","created_at":"2026-01-07T18:21:13.991472035Z","created_by":"cyou"}]}
{"id":"mala-99l1.13","title":"[Review] 1 non-blocking finding from mala-99l1.12","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-99l1.12\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Avoid exposing private `_claude_settings_sources_init` kwarg\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/infra/io/config.py:204-212\n\nThis change effectively renames the public constructor kwarg from `claude_settings_sources` to `_claude_settings_sources_init`, which reads as an internal/private detail and is now used by `from_env()` and tests. Even if this config is “internal”, the new API is surprising and makes programmatic construction less ergonomic.\n\nConsider renaming the InitVar to a public name (e.g. `claude_settings_sources_init`) and updating call sites, or (if you want to preserve the old kwarg) implementing a custom `__init__` so callers can still pass `claude_settings_sources=` while the stored field remains non-Optional.\n\n---\n\n\u003c!-- fp:64427fdc49b964ad --\u003e\n\u003c!-- review_finding:df28092c8819 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-07T18:39:11.094492691Z","created_by":"cyou","updated_at":"2026-01-07T18:43:02.076012876Z","closed_at":"2026-01-07T18:43:02.076012876Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-99l1.12"],"dependencies":[{"issue_id":"mala-99l1.13","depends_on_id":"mala-99l1","type":"parent-child","created_at":"2026-01-07T18:39:11.095062386Z","created_by":"cyou"}]}
{"id":"mala-99l1.14","title":"[Review] 3 non-blocking findings from mala-99l1.4","description":"## Review Findings\n\nThis issue consolidates 3 non-blocking findings from code review.\n\n**Source issue:** mala-99l1.4\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Avoid double-loading mala.yaml in create_orchestrator\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/orchestration/factory.py:486-490\n\n`create_orchestrator()` calls `load_config(config.repo_path)` to read `claude_settings_sources`, and later calls `_get_reviewer_config(config.repo_path)` which also calls `load_config()`. This adds redundant I/O/parsing and can yield inconsistent results if `mala.yaml` changes between reads.\n\nConsider loading `ValidationConfig` once and reusing it for both `yaml_claude_settings_sources` and reviewer settings (e.g., pass the already-loaded config into a helper).\n\n```py\nvalidation_config = load_config(config.repo_path)\nreviewer_config = _get_reviewer_config(config.repo_path)\n```\n\n---\n\n### Finding 2: [P2] Redundant loading of mala.yaml in create_orchestrator\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/orchestration/factory.py:489-507\n\nThe `create_orchestrator` function now calls `load_config` twice: once directly at the beginning to extract `claude_settings_sources`, and again indirectly through the call to `_get_reviewer_config`. This causes redundant I/O and parsing, and violates the principle of avoiding redundant reads stated in the module's comments.\n\n---\n\n### Finding 3: [P2] Resilience regression in mala.yaml loading\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/orchestration/factory.py:488-493\n\nThe new `load_config` call in `create_orchestrator` only catches `ConfigMissingError`, which means other `ConfigError` exceptions (such as syntax errors or schema violations) will now cause the application to crash during initialization. Previously, `_get_reviewer_config` caught all exceptions and logged a warning, allowing the orchestrator to proceed with defaults. The new wiring should be integrated with the existing resilient loading pattern.\n\n---\n\n\u003c!-- fp:2e304f99837a22fc --\u003e\n\u003c!-- fp:8a38713508cf6e03 --\u003e\n\u003c!-- fp:a50397239a7e1cbe --\u003e\n\u003c!-- review_finding:08c875c6ec35 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-07T19:54:39.271549653Z","created_by":"cyou","updated_at":"2026-01-07T20:02:00.215253741Z","closed_at":"2026-01-07T20:02:00.215253741Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-99l1.4"],"dependencies":[{"issue_id":"mala-99l1.14","depends_on_id":"mala-99l1","type":"parent-child","created_at":"2026-01-07T19:54:39.272144159Z","created_by":"cyou"}]}
{"id":"mala-99l1.15","title":"[Review] 3 non-blocking findings from mala-99l1.14","description":"## Review Findings\n\nThis issue consolidates 3 non-blocking findings from code review.\n\n**Source issue:** mala-99l1.14\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Preserve fallback-to-defaults on non-ConfigError mala.yaml read failures\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/orchestration/factory.py:497-517\n\n`load_config()` does a direct `read_text(encoding=\"utf-8\")`, which can raise `OSError` (permission issues, transient FS errors) or `UnicodeDecodeError`. `create_orchestrator()` only catches `ConfigMissingError`/`ConfigError`, so these cases now crash initialization instead of logging and proceeding with defaults (the previous reviewer-config path caught `Exception`).\n\nConsider catching additional read/decoding exceptions (or re-wrapping them inside `load_config()` as `ConfigError`) so orchestration stays resilient:\n```py\nexcept (ConfigError, OSError, UnicodeDecodeError) as e:\n    logger.warning(\"Failed to load mala.yaml config: %s\", e)\n```\n\n---\n\n### Finding 2: [P2] Keep _get_reviewer_config docs/behavior aligned for unexpected load failures\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/orchestration/factory.py:185-192\n\n`_get_reviewer_config()` claims it \"Returns defaults if config is missing or loading fails\", but it now only catches `ConfigError`. If `mala.yaml` exists but can’t be read/decoded, `load_config()` can raise non-`ConfigError` exceptions and this helper will raise instead of returning defaults.\n\nEither broaden the exception handling here as well, or ensure `load_config()` consistently wraps read/decoding failures into `ConfigError`.\n\n---\n\n### Finding 3: [P2] Resilience regression in mala.yaml loading\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/orchestration/factory.py:515-520\n\nThe refactoring in `create_orchestrator` and `_get_reviewer_config` narrows the exception handling from `Exception` to `ConfigError`. While this handles syntax and schema errors mentioned in Finding 3, it fails to fully 'integrate with the existing resilient loading pattern' which previously caught all exceptions. System-level issues like `PermissionError` during file reading (which do not inherit from `ConfigError`) will now cause the orchestrator to crash instead of falling back to defaults as it did before.\n\n---\n\n\u003c!-- fp:971e0ba532c84565 --\u003e\n\u003c!-- fp:3cfabec1511894ab --\u003e\n\u003c!-- fp:7fc7acee954ab59c --\u003e\n\u003c!-- review_finding:b6cc3ff61dc0 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-07T20:02:00.503275881Z","created_by":"cyou","updated_at":"2026-01-07T20:14:20.3361839Z","closed_at":"2026-01-07T20:14:20.3361839Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-99l1.14"],"dependencies":[{"issue_id":"mala-99l1.15","depends_on_id":"mala-99l1","type":"parent-child","created_at":"2026-01-07T20:02:00.503832927Z","created_by":"cyou"}]}
{"id":"mala-99l1.16","title":"[Review] 2 non-blocking findings from mala-99l1.9","description":"## Review Findings\n\nThis issue consolidates 2 non-blocking findings from code review.\n\n**Source issue:** mala-99l1.9\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Restore assertion that SDK `cwd` matches test repo path\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** tests/integration/pipeline/test_agent_session_runner.py:4378-4410\n\nThe updated test no longer asserts that `options.cwd` equals `session_config_with_local_settings.repo_path` (the old assertion was removed). Without that, the new check only proves that *some* `.claude/settings.local.json` exists at whatever `options.cwd` happens to be, which can still pass even if the runner points the SDK at the wrong directory. This undermines the intended end-to-end path validation (config → builder → SDK options) and makes the test less discriminating.\n\nConcrete issue in diff: the old `assert str(options.cwd) == str(session_config_with_local_settings.repo_path)` was replaced with existence/content checks under `Path(options.cwd)`.\n\nRecommended fix: re-add the direct equality assertion (or equivalent `Path(options.cwd).resolve() == repo_path.resolve()`), and keep the file content assertion as an additional guard if desired.\n\n---\n\n### Finding 2: [P2] Test does not meet acceptance: does not verify SDK init timeout=300\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** tests/integration/pipeline/test_agent_session_runner.py:4264-4410\n\nTask acceptance criteria explicitly says to “Verify SDK client was initialized with timeout=300 (not default)”. The new assertions only read `timeout` from the settings JSON file and conclude “SDK would read” it, but the test does not assert any SDK client initialization parameter (e.g., `timeout`) or any observable behavior of the SDK/client reflecting the merged timeout.\n\nAs written, this can pass even if the SDK is initialized with a default timeout and ignores the file at runtime, because the test never checks the client’s configured timeout.\n\nRecommended fix: capture the SDK client initialization arguments (or the constructed client/options field actually used to set timeout) and assert the value is `300`, rather than inferring from file contents.\n\n---\n\n\u003c!-- fp:a29e55109ac6c7d7 --\u003e\n\u003c!-- fp:591d501c33342332 --\u003e\n\u003c!-- review_finding:61108a883bb8 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-08T14:39:39.361664728Z","created_by":"cyou","updated_at":"2026-01-08T14:44:24.80111323Z","closed_at":"2026-01-08T14:44:24.80111323Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-99l1.9"],"dependencies":[{"issue_id":"mala-99l1.16","depends_on_id":"mala-99l1","type":"parent-child","created_at":"2026-01-08T14:39:39.362323281Z","created_by":"cyou"}]}
{"id":"mala-99l1.17","title":"[Review] 1 non-blocking finding from mala-99l1.16","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-99l1.16\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Test still fails to verify SDK timeout initialization\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** tests/integration/pipeline/test_agent_session_runner.py:4392-4403\n\nThe finding explicitly requested verifying the SDK client was initialized with `timeout=300` rather than inferring it from file contents. The current implementation only asserts the existence of a configuration file and relies on the assumption that the SDK will correctly read it. If the SDK is actually initialized with a default timeout or ignores the file, this test will still pass. To meet acceptance, the test should capture the arguments passed to the SDK client constructor or verify the property on the resulting client/options object if the SDK supports it.\n\n---\n\n\u003c!-- fp:f67e0360361e8180 --\u003e\n\u003c!-- review_finding:84fd76e59fc0 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-08T14:44:25.089670497Z","created_by":"cyou","updated_at":"2026-01-08T14:50:39.610089608Z","closed_at":"2026-01-08T14:50:39.610089608Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-99l1.16"],"dependencies":[{"issue_id":"mala-99l1.17","depends_on_id":"mala-99l1","type":"parent-child","created_at":"2026-01-08T14:44:25.090200852Z","created_by":"cyou"}]}
{"id":"mala-99l1.18","title":"[Review] 2 non-blocking findings from mala-99l1.17","description":"## Review Findings\n\nThis issue consolidates 2 non-blocking findings from code review.\n\n**Source issue:** mala-99l1.17\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Still doesn’t verify SDK client timeout=300 initialization\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** tests/integration/pipeline/test_agent_session_runner.py:4400-4445\n\nThe original finding asked to verify the SDK client is initialized with `timeout=300` (e.g., by capturing constructor args or inspecting the created client/options). This diff instead verifies that the CLI command contains `--setting-sources local`, which is orthogonal: the SDK could include that flag and still initialize the client with a default timeout or ignore the file.\n\nActionable fix: patch/mock the SDK client constructor (or wherever timeout is bound) in this test and assert the actual `timeout` argument/property equals `300`.\n\n---\n\n### Finding 2: [P3] Avoid asserting on private `_internal` transport and `_build_command`\n\n**Priority:** P3\n**Reviewer:** codex\n**Location:** tests/integration/pipeline/test_agent_session_runner.py:4421-4445\n\nThis test now imports `claude_agent_sdk._internal.transport.subprocess_cli.SubprocessCLITransport` and calls `transport._build_command()`. Both the module path (`_internal`) and method (`_build_command`) are private and may change without warning, creating unnecessary test brittleness unrelated to the behavioral guarantee under test.\n\nPrefer asserting via a public API surface (or patching the subprocess invocation layer that’s already used) so the test tracks externally stable behavior.\n\n---\n\n\u003c!-- fp:fe022266b2860bc2 --\u003e\n\u003c!-- fp:dc80c1ce8cd25117 --\u003e\n\u003c!-- review_finding:91165eae9c8e --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-08T14:50:39.896689075Z","created_by":"cyou","updated_at":"2026-01-08T14:54:48.218843417Z","closed_at":"2026-01-08T14:54:48.218843417Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-99l1.17"],"dependencies":[{"issue_id":"mala-99l1.18","depends_on_id":"mala-99l1","type":"parent-child","created_at":"2026-01-08T14:50:39.897209881Z","created_by":"cyou"}]}
{"id":"mala-99l1.2","title":"[T002] [integration-path-test] ValidationConfig skeleton + integration test for claude_settings_sources","description":"**Type**: task\n**Priority**: P2\n**Story**: Domain Layer\n**Parallel**: \n**Primary Files**: src/domain/validation/config.py, tests/unit/domain/test_validation_config.py\n**Subsystems**: domain\n**Dependencies**: T001\n\n**Goal**:\nAdd claude_settings_sources field to ValidationConfig with validation, and create failing integration test that exercises mala.yaml → ValidationConfig → MalaConfig path.\n\n**Context**:\n- ValidationConfig is the domain model for mala.yaml\n- Must validate sources are in {'local', 'project', 'user'}\n- Integration test must traverse the full composition path: mala.yaml parsing → ValidationConfig → factory/orchestrator → MalaConfig\n\n**Scope**:\n- In: Add field to ValidationConfig, write failing integration test for full config path\n- Out: Implementation of infra/CLI layers (covered by later tasks)\n\n**Changes**:\n- `src/domain/validation/config.py` — Exists — Add:\n  - `claude_settings_sources: tuple[str, ...] | None = None` field\n  - Validation in from_dict(): check each source in VALID_CLAUDE_SETTINGS_SOURCES\n  - Error format: \"Invalid Claude settings source 'foo'. Valid sources: local, project, user\"\n- `tests/unit/domain/test_validation_config.py` — Exists — Add failing integration test:\n  - Test name: `test_claude_settings_sources_full_path_integration`\n  - Create temp mala.yaml with `claude_settings_sources: [local, project]`\n  - Load via ValidationConfig.from_dict()\n  - Pass to factory/orchestrator (stub for now)\n  - Verify MalaConfig receives correct sources (will fail until T004)\n\n**Wiring Map**:\n- `mala.yaml:claude_settings_sources` → `ValidationConfig.claude_settings_sources` → `orchestrator/factory` → `MalaConfig` → `ResolvedConfig` → `AgentRuntimeBuilder`\n\n**Integration Path Test**:\nThis task includes the integration-path test that verifies the full mala.yaml → ValidationConfig → orchestrator → MalaConfig construction path.\n\n**Acceptance Criteria**:\n- ValidationConfig has claude_settings_sources field (tuple[str, ...] | None)\n- from_dict() rejects invalid sources with clear error message\n- from_dict() accepts valid sources: [local], [project], [user], [local, project], empty list []\n- Integration test exists and FAILS with expected message (orchestrator wiring not yet implemented)\n\n**Verification**:\n- Unit tests: Run `uv run pytest tests/unit/domain/test_validation_config.py -k claude_settings`\n- Valid sources: ['local'], ['project'], ['user'], ['local', 'project'], []\n- Invalid source: ['foo'] → ConfigError with \"Invalid Claude settings source 'foo'. Valid sources: local, project, user\"\n- Integration test runs and fails for expected reason (not import error)\n\n**Notes for Agent**:\n- Use VALID_CLAUDE_SETTINGS_SOURCES from T001\n- None means \"not specified in mala.yaml, use default\"\n- Empty list [] is valid (passed to SDK as-is)\n- Forgiving parsing: strip whitespace from source names","notes":"Quality gate failed: External review failed: tests/unit/domain/test_validation_config.py:839: [P1] Make integration-path test actually fail (don’t xfail it); tests/unit/cli/test_cli.py:1834: [P1] Missing implementation of _apply_config_overrides signature change; src/domain/validation/config.py:1044: [P1] ValidationConfig.from_dict fails to pass validation_triggers to constructor\nLog: /home/cyou/.claude/projects/-home-cyou-mala/a63e0eb9-cf98-4efc-91f7-cc326eb00159.jsonl","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-07T04:02:28.6950142Z","created_by":"cyou","updated_at":"2026-01-07T19:07:06.334155137Z","closed_at":"2026-01-07T19:07:06.334155137Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-99l1.2","depends_on_id":"mala-99l1","type":"parent-child","created_at":"2026-01-07T04:02:28.695615206Z","created_by":"cyou"},{"issue_id":"mala-99l1.2","depends_on_id":"mala-99l1.1","type":"blocks","created_at":"2026-01-07T04:05:38.57935762Z","created_by":"cyou"}]}
{"id":"mala-99l1.3","title":"[T003] Add claude_settings_sources to MalaConfig with env var parsing","description":"**Type**: task\n**Priority**: P2\n**Story**: Infra Layer\n**Parallel**:\n**Primary Files**: src/infra/io/config.py, tests/unit/infra/test_config.py\n**Subsystems**: infra\n**Dependencies**: T001\n\n**Goal**:\nAdd claude_settings_sources field to MalaConfig with environment variable parsing (MALA_CLAUDE_SETTINGS_SOURCES) and validation. Update constructor to accept optional parameter from orchestrator.\n\n**Context**:\n- MalaConfig is the infra-layer config loaded via from_env()\n- Must parse comma-separated env var: MALA_CLAUDE_SETTINGS_SOURCES=local,project,user\n- Must accept parameter from orchestrator to support mala.yaml precedence\n- Default to ('local', 'project') if not configured\n\n**Scope**:\n- In: MalaConfig field, env var parsing, constructor parameter, unit tests\n- Out: Orchestrator integration (T004), CLI parsing (T005), resolution logic (T006)\n\n**Changes**:\n- `src/infra/io/config.py` — Exists — MalaConfig changes:\n  - Add field: `claude_settings_sources: tuple[str, ...] = DEFAULT_CLAUDE_SETTINGS_SOURCES`\n  - Update __init__() to accept optional `claude_settings_sources: tuple[str, ...] | None` parameter\n  - Update from_env():\n    - Parse MALA_CLAUDE_SETTINGS_SOURCES (split by comma, strip whitespace, filter empty)\n    - Validate sources using VALID_CLAUDE_SETTINGS_SOURCES\n    - Error format: \"MALA_CLAUDE_SETTINGS_SOURCES: Invalid source 'foo'. Valid sources: local, project, user\"\n- `tests/unit/infra/test_config.py` — Exists — Add unit tests:\n  - Parse env var: MALA_CLAUDE_SETTINGS_SOURCES=local,project → ('local', 'project')\n  - Filter empty parts: local,,project → ('local', 'project')\n  - Reject invalid source: foo → ConfigurationError\n  - Default when not set: ('local', 'project')\n  - Constructor accepts override: MalaConfig(claude_settings_sources=('user',))\n\n**Acceptance Criteria**:\n- MalaConfig has claude_settings_sources field with default ('local', 'project')\n- from_env() parses MALA_CLAUDE_SETTINGS_SOURCES correctly\n- Constructor accepts optional claude_settings_sources parameter\n- Invalid sources rejected at parse time with clear error\n\n**Verification**:\n- Unit tests: Run `uv run pytest tests/unit/infra/test_config.py -k claude_settings`\n- Test env var parsing: MALA_CLAUDE_SETTINGS_SOURCES=local,project → ('local', 'project')\n- Test invalid source: MALA_CLAUDE_SETTINGS_SOURCES=foo → ConfigurationError\n- Test default: no env var → ('local', 'project')\n- Test constructor override: MalaConfig(claude_settings_sources=('user',))\n\n**Config Override Test**:\nAt least one test must verify env var override reaches MalaConfig via from_env() (not constructing MalaConfig directly).\n\n**Notes for Agent**:\n- Use DEFAULT_CLAUDE_SETTINGS_SOURCES from T001\n- Forgiving parsing: filter empty parts from comma-separated string\n- Constructor parameter enables orchestrator to pass mala.yaml value","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-07T04:02:52.701365629Z","created_by":"cyou","updated_at":"2026-01-07T18:13:07.005258019Z","closed_at":"2026-01-07T18:13:07.005258019Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-99l1.3","depends_on_id":"mala-99l1","type":"parent-child","created_at":"2026-01-07T04:02:52.702184913Z","created_by":"cyou"},{"issue_id":"mala-99l1.3","depends_on_id":"mala-99l1.1","type":"blocks","created_at":"2026-01-07T04:05:43.687011768Z","created_by":"cyou"}]}
{"id":"mala-99l1.4","title":"[T004] Wire ValidationConfig.claude_settings_sources to MalaConfig (orchestrator integration)","description":"**Type**: task\n**Priority**: P2\n**Story**: Orchestrator Layer\n**Parallel**:\n**Primary Files**: src/infra/io/config.py (or orchestrator/factory file)\n**Subsystems**: infra\n**Dependencies**: T002, T003\n\n**Goal**:\nUpdate orchestrator/factory to load ValidationConfig.claude_settings_sources and pass to MalaConfig constructor, implementing mala.yaml precedence over env var default.\n\n**Context**:\n- Bridges domain (ValidationConfig) → infra (MalaConfig)\n- Implements precedence: Env Var \u003e mala.yaml \u003e default\n- After this task, T002's integration test should pass\n\n**Scope**:\n- In: Orchestrator logic to pass ValidationConfig value to MalaConfig\n- Out: CLI resolution (T005, T006), runtime wiring (T007)\n\n**Changes**:\n- Identify factory/orchestrator location (likely in src/infra/io/config.py or src/orchestration/)\n- Update factory logic:\n  - Load ValidationConfig from mala.yaml\n  - If ValidationConfig.claude_settings_sources is not None:\n    - Pass to MalaConfig constructor as `claude_settings_sources=validation_config.claude_settings_sources`\n  - This overrides env var default if mala.yaml specifies sources\n- Update or add unit test verifying precedence: env var \u003e mala.yaml \u003e default\n\n**Wiring Map**:\n- `ValidationConfig.claude_settings_sources` → `orchestrator/factory` → `MalaConfig(claude_settings_sources=...)`\n\n**Acceptance Criteria**:\n- Orchestrator/factory loads ValidationConfig.claude_settings_sources\n- If present (not None), passes to MalaConfig constructor\n- Precedence verified: env var overrides mala.yaml, mala.yaml overrides default\n- T002's integration test now PASSES\n\n**Verification**:\n- Integration test from T002: Run `uv run pytest tests/unit/domain/test_validation_config.py::test_claude_settings_sources_full_path_integration` → PASS\n- Test precedence scenarios:\n  - mala.yaml=[local], env var not set → MalaConfig has ('local',)\n  - mala.yaml=[local], env var=project → MalaConfig has ('project',) (env wins)\n  - mala.yaml not set, env var not set → MalaConfig has ('local', 'project') (default)\n\n**Config Override Test**:\nVerify mala.yaml override reaches MalaConfig via orchestrator (not by constructing objects directly).\n\n**Notes for Agent**:\n- Find where ValidationConfig is loaded and MalaConfig is constructed\n- Likely in factory functions or orchestrator wiring code\n- Only pass ValidationConfig value if it's not None\n- Make T002's integration test pass after this change","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-07T04:03:13.809065428Z","created_by":"cyou","updated_at":"2026-01-07T19:54:38.982903797Z","closed_at":"2026-01-07T19:54:38.982903797Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-99l1.4","depends_on_id":"mala-99l1","type":"parent-child","created_at":"2026-01-07T04:03:13.809809743Z","created_by":"cyou"},{"issue_id":"mala-99l1.4","depends_on_id":"mala-99l1.2","type":"blocks","created_at":"2026-01-07T04:05:48.796520578Z","created_by":"cyou"},{"issue_id":"mala-99l1.4","depends_on_id":"mala-99l1.3","type":"blocks","created_at":"2026-01-07T04:05:53.905930851Z","created_by":"cyou"}]}
{"id":"mala-99l1.5","title":"[T005] Add --claude-settings-sources CLI flag to run and epic_verify","description":"**Type**: task\n**Priority**: P2\n**Story**: CLI Layer\n**Parallel**: [P]\n**Primary Files**: src/cli/cli.py\n**Subsystems**: cli\n**Dependencies**: T003\n\n**Goal**:\nAdd --claude-settings-sources CLI flag to run() and epic_verify() commands, capturing raw comma-separated string in CLIOverrides.\n\n**Context**:\n- CLI provides highest precedence for configuration\n- Flags added to both run() and epic_verify() for consistency\n- Raw string captured in CLIOverrides, parsing happens in build_resolved_config() (T006)\n\n**Scope**:\n- In: CLI flag definition, CLIOverrides field\n- Out: Parsing and resolution logic (T006)\n\n**Changes**:\n- `src/cli/cli.py` — Exists — Add to both run() and epic_verify():\n  - CLI option: `--claude-settings-sources TEXT`\n  - Type: `str | None`\n  - Help text: \"Comma-separated list of Claude settings sources (local, project, user). Default: local,project\"\n  - Pass raw value to CLIOverrides\n- `src/infra/io/config.py` — Exists — Add to CLIOverrides:\n  - Field: `claude_settings_sources: str | None = None`\n\n**Acceptance Criteria**:\n- run() command accepts --claude-settings-sources flag\n- epic_verify() command accepts --claude-settings-sources flag\n- CLIOverrides has claude_settings_sources field\n- Help text appears in --help output for both commands\n\n**Verification**:\n- Run: `mala run --help | grep claude-settings-sources` → shows help text\n- Run: `mala epic-verify --help | grep claude-settings-sources` → shows help text\n- Manual test: `mala run --claude-settings-sources local,project --help` → no parse error (validation in T006)\n\n**Notes for Agent**:\n- Follow existing CLI flag patterns in cli.py\n- Use Typer's Annotated with Option for flag definition\n- Raw string, no parsing yet (T006 handles parsing)\n- Apply to BOTH run() and epic_verify() commands","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-07T04:03:34.31935221Z","created_by":"cyou","updated_at":"2026-01-07T18:40:00.465175894Z","closed_at":"2026-01-07T18:40:00.465175894Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-99l1.5","depends_on_id":"mala-99l1","type":"parent-child","created_at":"2026-01-07T04:03:34.320041005Z","created_by":"cyou"},{"issue_id":"mala-99l1.5","depends_on_id":"mala-99l1.3","type":"blocks","created_at":"2026-01-07T04:05:59.015234009Z","created_by":"cyou"}]}
{"id":"mala-99l1.6","title":"[T006] Implement config resolution in build_resolved_config with CLI precedence","description":"**Type**: task\n**Priority**: P2\n**Story**: Config Resolution\n**Parallel**:\n**Primary Files**: src/infra/io/config.py, tests/unit/infra/test_config.py\n**Subsystems**: infra\n**Dependencies**: T004, T005\n\n**Goal**:\nAdd claude_settings_sources to ResolvedConfig and implement precedence resolution in build_resolved_config(): CLI \u003e Env Var \u003e mala.yaml \u003e default.\n\n**Context**:\n- ResolvedConfig holds final merged configuration\n- build_resolved_config() merges base_config (MalaConfig) with cli_overrides (CLIOverrides)\n- Must parse and validate CLI string here\n\n**Scope**:\n- In: ResolvedConfig field, resolution logic, CLI parsing helper, unit tests\n- Out: Runtime wiring (T007)\n\n**Changes**:\n- `src/infra/io/config.py` — Exists — Changes:\n  - Add to ResolvedConfig: `claude_settings_sources: tuple[str, ...]`\n  - Add helper function: `_parse_claude_settings_sources(raw: str) -\u003e tuple[str, ...]`\n    - Split by comma, strip whitespace, filter empty parts\n    - Validate using VALID_CLAUDE_SETTINGS_SOURCES\n    - Error format: \"CLI: Invalid source 'foo'. Valid sources: local, project, user\"\n  - Update build_resolved_config():\n    - If cli_overrides.claude_settings_sources is not None: parse and use\n    - Else: use base_config.claude_settings_sources (already has Env \u003e mala.yaml \u003e default)\n- `tests/unit/infra/test_config.py` — Exists — Add tests:\n  - CLI precedence: CLI overrides env var and mala.yaml\n  - Env precedence: env var overrides mala.yaml\n  - Default: no overrides → ('local', 'project')\n  - Parse CLI value: \"local,project,user\" → ('local', 'project', 'user')\n  - Invalid CLI source: raises ValueError with clear message\n\n**Acceptance Criteria**:\n- ResolvedConfig has claude_settings_sources field\n- build_resolved_config() implements correct precedence: CLI \u003e Env \u003e YAML \u003e default\n- Invalid CLI source rejected with clear error\n- All precedence tests pass\n\n**Verification**:\n- Unit tests: Run `uv run pytest tests/unit/infra/test_config.py -k resolved_config_claude_settings`\n- Test scenarios:\n  - CLI=\"local\", Env=\"project\", YAML=\"user\" → ResolvedConfig has ('local',) (CLI wins)\n  - CLI=None, Env=\"project\", YAML=\"user\" → ResolvedConfig has ('project',) (Env wins)\n  - CLI=None, Env=None, YAML=\"user\" → ResolvedConfig has ('user',) (YAML wins)\n  - CLI=None, Env=None, YAML=None → ResolvedConfig has ('local', 'project') (default)\n- Invalid source: CLI=\"foo\" → ValueError with \"CLI: Invalid source 'foo'. Valid sources: local, project, user\"\n\n**Config Override Test**:\nAt least one test must verify CLI override reaches ResolvedConfig via build_resolved_config() with real CLIOverrides (not mocking).\n\n**Notes for Agent**:\n- Final precedence chain: CLI \u003e (Env \u003e mala.yaml \u003e default)\n- base_config already has Env \u003e mala.yaml \u003e default applied (from T003 and T004)\n- Only parse CLI string here, validation happens in helper function","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-07T04:04:01.226341019Z","created_by":"cyou","updated_at":"2026-01-07T20:15:08.04699806Z","closed_at":"2026-01-07T20:15:08.04699806Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-99l1.6","depends_on_id":"mala-99l1","type":"parent-child","created_at":"2026-01-07T04:04:01.227023924Z","created_by":"cyou"},{"issue_id":"mala-99l1.6","depends_on_id":"mala-99l1.4","type":"blocks","created_at":"2026-01-07T04:06:04.131077237Z","created_by":"cyou"},{"issue_id":"mala-99l1.6","depends_on_id":"mala-99l1.5","type":"blocks","created_at":"2026-01-07T04:06:09.240886988Z","created_by":"cyou"}]}
{"id":"mala-99l1.7","title":"[T007] Add setting_sources to AgentRuntimeBuilder with logging and file checks","description":"**Type**: task\n**Priority**: P2\n**Story**: Runtime Layer\n**Parallel**:\n**Primary Files**: src/infra/agent_runtime.py, tests/unit/infra/test_agent_runtime.py\n**Subsystems**: infra\n**Dependencies**: T006\n\n**Goal**:\nAdd setting_sources parameter to AgentRuntimeBuilder, log resolved sources at INFO level, check .claude/settings.local.json existence, and pass to SDK adapter.\n\n**Context**:\n- AgentRuntimeBuilder creates agent runtime with SDK client\n- Must log final resolved sources before SDK initialization\n- Must warn if .claude/settings.local.json missing when \"local\" in sources\n- Pass sources to sdk_client_factory.create_options()\n\n**Scope**:\n- In: AgentRuntimeBuilder parameter, logging, file existence check, SDK wiring\n- Out: SDK adapter default change (T008), integration tests (T009)\n\n**Changes**:\n- `src/infra/agent_runtime.py` — Exists — Changes:\n  - Add to AgentRuntimeBuilder.__init__(): `setting_sources: list[str] | None = None`\n  - Store as `self._setting_sources`\n  - In build() method (before SDK client creation):\n    1. Log INFO: \"Claude settings sources: {', '.join(sources)}\" (use resolved sources)\n    2. If \"local\" in sources and `.claude/settings.local.json` does not exist:\n       - Log WARN: \"Claude settings file .claude/settings.local.json not found (will be skipped)\"\n    3. Pass `setting_sources=self._setting_sources` to sdk_client_factory.create_options()\n- `tests/unit/infra/test_agent_runtime.py` — New or Exists — Add unit tests:\n  - Test INFO log with sources\n  - Test WARN log when local file missing\n  - Test no WARN when local file exists\n  - Test sources passed to SDK adapter\n\n**Wiring Map**:\n- `ResolvedConfig.claude_settings_sources` → `orchestrator` → `AgentRuntimeBuilder(setting_sources=...)` → `sdk_client_factory.create_options(setting_sources=...)`\n\n**Acceptance Criteria**:\n- AgentRuntimeBuilder accepts setting_sources parameter\n- INFO log appears with resolved sources before SDK initialization\n- WARN log appears when .claude/settings.local.json missing and \"local\" in sources\n- setting_sources passed to sdk_client_factory.create_options()\n\n**Verification**:\n- Unit tests: Run `uv run pytest tests/unit/infra/test_agent_runtime.py -k setting_sources`\n- Test logging (use caplog fixture):\n  - sources=['local', 'project'] → INFO log \"Claude settings sources: local, project\"\n  - sources=['local'], file missing → WARN log appears\n  - sources=['local'], file exists → no WARN log\n- Test SDK adapter receives sources (mock sdk_client_factory)\n\n**Notes for Agent**:\n- Convert tuple to list when passing to builder: ('local', 'project') → ['local', 'project']\n- Log before SDK client creation (early in build() method)\n- Use Path(\".claude/settings.local.json\").exists() for file check\n- Only check .claude/settings.local.json (not project/user files)","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-07T04:04:27.103567696Z","created_by":"cyou","updated_at":"2026-01-07T20:29:21.404109935Z","closed_at":"2026-01-07T20:29:21.404109935Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-99l1.7","depends_on_id":"mala-99l1","type":"parent-child","created_at":"2026-01-07T04:04:27.10438427Z","created_by":"cyou"},{"issue_id":"mala-99l1.7","depends_on_id":"mala-99l1.6","type":"blocks","created_at":"2026-01-07T04:06:14.347456403Z","created_by":"cyou"}]}
{"id":"mala-99l1.8","title":"[T008] Change SDK adapter default from ['project', 'user'] to ['local', 'project']","description":"**Type**: task\n**Priority**: P2\n**Story**: SDK Adapter\n**Parallel**: [P]\n**Primary Files**: src/infra/sdk_adapter.py, tests/unit/infra/test_sdk_adapter.py\n**Subsystems**: infra\n**Dependencies**: T007\n\n**Goal**:\nChange SDKClientFactory default setting_sources from [\"project\", \"user\"] to [\"local\", \"project\"] to prioritize validation-specific settings.\n\n**Context**:\n- Breaking change: users relying on global user settings will need to opt-in\n- Improves reproducibility by defaulting to local validation settings\n- SDK adapter already accepts setting_sources parameter (from plan verification)\n\n**Scope**:\n- In: Default value change, unit tests\n- Out: User documentation (T010)\n\n**Changes**:\n- `src/infra/sdk_adapter.py` — Exists — Change default:\n  - Find: `setting_sources or [\"project\", \"user\"]`\n  - Replace with: `setting_sources or [\"local\", \"project\"]`\n- `tests/unit/infra/test_sdk_adapter.py` — New or Exists — Add/update tests:\n  - Test default: no setting_sources → SDK gets [\"local\", \"project\"]\n  - Test override: setting_sources=[\"user\"] → SDK gets [\"user\"]\n\n**Acceptance Criteria**:\n- SDKClientFactory.create_options() uses [\"local\", \"project\"] as default\n- Unit tests verify new default\n- Override still works: passing explicit sources uses them instead of default\n\n**Verification**:\n- Unit tests: Run `uv run pytest tests/unit/infra/test_sdk_adapter.py -k setting_sources`\n- Test default: create_options(setting_sources=None) → ClaudeAgentOptions has setting_sources=[\"local\", \"project\"]\n- Test override: create_options(setting_sources=[\"user\"]) → ClaudeAgentOptions has setting_sources=[\"user\"]\n\n**Notes for Agent**:\n- This is a BREAKING CHANGE (intentional)\n- Migration path: users can set claude_settings_sources: [project, user] in mala.yaml to restore old behavior\n- Update docstring to reflect new default","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-07T04:04:47.039654798Z","created_by":"cyou","updated_at":"2026-01-07T18:15:57.936599203Z","closed_at":"2026-01-07T18:15:57.936599203Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-99l1.8","depends_on_id":"mala-99l1","type":"parent-child","created_at":"2026-01-07T04:04:47.040404563Z","created_by":"cyou"}]}
{"id":"mala-99l1.9","title":"[T009] Add integration test for SDK settings merge with .claude/settings.local.json","description":"**Type**: task\n**Priority**: P2\n**Story**: Integration Testing\n**Parallel**:\n**Primary Files**: tests/integration/pipeline/test_agent_session_runner.py\n**Subsystems**: tests\n**Dependencies**: T007, T008\n\n**Goal**:\nAdd integration test verifying that SDK respects settings from .claude/settings.local.json when configured with [\"local\"] sources.\n\n**Context**:\n- Integration test exercises full path: config → AgentRuntimeBuilder → SDK → agent behavior\n- Must verify SDK merge behavior (not just that sources are passed)\n- Use timeout value as observable behavior (easy to verify)\n\n**Scope**:\n- In: Integration test for SDK settings merge\n- Out: E2E tests (not required per plan), unit tests (covered by other tasks)\n\n**Changes**:\n- `tests/integration/pipeline/test_agent_session_runner.py` — Exists — Add test:\n  - Test name: `test_agent_session_runner_respects_local_settings`\n  - Setup:\n    1. Create temp .claude/settings.local.json with {\"timeout\": 300}\n    2. Configure mala to use sources=[\"local\"]\n    3. Run lightweight agent task/verification\n  - Assertion:\n    - Verify SDK client was initialized with timeout=300 (not default)\n    - Confirms SDK respects settings merge from local file\n\n**Acceptance Criteria**:\n- Integration test exists and passes\n- Test creates .claude/settings.local.json with distinct timeout\n- Test configures sources=[\"local\"]\n- Test verifies SDK client has timeout from local file (not default)\n- Test cleanup: remove temp settings file\n\n**Verification**:\n- Run: `uv run pytest tests/integration/pipeline/test_agent_session_runner.py::test_agent_session_runner_respects_local_settings -v`\n- Test should:\n  1. Create temp .claude/settings.local.json\n  2. Run agent with sources=[\"local\"]\n  3. Assert SDK timeout matches local file value\n  4. Clean up temp file\n\n**Config Override Test**:\nThis test verifies that settings override from .claude/settings.local.json reaches the SDK runtime via the normal config path (not by mocking SDK internals).\n\n**Notes for Agent**:\n- Use pytest fixtures for temp file creation/cleanup\n- Verify SDK behavior (timeout honored), not just config passing\n- Keep agent task lightweight (avoid long-running validation)\n- Consider using mock agent if full agent run is too slow","notes":"Quality gate failed: External review failed: tests/integration/pipeline/test_agent_session_runner.py:4388: [P1] Test does not verify SDK reads timeout=300 from local settings file; tests/integration/pipeline/test_agent_session_runner.py:4279: [P1] Verify merged timeout is honored (not just setting_sources/logging); tests/integration/pipeline/test_agent_session_runner.py:4272: [P1] Fails to verify merged timeout value as required by AC\nLog: /home/cyou/.claude/projects/-home-cyou-mala/0bdc19b8-f2fd-4223-9afc-e810dbaf26ec.jsonl","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-07T04:05:07.49053502Z","created_by":"cyou","updated_at":"2026-01-08T14:39:39.071684468Z","closed_at":"2026-01-08T14:39:39.071684468Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-99l1.9","depends_on_id":"mala-99l1","type":"parent-child","created_at":"2026-01-07T04:05:07.491151846Z","created_by":"cyou"},{"issue_id":"mala-99l1.9","depends_on_id":"mala-99l1.7","type":"blocks","created_at":"2026-01-07T04:06:24.562100405Z","created_by":"cyou"},{"issue_id":"mala-99l1.9","depends_on_id":"mala-99l1.8","type":"blocks","created_at":"2026-01-07T04:06:29.678122068Z","created_by":"cyou"},{"issue_id":"mala-99l1.9","depends_on_id":"mala-99l1.6","type":"blocks","created_at":"2026-01-07T04:14:51.430471637Z","created_by":"cyou"}]}
{"id":"mala-9c4","title":"Log codex review output in verbose mode","description":"When running in verbose mode, the orchestrator should save the codex review output (raw JSON response) to a file. This enables debugging and auditing of codex review decisions.\n\nThe log file should be saved alongside other run artifacts in the runs directory, with a naming convention like:\n`{run_id}-{issue_id}-codex-review.json`\n\nThe path to this log file should be recorded in the runs JSON under the issue's `codex_review_log_path` field.","acceptance_criteria":"- In verbose mode, codex review raw output is saved to a file\n- File is saved in the runs directory with format {run_id}-{issue_id}-codex-review.json\n- The runs JSON includes codex_review_log_path field for each issue that had codex review run\n- In non-verbose mode, no log file is created (to save disk space)\n- Log includes all review attempts (not just the final one)","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-27T22:24:05.447731981Z","updated_at":"2025-12-27T22:26:53.987400446Z","closed_at":"2025-12-27T22:26:53.987400446Z","close_reason":"Closed"}
{"id":"mala-9g3k","title":"terminal logging improvements: (1) pretty print deadlock parameters, (2) when a task fails, log text should be all red, or all green if succeeded. (3) fixer logs should match those of the agents: truncated messages","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-06T02:57:47.717057939Z","created_by":"cyou","updated_at":"2026-01-06T03:16:08.826363624Z","closed_at":"2026-01-06T03:16:08.826363624Z","close_reason":"Closed"}
{"id":"mala-9gkd","title":"Remove deprecated MALA_* env vars after mala.yaml wiring complete","description":"After all config is wired through mala.yaml, remove/deprecate the env var support for: MALA_REVIEW_TIMEOUT, MALA_CERBERUS_SPAWN_ARGS, MALA_CERBERUS_WAIT_ARGS, MALA_CERBERUS_ENV, MALA_MAX_EPIC_VERIFICATION_RETRIES, MALA_MAX_DIFF_SIZE_KB. Keep path env vars (MALA_RUNS_DIR, MALA_LOCK_DIR) and API keys (LLM_API_KEY).","notes":"Quality gate failed: External review failed: src/domain/validation/e2e.py:445: [P1] Avoid appending duplicate top-level `validation_triggers` key; src/domain/validation/e2e.py:446: [P1] Fix duplicate validation_triggers key in E2E config injection\nLog: /home/cyou/.claude/projects/-home-cyou-mala/8d6c04ae-6afb-436d-8069-849eacfae0b3.jsonl","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-10T21:23:09.774862692Z","created_by":"cyou","updated_at":"2026-01-11T05:03:20.554438253Z","closed_at":"2026-01-11T05:03:20.554438253Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-9gkd","depends_on_id":"mala-cwzb","type":"blocks","created_at":"2026-01-10T21:26:55.423709765Z","created_by":"cyou"}]}
{"id":"mala-9ki","title":"Refactor: add tools/env.py and centralize env loading","description":"## Context\n`src/main.py` currently defines `USER_CONFIG_DIR`, `JSONL_LOG_DIR`, `SCRIPTS_DIR`, and performs dotenv loading at import time. This creates side effects and makes reuse/testing harder. The architecture plan calls for a small `tools/env.py` module that owns config paths and env loading, while preserving the **early** load needed for Braintrust setup.\n\n## Scope\nCreate a new module `src/tools/env.py` only. Do **not** modify `src/main.py` in this issue; integration will wire it up later.\n\n### Files\n- Add: `src/tools/env.py`\n\n## Requirements\n- `tools/env.py` should define:\n  - `USER_CONFIG_DIR = Path.home() / \".config\" / \"mala\"`\n  - `JSONL_LOG_DIR = USER_CONFIG_DIR / \"logs\"`\n  - `SCRIPTS_DIR` that points to repo `scripts/` (from `src/tools/env.py`, use `Path(__file__).resolve().parents[2] / \"scripts\"` or equivalent)\n- Provide two helpers:\n  - `load_user_env()` that loads `${USER_CONFIG_DIR}/.env` only (for early Braintrust setup)\n  - `load_env(repo_path: Path | None)` that calls `load_user_env()` and then (if `repo_path` is provided) loads `\u003crepo_path\u003e/.env` with `override=True`\n\n## Acceptance Criteria\n- `src/tools/env.py` exists with the above constants and functions.\n- No other files are modified in this issue.\n\n## Notes\n- Integration issue will wire these into `src/main.py`.\n","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-25T19:54:51.122065094Z","updated_at":"2025-12-25T20:08:26.994504903Z","closed_at":"2025-12-25T20:08:26.994504903Z","close_reason":"Closed"}
{"id":"mala-9lp","title":"Add progress gate between review retries","description":"## Problem\nReview retry loops are the #1 time/token sink. Issues hitting max_review_retries=5 consume 5-7x more time than 1-attempt issues. Pearson correlation between review_attempts and duration is 0.87-0.95.\n\nThe failure pattern: 'No progress: commit unchanged and no new validation evidence' — the agent gets stuck re-running the same thing without making progress.\n\n## Solution\nAdd a progress gate between review retries:\n- Before allowing another review attempt, require either:\n  - A new commit (different SHA from previous attempt)\n  - Changed files in the working tree\n- If neither condition is met, fail fast with actionable guidance instead of burning more attempts\n\n## Expected Impact\n~50% wall time reduction on failed issues\n\n## Evidence\n- mala-869.11: 31.5 min, 5 review attempts, failed with 'No progress'\n- mala-51q.2: 39.5 min, 5 review attempts, failed with 'No progress'\n- Both runs show same pattern across multiple issues","notes":"Quality gate failed: Timeout after 30 minutes\nLog: /home/cyou/.claude/projects/-home-cyou-mala/8432d8b2-63f1-4961-81ff-078998356364.jsonl","status":"closed","priority":0,"issue_type":"task","created_at":"2025-12-29T00:43:22.796559893Z","created_by":"cyou","updated_at":"2025-12-29T04:20:13.147765271Z","closed_at":"2025-12-29T04:20:13.147765271Z","close_reason":"Closed","labels":["needs-followup"]}
{"id":"mala-9mb","title":"Update orchestrator for strict validation pipeline","description":"Context:\n- Orchestrator must run the new strict gate chain and enforce issue closure rules.\n- Integrates EvidenceGate, clean-room validation, Codex review, and run-level validation.\n\nScope:\n- In: add Gate 1-4 sequencing; enforce clean-git checks; handle no-op/obsolete IssueResolution; respect disable-validations; run-level validation in worktree; follow-up issue on failure.\n- Out: changes to validation module internals (separate tasks).\n\nAcceptance Criteria:\n- Issues are closed only after run-level validation passes.\n- No-op/obsolete issues do not force commits and skip per-issue validation.\n- Failures trigger correct retries and non-zero exit on run-level failure.\n\nTest Plan:\n- TDD: add orchestrator tests for gate flow, retry exhaustion, and no-op issues.\n- Run orchestrator test suite.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-26T23:00:01.754561703Z","updated_at":"2025-12-27T01:40:18.566144768Z","closed_at":"2025-12-27T01:40:18.566144768Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-9mb","depends_on_id":"mala-cij","type":"blocks","created_at":"2025-12-26T23:00:26.592166614Z","created_by":"daemon"},{"issue_id":"mala-9mb","depends_on_id":"mala-ng3","type":"blocks","created_at":"2025-12-26T23:00:26.60370918Z","created_by":"daemon"},{"issue_id":"mala-9mb","depends_on_id":"mala-e0i","type":"blocks","created_at":"2025-12-26T23:00:26.614703142Z","created_by":"daemon"},{"issue_id":"mala-9mb","depends_on_id":"mala-2eu","type":"blocks","created_at":"2025-12-26T23:00:26.625720633Z","created_by":"daemon"}]}
{"id":"mala-9pg","title":"CLI command to find agent run, get metadata/logs, open in Claude Code","description":"Context:\n- Need to debug and inspect past agent runs\n- Run data stored in ~/.config/mala/runs/*.json\n- Claude JSONL logs contain detailed execution history\n- No easy way to find and open specific runs\n\nLocation:\n- src/cli/cli.py (CLI entry point)\n- src/orchestration/types.py (RunMetadata definition)\n- ~/.config/mala/runs/*.json (run metadata storage)\n\nScope:\n- In: CLI command to search/filter runs by date, issue, status\n- In: Display run metadata (issues worked, duration, outcome)\n- In: Open run logs directly in Claude Code for inspection\n- Out: Real-time monitoring (see mala-bdu); log streaming\n\nAcceptance Criteria:\n- `mala runs list` shows recent runs with summary info\n- `mala runs show \u003crun-id\u003e` displays detailed run metadata\n- `mala runs logs \u003crun-id\u003e [issue-id]` opens JSONL log in Claude Code\n- Filtering by --since, --issue, --status works correctly\n\nTest Plan:\n- Unit: Test run metadata parsing and filtering logic\n- Integration: Create test runs, verify CLI commands find them\n- Manual: Run mala, use CLI to inspect the completed run\n\nNotes for Agent:\n- Run files are JSON with RunMetadata structure\n- Log paths are in issues.\u003cid\u003e.log_path within run metadata\n- Consider using `claude code` CLI or file:// URLs to open logs","status":"closed","priority":2,"issue_type":"feature","created_at":"2025-12-28T06:21:39.636542746Z","updated_at":"2026-01-06T03:33:47.873724303Z","closed_at":"2026-01-06T03:33:47.873724303Z","close_reason":"Closed"}
{"id":"mala-9qr","title":"Refactor: extract hook logic from src/cli.py","description":"Goal: Make hook logic self-contained by moving block_dangerous_commands (and related constants) into a new module (e.g., src/hooks.py).\\n\\nScope/files: src/cli.py, new src/hooks.py.\\n\\nWork:\\n- Move DANGEROUS_PATTERNS + block_dangerous_commands to new module.\\n- Update imports in cli.py.\\n- Keep behavior unchanged.\\n\\nAcceptance criteria:\\n- CLI behavior unchanged.\\n- Hook still used in ClaudeAgentOptions.hooks.\\n- Tests (if any) still pass.","status":"closed","priority":3,"issue_type":"task","created_at":"2025-12-25T21:41:10.672408295Z","updated_at":"2025-12-26T00:06:41.954204714Z","closed_at":"2025-12-26T00:06:41.954204714Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-9qr","depends_on_id":"mala-akl","type":"blocks","created_at":"2025-12-25T21:47:25.567206045Z","created_by":"daemon"},{"issue_id":"mala-9qr","depends_on_id":"mala-c01","type":"parent-child","created_at":"2025-12-25T22:05:59.455394242Z","created_by":"daemon"}]}
{"id":"mala-9r2j","title":"Align validation-triggers docs with actual base-pool behavior","description":"Docs currently state base pool is global_validation_commands, but code uses commands; either update implementation or update docs to match final behavior. Update docs/validation-triggers.md after code decision.","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-09T16:53:34.485027416Z","created_by":"cyou","updated_at":"2026-01-09T21:47:49.884447812Z","closed_at":"2026-01-09T21:47:49.884447812Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-9r2j","depends_on_id":"mala-642f","type":"blocks","created_at":"2026-01-09T16:54:37.954889268Z","created_by":"cyou"},{"issue_id":"mala-9r2j","depends_on_id":"mala-7wp2","type":"blocks","created_at":"2026-01-09T16:54:43.074715773Z","created_by":"cyou"},{"issue_id":"mala-9r2j","depends_on_id":"mala-smaj","type":"parent-child","created_at":"2026-01-09T16:55:16.831974749Z","created_by":"cyou"}]}
{"id":"mala-9v8","title":"Dependency-aware scheduling using beads graph","description":"Context:\nUse beads dependency relations (blocks, parent-child) to schedule work optimally.\n\nLocation:\n- src/infra/clients/beads_client.py (get_ready methods, filtering)\n- src/pipeline/issue_execution_coordinator.py (issue selection)\n- src/orchestration/orchestrator.py (scheduling decisions)\n\nScope:\n- In: Query beads dependency graph for scheduling\n- In: Don't start child issues until parent blockers are closed\n- In: Parallelize independent subtrees automatically\n- In: Reorder work queue when dependencies resolve\n- Out: Distributed scheduling; cross-project dependencies\n\nAcceptance Criteria:\n- Issues with unresolved blockers are not scheduled\n- Independent subtrees run in parallel\n- Critical path is prioritized\n- Visualization of dependency graph available (`mala deps` or similar)\n- Efficient scheduling with minimal wasted agent time\n\nTest Plan:\n- Unit: Test dependency graph traversal\n- Unit: Test critical path calculation\n- Integration: Create dependent issues, verify scheduling order\n- Manual: Run with complex dependency graph, observe optimal parallelism\n\nNotes for Agent:\n- BeadsClient already has some filtering logic\n- `bd deps` and `bd blocked` provide dependency info\n- Consider caching dependency graph for performance","status":"closed","priority":3,"issue_type":"feature","created_at":"2025-12-28T06:31:49.709283289Z","updated_at":"2026-01-04T03:03:32.423307687Z","closed_at":"2026-01-04T03:03:32.423307687Z","close_reason":"Closed"}
{"id":"mala-a22p","title":"use cerberus for epic verification","status":"tombstone","priority":2,"issue_type":"feature","created_at":"2026-01-03T21:34:42.041665534Z","created_by":"cyou","updated_at":"2026-01-06T22:52:10.761723406Z","deleted_at":"2026-01-06T22:52:10.761723406Z","deleted_by":"batch delete","delete_reason":"batch delete","original_type":"feature"}
{"id":"mala-a2z","title":"Implement baseline auto-refresh in spec_runner.py","description":"Context:\n- Must capture baseline from main repo BEFORE worktree creation\n- If baseline missing or stale, auto-refresh by running tests in temp worktree\n- Need locking to prevent parallel agents from clobbering\n- Must use same pytest args as validation spec\n\nScope:\n- In: Add `_refresh_baseline(spec, repo_path) -\u003e float` method\n- In: Before worktree setup in `run_validation_spec`, check/refresh baseline\n- In: Use file locking (existing locking.py) with double-check pattern\n- In: Pass `baseline_percent` to `_check_coverage`\n- In: Update `_check_coverage` to accept and use baseline_percent\n- Out: CLI changes, CoverageConfig changes (done in other issues)\n\nAcceptance Criteria:\n- Baseline captured from main repo before worktree creation\n- If baseline missing/stale, `_refresh_baseline` runs in temp worktree\n- Lock acquired before refresh, released after\n- Double-check pattern: re-verify staleness after acquiring lock\n- Temp worktree cleaned up after refresh\n- Atomic rename of coverage.xml to main repo\n- `_check_coverage` uses baseline_percent when min_percent is None\n\nTest Plan:\n- Test baseline captured before worktree setup\n- Test missing baseline triggers refresh\n- Test stale baseline triggers refresh\n- Test fresh baseline skips refresh\n- Test lock prevents concurrent refreshes\n- Mock worktree creation/cleanup\n\nNotes for Agent:\n- Use existing `create_worktree` from worktree.py\n- Use existing locking functions from locking.py\n- Run pytest with same args from spec, just override `--cov-fail-under=0`\n- Write to temp file, then `os.rename()` for atomic update\n- Primary file: src/validation/spec_runner.py","status":"tombstone","priority":1,"issue_type":"task","created_at":"2025-12-27T18:14:14.27972069Z","updated_at":"2025-12-27T18:20:40.634273311Z","labels":["validation"],"dependencies":[{"issue_id":"mala-a2z","depends_on_id":"mala-wg1","type":"blocks","created_at":"2025-12-27T18:15:02.015029041Z","created_by":"daemon"},{"issue_id":"mala-a2z","depends_on_id":"mala-da4","type":"blocks","created_at":"2025-12-27T18:15:02.027510339Z","created_by":"daemon"}],"deleted_at":"2025-12-27T18:20:40.634273311Z","deleted_by":"daemon","delete_reason":"delete","original_type":"task"}
{"id":"mala-a3ta","title":"Epic: Per-Issue Review Configuration","description":"## Summary\nMake the currently hard-coded per-issue review step optional and configurable via `mala.yaml`. Adds a root-level `per_issue_review` section that controls whether post-session code review runs for each issue.\n\n## Plan Reference\n`docs/2026-01-12-per-issue-review-config-plan.md`\n\n## Key Design Decisions\n1. **Disabled by default** — Breaking change from current \"always review\" behavior\n2. **Independent from triggers** — `per_issue_review.enabled` controls per-issue reviews; trigger `code_review.enabled` controls trigger reviews (separate paths)\n3. **Reuse CodeReviewConfig** — Same dataclass as trigger code_review; all fields available\n4. **No CLI override for enabled** — Config-only control (existing `--max-review-retries` still works)\n5. **Shared reviewer** — First enabled source (per_issue_review if enabled, else triggers) determines global reviewer settings\n\n## Scope\n### In Scope\n- Add `per_issue_review` as root-level key in mala.yaml\n- Reuse existing `CodeReviewConfig` dataclass\n- Wire config through ValidationConfig → _DerivedConfig → orchestrator → lifecycle\n- Update `_extract_reviewer_config()` priority\n- Add `mala init` prompts before triggers section\n- Update docs with migration note\n\n### Out of Scope\n- CLI flags to override per-issue review settings\n- Changes to trigger-based code review behavior\n- Migration tooling or deprecation warnings\n\n## Breaking Change\nPer-issue review is **DISABLED by default** as of 2026-01-12. Users must add `per_issue_review: enabled: true` to restore previous behavior.","status":"closed","priority":1,"issue_type":"epic","created_at":"2026-01-12T01:42:58.069696991Z","created_by":"cyou","updated_at":"2026-01-12T02:53:10.60654964Z","closed_at":"2026-01-12T02:53:10.60654964Z","close_reason":"Closed"}
{"id":"mala-a3ta.1","title":"[T001] [integration-path-test] Skeleton: Add per_issue_review field + failing integration test","description":"## Goal\nAdd the `per_issue_review` field stub to `ValidationConfig` and create a failing integration test that exercises the full config loading path. This establishes the compile-ready wiring surface before implementation.\n\n## Context\n- `ValidationConfig` is the container for root-level mala.yaml keys (config.py:782+)\n- `CodeReviewConfig` dataclass already exists (config.py:251-277)\n- Need stub that compiles but doesn't parse actual YAML values yet\n\n## Scope\n- **In**: Add `per_issue_review: CodeReviewConfig` field to `ValidationConfig` with default `CodeReviewConfig(enabled=False)`\n- **In**: Create failing integration test proving config flows through system\n- **Out**: Full parsing implementation (T002), orchestration wiring (T003-T005)\n\n## Changes\n- `src/domain/validation/config.py` — Exists — Add `per_issue_review: CodeReviewConfig = field(default_factory=lambda: CodeReviewConfig(enabled=False))` to `ValidationConfig`\n- `tests/integration/domain/validation/test_config_loading.py` — Exists — Add failing test: `test_per_issue_review_config_loads_from_yaml`\n\n## Wiring Map\n`mala.yaml (per_issue_review:) → config_loader.py → ValidationConfig.per_issue_review → _DerivedConfig → orchestrator`\n\n## Acceptance Criteria\n- `ValidationConfig` has `per_issue_review` field with correct type annotation\n- Default is `CodeReviewConfig(enabled=False)`\n- Project builds/compiles without errors\n- Integration test fails for the intended reason (per_issue_review not yet parsed from YAML)\n\n## Verification\n```bash\nuvx ty check                                  # Type check passes\nuv run pytest tests/integration/domain/validation/test_config_loading.py -k per_issue_review -v  # Test fails (expected)\n```\n\n## Notes for Agent\n- The test should load a mala.yaml with `per_issue_review: enabled: true` and assert the value reaches ValidationConfig\n- Test will fail initially because parsing isn't implemented yet - this is correct TDD behavior\n- Do NOT implement parsing - that's T002","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-12T01:43:17.895264242Z","created_by":"cyou","updated_at":"2026-01-12T02:02:16.782970984Z","closed_at":"2026-01-12T02:02:16.782970984Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-a3ta.1","depends_on_id":"mala-a3ta","type":"parent-child","created_at":"2026-01-12T01:43:17.895924108Z","created_by":"cyou"}]}
{"id":"mala-a3ta.2","title":"[T002] Implement per_issue_review parsing in config_loader","description":"## Goal\nImplement parsing of the root-level `per_issue_review` section by adding parsing code to `ValidationConfig.from_dict()` and reusing the existing `_parse_code_review_config()` helper.\n\n## Context\n- `ValidationConfig.from_dict()` is in `config.py:844` - this is where root-level keys are parsed\n- `_parse_code_review_config()` helper exists in `config_loader.py:501` and handles all CodeReviewConfig fields\n- Pattern: `from_dict()` calls helper, similar to `validation_triggers` calling `_parse_validation_triggers()`\n- Add `per_issue_review` to allowed root-level keys if there's a validation set (check for `_VALIDATION_CONFIG_FIELDS` or similar)\n\n## Scope\n- **In**: Parse `per_issue_review` section in `ValidationConfig.from_dict()` \n- **In**: Call `_parse_code_review_config()` from config_loader.py\n- **In**: Add `per_issue_review` to ValidationConfig dataclass constructor call\n- **In**: Unit tests for valid/invalid/missing configs\n- **Out**: Orchestration wiring (T003-T005), CLI init (T006)\n\n## Changes\n- `src/domain/validation/config.py` — Exists — Add parsing block in `ValidationConfig.from_dict()` for `per_issue_review`:\n  ```python\n  # Parse per_issue_review - use lazy import to avoid circular dependency\n  per_issue_review: CodeReviewConfig | None = None\n  if \"per_issue_review\" in data:\n      fields_set.add(\"per_issue_review\")\n      pir_data = data[\"per_issue_review\"]\n      if pir_data is not None:\n          if not isinstance(pir_data, dict):\n              raise ConfigError(f\"per_issue_review must be an object, got {type(pir_data).__name__}\")\n          from src.domain.validation.config_loader import _parse_code_review_config\n          per_issue_review = _parse_code_review_config(cast(\"dict[str, object]\", pir_data), \"per_issue_review\")\n  ```\n- `src/domain/validation/config.py` — Exists — Add `per_issue_review=per_issue_review` to `cls(...)` constructor call\n- `tests/unit/domain/validation/test_config_loader.py` — Exists — Add unit tests for per_issue_review parsing\n\n## Acceptance Criteria\n- `per_issue_review:` section is parsed from mala.yaml root level\n- All `CodeReviewConfig` fields are supported (enabled, reviewer_type, max_retries, finding_threshold, track_review_issues, failure_mode, cerberus, agent_sdk_timeout, agent_sdk_model, baseline)\n- Missing `per_issue_review` section results in default `CodeReviewConfig(enabled=False)` from T001's field default\n- Partial config (only some fields) uses defaults for missing fields\n- Invalid field values raise `ConfigError` with helpful message\n\n## Verification\n```bash\nuv run pytest tests/unit/domain/validation/test_config_loader.py -k per_issue_review -v\nuv run pytest tests/integration/domain/validation/test_config_loading.py -k per_issue_review -v  # Now passes (T001's test)\nuvx ty check\nuvx ruff check src/domain/validation/config.py\n```\n\n## Notes for Agent\n- The parsing happens in `config.py` (from_dict method), NOT in config_loader.py\n- Reuse `_parse_code_review_config()` via lazy import - do not duplicate parsing logic\n- Follow existing patterns for `validation_triggers` and `evidence_check` parsing\n- Test cases should include: all fields set, minimal config (just enabled), missing section, invalid values\n- `baseline` field is allowed syntactically but ignored semantically in per-issue context","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-12T01:43:32.650265254Z","created_by":"cyou","updated_at":"2026-01-12T02:10:34.000817349Z","closed_at":"2026-01-12T02:10:34.000817349Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-a3ta.2","depends_on_id":"mala-a3ta","type":"parent-child","created_at":"2026-01-12T01:43:32.650964529Z","created_by":"cyou"},{"issue_id":"mala-a3ta.2","depends_on_id":"mala-a3ta.1","type":"blocks","created_at":"2026-01-12T01:45:10.560926098Z","created_by":"cyou"}]}
{"id":"mala-a3ta.3","title":"[T003] Wire per_issue_review through _DerivedConfig + _derive_config()","description":"## Goal\nAdd `per_issue_review` field to `_DerivedConfig` and update `_derive_config()` in factory.py to:\n1. Pass the config through from ValidationConfig\n2. Update `max_review_retries` extraction to check `per_issue_review` first (when enabled)\n\n## Context\n- `_DerivedConfig` is an intermediate dataclass for orchestration configuration (types.py)\n- `_derive_config()` in factory.py maps ValidationConfig fields to _DerivedConfig\n- Current `max_review_retries` logic (lines 165-169) only checks triggers - needs to check `per_issue_review` first\n- This task sets up the plumbing for T004 (reviewer extraction) and T005 (orchestrator enablement)\n\n## Scope\n- **In**: Add `per_issue_review: CodeReviewConfig | None` to `_DerivedConfig`\n- **In**: Update `_derive_config()` to pass `per_issue_review` from ValidationConfig\n- **In**: Update `max_review_retries` extraction to prioritize `per_issue_review.max_retries` (when enabled)\n- **In**: Unit tests for field propagation and max_review_retries precedence\n- **Out**: Reviewer priority logic (T004), orchestrator._is_review_enabled() (T005)\n\n## Changes\n- `src/orchestration/types.py` — Exists — Add `per_issue_review: CodeReviewConfig | None = None` to `_DerivedConfig`\n- `src/orchestration/factory.py` — Exists — Update `_derive_config()`:\n  1. Set `per_issue_review` from `config.per_issue_review`\n  2. Update lines 165-169 to check `per_issue_review.max_retries` first (only when `per_issue_review.enabled=True`)\n- `tests/unit/orchestration/test_factory.py` — Exists — Add tests:\n  - `test_derive_config_passes_per_issue_review`\n  - `test_derive_config_max_review_retries_from_per_issue_review` (when enabled)\n  - `test_derive_config_max_review_retries_falls_back_to_triggers` (when disabled)\n\n## Wiring Map\n`ValidationConfig.per_issue_review → _derive_config() → _DerivedConfig.per_issue_review → create_orchestrator()`\n\n## max_review_retries Precedence (CRITICAL)\n```\n1. CLI --max-review-retries (OrchestratorConfig.max_review_retries) - handled in orchestrator.__init__\n   ↓ if not set\n2. per_issue_review.max_retries (only when per_issue_review.enabled=True)\n   ↓ if not enabled or not set\n3. First enabled trigger code_review.max_retries (existing behavior)\n   ↓ if not found\n4. Default: None (falls back to default 3 in orchestrator)\n```\n\n## Acceptance Criteria\n- `_DerivedConfig` has `per_issue_review` field with type `CodeReviewConfig | None`\n- `_derive_config()` copies `per_issue_review` from ValidationConfig to _DerivedConfig\n- When `per_issue_review.enabled=True`: its `max_retries` is used for `_DerivedConfig.max_review_retries`\n- When `per_issue_review.enabled=False`: falls back to trigger config (existing behavior)\n- Unit tests verify field propagation and precedence\n\n## Verification\n```bash\nuv run pytest tests/unit/orchestration/test_factory.py -k \"derive_config or max_review_retries\" -v\nuvx ty check\nuvx ruff check src/orchestration/types.py src/orchestration/factory.py\n```\n\n## Notes for Agent\n- Follow existing patterns in `_derive_config()` for other fields\n- The field should be `CodeReviewConfig | None` to allow explicit None vs default distinction\n- CRITICAL: Only use per_issue_review.max_retries when `enabled=True` - ignore when disabled\n- Do NOT modify `_extract_reviewer_config()` yet - that's T004\n- Existing code at lines 165-169 needs modification, not replacement","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-12T01:43:48.495881001Z","created_by":"cyou","updated_at":"2026-01-12T02:17:40.762641551Z","closed_at":"2026-01-12T02:17:40.762641551Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-a3ta.3","depends_on_id":"mala-a3ta","type":"parent-child","created_at":"2026-01-12T01:43:48.496598267Z","created_by":"cyou"},{"issue_id":"mala-a3ta.3","depends_on_id":"mala-a3ta.1","type":"blocks","created_at":"2026-01-12T01:45:10.674578211Z","created_by":"cyou"}]}
{"id":"mala-a3ta.4","title":"[T004] Update _extract_reviewer_config() to prioritize per_issue_review","description":"## Goal\nUpdate `_extract_reviewer_config()` in factory.py to check `per_issue_review` FIRST (only if enabled) before falling back to trigger configs. This determines the shared reviewer settings.\n\n## Context\n- `_extract_reviewer_config()` at factory.py:318-345 determines global reviewer settings\n- Currently falls through trigger configs using `_get_first_enabled_code_review()`\n- Must add per_issue_review as highest priority source **only if enabled=True**\n- When `per_issue_review.enabled=False`, section is completely ignored (no fallback use)\n\n## Scope\n- **In**: Add per_issue_review check at top of `_extract_reviewer_config()`\n- **In**: Unit tests for priority order (per_issue_review \u003e triggers \u003e defaults)\n- **In**: Test that disabled per_issue_review is ignored\n- **Out**: Orchestrator._is_review_enabled() (T005)\n\n## Changes\n- `src/orchestration/factory.py` — Exists — Modify `_extract_reviewer_config()` to check per_issue_review first (if enabled)\n- `tests/unit/orchestration/test_factory.py` — Exists — Add tests for reviewer priority scenarios\n\n## Acceptance Criteria\n- When `per_issue_review.enabled=True`: Its reviewer_type, cerberus, agent_sdk_* settings take priority\n- When `per_issue_review.enabled=False`: Section is completely ignored, falls back to triggers\n- When per_issue_review enabled but triggers also enabled: per_issue_review wins\n- When per_issue_review disabled and triggers enabled: trigger config wins\n- When both disabled: falls back to defaults (agent_sdk with timeout=600, model=sonnet)\n\n## Verification\n```bash\nuv run pytest tests/unit/orchestration/test_factory.py -k extract_reviewer -v\nuv run pytest tests/unit/orchestration/test_factory.py -k reviewer_priority -v\nuvx ty check\n```\n\n## Notes for Agent\n- Critical: Only use per_issue_review settings when `enabled=True`\n- When `enabled=False`, do NOT use any fields from per_issue_review\n- The `max_review_retries` field from per_issue_review should be used for per-issue retry limit (separate from reviewer settings)\n- Test the \"disabled per_issue_review with other fields set\" edge case explicitly","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-12T01:44:02.159239504Z","created_by":"cyou","updated_at":"2026-01-12T02:23:11.698039619Z","closed_at":"2026-01-12T02:23:11.698039619Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-a3ta.4","depends_on_id":"mala-a3ta","type":"parent-child","created_at":"2026-01-12T01:44:02.15998293Z","created_by":"cyou"},{"issue_id":"mala-a3ta.4","depends_on_id":"mala-a3ta.3","type":"blocks","created_at":"2026-01-12T01:45:10.797048524Z","created_by":"cyou"}]}
{"id":"mala-a3ta.5","title":"[T005] Modify orchestrator._is_review_enabled() to check per_issue_review.enabled","description":"## Goal\nModify `orchestrator._is_review_enabled()` to return the value of `per_issue_review.enabled`, making it the canonical decision point for per-issue review enablement. This is the final wiring step.\n\n## Context\n- `_is_review_enabled()` at orchestrator.py:693 is the canonical decision point for **per-issue** review only\n- This does NOT affect trigger-based reviews (those use trigger_config.code_review.enabled at run_coordinator.py:1361)\n- Current behavior: always returns True unless disabled_validations override\n- New behavior: returns `per_issue_review.enabled` (defaulting to False)\n- The value flows through: orchestrator → run_config → orchestration_wiring → agent_session_runner → LifecycleConfig.review_enabled\n\n## Scope\n- **In**: Modify `_is_review_enabled()` to check `per_issue_review.enabled`\n- **In**: Store per_issue_review in orchestrator for access\n- **In**: Unit tests for enabled/disabled/missing scenarios\n- **In**: Integration test verifying trigger reviews unaffected\n- **Out**: CLI init prompts (T006), documentation (T007)\n\n## Changes\n- `src/orchestration/orchestrator.py` — Exists — Modify `_is_review_enabled()` to check `self._per_issue_review.enabled`\n- `tests/unit/orchestration/test_orchestrator.py` — Exists — Add tests for `_is_review_enabled()` with per_issue_review\n\n## Precedence Order\n```\n1. CLI --disable-validations review (OrchestratorConfig.disabled_validations)\n   ↓ if not disabled\n2. per_issue_review.enabled from mala.yaml\n   ↓ if not set\n3. Default: False (breaking change)\n```\n\n## Acceptance Criteria\n- `_is_review_enabled()` returns False when `per_issue_review.enabled=False`\n- `_is_review_enabled()` returns True when `per_issue_review.enabled=True`\n- `--disable-validations review` overrides `per_issue_review.enabled=True` (returns False)\n- Missing `per_issue_review` section → returns False (default disabled)\n- Trigger-based reviews unaffected (run_coordinator checks trigger_config.code_review.enabled separately)\n\n## Verification\n```bash\nuv run pytest tests/unit/orchestration/test_orchestrator.py -k is_review_enabled -v\nuv run pytest tests/integration/ -k \"per_issue_review and trigger\" -v  # Verify independence\nuvx ty check\n```\n\n## Notes for Agent\n- The orchestrator must store per_issue_review config (passed from factory)\n- Check disabled_validations first (existing behavior), then per_issue_review.enabled\n- Do NOT modify run_coordinator.py - trigger reviews use a separate path\n- Integration test should verify: `per_issue_review` absent + `validation_triggers.run_end.code_review.enabled=True` → trigger review still runs","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-12T01:44:18.090177627Z","created_by":"cyou","updated_at":"2026-01-12T02:42:21.486827648Z","closed_at":"2026-01-12T02:42:21.486827648Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-a3ta.5","depends_on_id":"mala-a3ta","type":"parent-child","created_at":"2026-01-12T01:44:18.090785034Z","created_by":"cyou"},{"issue_id":"mala-a3ta.5","depends_on_id":"mala-a3ta.3","type":"blocks","created_at":"2026-01-12T01:45:10.915638158Z","created_by":"cyou"}]}
{"id":"mala-a3ta.6","title":"[T006] Add mala init prompts for per-issue review before triggers","description":"## Goal\nAdd interactive questionary prompts to `mala init` for configuring per-issue review settings. These prompts should appear BEFORE the validation triggers section.\n\n## Context\n- `mala init` uses questionary for interactive prompts (cli.py)\n- Current flow: preset → evidence_check → validation_triggers\n- New flow: preset → evidence_check → **per_issue_review** → validation_triggers\n- When user declines per-issue review, the section is omitted from generated mala.yaml\n\n## Scope\n- **In**: Add per_issue_review prompts after evidence_check, before triggers\n- **In**: Prompt for: enabled, reviewer_type, max_retries, finding_threshold\n- **In**: Generate valid YAML section when enabled\n- **In**: Omit section when disabled (matches loader defaults)\n- **Out**: Advanced options (cerberus spawn_args, agent_sdk_model) - use defaults\n\n## Changes\n- `src/cli/cli.py` — Exists — Add per_issue_review prompts in init command flow\n- Tests — TBD — Add test for init prompt flow (if existing test pattern exists)\n\n## Prompt Flow\n```\n? Enable per-issue code review? (Reviews code after each issue session)\n  \u003e No (faster, no review overhead)    [default]\n    Yes (review every issue session)\n\n# If Yes selected:\n? Reviewer type:\n  \u003e Cerberus (multi-model consensus)   [default]\n    Agent SDK (single model)\n\n? Maximum review retries: 3           [default]\n\n? Minimum finding priority to block (P0 = critical only, none = all findings):\n  \u003e none (report all)\n    P3 (low and above)\n    P2 (medium and above)\n    P1 (high and above)\n    P0 (critical only)\n```\n\n## User-Visible Rule (show during prompts)\n\u003e **Note**: Enabling per-issue review also determines the reviewer type used for all reviews, including trigger-based reviews.\n\n## Acceptance Criteria\n- Prompts appear in order: evidence_check → per_issue_review → validation_triggers\n- Declining generates no `per_issue_review` section in mala.yaml\n- Accepting generates valid `per_issue_review:` YAML section\n- Generated YAML matches expected schema (enabled, reviewer_type, max_retries, finding_threshold)\n\n## Verification\n```bash\n# Manual verification:\nmala init --help  # Verify command exists\nmala init  # Interactive test: decline per-issue review → verify no section\nmala init  # Interactive test: accept → verify valid YAML generated\n\nuvx ty check\nuvx ruff check src/cli/cli.py\n```\n\n## Notes for Agent\n- Follow existing questionary patterns in cli.py\n- Use questionary.select() for choice prompts, questionary.text() with default for max_retries\n- Keep prompts concise - users should understand quickly\n- Default to \"No\" (disabled) to match the breaking change behavior","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-12T01:44:41.114998454Z","created_by":"cyou","updated_at":"2026-01-12T02:33:39.740404623Z","closed_at":"2026-01-12T02:33:39.740404623Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-a3ta.6","depends_on_id":"mala-a3ta","type":"parent-child","created_at":"2026-01-12T01:44:41.11562221Z","created_by":"cyou"},{"issue_id":"mala-a3ta.6","depends_on_id":"mala-a3ta.2","type":"blocks","created_at":"2026-01-12T01:45:11.022976938Z","created_by":"cyou"}]}
{"id":"mala-a3ta.7","title":"[T007] Update docs/project-config.md with per_issue_review section","description":"## Goal\nDocument the new `per_issue_review` configuration section in docs/project-config.md, including the migration note for the breaking change.\n\n## Context\n- docs/project-config.md documents mala.yaml configuration\n- Need to add per_issue_review section with all fields\n- Need to add migration note for breaking change (disabled by default as of 2026-01-12)\n- Should include the user-visible rule for reviewer selection\n\n## Scope\n- **In**: Document per_issue_review section with all fields\n- **In**: Add migration note with concrete date\n- **In**: Document reviewer selection priority rule\n- **In**: Add example YAML snippets\n- **Out**: CLI documentation (covered by existing --help), architecture docs\n\n## Changes\n- `docs/project-config.md` — Exists — Add per_issue_review documentation section\n\n## Documentation Content\n### Migration Note\n```markdown\n\u003e **Breaking Change (2026-01-12)**: Per-issue code review is now disabled by default. \n\u003e To restore the previous behavior where every issue session is reviewed, add:\n\u003e ```yaml\n\u003e per_issue_review:\n\u003e   enabled: true\n\u003e ```\n```\n\n### Per-Issue Review Section\n- `enabled` — Enable/disable per-issue review (default: false)\n- `reviewer_type` — Reviewer to use: cerberus | agent_sdk (default: cerberus)\n- `max_retries` — Maximum review retry attempts (default: 3)\n- `finding_threshold` — Minimum finding priority: P0 | P1 | P2 | P3 | none (default: none)\n- `track_review_issues` — Create beads issues for findings (default: true)\n- `failure_mode` — continue | abort | remediate (default: continue)\n- `cerberus` — Cerberus-specific settings (timeout, spawn_args, etc.)\n- `agent_sdk_timeout` — Agent SDK timeout in seconds (default: 600)\n- `agent_sdk_model` — Agent SDK model: sonnet | opus | haiku (default: sonnet)\n\n### Reviewer Selection Rule\n```markdown\n\u003e **Reviewer Selection**: Enabling `per_issue_review` also determines the reviewer type \n\u003e (Cerberus or Agent SDK) used for all reviews, including trigger-based reviews. To use \n\u003e different reviewer settings for triggers, leave `per_issue_review.enabled: false` so \n\u003e your trigger configs take priority.\n```\n\n## Acceptance Criteria\n- per_issue_review section documented with all fields\n- Migration note present with 2026-01-12 date\n- Example YAML snippet showing basic usage\n- Reviewer selection priority explained\n- Fields documented with types and defaults\n\n## Verification\n```bash\n# Manual: Review docs/project-config.md for completeness and accuracy\n# Ensure all CodeReviewConfig fields are documented\n# Verify YAML example is valid syntax\n```\n\n## Notes for Agent\n- Follow existing documentation style in project-config.md\n- Place section logically (after evidence_check, before validation_triggers if ordering exists)\n- Include both minimal example (just `enabled: true`) and full example (all fields)","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-12T01:44:58.631790416Z","created_by":"cyou","updated_at":"2026-01-12T02:44:15.833745496Z","closed_at":"2026-01-12T02:44:15.833745496Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-a3ta.7","depends_on_id":"mala-a3ta","type":"parent-child","created_at":"2026-01-12T01:44:58.632454742Z","created_by":"cyou"},{"issue_id":"mala-a3ta.7","depends_on_id":"mala-a3ta.5","type":"blocks","created_at":"2026-01-12T01:45:11.127781562Z","created_by":"cyou"},{"issue_id":"mala-a3ta.7","depends_on_id":"mala-a3ta.6","type":"blocks","created_at":"2026-01-12T01:45:11.232670146Z","created_by":"cyou"}]}
{"id":"mala-a3ta.8","title":"[Review] 1 non-blocking finding from mala-a3ta.6","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-a3ta.6\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Avoid parsing per_issue_review via trigger-specific code_review parser\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/domain/validation/config.py:1099-1117\n\n`ValidationConfig.from_dict` parses `per_issue_review` by calling `_parse_code_review_config(..., \"per_issue_review\")` (`src/domain/validation/config.py:1109-1117`). That helper is explicitly trigger-oriented (parameter name `trigger_name`) and already contains trigger-specific validation (e.g., special-casing `trigger_name == \"session_end\"` for `baseline` warnings/ignores in `src/domain/validation/config_loader.py:621-640`).\n\nConcrete failure path: if `_parse_code_review_config` adds/has trigger-specific rules beyond the shown `session_end` case, `per_issue_review` may be validated/normalized incorrectly because it’s being treated as a trigger named \"per_issue_review\". This is especially risky because the new `mala init` prompt emits `per_issue_review.finding_threshold`/`max_retries` values that should be validated under per-issue-review semantics, not trigger semantics.\n\nFix direction: introduce a dedicated per-issue-review parse path (or a mode/flag) so `per_issue_review` doesn’t go through trigger-specific branches intended for `validation_triggers.*.code_review`.\n\n---\n\n\u003c!-- fp:268e329ba6125859 --\u003e\n\u003c!-- review_finding:ad36c1c769b2 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-12T02:33:40.061129033Z","created_by":"cyou","updated_at":"2026-01-12T02:48:02.866175281Z","closed_at":"2026-01-12T02:48:02.866175281Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-a3ta.6"],"dependencies":[{"issue_id":"mala-a3ta.8","depends_on_id":"mala-a3ta","type":"parent-child","created_at":"2026-01-12T02:33:40.061730491Z","created_by":"cyou"}]}
{"id":"mala-a6wz","title":"Update checkpoint_request prompt to stop after checkpoint","description":"Plan says checkpoint prompt should end after emitting \u003ccheckpoint\u003e block. Prompt currently instructs agent to continue working. Align prompt text with plan to avoid wasting tokens.","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-03T22:29:10.229129706Z","created_by":"cyou","updated_at":"2026-01-03T22:46:06.354375638Z","closed_at":"2026-01-03T22:46:06.354375638Z","close_reason":"Closed"}
{"id":"mala-acce","title":"[Review] 2 non-blocking findings from mala-ari7.5","description":"## Review Findings\n\nThis issue consolidates 2 non-blocking findings from code review.\n\n**Source issue:** mala-ari7.5\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Redundant blocking issue calculation\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/pipeline/agent_session_runner.py:472-481\n\nThe extracted helper `_emit_review_result_events` recalculates the `blocking_count` of review issues, which was already calculated and logged just a few lines earlier in `_handle_review_effect`. While not strictly a bug, passing the pre-calculated count would be more efficient and ensure consistency.\n\n---\n\n### Finding 2: [P3] Potential 'None' string in warning message\n\n**Priority:** P3\n**Reviewer:** gemini\n**Location:** src/pipeline/agent_session_runner.py:466-469\n\nIf `result.effect` is `Effect.RUN_REVIEW`, the warning message `f\"Review tool error: {review_result.parse_error}; retrying\"` is emitted. While currently `RUN_REVIEW` is only triggered when `parse_error` is non-None, relying on this implicit lifecycle state machine behavior makes the helper less robust to future changes where `RUN_REVIEW` might be used for other types of retries.\n\n---\n","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T08:28:13.195810744Z","created_by":"cyou","updated_at":"2026-01-03T08:35:05.09367176Z","closed_at":"2026-01-03T08:35:05.09367176Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_findings:bc4b82e53c71","source:mala-ari7.5"]}
{"id":"mala-actw","title":"[Review] src/pipeline/agent_session_runner.py:900-904: [P2] Clear pending tool IDs on idle-timeout retry","description":"## Review Finding\n\nThis issue was auto-created from a P2 review finding.\n\n**Source issue:** mala-cavk.2\n**Reviewer:** codex\n**Location:** src/pipeline/agent_session_runner.py:900-904\n\n## Details\n\n`_run_message_iteration` only clears `state.pending_tool_ids` and resets `tool_calls_this_turn` once before the retry loop. After an idle timeout, retries reuse the same `state.pending_tool_ids` from the previous attempt, so `_iter_messages` sets `current_timeout=None` and can hang indefinitely because those tool IDs will never resolve on the new stream. The pre-refactor code cleared these per attempt. Reset `state.pending_tool_ids` (and `tool_calls_this_turn`) at the start of each retry attempt inside the `while pending_query is not None` loop.","notes":"Quality gate failed: External review failed: spawn failed: Artifact written: /home/cyou/.claude/projects/-home-cyou-mala/cerberus/d37bf16c-d780-4dfb-8fd9-3ee2be2fbe17/latest.md\nReview gate already active (status: pending, age: 66s)\nUse /home/cyou/.claude/plugins/cache/cerberus/cerberus/1.10.6/bin/review-gate resolve to complete the existing gate first.\nLog: /home/cyou/.claude/projects/-home-cyou-mala/d37bf16c-d780-4dfb-8fd9-3ee2be2fbe17.jsonl","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-01T21:33:48.212980929Z","created_by":"cyou","updated_at":"2026-01-02T00:48:06.805484617Z","closed_at":"2026-01-02T00:48:06.805484617Z","close_reason":"Closed","labels":["auto_generated","needs-followup","review_finding","review_finding:72762dbdf0fd","source:mala-cavk.2"]}
{"id":"mala-aepf","title":"[Review] 1 non-blocking finding from mala-fag4","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-fag4\n**Highest priority:** P3\n\n---\n\n### Finding 1: [P3] Fix invalid `--disable` values in docs examples\n\n**Priority:** P3\n**Reviewer:** codex\n**Location:** docs/quality-hardening-plan.md:241-245\n\n`docs/quality-hardening-plan.md` documents `--disable slow-tests` and lists `slow-tests`/`codex-review` as valid values. In the current CLI implementation, valid values are defined in `src/cli/cli.py` (e.g., `integration-tests`, `review`, `coverage`, `e2e`, …) and `codex-review` is explicitly treated as a legacy value that should be rejected (see `tests/unit/cli/test_cli.py`). As written, running `mala run --disable slow-tests` (or `--disable codex-review`) will error with “Unknown --disable value(s) …”, so these examples/values should be updated to the accepted names (likely `integration-tests` and `review`).\n\n---\n\n\u003c!-- fp:e25c0ac5bdb6da20 --\u003e\n\u003c!-- review_finding:6568630b7e57 --\u003e","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-06T03:44:20.22144612Z","created_by":"cyou","updated_at":"2026-01-06T03:50:16.024226022Z","closed_at":"2026-01-06T03:50:16.024226022Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-fag4"]}
{"id":"mala-ahb6","title":"Epic verification interrupt cancellation order incorrect","description":"In src/pipeline/epic_verification_coordinator.py _execute_remediation_issues(), when interrupt wins the wait race, the code cancels remediation tasks but does not cancel gather_task until after awaiting asyncio.gather(*tasks, ...). \n\nThis leaves gather_task pending during cancellation which can keep it running longer than necessary and complicate cleanup.\n\n## Fix\nCancel gather_task immediately when interrupt wins, then cancel remediation tasks, then await cleanup deterministically.\n\n## Context\nBlocked mala-s9p8.5 from passing review (lines L284-314).","status":"closed","priority":1,"issue_type":"bug","created_at":"2026-01-08T21:02:28.039607558Z","created_by":"cyou","updated_at":"2026-01-08T21:37:08.413921803Z","closed_at":"2026-01-08T21:37:08.413921803Z","close_reason":"FALSE POSITIVE: Agent correctly implemented this. The code at lines 350-362 does cancel gather_task first, then remediation tasks, then awaits cleanup. See src/pipeline/epic_verification_coordinator.py:350-362."}
{"id":"mala-aht","title":"Epic: Healthcheck reliability fixes","description":"Context:\n- Umbrella for issues from the code health review (orchestrator + beads reliability).\n- Keep fixes small and parallel where possible.\n\nScope:\n- In: reliability/correctness fixes from the review output.\n- Out: new features or architecture changes.\n\nAcceptance Criteria:\n- Child issues created and linked.\n\nTest Plan:\n- N/A (epic).","status":"closed","priority":1,"issue_type":"epic","created_at":"2025-12-26T19:54:23.723935931Z","updated_at":"2025-12-26T22:52:42.875836878Z","closed_at":"2025-12-26T22:52:42.875836878Z","close_reason":"All children completed"}
{"id":"mala-aht.1","title":"Stop gate retry loop when Claude log is missing","description":"Context:\n- run_implementer only proceeds if the Claude session log exists after a ResultMessage (src/orchestrator.py:348).\n- If the log never appears, the loop spins until global timeout, causing false failures and queue stalls.\n\nScope:\n- In: add a bounded wait for log creation; if missing, fail fast with a clear summary and exit the loop.\n- Out: changing the log directory or disabling the quality gate.\n\nAcceptance Criteria:\n- When log file never appears, run_implementer exits within a bounded wait and sets a summary indicating missing log.\n- Orchestrator does not stall for the full timeout due to missing logs.\n\nTest Plan:\n- TDD: add a failing unit test with a mocked client that yields ResultMessage but no log file; assert run_implementer returns quickly with a failure summary.\n- Run pytest for the new test.","status":"closed","priority":1,"issue_type":"bug","created_at":"2025-12-26T19:54:34.157781045Z","updated_at":"2025-12-26T20:26:33.740210331Z","closed_at":"2025-12-26T20:26:33.740210331Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-aht.1","depends_on_id":"mala-aht","type":"parent-child","created_at":"2025-12-26T19:54:34.165963474Z","created_by":"daemon"}]}
{"id":"mala-aht.2","title":"Avoid releasing locks owned by other runs","description":"Context:\n- Orchestrator always calls release_all_locks on shutdown (src/orchestrator.py:835), which deletes every lock file in LOCK_DIR (src/tools/locking.py:111).\n- If multiple mala runs share LOCK_DIR, one run can clear locks for another, enabling concurrent writes. Needs verification.\n\nScope:\n- In: only clean up locks owned by this run's agents or isolate each run with a unique LOCK_DIR; keep admin/global cleanup for manual use.\n- Out: redesigning the locking protocol.\n\nAcceptance Criteria:\n- Run cleanup leaves locks owned by other agent IDs intact.\n- Each run only removes its own locks.\n\nTest Plan:\n- TDD: add a unit test that creates lock files for two agent IDs; run cleanup and verify only matching locks are removed.\n- Run pytest for new locking/orchestrator tests.","status":"closed","priority":1,"issue_type":"bug","created_at":"2025-12-26T19:54:42.017640957Z","updated_at":"2025-12-26T20:40:46.158277204Z","closed_at":"2025-12-26T20:40:46.158277204Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-aht.2","depends_on_id":"mala-aht","type":"parent-child","created_at":"2025-12-26T19:54:42.026062315Z","created_by":"daemon"},{"issue_id":"mala-aht.2","depends_on_id":"mala-aht.1","type":"blocks","created_at":"2025-12-26T19:55:09.816278845Z","created_by":"daemon"}]}
{"id":"mala-aht.3","title":"Terminate bd/git subprocesses on timeout","description":"Context:\n- _run_subprocess_async wraps subprocess.run in a thread and uses asyncio.wait_for; timeouts return but the subprocess keeps running (src/beads_client.py:38-70).\n- Hanging bd/git processes can accumulate during long runs.\n\nScope:\n- In: ensure timed-out commands are terminated (e.g., pass timeout to subprocess.run and handle TimeoutExpired, or switch to asyncio subprocess and kill).\n- Out: changing bd CLI usage.\n\nAcceptance Criteria:\n- Timed-out commands are terminated and return a timeout result.\n- Timeout warnings still emit.\n\nTest Plan:\n- TDD: add a failing test mocking subprocess.run to raise TimeoutExpired and assert a safe timeout result.\n- Run pytest for BeadsClient tests.","notes":"Quality gate failed: Timeout after 30 minutes\nLog: /home/cyou/.claude/projects/-home-cyou-mala/ce7cad14-805f-4dad-856c-647c4baa772c.jsonl","status":"closed","priority":2,"issue_type":"bug","created_at":"2025-12-26T19:54:51.724656053Z","updated_at":"2025-12-26T21:45:47.050205724Z","closed_at":"2025-12-26T21:45:47.050205724Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-aht.3","depends_on_id":"mala-aht","type":"parent-child","created_at":"2025-12-26T19:54:51.74113736Z","created_by":"daemon"}]}
{"id":"mala-aht.4","title":"Consolidate sync/async BeadsClient implementations","description":"Context:\n- BeadsClient maintains parallel sync and async implementations for the same actions (src/beads_client.py:80 and :244).\n- This duplication risks drift and unused code; needs verification that sync APIs are still required beyond tests.\n\nScope:\n- In: consolidate shared logic or deprecate/remove sync methods if unused; update tests accordingly.\n- Out: changing bd command semantics.\n\nAcceptance Criteria:\n- One authoritative implementation per action.\n- Tests updated and passing.\n\nTest Plan:\n- Run pytest for BeadsClient/orchestrator tests.","status":"closed","priority":2,"issue_type":"chore","created_at":"2025-12-26T19:55:00.28451646Z","updated_at":"2025-12-26T22:43:14.357220042Z","closed_at":"2025-12-26T22:43:14.357220042Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-aht.4","depends_on_id":"mala-aht","type":"parent-child","created_at":"2025-12-26T19:55:00.300728599Z","created_by":"daemon"},{"issue_id":"mala-aht.4","depends_on_id":"mala-aht.3","type":"blocks","created_at":"2025-12-26T19:55:13.994459065Z","created_by":"daemon"}]}
{"id":"mala-ai5","title":"Disallow git stash and other multi-agent conflicting operations","description":"Context:\n- Multiple agents may work in the same repository concurrently.\n- Operations like git stash, git rebase -i, git reset --hard, and similar commands can cause confusion and conflicts between agents.\n- These operations modify the working tree or history in ways that are difficult for other agents to detect or recover from.\n\nScope:\n- In: Add validation/hooks to detect and block dangerous git operations in agent contexts.\n- In: Document which git operations are disallowed and why.\n- In: Consider a pre-commit hook or wrapper to enforce these restrictions.\n- Out: Does not block operations in non-agent contexts (user override possible).\n\nDisallowed Operations:\n- git stash (all subcommands) - hides changes that other agents cannot see\n- git reset --hard - discards uncommitted changes silently\n- git rebase -i - interactive rebase requires human input\n- git checkout -f / git checkout --force - discards local changes\n- git clean -f - removes untracked files without warning\n- git merge --abort / git rebase --abort - may discard other agents' work\n\nAcceptance Criteria:\n- Dangerous git operations are detected and blocked with clear error messages.\n- Agents receive guidance on safe alternatives (e.g., commit instead of stash).\n- Documentation explains the rationale and lists all blocked operations.\n- Override mechanism exists for legitimate use cases.\n\nTest Plan:\n- Unit tests verify detection of each disallowed operation.\n- Integration test confirms blocking works in agent context.\n- Test override mechanism.\n\nNotes for Agent:\n- Consider implementing as a git hook, CLI wrapper, or agent-level validation.\n- Prefer failing fast with helpful error messages over silent failures.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-26T23:03:09.392627299Z","updated_at":"2025-12-26T23:36:27.199636643Z","closed_at":"2025-12-26T23:36:27.199636643Z","close_reason":"Closed"}
{"id":"mala-aio7","title":"Move session_dir parsing from DefaultReviewer to CerberusGateCLI","description":"## Problem\n\nDefaultReviewer still has JSON parsing logic (lines 247-257) to extract `session_dir` from wait output. This violates the Phase 3 acceptance criteria: 'DefaultReviewer has no parsing logic'.\n\n## Current Code\n\n```python\n# src/infra/clients/cerberus_review.py:247-257\ntry:\n    wait_data = json.loads(wait_result.stdout)\n    if isinstance(wait_data, dict):\n        session_dir = wait_data.get('session_dir')\n        if isinstance(session_dir, str) and session_dir:\n            review_log_path = Path(session_dir)\nexcept (json.JSONDecodeError, TypeError):\n    pass\n```\n\n## Fix Approach\n\n1. Add `session_dir: Path | None = None` field to `WaitResult` dataclass in CerberusGateCLI\n2. Parse `session_dir` from stdout JSON in `CerberusGateCLI.wait()`\n3. Update DefaultReviewer to use `wait_result.session_dir` directly\n4. Remove JSON parsing from DefaultReviewer\n\n## Acceptance Criteria\n\n- [ ] No `json.loads` calls in DefaultReviewer\n- [ ] WaitResult includes session_dir field\n- [ ] Existing tests pass\n\n## References\n\n- Parent refactor: mala-skxg (Phase 3)\n- File: src/infra/clients/cerberus_review.py","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-04T04:11:47.237404289Z","created_by":"cyou","updated_at":"2026-01-04T04:32:32.449772509Z","closed_at":"2026-01-04T04:32:32.449772509Z","close_reason":"Closed"}
{"id":"mala-akl","title":"mala run exit code: treat no-issue runs as success","description":"Problem: src/cli.py returns exit code 1 when success_count == 0. This makes no-op runs (no issues ready) look like failures in automation.\\n\\nScope/files: src/cli.py.\\n\\nWork:\\n- Distinguish 'no issues processed' from 'issues processed but failed'.\\n- Update exit code to 0 when there were no issues to process. Keep non-zero when actual failures occurred.\\n\\nAcceptance criteria:\\n- If no issues are ready, mala run exits 0.\\n- If issues ran and all failed, still exits non-zero.\\n- If some succeeded, exits 0.\\n\\nDependency: Avoid concurrent edits with mala-20u (same file).","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-25T21:38:26.013087673Z","updated_at":"2025-12-26T00:00:32.928066608Z","closed_at":"2025-12-26T00:00:32.928066608Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-akl","depends_on_id":"mala-20u","type":"blocks","created_at":"2025-12-25T21:38:51.94427495Z","created_by":"daemon"}]}
{"id":"mala-anh","title":"Refactor: split src/cli.py into focused modules","description":"Goal: Reduce the 600+ line cli module by extracting focused modules (e.g., orchestrator.py, hooks.py, prompt.py, git_utils.py). Keep external behavior unchanged.\\n\\nScope/files: src/cli.py plus new modules under src/.\\n\\nSuggested steps:\\n1) Identify pure helpers (git commit/branch, prompt loading, hook logic).\\n2) Move orchestration class to its own module.\\n3) Keep CLI wiring in cli.py, import helpers.\\n4) Update imports/tests.\\n\\nAcceptance criteria:\\n- CLI commands unchanged.\\n- No behavior changes to orchestrator loop.\\n- Tests pass (or at least no regressions).","status":"closed","priority":3,"issue_type":"task","created_at":"2025-12-25T21:40:15.520698126Z","updated_at":"2025-12-25T21:41:31.71957273Z","closed_at":"2025-12-25T21:41:31.71957273Z","close_reason":"Split into smaller refactor tasks: mala-9qr (hooks), mala-dqt (git helpers), mala-as5 (orchestrator extraction)."}
{"id":"mala-apsz","title":"[Review] src/infra/io/event_sink.py:1350-1351: [P2] Validation success visibility and issue_id inconsistent with start event","description":"## Review Finding\n\nThis issue was auto-created from a P2 review finding.\n\n**Source issue:** mala-40pg.3\n**Reviewer:** gemini\n**Location:** src/infra/io/event_sink.py:1350-1351\n\n## Details\n\nWhile `on_validation_started` was changed to `log()` for normal visibility and correctly includes the `issue_id`, the `passed` branch of `on_validation_result` still uses `log_verbose()` and lacks the `issue_id` parameter. This creates a poor UX in normal mode where the user sees a \"Starting validation...\" message but never receives a corresponding \"Validation passed\" completion message. Furthermore, in verbose mode, the success log will use an inconsistent prefix (`[agent-id]` instead of `[ISSUE-ID]`). It should be changed to use `log()` with `issue_id` to match the start event and other completion events like `on_gate_passed`.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-01T19:41:09.703261298Z","created_by":"cyou","updated_at":"2026-01-01T23:49:33.632510829Z","closed_at":"2026-01-01T23:49:33.632510829Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_finding:d71a07ca9d47","source:mala-40pg.3"]}
{"id":"mala-aqp4","title":"Epic: Architecture review 2025-12-30 (duplication, boundaries)","description":"## Context\nArchitecture review from 2025-12-30 using multi-model synthesis (Codex gpt-5.2-codex, Claude opus).\n\n## Key Findings (4 P1 issues)\n\n1. **Duplicated prompt loading** - `_get_gate_followup_prompt()` in orchestrator.py (test-exported) and agent_session_runner.py (runtime)\n2. **Duplicated MCP config** - `get_mcp_servers()` and `get_disallowed_tools()` across 3 modules\n3. **Pipeline bypasses event sink** - Direct console logging violates documented boundary contract\n4. **src/logging shadows stdlib** - Package name collision breaks third-party tools\n\n## Execution Strategy\n1. Rename logging module first (LOGGING-RENAME) - unblocks analysis tools\n2. Consolidate duplications (PROMPT-DUP, MCP-DUP) - can parallelize\n3. Fix event sink boundary (SINK-BOUNDARY) - depends on logging rename\n\n## Related Epics\n- mala-7si.1: Event sink wiring (closed)\n- mala-3qp: Prior architecture review (closed)\n\n## Primary Files\n- src/orchestrator.py, src/pipeline/*.py\n- src/logging/ (to be renamed)\n- src/prompts.py, src/mcp.py (new shared modules)","status":"closed","priority":1,"issue_type":"epic","created_at":"2025-12-30T17:26:16.585405021Z","created_by":"cyou","updated_at":"2025-12-30T22:42:28.623213077Z","closed_at":"2025-12-30T22:42:28.623213077Z","close_reason":"All children completed"}
{"id":"mala-aqp4.1","title":"Rename src/logging to src/log_output to avoid stdlib collision","description":"## Context\n- `src/logging` package collides with Python's stdlib `logging` module\n- When `src` is on Python path, `import logging` resolves to `src/logging` instead of stdlib\n- Broke grimp analysis during architecture review: `AttributeError: module 'logging' has no attribute 'getLogger'`\n- Will break any third-party library expecting stdlib logging\n- Confidence: High (directly observed during review)\n\n## Primary Files\n- src/log_output/ (NEW - rename from src/logging)\n- All files importing from src.logging (~10 files)\n\n## Scope\n**In:** Rename `src/logging/` directory to `src/log_output/`\n**In:** Update all imports across codebase (`from src.logging.console` → `from src.log_output.console`)\n**Out:** Changing logging behavior or API\n\n## Acceptance Criteria\n- No package named `logging` under `src/`\n- All imports updated to new module name\n- `python -c \"import logging; logging.getLogger('test')\"` succeeds with src on path\n- All tests pass\n\n## Test Plan\n- Run `uv run pytest` (full suite)\n- Run grimp analysis to verify it now works\n- `rg \"from src.logging\" src/` returns empty\n\n## Notes for Agent\n- Use git mv for directory rename to preserve history\n- Search all *.py files for imports to update\n- Files to update (grep result): cli.py, orchestrator.py, event_sink_console.py, cerberus_review.py, validation/spec_executor.py, pipeline/agent_session_runner.py, pipeline/run_coordinator.py","notes":"Quality gate failed: Timeout after 60 minutes\nLog: /home/cyou/.claude/projects/-home-cyou-mala/fd10381d-057e-45e7-9c2a-d7f9fea33343.jsonl","status":"closed","priority":1,"issue_type":"chore","created_at":"2025-12-30T17:26:29.260052468Z","created_by":"cyou","updated_at":"2025-12-30T20:25:22.098607324Z","closed_at":"2025-12-30T20:25:22.098607324Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-aqp4.1","depends_on_id":"mala-aqp4","type":"parent-child","created_at":"2025-12-30T17:26:29.266933305Z","created_by":"daemon"}]}
{"id":"mala-aqp4.2","title":"Consolidate duplicate _get_gate_followup_prompt() to shared prompts module","description":"## Context\n- `_get_gate_followup_prompt()` is defined in both `orchestrator.py:120` and `agent_session_runner.py:74`\n- Orchestrator version is test-exported (`tests/test_orchestrator.py:27,131,161`)\n- Pipeline version is actually used at runtime (agent_session_runner.py:668)\n- Tests verify one copy while production uses another - changes may pass tests but behave differently\n- Confidence: High (verified via grep)\n\n## Primary Files\n- src/prompts.py (NEW)\n- src/orchestrator.py\n- src/pipeline/agent_session_runner.py\n- tests/test_orchestrator.py\n\n## Scope\n**In:** Create `src/prompts.py` with `get_gate_followup_prompt()` and GATE_FOLLOWUP_FILE constant\n**In:** Remove duplicate from orchestrator.py and agent_session_runner.py, import from shared\n**In:** Update test imports in `tests/test_orchestrator.py:27`\n**Out:** Changing prompt content or file locations\n\n## Acceptance Criteria\n- Single definition of `_get_gate_followup_prompt()` in `src/prompts.py`\n- `orchestrator.py` imports from prompts.py\n- `agent_session_runner.py` imports from prompts.py\n- Tests import from prompts.py\n- `rg \"def _get_gate_followup_prompt\" src/` returns exactly one match\n\n## Test Plan\n- TDD: Update test imports first, verify they fail without implementation\n- Implement shared module\n- Run `uv run pytest tests/test_orchestrator.py -k followup`\n\n## Notes for Agent\n- Keep the @lru_cache decorator on the shared function\n- Move GATE_FOLLOWUP_FILE constant to prompts.py as well\n- Also move _PROMPT_DIR if needed for the constant","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-30T17:26:39.784671825Z","created_by":"cyou","updated_at":"2025-12-30T21:53:55.112150196Z","closed_at":"2025-12-30T21:53:55.112150196Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-aqp4.2","depends_on_id":"mala-aqp4","type":"parent-child","created_at":"2025-12-30T17:26:39.791506361Z","created_by":"daemon"},{"issue_id":"mala-aqp4.2","depends_on_id":"mala-aqp4.1","type":"blocks","created_at":"2025-12-30T17:30:44.226261008Z","created_by":"daemon"}]}
{"id":"mala-aqp4.3","title":"Consolidate duplicate MCP server configuration to shared module","description":"## Context\n- `get_mcp_servers()` and `_get_disallowed_tools()` duplicated across 3 files:\n  - `src/orchestrator.py:334-349` (public `get_mcp_servers()`)\n  - `src/pipeline/agent_session_runner.py:358-386` (private `_get_mcp_servers()`)\n  - `src/pipeline/run_coordinator.py:76-103` (private `_get_mcp_servers()`)\n- Updating Morph configuration requires changes in 3 places, risking behavioral divergence\n- Confidence: High (verified via grep)\n\n## Primary Files\n- src/mcp.py (NEW)\n- src/orchestrator.py\n- src/pipeline/agent_session_runner.py\n- src/pipeline/run_coordinator.py\n\n## Scope\n**In:** Create `src/mcp.py` with `get_mcp_servers()` and `get_disallowed_tools()`\n**In:** Remove duplicates from all three files, import from shared module\n**In:** Move `MORPH_DISALLOWED_TOOLS` constant from hooks.py to mcp.py\n**Out:** Changing MCP protocol or Morph integration behavior\n\n## Acceptance Criteria\n- Single source of truth for MCP configuration in `src/mcp.py`\n- All three files import from shared module\n- `rg \"def.*get_mcp_servers\" src/` returns exactly one match\n- `rg \"def.*get_disallowed_tools\" src/` returns exactly one match\n\n## Test Plan\n- Run existing tests: `uv run pytest -m integration`\n- Verify MCP server behavior unchanged\n- `uv run pytest tests/test_morph_integration.py` if exists\n\n## Notes for Agent\n- Unify function signatures (make parameters consistent across all use sites)\n- The orchestrator version takes `repo_path`, `morph_api_key`, `morph_enabled` - use this as canonical\n- Consider if hooks.py still needs MORPH_DISALLOWED_TOOLS or if it should import from mcp.py","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-30T17:26:53.255482712Z","created_by":"cyou","updated_at":"2025-12-30T22:22:04.748494155Z","closed_at":"2025-12-30T22:22:04.748494155Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-aqp4.3","depends_on_id":"mala-aqp4","type":"parent-child","created_at":"2025-12-30T17:26:53.262731297Z","created_by":"daemon"},{"issue_id":"mala-aqp4.3","depends_on_id":"mala-aqp4.2","type":"blocks","created_at":"2025-12-30T17:27:22.957168478Z","created_by":"daemon"}]}
{"id":"mala-aqp4.4","title":"Route pipeline module output through event sink protocol","description":"## Context\n- `MalaEventSink` protocol (`src/event_sink.py:1-10,45-54`) explicitly defines boundary: \"Defines the MalaEventSink protocol to decouple orchestration logic from presentation concerns. The orchestrator emits semantic events through the sink.\"\n- `AgentSessionRunner` imports `log`, `log_verbose` from `src.logging.console` (line 50-54) and calls directly (line 566-571)\n- `RunCoordinator` imports `log_agent_text`, `log_tool` (line 40-44) and calls directly (line 491-494)\n- This violates documented boundary contract, makes pipeline stages untestable with NullEventSink\n- Confidence: High (boundary contract cited from event_sink.py docstring)\n\n## Primary Files\n- src/pipeline/agent_session_runner.py\n- src/pipeline/run_coordinator.py\n- src/event_sink.py (may need new methods)\n- src/event_sink_console.py (implement new methods)\n\n## Scope\n**In:** Remove direct console imports from pipeline modules\n**In:** Route output through event_sink callbacks (already passed via config/callbacks)\n**In:** Add missing event sink methods if output types aren't covered\n**Out:** Changing EventSink protocol API design\n**Out:** Changing CLI user-visible output format\n\n## Acceptance Criteria\n- `agent_session_runner.py` has no imports from `src.logging.console` or `src.log_output.console`\n- `run_coordinator.py` has no imports from `src.logging.console` or `src.log_output.console`\n- `rg \"from src.(logging|log_output).console\" src/pipeline/` returns empty\n- Pipeline stages testable with NullEventSink (no stdout)\n\n## Test Plan\n- Add unit test with mock event sink verifying events emitted\n- Run `uv run pytest tests/` (may need to add pipeline-specific tests)\n- Verify CLI output unchanged visually\n\n## Notes for Agent\n- Check if all needed event types exist in MalaEventSink; add if missing\n- Follow pattern from orchestrator.py which already uses event sink\n- The event_sink is passed via AgentSessionRunnerConfig and RunCoordinatorConfig\n- May need to add methods like `on_log_waiting`, `on_fixer_text`, etc.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-30T17:27:06.95160185Z","created_by":"cyou","updated_at":"2025-12-30T22:33:47.623553635Z","closed_at":"2025-12-30T22:33:47.623553635Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-aqp4.4","depends_on_id":"mala-aqp4","type":"parent-child","created_at":"2025-12-30T17:27:06.958324986Z","created_by":"daemon"},{"issue_id":"mala-aqp4.4","depends_on_id":"mala-aqp4.1","type":"blocks","created_at":"2025-12-30T17:27:16.06289189Z","created_by":"daemon"},{"issue_id":"mala-aqp4.4","depends_on_id":"mala-aqp4.3","type":"blocks","created_at":"2025-12-30T17:27:30.371689554Z","created_by":"daemon"}]}
{"id":"mala-ari7","title":"Epic: Decompose _handle_review_effect (CCN 34 → \u003c15)","description":"## Summary\nThe 223-line `_handle_review_effect` method (CCN 34) interleaves event_sink calls, no-progress checks, retry policy decisions, and follow-up prompt building. The method returns a 4-tuple which indicates it's doing too much.\n\n## Primary Files\n- `src/pipeline/agent_session_runner.py:1008-1230`\n\n## Category\nCohesion\n\n## Source\nclaude\n\n## Fix Approach\n1. Extract `_check_review_no_progress()` for early exit\n2. Extract `_emit_review_events()` for event sink calls\n3. Extract `_build_review_retry_prompt()` for prompt formatting\n4. Return a dataclass instead of a 4-tuple\n\n## Non-Goals\n- Changing review retry semantics\n- Modifying event sink protocol\n\n## Acceptance Criteria\n- [ ] Return type simplified to dataclass\n- [ ] Event emission testable without running full review\n- [ ] CCN reduced below 15\n\n## Test Plan\n- Unit tests for each extracted helper\n- Integration test verifying review effect handling end-to-end","status":"closed","priority":2,"issue_type":"epic","created_at":"2026-01-03T05:49:03.313659181Z","created_by":"cyou","updated_at":"2026-01-03T18:15:01.580661612Z","closed_at":"2026-01-03T18:15:01.580661612Z","close_reason":"Closed"}
{"id":"mala-ari7.1","title":"Define ReviewEffectResult dataclass for _handle_review_effect return type","description":"## Summary\nReplace the 4-tuple return type `tuple[str | None, bool, str | None, TransitionResult | None]` with a typed dataclass.\n\n## Primary Files\n- `src/pipeline/agent_session_runner.py` - Define dataclass near method, update return statements\n- `tests/test_agent_session_runner.py` - Update any tests that check return values\n\n## Implementation\n1. Create `ReviewEffectResult` dataclass with fields:\n   - `pending_query: str | None`\n   - `should_break: bool`  \n   - `cerberus_log_path: str | None`\n   - `transition_result: TransitionResult | None`\n2. Update all 5 return statements in `_handle_review_effect` to use the dataclass\n3. Update the caller (likely in the same file) to use dataclass field access\n\n## Why First\nThis is a pure refactoring that must happen before extracting helpers, as the helpers will return this dataclass.\n\n## Acceptance Criteria\n- [ ] Dataclass defined with proper type annotations\n- [ ] All return statements use dataclass\n- [ ] Caller updated to use field access\n- [ ] Tests pass","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T05:53:30.239229395Z","created_by":"cyou","updated_at":"2026-01-03T08:14:33.693426856Z","closed_at":"2026-01-03T08:14:33.693426856Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-ari7.1","depends_on_id":"mala-mgfb.1","type":"blocks","created_at":"2026-01-03T06:02:11.987988131Z","created_by":"daemon"},{"issue_id":"mala-ari7.1","depends_on_id":"mala-ari7","type":"parent-child","created_at":"2026-01-03T17:58:44.886523627Z","created_by":"daemon"}]}
{"id":"mala-ari7.2","title":"Extract _check_review_no_progress helper method","description":"## Summary\nExtract the no-progress detection logic (lines 1047-1084) into a separate helper method that returns early when no progress is detected.\n\n## Primary Files\n- `src/pipeline/agent_session_runner.py` - Extract helper, update _handle_review_effect\n- `tests/test_agent_session_runner.py` - Add unit tests for the helper\n\n## Implementation\n1. Create `_check_review_no_progress()` method that:\n   - Takes: `input`, `log_path`, `lifecycle`, `lifecycle_ctx`\n   - Returns: `ReviewEffectResult | None` (None if no early exit needed)\n   - Handles the no-progress callback check\n   - Creates synthetic failed review result\n   - Emits `on_review_skipped_no_progress` event\n2. In `_handle_review_effect`, replace lines 1047-1084 with:\n   ```python\n   if no_progress_result := self._check_review_no_progress(input, log_path, lifecycle, lifecycle_ctx):\n       return no_progress_result\n   ```\n\n## Acceptance Criteria\n- [ ] Helper method extracted and tested independently\n- [ ] Event emission (`on_review_skipped_no_progress`) preserved\n- [ ] Synthetic review result creation isolated\n- [ ] CCN of _handle_review_effect reduced by ~5","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T05:53:39.156832855Z","created_by":"cyou","updated_at":"2026-01-03T09:00:43.427450736Z","closed_at":"2026-01-03T09:00:43.427450736Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-ari7.2","depends_on_id":"mala-ari7.1","type":"blocks","created_at":"2026-01-03T05:54:25.858272312Z","created_by":"daemon"},{"issue_id":"mala-ari7.2","depends_on_id":"mala-ari7","type":"parent-child","created_at":"2026-01-03T17:58:44.894670389Z","created_by":"daemon"}]}
{"id":"mala-ari7.3","title":"Extract _emit_gate_passed_events helper method","description":"## Summary\nExtract the event emission for gate passed (lines 1031-1045) into a helper method.\n\n## Primary Files\n- `src/pipeline/agent_session_runner.py` - Extract helper, update _handle_review_effect\n- `tests/test_agent_session_runner.py` - Add unit tests for event emission\n\n## Implementation\n1. Create `_emit_gate_passed_events()` method that:\n   - Takes: `issue_id: str`, `review_attempt: int`\n   - Returns: `None`\n   - Emits `on_gate_passed` and `on_validation_result` events when review_attempt == 1\n2. In `_handle_review_effect`, replace lines 1031-1045 with:\n   ```python\n   self._emit_gate_passed_events(input.issue_id, lifecycle_ctx.retry_state.review_attempt)\n   ```\n\n## Acceptance Criteria\n- [ ] Helper method extracted and tested independently\n- [ ] Events only emitted on first review attempt\n- [ ] Null-check for event_sink preserved\n- [ ] CCN of _handle_review_effect reduced by ~2","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T05:53:47.085556767Z","created_by":"cyou","updated_at":"2026-01-03T08:51:26.914996756Z","closed_at":"2026-01-03T08:51:26.914996756Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-ari7.3","depends_on_id":"mala-ari7.1","type":"blocks","created_at":"2026-01-03T05:54:25.922447805Z","created_by":"daemon"},{"issue_id":"mala-ari7.3","depends_on_id":"mala-ari7","type":"parent-child","created_at":"2026-01-03T17:58:44.903339697Z","created_by":"daemon"}]}
{"id":"mala-ari7.4","title":"Extract _build_review_retry_prompt helper method","description":"## Summary\nExtract the review retry prompt building logic (lines 1215-1227) into a separate helper method.\n\n## Primary Files\n- `src/pipeline/agent_session_runner.py` - Extract helper, update _handle_review_effect\n- `tests/test_agent_session_runner.py` - Add unit tests for prompt formatting\n\n## Implementation\n1. Create `_build_review_retry_prompt()` method that:\n   - Takes: `review_result`, `lifecycle_ctx`, `issue_id: str`\n   - Returns: `str` (the formatted prompt)\n   - Imports `format_review_issues` from cerberus_review\n   - Formats issues and builds the follow-up prompt\n2. In `_handle_review_effect`, replace lines 1215-1227 with:\n   ```python\n   pending_query = self._build_review_retry_prompt(review_result, lifecycle_ctx, input.issue_id)\n   ```\n\n## Acceptance Criteria\n- [ ] Helper method extracted and tested independently\n- [ ] Prompt formatting logic isolated and testable\n- [ ] Import moved to module level or helper\n- [ ] CCN of _handle_review_effect reduced by ~2","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T05:53:57.009973541Z","created_by":"cyou","updated_at":"2026-01-03T08:41:40.099086204Z","closed_at":"2026-01-03T08:41:40.099086204Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-ari7.4","depends_on_id":"mala-ari7.1","type":"blocks","created_at":"2026-01-03T05:54:25.976258224Z","created_by":"daemon"},{"issue_id":"mala-ari7.4","depends_on_id":"mala-ari7","type":"parent-child","created_at":"2026-01-03T17:58:44.912197233Z","created_by":"daemon"}]}
{"id":"mala-ari7.5","title":"Extract _emit_review_result_events helper method","description":"## Summary\nExtract the event emission after review result processing (lines 1158-1213) into a helper method that handles all review outcome events.\n\n## Primary Files\n- `src/pipeline/agent_session_runner.py` - Extract helper, update _handle_review_effect\n- `tests/test_agent_session_runner.py` - Add unit tests for event emission paths\n\n## Implementation\n1. Create `_emit_review_result_events()` method that:\n   - Takes: `input`, `result: TransitionResult`, `review_result`, `lifecycle_ctx`\n   - Returns: `None`\n   - Handles event emission for:\n     - `COMPLETE_SUCCESS`: `on_review_passed`\n     - `RUN_REVIEW` (parse error): `on_warning`\n     - `SEND_REVIEW_RETRY`: `on_review_retry`\n2. Move the blocking_count calculation and logging into this helper\n3. In `_handle_review_effect`, call the helper after `lifecycle.on_review_result()`\n\n## Acceptance Criteria\n- [ ] Helper method extracted and tested independently\n- [ ] All event paths covered by tests\n- [ ] Debug logging preserved\n- [ ] CCN of _handle_review_effect reduced by ~8","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T05:54:07.655953369Z","created_by":"cyou","updated_at":"2026-01-03T08:28:13.174251356Z","closed_at":"2026-01-03T08:28:13.174251356Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-ari7.5","depends_on_id":"mala-ari7.1","type":"blocks","created_at":"2026-01-03T05:54:26.0305811Z","created_by":"daemon"},{"issue_id":"mala-ari7.5","depends_on_id":"mala-ari7","type":"parent-child","created_at":"2026-01-03T17:58:44.920655423Z","created_by":"daemon"}]}
{"id":"mala-ari7.6","title":"Final cleanup: verify CCN \u003c 15 and add integration test","description":"## Summary\nFinal verification that the refactoring achieved the CCN goal and all pieces work together.\n\n## Primary Files\n- `src/pipeline/agent_session_runner.py` - Verify structure, add docstrings\n- `tests/test_agent_session_runner.py` - Add integration test for full flow\n\n## Implementation\n1. Run complexity analysis to verify CCN \u003c 15 for `_handle_review_effect`\n2. Ensure all extracted helpers have docstrings\n3. Add integration test that exercises the full `_handle_review_effect` flow\n4. Update any stale comments in the method\n5. Verify imports are at module level (not inline)\n\n## Verification Commands\n```bash\n# Check complexity\nuvx radon cc src/pipeline/agent_session_runner.py -a -nc | grep _handle_review_effect\n```\n\n## Acceptance Criteria\n- [ ] CCN of _handle_review_effect \u003c 15\n- [ ] All helpers have docstrings\n- [ ] Integration test covers success, failure, retry, and no-progress paths\n- [ ] No inline imports remain","notes":"Quality gate failed: Quality gate failed: No commit with bd-mala-ari7.6 found after run baseline (stale commits from previous runs are rejected)\nLog: /home/cyou/.claude/projects/-home-cyou-mala/686bc73b-3471-4a96-8127-f327b18d73b7.jsonl","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T05:54:18.195593815Z","created_by":"cyou","updated_at":"2026-01-03T14:56:24.959218887Z","closed_at":"2026-01-03T14:56:24.959218887Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-ari7.6","depends_on_id":"mala-ari7.2","type":"blocks","created_at":"2026-01-03T05:54:31.498963609Z","created_by":"daemon"},{"issue_id":"mala-ari7.6","depends_on_id":"mala-ari7.3","type":"blocks","created_at":"2026-01-03T05:54:31.554375067Z","created_by":"daemon"},{"issue_id":"mala-ari7.6","depends_on_id":"mala-ari7.4","type":"blocks","created_at":"2026-01-03T05:54:31.603800273Z","created_by":"daemon"},{"issue_id":"mala-ari7.6","depends_on_id":"mala-ari7.5","type":"blocks","created_at":"2026-01-03T05:54:31.661904845Z","created_by":"daemon"},{"issue_id":"mala-ari7.6","depends_on_id":"mala-ari7","type":"parent-child","created_at":"2026-01-03T17:58:44.928581846Z","created_by":"daemon"}]}
{"id":"mala-as5","title":"Refactor: move MalaOrchestrator to its own module","description":"Goal: Extract MalaOrchestrator class from src/cli.py into a dedicated module (e.g., src/orchestrator.py) to reduce file size and clarify responsibilities.\\n\\nScope/files: src/cli.py, new src/orchestrator.py.\\n\\nWork:\\n- Move IssueResult + MalaOrchestrator to new module.\\n- Adjust imports in cli.py.\\n- Keep CLI command signatures and behavior unchanged.\\n\\nAcceptance criteria:\\n- Orchestrator behavior unchanged.\\n- CLI still works.\\n- Tests (if any) still pass.","status":"closed","priority":3,"issue_type":"task","created_at":"2025-12-25T21:41:26.002972067Z","updated_at":"2025-12-26T00:15:52.939703847Z","closed_at":"2025-12-26T00:15:52.939703847Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-as5","depends_on_id":"mala-dqt","type":"blocks","created_at":"2025-12-25T21:47:35.258531693Z","created_by":"daemon"},{"issue_id":"mala-as5","depends_on_id":"mala-c01","type":"parent-child","created_at":"2025-12-25T22:05:59.484296839Z","created_by":"daemon"}]}
{"id":"mala-au9g","title":"Epic: Decompose MalaOrchestrator god object","description":"## Summary\n`MalaOrchestrator` owns gate/review execution, epic verification loops, run metadata, prompt assembly, issue finalization, and task scheduling. The class is 1110 LOC with fan-out to 27 modules. This centralizes too many responsibilities.\n\n## Primary Files\n- `src/orchestration/orchestrator.py:348-1040`\n\n## Category\nCohesion\n\n## Source\ncodex\n\n## Fix Approach\n1. Extract discrete coordinators:\n   - `IssueFinalizer`\n   - `EpicVerificationCoordinator`\n   - `GateReviewCoordinator`\n2. Each coordinator has explicit inputs/outputs\n3. Keep orchestrator as thin wiring + flow control\n\n## Non-Goals\n- Creating abstract factory patterns for every responsibility\n- Breaking the factory.py dependency injection pattern\n\n## Acceptance Criteria\n- [ ] Orchestrator delegates epic verification, gate/review execution, and result finalization to separate components\n- [ ] Each new component has focused tests without requiring full orchestrator setup\n- [ ] Orchestrator imports reduced below 20 modules\n\n## Test Plan\n- Unit tests for each extracted coordinator with mock dependencies\n- Integration test verifying orchestrator correctly wires coordinators\n\n## Agent Notes\nStart with `_finalize_issue_result` as it has clearest boundaries; epic verification has more git/beads entanglement.","status":"closed","priority":1,"issue_type":"epic","created_at":"2026-01-03T05:48:18.708609192Z","created_by":"cyou","updated_at":"2026-01-03T18:33:45.808306441Z","closed_at":"2026-01-03T18:33:45.808306441Z","close_reason":"Closed"}
{"id":"mala-au9g.1","title":"Extract IssueFinalizer coordinator from MalaOrchestrator","description":"## Summary\nExtract `_finalize_issue_result` and supporting methods into `src/pipeline/issue_finalizer.py`.\n\n## Target Methods (lines 560-678 in orchestrator.py)\n- `_record_issue_run` - Record IssueRun to RunMetadata\n- `_cleanup_session_paths` - Remove session/review log paths\n- `_emit_completion` - Emit event and handle failure tracking\n- `_finalize_issue_result` - Main finalization logic (gate metadata, issue close, epic check trigger)\n\n## Design Pattern (follow existing pipeline extractors)\nFollow the pattern from `gate_runner.py` and `review_runner.py`:\n- Dataclass config: `IssueFinalizeConfig`\n- Input type: `IssueFinalizeInput` (issue_id, result, log_path, stored_gate_result)\n- Output type: `IssueFinalizeOutput` (success, closed, gate_metadata)\n- Use callbacks for orchestrator-owned operations (beads.close_async, event_sink, epic_closure_trigger)\n\n## Callback Design\nThe finalizer needs callbacks for:\n- `close_issue: Callable[[str], Awaitable[bool]]` - Beads close\n- `mark_needs_followup: Callable[[str, str, Path | None], Awaitable[None]]` - Beads mark\n- `on_issue_closed: Callable[[str, str], None]` - Event sink\n- `on_issue_completed: Callable[[str, str, bool, float, str], None]` - Event sink\n- `trigger_epic_closure: Callable[[str], Awaitable[None]]` - Trigger epic check (orchestrator callback)\n- `create_tracking_issues: Callable[[str, list], Awaitable[None]]` - Review tracking\n\n## Files to Create/Modify\n- **Create**: `src/pipeline/issue_finalizer.py` (~150 LOC)\n- **Modify**: `src/orchestration/orchestrator.py` (remove methods, add IssueFinalizer init and calls)\n- **Modify**: `tests/test_orchestrator.py` if affected\n\n## Acceptance Criteria\n- All existing tests pass\n- IssueFinalizer can be tested in isolation with mock callbacks\n- Orchestrator `_finalize_issue_result` delegates to IssueFinalizer\n- Gate metadata building stays in `gate_metadata.py` (reuse existing)\n","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T05:53:17.726238392Z","created_by":"cyou","updated_at":"2026-01-03T07:06:40.529597853Z","closed_at":"2026-01-03T07:06:40.529597853Z","close_reason":"Closed"}
{"id":"mala-au9g.2","title":"Extract EpicVerificationCoordinator from MalaOrchestrator","description":"## Summary\nExtract `_check_epic_closure` and `_execute_remediation_issues` into `src/pipeline/epic_verification_coordinator.py`.\n\n## Target Methods (lines 404-558 in orchestrator.py)\n- `_check_epic_closure` - Check if closing an issue should close parent epic, with retry loop\n- `_execute_remediation_issues` - Spawn and finalize remediation issue agents\n\n## Dependencies\n- **BLOCKED BY**: `mala-au9g.1` (IssueFinalizer) - remediation uses `_finalize_issue_result`\n- Uses `epic_verifier.verify_and_close_epic` (already extracted to `infra/epic_verifier.py`)\n- Uses `spawn_agent` callback for remediation issues\n\n## Design Pattern\nFollow the pattern from `issue_execution_coordinator.py`:\n- Dataclass config: `EpicVerificationConfig` (max_retries)\n- Protocol-based callbacks for orchestrator-owned operations\n- State management: `verified_epics`, `epics_being_verified` sets\n\n## Callback Design\nThe coordinator needs callbacks for:\n- `get_parent_epic: Callable[[str], Awaitable[str | None]]` - Beads lookup\n- `verify_epic: Callable[[str, bool], Awaitable[VerificationResult]]` - EpicVerifier\n- `spawn_remediation: Callable[[str], Awaitable[Task | None]]` - Spawn agent for remediation\n- `finalize_remediation: Callable[[str, IssueResult], Awaitable[None]]` - IssueFinalizer\n- `close_eligible_epics: Callable[[], Awaitable[bool]]` - Fallback for mock providers\n- `on_epic_closed: Callable[[str], None]` - Event sink\n- `on_warning: Callable[[str], None]` - Event sink\n\n## Files to Create/Modify\n- **Create**: `src/pipeline/epic_verification_coordinator.py` (~200 LOC)\n- **Modify**: `src/orchestration/orchestrator.py` (remove methods, add coordinator init)\n- **Modify**: `src/pipeline/__init__.py` (add export)\n\n## Acceptance Criteria\n- All existing tests pass\n- Coordinator can be tested in isolation with mock callbacks\n- Verified/being_verified state lives in coordinator\n- Retry loop and remediation execution move out of orchestrator\n","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T05:53:36.974615902Z","created_by":"cyou","updated_at":"2026-01-03T07:35:14.023230459Z","closed_at":"2026-01-03T07:35:14.023230459Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-au9g.2","depends_on_id":"mala-au9g.1","type":"blocks","created_at":"2026-01-03T05:54:25.280065486Z","created_by":"daemon"}]}
{"id":"mala-au9g.3","title":"Consolidate gate/review callback wiring into SessionCallbackFactory","description":"## Summary\nExtract `_build_session_callbacks` and gate/review callback setup into `src/pipeline/session_callback_factory.py`.\n\n## Target Methods (lines 348-402, 727-808 in orchestrator.py)\n- `_run_quality_gate_sync` - Sync wrapper for GateRunner\n- `_run_quality_gate_async` - Async wrapper via to_thread\n- `_build_session_callbacks` - Build SessionCallbacks with closures\n\n## Current State\nGate/review execution is already well-extracted:\n- `GateRunner` handles gate checking logic\n- `ReviewRunner` handles review execution logic\n- `AgentSessionRunner` uses `SessionCallbacks` for hooks\n\nThe remaining orchestrator code is **callback wiring** - creating closures that bridge orchestrator state to the runners. This is primarily a **cohesion** improvement, not a major extraction.\n\n## Design Pattern\nCreate a factory class that encapsulates callback construction:\n- Input: orchestrator state references (gate_runner, review_runner, log_provider, event_sink, paths dicts)\n- Output: `SessionCallbacks` instance\n- Reduces orchestrator method count and clarifies callback ownership\n\n## Files to Create/Modify\n- **Create**: `src/pipeline/session_callback_factory.py` (~100 LOC)\n- **Modify**: `src/orchestration/orchestrator.py` (replace `_build_session_callbacks` with factory call)\n- Gate sync/async wrappers remain in orchestrator (they manage orchestrator state like `last_gate_results`)\n\n## Design Decision\nKeep `_run_quality_gate_sync/async` in orchestrator because:\n- They manage `self.last_gate_results` and `self.per_issue_spec` sync\n- The async wrapper is a simple `to_thread` call\n- Moving them would require passing more state through callbacks\n\n## Acceptance Criteria\n- All existing tests pass\n- SessionCallbackFactory can be tested with mock dependencies\n- Orchestrator `_build_session_callbacks` replaced with factory call\n- Callback wiring is consolidated in one place\n","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T05:53:57.836399012Z","created_by":"cyou","updated_at":"2026-01-03T07:28:23.695175225Z","closed_at":"2026-01-03T07:28:23.695175225Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-au9g.3","depends_on_id":"mala-au9g.1","type":"blocks","created_at":"2026-01-03T06:02:11.970471018Z","created_by":"daemon"}]}
{"id":"mala-au9g.4","title":"Create OrchestrationWiring class for thin orchestrator","description":"## Summary\nFinal refactor: reduce MalaOrchestrator to thin wiring by consolidating remaining logic.\n\n## Prerequisites\n- **BLOCKED BY**: `mala-au9g.1` (IssueFinalizer)\n- **BLOCKED BY**: `mala-au9g.2` (EpicVerificationCoordinator)\n- **BLOCKED BY**: `mala-au9g.3` (SessionCallbackFactory)\n\n## Current Orchestrator Responsibilities (after extractions)\nAfter the three extractions, orchestrator will still own:\n1. `run()` / `run_sync()` - Main entry point and run coordination\n2. `run_implementer()` - Agent session setup and execution\n3. `spawn_agent()` - Issue claiming and task creation\n4. `_run_main_loop()` - Delegates to IssueExecutionCoordinator\n5. `_finalize_run()` - Run-level finalization\n6. `_abort_active_tasks()` - Abort handling\n7. Initialization (`__init__`, `_init_from_factory`, `_init_runtime_state`, `_init_pipeline_runners`)\n8. State properties (active_tasks, failed_issues, max_issues, abort_run)\n\n## Refactor Goals\n1. Review remaining methods for further consolidation opportunities\n2. Ensure orchestrator is primarily:\n   - Initialization and dependency wiring\n   - Main run loop delegation\n   - State property accessors\n3. Move any remaining policy logic to appropriate coordinators\n4. Document the final orchestrator API surface\n\n## Files to Modify\n- **Modify**: `src/orchestration/orchestrator.py` (review and tidy)\n- **Modify**: `src/orchestration/__init__.py` (update exports)\n- **Create**: `docs/architecture/orchestrator-decomposition.md` (optional, if requested)\n\n## Acceptance Criteria\n- Orchestrator is under 600 LOC (down from 1110)\n- All tests pass\n- Clear separation: orchestrator = wiring, pipeline = logic\n- No remaining monolithic methods over 50 LOC\n","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T05:54:13.94257258Z","created_by":"cyou","updated_at":"2026-01-03T08:09:42.18916243Z","closed_at":"2026-01-03T08:09:42.18916243Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-au9g.4","depends_on_id":"mala-au9g.1","type":"blocks","created_at":"2026-01-03T05:54:29.785007213Z","created_by":"daemon"},{"issue_id":"mala-au9g.4","depends_on_id":"mala-au9g.2","type":"blocks","created_at":"2026-01-03T05:54:29.800695183Z","created_by":"daemon"},{"issue_id":"mala-au9g.4","depends_on_id":"mala-au9g.3","type":"blocks","created_at":"2026-01-03T05:54:29.809414998Z","created_by":"daemon"}]}
{"id":"mala-au9g.5","title":"test issue","notes":"Quality gate failed: Quality gate failed: No commit with bd-mala-au9g.5 found after run baseline (stale commits from previous runs are rejected)\nLog: /home/cyou/.claude/projects/-home-cyou-mala/df73f2cb-9e67-4f6c-aa4c-674fd997cdef.jsonl","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T17:48:44.097167002Z","created_by":"cyou","updated_at":"2026-01-03T17:53:41.880813222Z","closed_at":"2026-01-03T17:53:41.880813222Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-au9g.5","depends_on_id":"mala-au9g","type":"parent-child","created_at":"2026-01-03T17:48:44.097580618Z","created_by":"daemon"}]}
{"id":"mala-au9g.6","title":"[Human Review] Epic mala-au9g requires manual verification","description":"## Context\nThis epic has been flagged for human review.\n\n## Reason\nLow confidence (0.00)\n\n## Epic ID\nmala-au9g\n\n## Verification Details\n- Confidence: 0.00\n- Reasoning: No commits found for child issues\n\n## Resolution\nReview the epic and its child issues manually. When satisfied, close this issue\nto unblock the epic, or use `--epic-override mala-au9g` to force closure.\n","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-03T18:05:12.771025648Z","created_by":"cyou","updated_at":"2026-01-03T18:05:20.105529283Z","closed_at":"2026-01-03T18:05:20.105529283Z","close_reason":"Closed","labels":["epic_human_review","epic_id:mala-au9g"],"dependencies":[{"issue_id":"mala-au9g.6","depends_on_id":"mala-au9g","type":"parent-child","created_at":"2026-01-03T18:05:12.771463785Z","created_by":"daemon"}]}
{"id":"mala-avh","title":"Add better logging of CLI args to JSON and terminal","description":"Improve visibility into what CLI arguments were used for a mala run.\n\n## Problem\nCurrently it's hard to see what flags were passed (e.g., --disable-validations) in both:\n- Terminal output at run start\n- JSON run metadata/logs\n\n## Proposed Solution\n1. Log parsed CLI args at run start in terminal (similar to how validation commands are shown)\n2. Include full CLI args in run metadata JSON for debugging/auditing\n\n## Specific Args to Log\n- --disable-validations (which ones)\n- --coverage-threshold\n- --wip flag\n- --max-issues\n- --max-gate-retries / --max-review-retries\n- --braintrust flag\n\n## Acceptance Criteria\n- [ ] Terminal shows key CLI args at run start (dimmed/gray like other config lines)\n- [ ] Run metadata JSON includes cli_args or similar field\n- [ ] Easy to see why validations were skipped when debugging","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-27T20:22:35.103181662Z","updated_at":"2025-12-28T04:34:29.507325034Z","closed_at":"2025-12-28T04:34:29.507325034Z","close_reason":"Closed"}
{"id":"mala-awsa","title":"[Review] src/infra/clients/cerberus_review.py:191-195: [P2] Scope gate resolve to the current session","description":"## Review Finding\n\nThis issue was auto-created from a P2 review finding.\n\n**Source issue:** mala-m70v\n**Reviewer:** codex\n**Location:** src/infra/clients/cerberus_review.py:191-195\n\n## Details\n\nOn an \"already active\" error, the code always runs `review-gate resolve` without any session scoping. If another run is legitimately active in the same repo, this will clear that gate and discard its in-flight review, then spawn a new one. That can cause concurrent runs to interfere and lose review results. Consider resolving only the current session (e.g., pass a session ID / verify the gate belongs to this run) or treat it as a hard error instead of unconditionally resolving.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-01T23:45:36.302392711Z","created_by":"cyou","updated_at":"2026-01-01T23:55:18.785591003Z","closed_at":"2026-01-01T23:55:18.785591003Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_finding:e41686bb36de","source:mala-m70v"]}
{"id":"mala-axhy","title":"[Review] 7 non-blocking findings from mala-eyxq","description":"## Review Findings\n\nThis issue consolidates 7 non-blocking findings from code review.\n\n**Source issue:** mala-eyxq\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] SyntheticReviewOutcome missing review_log_path attribute\n\n**Priority:** P2\n**Reviewer:** claude\n**Location:** src/pipeline/lifecycle_effect_handler.py:43-56\n\nThe `SyntheticReviewOutcome` dataclass is missing the `review_log_path` attribute that's part of the `ReviewResultProtocol` in `src/core/protocols.py`. While the current usage only accesses `passed`, `issues`, `parse_error`, and `fatal_error`, the protocol defines `review_log_path: Path | None` as a required attribute. This could cause runtime `AttributeError` if any code path attempts to access this attribute on the synthetic outcome.\n\n---\n\n### Finding 2: [P3] Review formatter TYPE_CHECKING import references wrong module\n\n**Priority:** P3\n**Reviewer:** claude\n**Location:** src/pipeline/review_formatter.py:16-19\n\nThe docstring claims the formatter \"works with any object that satisfies the ReviewIssue protocol from src.domain.lifecycle\", and the `TYPE_CHECKING` import references `src.domain.lifecycle.ReviewIssue`. However, `ReviewIssue` in `src.domain.lifecycle` is a Protocol (not a runtime-checkable class), and the import path is conceptually inconsistent with the stated goal of decoupling from domain. Consider using `ReviewIssueProtocol` from `src.core.protocols` for consistency with the rest of the pipeline layer.\n\n---\n\n### Finding 3: [P2] Type `SyntheticReviewOutcome.issues` as `list[ReviewIssue]`\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/pipeline/lifecycle_effect_handler.py:43-55\n\n`SyntheticReviewOutcome` is intended to satisfy `src.domain.lifecycle.ReviewOutcome`, but `issues` is currently annotated as plain `list` (effectively `list[Any]`). This weakens guarantees (it can silently hold non-`ReviewIssue` items) and can trip static checking when passing `synthetic` into `ImplementerLifecycle.on_review_result(...)` (lists are invariant, so `list[Any]` may not be accepted as `list[ReviewIssue]` depending on checker settings).\n\nConcretely, this path runs when `check_review_no_progress()` detects no progress (review attempt \u003e 1 + callback configured). Tightening the annotation keeps the new decoupled path type-safe without changing runtime behavior.\n\n---\n\n### Finding 4: [P2] Indent multi-line issue bodies in formatter\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/pipeline/review_formatter.py:98-100\n\nThe formatter appends `issue.body` directly, which will result in incorrect indentation for multi-line descriptions. This can break the structural clarity of follow-up prompts sent to the agent.\n\n---\n\n### Finding 5: [P2] Use portable absolute path check\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/pipeline/review_formatter.py:37-38\n\nThe `file_path.startswith(\"/\")` check is Unix-specific and will fail on other platforms like Windows. Use `Path(file_path).is_absolute()` for a more robust and portable check.\n\n---\n\n### Finding 6: [P3] Resolve base_path outside of the loop\n\n**Priority:** P3\n**Reviewer:** gemini\n**Location:** src/pipeline/review_formatter.py:41\n\n`_to_relative_path` calls `.resolve()` on every issue. For performance, `base_path` should be resolved once at the beginning of `format_review_issues` and then passed to the helper.\n\n---\n\n### Finding 7: [P3] Avoid extra space when reviewer is missing\n\n**Priority:** P3\n**Reviewer:** gemini\n**Location:** src/pipeline/review_formatter.py:96-97\n\nThe formatter adds an extra space after the colon if `reviewer_tag` is empty (e.g., `L10:  Title`). Conditionalizing the space would produce cleaner output.\n\n---\n\n\u003c!-- fp:26a53b2044e46424 --\u003e\n\u003c!-- fp:ed9214cdc7460f77 --\u003e\n\u003c!-- fp:a95b56798ab1d995 --\u003e\n\u003c!-- fp:c0326121495a3de2 --\u003e\n\u003c!-- fp:c2ad2c663105fdf1 --\u003e\n\u003c!-- fp:477fd7d00de08bd8 --\u003e\n\u003c!-- fp:f984f92da0db58ab --\u003e\n\u003c!-- review_finding:d8746e82ffa4 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-05T01:40:58.25388972Z","created_by":"cyou","updated_at":"2026-01-05T01:49:59.699294398Z","closed_at":"2026-01-05T01:49:59.699294398Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-eyxq"]}
{"id":"mala-b04k","title":"[Review] src/domain/validation/spec_runner.py:141-142: [P2] Comment may conflict with existing setup cache design","description":"## Review Finding\n\nThis issue was auto-created from a P2 review finding.\n\n**Source issue:** mala-fdp1.13\n**Reviewer:** claude\n**Location:** src/domain/validation/spec_runner.py:141-142\n\n## Details\n\nThe new comment claims 'setup commands always run (not cached), so setup_files changes automatically trigger setup re-run.' However, the codebase has `should_invalidate_setup_cache()` in `validation_gating.py:125`, tests for it in `TestShouldInvalidateSetupCache`, and docstrings describing 'setup_files: Lock/dependency files that invalidate setup cache' (spec.py:153, config.py:336). This suggests setup caching was designed to exist but isn't wired up yet. The comment could mislead future developers about the intended architecture. Consider either (1) updating the comment to note this is the current behavior but cache invalidation support exists, or (2) wiring up `should_invalidate_setup_cache` to match the existing design.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-02T16:23:42.530790439Z","created_by":"cyou","updated_at":"2026-01-02T16:57:17.302421625Z","closed_at":"2026-01-02T16:57:17.302421625Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_finding:69d7a726837f","source:mala-fdp1.13"]}
{"id":"mala-b0m","title":"Graceful shutdown with agent wrap-up","description":"## Goal\nHandle agent exit gracefully: signal active agents to wrap up, clean up partial state, and ensure consistent issue status before exit.\n\nLocation:\n- src/orchestration/orchestrator.py:322-326 (_cleanup_agent_locks)\n- src/pipeline/agent_session_runner.py (session cleanup)\n- src/infra/tools/locking.py (lock release)\n\n## Problem\nWhen agents exit early (context exhaustion, error, timeout, orchestrator shutdown), they may leave:\n- Partial work uncommitted in the git working tree\n- Issue in inconsistent state (e.g., still marked as in_progress)\n- Locks held without cleanup\n\n## Current Behavior\n- Orchestrator calls `reset_issue()` which sets status back, but doesn't clean up git state\n- Partial file changes may remain uncommitted in the working tree\n- Next agent picking up the issue sees a dirty state\n\n## Proposed Solution\nOn agent exit (early or orchestrator shutdown):\n\n1. **Signal agents to wrap up**: Give active agents a chance to commit partial work or rollback cleanly\n2. **Reset git state**: Discard uncommitted changes for files the agent touched\n   - `git checkout -- \u003cfiles\u003e` or `git restore \u003cfiles\u003e`\n   - Consider `git clean` for new untracked files created by the agent\n   - Be careful not to discard work from other concurrent agents\n3. **Mark issue as open**: Use `bd update \u003cissue\u003e --status open` (not just reset to previous state)\n4. **Release all locks**: Ensure locks are released before git reset\n5. **Emit final summary**: Log what was cleaned up and issue states\n\nScope:\n- In: Signal handling for graceful shutdown; git state cleanup; lock release\n- Out: Automatic work saving (see mala-d6x context exhaustion)\n\n## Acceptance Criteria\n- Early exit leaves working tree clean (no partial changes from failed agent)\n- Orchestrator shutdown signals all agents before force-terminating\n- Issue status is set to 'open' on failure\n- Locks are released\n- Other agents' uncommitted work is not affected\n- Final summary emitted showing cleanup actions\n\nTest Plan:\n- Unit: Test git cleanup for specific file patterns\n- Unit: Test lock release ordering\n- Integration: Kill agent mid-work, verify clean state\n- Manual: Ctrl+C during run, verify no orphaned changes\n\nNotes for Agent:\n- Track which files the agent modified (from tool calls in JSONL log)\n- Or simpler: reset only files the agent held locks for\n- Safest approach: only reset files associated with locks held by the exiting agent\n- Related to mala-1qoc (clean up uncommitted changes after timeout)","status":"closed","priority":1,"issue_type":"feature","created_at":"2025-12-26T00:55:42.940976936Z","updated_at":"2026-01-06T04:43:59.997960062Z","closed_at":"2026-01-06T04:43:59.997960062Z","close_reason":"Closed"}
{"id":"mala-b79o","title":"Epic: Route logging through event sink instead of direct console.log","description":"## Summary\nDirect console logging via `log()` in domain/infra bypasses the `MalaEventSink` abstraction. This makes output harder to suppress, test, or redirect. Splits formatting responsibilities across modules.\n\n## Primary Files\n- `src/domain/validation/spec_executor.py:146,221,363,380`\n- `src/infra/clients/cerberus_review.py:195,590`\n\n## Category\nTestability\n\n## Source\ncodex\n\n## Fix Approach\nRoute logging through an injected sink/logger (or return structured events) from spec executor and reviewer; keep console output centralized in `ConsoleEventSink`.\n\n## Non-Goals\n- Adding structured logging infrastructure\n- Changing log message formats\n\n## Acceptance Criteria\n- [ ] No direct `log()` calls from domain/infra execution paths; output flows through injected interface\n- [ ] Tests can silence output via `NullEventSink` without patching `console.log`\n\n## Test Plan\n- Verify no direct `log()` calls in domain/ after refactor\n- Unit tests with NullEventSink verify no console output\n\n## Agent Notes\nSome `log()` calls are in error paths; ensure error events are still emitted through sink.","status":"closed","priority":2,"issue_type":"epic","created_at":"2026-01-03T05:49:32.301877957Z","created_by":"cyou","updated_at":"2026-01-03T18:31:01.493340265Z","closed_at":"2026-01-03T18:31:01.493340265Z","close_reason":"Closed"}
{"id":"mala-b79o.1","title":"Add validation step events to MalaEventSink protocol","description":"## Goal\nAdd event methods to MalaEventSink protocol for validation step lifecycle events.\n\n## Files to modify\n- `src/infra/io/event_sink.py` - Add protocol methods + BaseEventSink no-ops + ConsoleEventSink implementations\n\n## New event methods to add\n1. `on_validation_step_running(step_name: str, agent_id: str | None = None)` - Called when a validation step starts\n2. `on_validation_step_skipped(step_name: str, reason: str, agent_id: str | None = None)` - Called when step is skipped (e.g., lint cache hit)\n3. `on_validation_step_passed(step_name: str, duration_seconds: float, agent_id: str | None = None)` - Called when step succeeds\n4. `on_validation_step_failed(step_name: str, exit_code: int, agent_id: str | None = None)` - Called when step fails\n\n## Implementation notes\n- Follow existing pattern: add to MalaEventSink protocol, BaseEventSink (no-op), ConsoleEventSink (calls log())\n- ConsoleEventSink should use same icons/colors as current spec_executor.py log() calls:\n  - Running: '▸' cyan\n  - Skipped: '○' cyan  \n  - Passed: '✓' green (include duration)\n  - Failed: '✗' red (include exit code)\n\n## Testing\n- Ensure `tests/test_event_sink.py` still passes (protocol compliance checks)\n- No new tests needed for this task; sink implementation tested via existing assertions","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T05:53:37.182441177Z","created_by":"cyou","updated_at":"2026-01-03T06:37:33.576621385Z","closed_at":"2026-01-03T06:37:33.576621385Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-b79o.1","depends_on_id":"mala-b79o","type":"parent-child","created_at":"2026-01-03T17:58:44.837936385Z","created_by":"daemon"}]}
{"id":"mala-b79o.2","title":"Route SpecCommandExecutor logging through event sink","description":"## Goal\nReplace direct log() calls in SpecCommandExecutor with event sink emissions.\n\n## Prerequisites\n- mala-b79o.1 (event sink protocol methods must exist first)\n\n## Files to modify\n- `src/domain/validation/spec_executor.py` - Accept optional sink, emit events\n\n## Changes required\n1. Add optional `event_sink: MalaEventSink | None = None` parameter to `SpecCommandExecutor.__init__()`\n2. Store sink as instance attribute\n3. Replace the 4 log() calls with event sink emissions:\n   - Line 146: `log('▸', f'Running {cmd.name}...')` → `sink.on_validation_step_running(cmd.name)`\n   - Line 221: `log('○', f'Skipping {cmd.name}...')` → `sink.on_validation_step_skipped(cmd.name, 'no changes since last check')`\n   - Line 363: `log('✓', f'{cmd.name} passed')` → `sink.on_validation_step_passed(cmd.name, step.duration_seconds)`\n   - Line 380: `log('✗', f'{cmd.name} failed')` → `sink.on_validation_step_failed(cmd.name, step.returncode)`\n4. Guard with `if self.event_sink is not None:` checks (graceful when no sink provided)\n5. Remove the `from src.infra.io.log_output.console import Colors, log` import\n\n## Testing\n- Update `tests/test_spec_executor.py` (if exists) or create minimal test to verify events are emitted\n- Verify existing tests still pass with NullEventSink\n\n## Notes\n- Do NOT modify callers yet - they can pass `event_sink=None` for now\n- Callers will be updated in a follow-up integration task","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T05:53:50.409901214Z","created_by":"cyou","updated_at":"2026-01-03T15:46:06.255468243Z","closed_at":"2026-01-03T15:46:06.255468243Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-b79o.2","depends_on_id":"mala-b79o.1","type":"blocks","created_at":"2026-01-03T05:54:39.806321863Z","created_by":"daemon"},{"issue_id":"mala-b79o.2","depends_on_id":"mala-idx6.6","type":"blocks","created_at":"2026-01-03T05:59:32.204213088Z","created_by":"daemon"},{"issue_id":"mala-b79o.2","depends_on_id":"mala-b79o","type":"parent-child","created_at":"2026-01-03T17:58:44.845507341Z","created_by":"daemon"}]}
{"id":"mala-b79o.3","title":"Add review warning event to MalaEventSink protocol","description":"## Goal\nAdd event method to MalaEventSink for review-related warnings (verdict mismatch).\n\n## Files to modify\n- `src/infra/io/event_sink.py` - Add protocol method + BaseEventSink no-op + ConsoleEventSink implementation\n\n## New event method to add\n`on_review_warning(message: str, agent_id: str | None = None)` - Called for review-related warnings\n\n## Implementation notes\n- Follow existing pattern: add to MalaEventSink protocol, BaseEventSink (no-op), ConsoleEventSink\n- ConsoleEventSink should use: `log('\\!', message, Colors.YELLOW, agent_id=agent_id)`\n- This matches current cerberus_review.py line 590 behavior\n\n## Testing\n- Ensure `tests/test_event_sink.py` still passes\n- No new tests needed; tested via protocol assertion at module import","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T05:53:59.053271332Z","created_by":"cyou","updated_at":"2026-01-03T06:44:28.062178354Z","closed_at":"2026-01-03T06:44:28.062178354Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-b79o.3","depends_on_id":"mala-b79o.1","type":"blocks","created_at":"2026-01-03T06:02:11.998899537Z","created_by":"daemon"},{"issue_id":"mala-b79o.3","depends_on_id":"mala-b79o","type":"parent-child","created_at":"2026-01-03T17:58:44.853187006Z","created_by":"daemon"}]}
{"id":"mala-b79o.4","title":"Route DefaultReviewer logging through event sink","description":"## Goal\nReplace direct log() calls in DefaultReviewer with event sink emissions.\n\n## Prerequisites\n- mala-b79o.3 (review warning event method must exist first)\n\n## Files to modify\n- `src/infra/clients/cerberus_review.py` - Accept optional sink, emit events\n\n## Changes required\n1. Add optional `event_sink: MalaEventSink | None = None` field to `DefaultReviewer` dataclass\n2. Replace the 2 log() calls with event sink emissions:\n   - Line 195: `log('→', 'Resolving stale gate...')` → `sink.on_review_warning('Resolving stale gate from previous attempt')`  \n   - Line 590: `log('\\!', f'Exit code... disagree')` → `sink.on_review_warning(message)`\n3. Guard with `if self.event_sink is not None:` checks\n4. Remove the import of `Colors, log` from console module (lines 193, 588)\n\n## Testing\n- Update `tests/test_cerberus_review.py` to verify warnings are emitted when sink provided\n- Verify existing tests still pass with no sink\n\n## Notes\n- Consider if 'resolving stale gate' should use existing `on_warning()` method instead of new `on_review_warning()`\n- If using `on_warning()`, mala-b79o.3 may be simplified or removed","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T05:54:09.204172659Z","created_by":"cyou","updated_at":"2026-01-03T06:56:55.365179887Z","closed_at":"2026-01-03T06:56:55.365179887Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-b79o.4","depends_on_id":"mala-b79o.3","type":"blocks","created_at":"2026-01-03T05:54:39.86659055Z","created_by":"daemon"},{"issue_id":"mala-b79o.4","depends_on_id":"mala-b79o","type":"parent-child","created_at":"2026-01-03T17:58:44.861857224Z","created_by":"daemon"}]}
{"id":"mala-b79o.5","title":"Wire event sink through SpecValidationRunner to SpecCommandExecutor","description":"## Goal\nPass event sink from validation orchestration down to SpecCommandExecutor so validation step events reach the console.\n\n## Prerequisites\n- mala-b79o.2 (SpecCommandExecutor must accept event_sink parameter)\n\n## Files to analyze/modify\nNeed to trace how SpecCommandExecutor is instantiated. Likely path:\n- `src/domain/validation/spec_runner.py` or similar orchestration module\n- Possibly `src/orchestration/` modules that create validation runners\n\n## Changes required\n1. Find where SpecCommandExecutor is instantiated\n2. Thread event_sink from higher-level context (MalaOrchestrator has access to sink)\n3. Pass sink to SpecCommandExecutor constructor\n\n## Testing\n- Run `uv run mala --dry-run` or similar to verify validation step logs appear\n- Ensure existing integration/e2e tests still pass\n\n## Notes\n- This is an integration task that connects the refactored executor to the rest of the system\n- May require modifying SpecValidationRunner or factory methods","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T05:54:19.634560556Z","created_by":"cyou","updated_at":"2026-01-03T16:03:47.949248654Z","closed_at":"2026-01-03T16:03:47.949248654Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-b79o.5","depends_on_id":"mala-b79o.2","type":"blocks","created_at":"2026-01-03T05:54:39.929291142Z","created_by":"daemon"},{"issue_id":"mala-b79o.5","depends_on_id":"mala-au9g.4","type":"blocks","created_at":"2026-01-03T05:59:32.240589827Z","created_by":"daemon"},{"issue_id":"mala-b79o.5","depends_on_id":"mala-b79o","type":"parent-child","created_at":"2026-01-03T17:58:44.870300703Z","created_by":"daemon"}]}
{"id":"mala-b79o.6","title":"Wire event sink to DefaultReviewer in orchestration","description":"## Goal\nPass event sink to DefaultReviewer instances so review warnings reach the console.\n\n## Prerequisites\n- mala-b79o.4 (DefaultReviewer must accept event_sink parameter)\n\n## Files to analyze/modify\nNeed to trace where DefaultReviewer is instantiated. Likely:\n- `src/orchestration/factory.py` or similar factory module\n- Wherever reviewer is created and passed to lifecycle/pipeline\n\n## Changes required\n1. Find where DefaultReviewer is instantiated (likely in factory or orchestrator setup)\n2. Pass event_sink from MalaOrchestrator context to DefaultReviewer\n3. Verify sink is available at instantiation point\n\n## Testing\n- Trigger a review verdict mismatch scenario (may need specific test setup)\n- Verify warning appears in console output\n- Ensure existing tests pass\n\n## Notes\n- May be combined with mala-b79o.5 if instantiation happens in same location","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T05:54:32.899449951Z","created_by":"cyou","updated_at":"2026-01-03T08:11:14.900669939Z","closed_at":"2026-01-03T08:11:14.900669939Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-b79o.6","depends_on_id":"mala-b79o.4","type":"blocks","created_at":"2026-01-03T05:54:39.985728923Z","created_by":"daemon"},{"issue_id":"mala-b79o.6","depends_on_id":"mala-au9g.4","type":"blocks","created_at":"2026-01-03T05:59:32.250309863Z","created_by":"daemon"},{"issue_id":"mala-b79o.6","depends_on_id":"mala-b79o","type":"parent-child","created_at":"2026-01-03T17:58:44.878638933Z","created_by":"daemon"}]}
{"id":"mala-b8d","title":"Allow specification of thinking mode for codex","description":"Add configuration option to specify the thinking mode (e.g., high, medium, low) when using codex for reviews and other operations.","status":"closed","priority":3,"issue_type":"task","created_at":"2025-12-27T23:51:54.88471213Z","updated_at":"2025-12-28T05:48:12.544139537Z","closed_at":"2025-12-28T05:48:12.544139537Z","close_reason":"Closed"}
{"id":"mala-b8g4","title":"[Review] src/orchestration/orchestrator.py:111-140: [P2] Centralize lint tool extraction logic","description":"## Review Finding\n\nThis issue was auto-created from a P2 review finding.\n\n**Source issue:** mala-pc7u\n**Reviewer:** gemini\n**Location:** src/orchestration/orchestrator.py:111-140\n\n## Details\n\nThe `_LINT_COMMAND_KINDS` constant and `_extract_lint_tools_from_spec` function are duplicated exactly in `src/orchestration/orchestrator.py` and `src/pipeline/run_coordinator.py`. This logic should be moved to a shared location, such as a method on `ValidationSpec` in `src/domain/validation/spec.py`, to ensure consistency and improve maintainability.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-02T17:05:46.347928042Z","created_by":"cyou","updated_at":"2026-01-02T17:20:28.865385168Z","closed_at":"2026-01-02T17:20:28.865385168Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_finding:b1f9fc70c8ed","source:mala-pc7u"]}
{"id":"mala-bdu","title":"Enhance status command to show last messages from agents","description":"Context:\n- Status command should show what each agent is currently doing.\n- Display last N messages or current activity from each running agent.\n\nScope:\n- In: read agent logs; parse recent messages; display in status output.\n- Out: real-time streaming; TUI/dashboard.\n\nAcceptance Criteria:\n- `mala status` shows last message/activity from each active agent.\n- Clear indication of agent state (working, waiting, idle).\n- Timestamps on last activity.\n\nTest Plan:\n- Manual: run mala, check status mid-run, verify agent messages shown.\n- Unit: mock log reading and verify output format.","status":"closed","priority":2,"issue_type":"feature","created_at":"2025-12-27T01:14:57.841683856Z","updated_at":"2026-01-05T03:35:58.510532529Z","closed_at":"2026-01-05T03:35:58.510532529Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-bdu","depends_on_id":"mala-6c0","type":"blocks","created_at":"2025-12-27T01:15:02.133910704Z","created_by":"daemon"}]}
{"id":"mala-bgl","title":"Save progress state on context exhaustion","status":"tombstone","priority":2,"issue_type":"epic","created_at":"2025-12-26T00:55:42.565812819Z","updated_at":"2025-12-26T00:56:32.19480344Z","deleted_at":"2025-12-26T00:56:32.19480344Z","deleted_by":"daemon","delete_reason":"delete","original_type":"epic"}
{"id":"mala-bp3h","title":"Epic: Fix import-linter contracts (2024-12-31 plan)","description":"Addresses 4 broken import-linter contracts from architecture review.\n\n## Context\n\n- 4 contracts broken: Layered Architecture, Domain layer independence, Domain must not depend on infra, CLI only depends on orchestrator\n- 5 contracts kept: Pipeline modules acyclic, Infra modules independent, SDK confined to infra, Only CLI imports typer, Hooks isolated\n- Plan reviewed 3x with auto-resolution; some P1 concerns addressed in child issues\n\n## Spec\n\nSee: `doc/2024-12-31-architecture-fix-plan.md`\n\n## Scope\n\n**In:**\n- Task 1: Extract orchestrator_types.py to break cycle (extends mala-29eu.2)\n- Task 2: Define local Protocol types in protocols.py\n- Task 3: Fix Contract 2 definition (independence → forbidden)\n- Task 4: Create cli_support.py for CLI utilities\n- Task 5: Merge event_sink_console into event_sink\n- Task 6: Move BraintrustProvider to braintrust_integration\n- Task 7: Update test imports + final verification\n\n**Out:**\n- CCN optimization (run_session, _run_refresh, etc.)\n- Full package restructure\n- Behavior changes\n\n## Acceptance Criteria\n\n- [ ] `uvx --from import-linter lint-imports` passes all 9 contracts\n- [ ] All tests pass: `uv run pytest -m \"unit or integration\"`\n- [ ] Linting passes: `uvx ruff check . \u0026\u0026 uvx ty check`\n- [ ] No re-exports for backward compatibility (clean break)","status":"closed","priority":1,"issue_type":"epic","created_at":"2025-12-31T16:56:48.178034875Z","created_by":"cyou","updated_at":"2026-01-01T03:45:54.467037113Z","closed_at":"2026-01-01T03:45:54.467037113Z","close_reason":"Closed","labels":["import-linter","refactor"]}
{"id":"mala-bp3h.1","title":"Extract orchestrator_types.py to break cycle","description":"Extract shared types from orchestrator_factory.py to break the circular dependency.\n\n## Context\n\n- orchestrator.py imports from orchestrator_factory (line 76: DEFAULT_AGENT_TIMEOUT_MINUTES)\n- orchestrator_factory.py imports from orchestrator (line 40: MalaOrchestrator)\n- This creates a circular dependency that import-linter flags\n- Extends work from mala-29eu.2 which created orchestrator_factory.py\n- Related to mala-29eu.2 but focuses on breaking the cycle, not factory pattern\n\n## Scope\n\n**In:**\n- Create `src/orchestrator_types.py` with:\n  - `OrchestratorConfig` dataclass (move from orchestrator_factory.py lines 48-90)\n  - `OrchestratorDependencies` dataclass (move from orchestrator_factory.py lines 92+)\n  - `_DerivedConfig` dataclass (if exists)\n  - `DEFAULT_AGENT_TIMEOUT_MINUTES` constant (line 45)\n- Update orchestrator.py line 76 to import from orchestrator_types\n- Update orchestrator_factory.py to import from orchestrator_types\n- Update cli.py lazy imports to use orchestrator_types\n- Update pyproject.toml line 143: add `src.orchestrator_types` to orchestration layer\n\n**Out:**\n- Changing factory function logic\n- Behavior changes\n\n## Primary Files\n\n- src/orchestrator_types.py (NEW)\n- src/orchestrator_factory.py\n- src/orchestrator.py\n- pyproject.toml\n\n## Acceptance Criteria\n\n- [ ] `src/orchestrator_types.py` exists with OrchestratorConfig, OrchestratorDependencies, DEFAULT_AGENT_TIMEOUT_MINUTES\n- [ ] orchestrator.py imports DEFAULT_AGENT_TIMEOUT_MINUTES from orchestrator_types\n- [ ] orchestrator_factory.py imports types from orchestrator_types (no local definitions)\n- [ ] pyproject.toml line 143 includes `src.orchestrator_types`\n- [ ] `uvx --from import-linter lint-imports --contract \"Layered Architecture\"` shows improvement (no cycle)\n- [ ] Tests pass: `uv run pytest tests/unit/test_orchestrator*.py -v`\n- [ ] Linting: `uvx ruff check . \u0026\u0026 uvx ty check`\n\n## Test Plan\n\n1. Run `uvx --from import-linter lint-imports` before changes (baseline)\n2. Create orchestrator_types.py with moved types\n3. Update imports in orchestrator.py, orchestrator_factory.py, cli.py\n4. Update pyproject.toml layer definition\n5. Run `uvx --from import-linter lint-imports --contract \"Layered Architecture\"` - cycle should be gone\n6. Run `uv run pytest tests/unit/test_orchestrator*.py -v`\n7. Run `uvx ruff check . \u0026\u0026 uvx ty check`\n\n## Notes for Agent\n\n- OrchestratorConfig is a dataclass, not Pydantic BaseModel\n- cli.py uses lazy imports via __getattr__ pattern - update _LAZY_NAMES and lazy load logic\n- TYPE_CHECKING imports in orchestrator.py (line 83) also need updating\n- This task builds on mala-29eu.2 which already created orchestrator_factory.py","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-31T16:57:09.467238356Z","created_by":"cyou","updated_at":"2025-12-31T18:38:11.22415722Z","closed_at":"2025-12-31T18:38:11.22415722Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-bp3h.1","depends_on_id":"mala-bp3h","type":"parent-child","created_at":"2025-12-31T16:57:09.474337239Z","created_by":"daemon"}]}
{"id":"mala-bp3h.2","title":"Define local Protocol types in protocols.py","description":"Replace TYPE_CHECKING imports from domain/infra modules with local Protocol types.\n\n## Context\n\n- protocols.py (lines 28-32) has TYPE_CHECKING imports from:\n  - cerberus_review.ReviewResult\n  - models.EpicVerdict\n  - quality_gate.CommitResult, GateResult, ValidationEvidence\n  - session_log_parser.JsonlEntry\n  - validation.spec.ValidationSpec\n- These imports (even in TYPE_CHECKING) violate \"Layered Architecture\" contract\n- protocols.py is in infra layer but imports from domain modules\n- Solution: define local Protocol types that match the shapes needed\n\n## Scope\n\n**In:**\n- Remove TYPE_CHECKING imports from domain/infra modules (lines 28-32)\n- Define local Protocol types:\n  - `ReviewResultProtocol` - matches cerberus_review.ReviewResult shape\n  - `EpicVerdictProtocol` - matches models.EpicVerdict shape\n  - `GateResultProtocol` - matches quality_gate.GateResult shape\n  - `CommitResultProtocol` - matches quality_gate.CommitResult shape\n  - `ValidationEvidenceProtocol` - matches quality_gate.ValidationEvidence shape\n  - `ValidationSpecProtocol` - matches validation.spec.ValidationSpec shape\n  - `JsonlEntryProtocol` - matches session_log_parser.JsonlEntry shape\n- Update method signatures to use new Protocol types\n\n**Out:**\n- Moving protocols.py to different layer\n- Creating protocols/ package (simpler to keep single file)\n- Changing runtime behavior\n\n## Primary Files\n\n- src/protocols.py\n\n## Acceptance Criteria\n\n- [ ] No TYPE_CHECKING imports from cerberus_review, models, quality_gate, session_log_parser, validation.spec\n- [ ] Local Protocol types defined for each removed import\n- [ ] Protocol types match the shape of the original types (structural typing)\n- [ ] `uvx --from import-linter lint-imports --contract \"Layered Architecture\"` - no protocols violations\n- [ ] `uvx ty check` passes (structural typing works)\n- [ ] Tests pass: `uv run pytest tests/ -v -k protocol`\n\n## Test Plan\n\n1. Read current protocols.py TYPE_CHECKING imports\n2. Examine each imported type to understand its shape\n3. Define Protocol types that match the shapes\n4. Remove the TYPE_CHECKING imports\n5. Update method signatures to use new Protocol types\n6. Run `uvx --from import-linter lint-imports` - protocols violations should be gone\n7. Run `uvx ty check` - structural typing should work\n8. Run `uv run pytest tests/ -v -k protocol`\n\n## Notes for Agent\n\n- Protocol types only need the attributes/methods that protocols.py actually uses\n- Use `@runtime_checkable` decorator on Protocol types for isinstance checks\n- Example shape for GateResultProtocol:\n  ```python\n  class GateResultProtocol(Protocol):\n      @property\n      def passed(self) -\u003e bool: ...\n      @property\n      def failure_reasons(self) -\u003e list[str]: ...\n      # ... other used attributes\n  ```\n- Structural typing: implementations don't need to explicitly inherit from Protocol","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-31T16:57:28.51105867Z","created_by":"cyou","updated_at":"2025-12-31T18:54:05.750888886Z","closed_at":"2025-12-31T18:54:05.750888886Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-bp3h.2","depends_on_id":"mala-bp3h","type":"parent-child","created_at":"2025-12-31T16:57:28.518952068Z","created_by":"daemon"}]}
{"id":"mala-bp3h.3","title":"Fix Contract 2 definition (independence to forbidden)","description":"Update Contract 2 \"Domain layer independence\" to match its stated intent.\n\n## Context\n\n- Contract 2 comment says: \"Core domain modules must not depend on orchestration or CLI\"\n- But actual contract is `type = \"independence\"` between lifecycle and models\n- `independence` prevents lifecycle↔models imports - NOT the stated intent\n- lifecycle needs to import ResolutionOutcome from validation.spec (which re-exports from models)\n- The stated intent is domain not depending on UPPER layers (orchestration/CLI)\n\n## Scope\n\n**In:**\n- Update pyproject.toml Contract 2 (lines 157-163):\n  - Change type from `independence` to `forbidden`\n  - Change modules list to source_modules (domain modules)\n  - Add forbidden_modules (orchestration/CLI modules)\n\n**Out:**\n- Code changes to lifecycle.py or models.py\n- Changing actual import structure\n\n## Primary Files\n\n- pyproject.toml\n\n## Contract Change\n\n**Before:**\n```toml\n[[tool.importlinter.contracts]]\nname = \"Domain layer independence\"\ntype = \"independence\"\nmodules = [\n    \"src.lifecycle\",\n    \"src.models\",\n]\n```\n\n**After:**\n```toml\n[[tool.importlinter.contracts]]\nname = \"Domain layer independence\"\ntype = \"forbidden\"\nsource_modules = [\n    \"src.lifecycle\",\n    \"src.models\",\n    \"src.quality_gate\",\n    \"src.validation\",\n    \"src.prompts\",\n]\nforbidden_modules = [\n    \"src.cli\",\n    \"src.main\",\n    \"src.orchestrator\",\n    \"src.orchestrator_factory\",\n    \"src.orchestrator_types\",\n]\n```\n\n## Acceptance Criteria\n\n- [ ] Contract 2 type changed from `independence` to `forbidden`\n- [ ] Contract 2 has source_modules (domain) and forbidden_modules (orchestration/CLI)\n- [ ] `uvx --from import-linter lint-imports --contract \"Domain layer independence\"` passes\n- [ ] No code changes needed - lifecycle can continue importing from validation.spec\n- [ ] Comment updated to match new contract behavior\n\n## Test Plan\n\n1. Run `uvx --from import-linter lint-imports --contract \"Domain layer independence\"` (baseline - should fail)\n2. Update pyproject.toml with new contract definition\n3. Run `uvx --from import-linter lint-imports --contract \"Domain layer independence\"` (should pass)\n4. Run `uv run pytest` (no behavior changes, should still pass)\n\n## Notes for Agent\n\n- This is a semantic shift: independence (bidirectional) → forbidden (unidirectional)\n- After change: lifecycle CAN import from models (same layer, allowed)\n- After change: lifecycle CANNOT import from orchestrator (forbidden)\n- The intent was always \"domain can't depend on upper layers\" not \"domain modules can't depend on each other\"","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-31T16:57:49.374773007Z","created_by":"cyou","updated_at":"2025-12-31T18:44:36.744120152Z","closed_at":"2025-12-31T18:44:36.744120152Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-bp3h.3","depends_on_id":"mala-bp3h","type":"parent-child","created_at":"2025-12-31T16:57:49.381715471Z","created_by":"daemon"},{"issue_id":"mala-bp3h.3","depends_on_id":"mala-bp3h.1","type":"blocks","created_at":"2025-12-31T16:59:35.768961487Z","created_by":"daemon"}]}
{"id":"mala-bp3h.4","title":"Create cli_support.py for CLI utilities","description":"Create CLI support module to route CLI utilities through orchestration layer.\n\n## Context\n\n- cli.py directly imports from infra modules:\n  - src.tools.env (line 18): USER_CONFIG_DIR, get_runs_dir, load_user_env\n  - src.tools.locking (line 805 lazy): get_lock_dir\n  - src.log_output.run_metadata (lines 809, 813 lazy): get_running_instances, get_running_instances_for_dir\n  - src.log_output.console (line 106 area): Colors, log, set_verbose\n- This violates \"CLI only depends on orchestrator\" contract\n- Solution: create cli_support.py in orchestration layer that re-exports these\n\n## Scope\n\n**In:**\n- Create `src/cli_support.py` with re-exports:\n  - From src.tools.env: USER_CONFIG_DIR, SCRIPTS_DIR, get_runs_dir, load_user_env\n  - From src.tools.locking: get_lock_dir\n  - From src.log_output.run_metadata: get_running_instances, get_running_instances_for_dir\n  - From src.log_output.console: Colors, log, set_verbose\n- Update cli.py imports to use cli_support\n- Update pyproject.toml: add src.cli_support to orchestration layer (line 143)\n\n**Out:**\n- Moving utilities to orchestration layer (they stay in infra, cli_support just re-exports)\n- Changing utility behavior\n\n## Primary Files\n\n- src/cli_support.py (NEW)\n- src/cli.py\n- pyproject.toml\n\n## Acceptance Criteria\n\n- [ ] `src/cli_support.py` exists with all re-exports\n- [ ] cli.py line 18 imports from cli_support instead of tools.env\n- [ ] cli.py lazy imports (lines 804-815) use cli_support\n- [ ] cli.py console imports (Colors, log, set_verbose) use cli_support\n- [ ] pyproject.toml line 143 includes `src.cli_support`\n- [ ] `uvx --from import-linter lint-imports --contract \"CLI only depends on orchestrator\"` passes\n- [ ] Manual test: `uv run mala status` works\n- [ ] Tests pass: `uv run pytest tests/unit/test_cli*.py -v`\n\n## Test Plan\n\n1. Create src/cli_support.py with re-exports\n2. Update pyproject.toml to add cli_support to orchestration layer\n3. Update cli.py line 18 imports\n4. Update cli.py lazy imports (_LAZY_NAMES, __getattr__ logic)\n5. Update cli.py console imports\n6. Run `uvx --from import-linter lint-imports --contract \"CLI only depends on orchestrator\"`\n7. Run `uv run mala status` (manual verification)\n8. Run `uv run pytest tests/unit/test_cli*.py -v`\n\n## Notes for Agent\n\n- cli_support.py is in orchestration layer but imports from infra - this is allowed (orchestration can import infra)\n- CLI can import from orchestration layer - this fixes the contract\n- Keep cli_support.py focused - only re-exports needed by CLI\n- _LAZY_NAMES in cli.py (lines 30-41) defines lazy-loaded names - these need updates\n- The __getattr__ function (around line 44) handles lazy loading - update source modules","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-31T16:58:12.270790403Z","created_by":"cyou","updated_at":"2025-12-31T19:25:43.028578342Z","closed_at":"2025-12-31T19:25:43.028578342Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-bp3h.4","depends_on_id":"mala-bp3h","type":"parent-child","created_at":"2025-12-31T16:58:12.278440644Z","created_by":"daemon"},{"issue_id":"mala-bp3h.4","depends_on_id":"mala-bp3h.3","type":"blocks","created_at":"2025-12-31T16:59:35.781821115Z","created_by":"daemon"}]}
{"id":"mala-bp3h.5","title":"Merge event_sink_console into event_sink","description":"Merge ConsoleEventSink from event_sink_console.py into event_sink.py.\n\n## Context\n\n- event_sink_console.py imports from event_sink.py (the protocol)\n- import-linter flags this as a \"Layered Architecture\" violation for same-layer imports\n- event_sink.py has MalaEventSink protocol + EventRunConfig + NullEventSink\n- event_sink_console.py has ConsoleEventSink implementation (~300 lines)\n- Neither module is in the \"Infra modules independent\" contract (Contract 6)\n- Merging is simpler than adjusting contract definitions\n\n## Scope\n\n**In:**\n- Move ConsoleEventSink class from event_sink_console.py to event_sink.py\n- Add log_output.console imports to event_sink.py\n- Delete event_sink_console.py\n- Update imports in orchestrator.py (line 13) and orchestrator_factory.py\n- Update pyproject.toml: remove src.event_sink_console from relevant contracts\n\n**Out:**\n- Changing ConsoleEventSink behavior\n- Creating new abstractions\n\n## Primary Files\n\n- src/event_sink.py\n- src/event_sink_console.py (DELETE)\n- src/orchestrator.py\n- src/orchestrator_factory.py\n- pyproject.toml\n\n## Acceptance Criteria\n\n- [ ] ConsoleEventSink class moved to event_sink.py\n- [ ] event_sink_console.py deleted\n- [ ] orchestrator.py imports ConsoleEventSink from event_sink\n- [ ] orchestrator_factory.py imports ConsoleEventSink from event_sink\n- [ ] pyproject.toml: src.event_sink_console removed from line 149, 202, 310\n- [ ] `uvx --from import-linter lint-imports` - no event_sink violations\n- [ ] Tests pass: `uv run pytest tests/ -v -k event_sink`\n- [ ] Linting: `uvx ruff check . \u0026\u0026 uvx ty check`\n\n## Test Plan\n\n1. Read event_sink_console.py to understand ConsoleEventSink implementation\n2. Copy ConsoleEventSink class to event_sink.py (after NullEventSink)\n3. Add imports: from .log_output.console import Colors, log, etc.\n4. Update orchestrator.py line 13: from .event_sink import ConsoleEventSink\n5. Update orchestrator_factory.py similarly\n6. Delete event_sink_console.py\n7. Update pyproject.toml to remove src.event_sink_console references\n8. Run `uvx --from import-linter lint-imports`\n9. Run `uv run pytest tests/ -v -k event_sink`\n10. Run `uvx ruff check . \u0026\u0026 uvx ty check`\n\n## Notes for Agent\n\n- ConsoleEventSink uses Colors and log from log_output.console\n- Keep the class structure intact - just move it\n- event_sink.py will grow but stays under 500 lines\n- Preserve all existing docstrings and formatting","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-31T16:58:31.920297318Z","created_by":"cyou","updated_at":"2025-12-31T19:45:40.457052006Z","closed_at":"2025-12-31T19:45:40.457052006Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-bp3h.5","depends_on_id":"mala-bp3h","type":"parent-child","created_at":"2025-12-31T16:58:31.927328522Z","created_by":"daemon"},{"issue_id":"mala-bp3h.5","depends_on_id":"mala-bp3h.4","type":"blocks","created_at":"2025-12-31T16:59:35.789147838Z","created_by":"daemon"},{"issue_id":"mala-bp3h.5","depends_on_id":"mala-bp3h.1","type":"blocks","created_at":"2025-12-31T16:59:35.796004764Z","created_by":"daemon"}]}
{"id":"mala-bp3h.6","title":"Move BraintrustProvider to braintrust_integration","description":"Move BraintrustProvider and BraintrustSpan from telemetry.py to braintrust_integration.py.\n\n## Context\n\n- telemetry.py imports from braintrust_integration.py (lines 166, 220, 248)\n- BraintrustProvider (lines 199-250) and BraintrustSpan (lines 154-197) wrap braintrust_integration\n- import-linter flags this as a \"Layered Architecture\" violation\n- These classes belong in braintrust_integration.py since they're Braintrust-specific\n- telemetry.py should only have the abstract protocol and null implementations\n\n## Scope\n\n**In:**\n- Move BraintrustSpan class (lines 154-197) to braintrust_integration.py\n- Move BraintrustProvider class (lines 199-250) to braintrust_integration.py\n- Keep in telemetry.py: TelemetryProvider protocol, TelemetrySpan protocol, NullTelemetryProvider, NullSpan\n- Update orchestrator.py line 15: import BraintrustProvider from braintrust_integration\n- Update orchestrator_factory.py similarly\n\n**Out:**\n- Changing provider behavior\n- Moving abstract protocols\n\n## Primary Files\n\n- src/telemetry.py\n- src/braintrust_integration.py\n- src/orchestrator.py\n- src/orchestrator_factory.py\n\n## Acceptance Criteria\n\n- [ ] BraintrustSpan class moved to braintrust_integration.py\n- [ ] BraintrustProvider class moved to braintrust_integration.py\n- [ ] telemetry.py only has protocols and null implementations\n- [ ] telemetry.py has no imports from braintrust_integration\n- [ ] orchestrator.py imports BraintrustProvider from braintrust_integration\n- [ ] orchestrator_factory.py imports BraintrustProvider from braintrust_integration\n- [ ] `uvx --from import-linter lint-imports` - no telemetry violations\n- [ ] Tests pass: `uv run pytest tests/ -v -k \"telemetry or braintrust\"`\n- [ ] Linting: `uvx ruff check . \u0026\u0026 uvx ty check`\n\n## Test Plan\n\n1. Read telemetry.py to identify BraintrustSpan and BraintrustProvider\n2. Read braintrust_integration.py to understand its structure\n3. Move BraintrustSpan to braintrust_integration.py\n4. Move BraintrustProvider to braintrust_integration.py\n5. The runtime imports (TracedAgentExecution, is_braintrust_enabled, flush_braintrust) now become local\n6. Update orchestrator.py: from .braintrust_integration import BraintrustProvider\n7. Update orchestrator_factory.py similarly\n8. Run `uvx --from import-linter lint-imports`\n9. Run `uv run pytest tests/ -v -k \"telemetry or braintrust\"`\n10. Run `uvx ruff check . \u0026\u0026 uvx ty check`\n\n## Notes for Agent\n\n- BraintrustSpan imports from braintrust_integration at runtime (line 166) - these become local after move\n- BraintrustProvider imports is_braintrust_enabled and flush_braintrust - these become local after move\n- telemetry.py should still export TelemetryProvider and TelemetrySpan protocols\n- The moved classes may need to import protocols from telemetry.py (for type hints) - use TYPE_CHECKING","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-31T16:58:53.356223858Z","created_by":"cyou","updated_at":"2025-12-31T19:52:41.896057634Z","closed_at":"2025-12-31T19:52:41.896057634Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-bp3h.6","depends_on_id":"mala-bp3h","type":"parent-child","created_at":"2025-12-31T16:58:53.362899464Z","created_by":"daemon"},{"issue_id":"mala-bp3h.6","depends_on_id":"mala-bp3h.5","type":"blocks","created_at":"2025-12-31T16:59:35.802963799Z","created_by":"daemon"}]}
{"id":"mala-bp3h.7","title":"Update test imports + final verification","description":"Update test imports for moved types and run final verification.\n\n## Context\n\n- Tasks 1-6 move types between modules\n- Tests may import from old locations that no longer exist\n- Need to update all test imports to use new paths\n- Final verification: all 9 import-linter contracts must pass\n\n## Scope\n\n**In:**\n- Search for test files importing from old locations\n- Update imports to new paths:\n  - OrchestratorConfig from orchestrator_types (not orchestrator_factory)\n  - ConsoleEventSink from event_sink (not event_sink_console)\n  - BraintrustProvider from braintrust_integration (not telemetry)\n  - CLI utilities from cli_support (if any tests use them)\n- Update mock paths (e.g., @patch decorators)\n- Run final verification\n\n**Out:**\n- Changing test logic\n- Adding new tests\n\n## Primary Files\n\n- tests/**/*.py (various)\n\n## Search Patterns\n\n```bash\n# Orchestrator types\ngrep -r \"from src.orchestrator_factory import OrchestratorConfig\" tests/\ngrep -r \"from src.orchestrator_factory import OrchestratorDependencies\" tests/\ngrep -r \"from src.orchestrator_factory import DEFAULT_AGENT_TIMEOUT\" tests/\n\n# Event sink\ngrep -r \"from src.event_sink_console import\" tests/\n\n# Telemetry\ngrep -r \"from src.telemetry import BraintrustProvider\" tests/\ngrep -r \"from src.telemetry import BraintrustSpan\" tests/\n\n# Mock paths\ngrep -r \"@patch.*orchestrator_factory\" tests/\ngrep -r \"@patch.*event_sink_console\" tests/\ngrep -r \"@patch.*telemetry\" tests/\n```\n\n## Acceptance Criteria\n\n- [ ] No test imports from deleted modules (event_sink_console)\n- [ ] No test imports from moved types at old locations\n- [ ] All mock paths updated to new module locations\n- [ ] `uvx --from import-linter lint-imports` - ALL 9 contracts pass\n- [ ] `uvx ruff check .` passes\n- [ ] `uvx ty check` passes\n- [ ] `uv run pytest -m \"unit or integration\" -n auto` passes\n- [ ] Coverage threshold: 85%\n\n## Test Plan\n\n1. Run grep patterns to find test files needing updates\n2. Update each import to use new path\n3. Update mock paths in @patch decorators\n4. Run `uvx --from import-linter lint-imports` - should show 9/9 contracts pass\n5. Run `uvx ruff check .`\n6. Run `uvx ty check`\n7. Run `uv run pytest -m \"unit or integration\" -n auto`\n8. Verify coverage threshold met\n\n## Notes for Agent\n\n- This task depends on ALL other tasks completing first\n- Focus on imports and mocks, not test logic\n- If a test imports a type that was moved, just update the import path\n- Some mocks may patch module paths - update to new module locations","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-31T16:59:13.999166622Z","created_by":"cyou","updated_at":"2025-12-31T20:49:59.257825953Z","closed_at":"2025-12-31T20:49:59.257825953Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-bp3h.7","depends_on_id":"mala-bp3h","type":"parent-child","created_at":"2025-12-31T16:59:14.010244961Z","created_by":"daemon"},{"issue_id":"mala-bp3h.7","depends_on_id":"mala-bp3h.1","type":"blocks","created_at":"2025-12-31T16:59:23.245715303Z","created_by":"daemon"},{"issue_id":"mala-bp3h.7","depends_on_id":"mala-bp3h.2","type":"blocks","created_at":"2025-12-31T16:59:23.259531244Z","created_by":"daemon"},{"issue_id":"mala-bp3h.7","depends_on_id":"mala-bp3h.3","type":"blocks","created_at":"2025-12-31T16:59:23.266560999Z","created_by":"daemon"},{"issue_id":"mala-bp3h.7","depends_on_id":"mala-bp3h.4","type":"blocks","created_at":"2025-12-31T16:59:23.273228626Z","created_by":"daemon"},{"issue_id":"mala-bp3h.7","depends_on_id":"mala-bp3h.5","type":"blocks","created_at":"2025-12-31T16:59:23.279600625Z","created_by":"daemon"},{"issue_id":"mala-bp3h.7","depends_on_id":"mala-bp3h.6","type":"blocks","created_at":"2025-12-31T16:59:23.285950445Z","created_by":"daemon"}]}
{"id":"mala-brwf","title":"Epic: Decompose coverage baseline refresh (CCN 54 → \u003c20)","description":"## Summary\nDecompose the monolithic `_run_refresh` method in `src/domain/validation/coverage.py` which has CCN 54 (threshold 15), 179 NLOC, and mixes lock handling, worktree creation, dependency installation, command argument rewriting, coverage execution with fallback logic, and result parsing.\n\n## Primary Files\n- `src/domain/validation/coverage.py:526-774`\n\n## Category\nComplexity\n\n## Source\nMultiple generators (codex, gemini, claude)\n\n## Fix Approach\n1. Extract pure command-rewrite helpers for argument manipulation\n2. Create `BaselineRefresher` service with injected `CommandRunner`, `WorktreeManager`, and `LockManager`\n3. Extract `_run_coverage_with_fallback()` for combine/xml fallback logic\n4. Create pure parser for baseline extraction\n\n## Non-Goals\n- Changing baseline refresh semantics or worktree behavior\n- Adding new coverage backends\n\n## Acceptance Criteria\n- [ ] `_run_refresh` CCN reduced below 20\n- [ ] Each extracted helper has focused responsibility\n- [ ] Core command-rewrite logic covered by unit tests without filesystem or subprocesses\n\n## Test Plan\n- Unit tests for pure command manipulation helpers with various pytest marker combinations\n- Integration test verifying full refresh flow in isolated temp directory\n\n## Agent Notes\nThe combine/xml fallback path has three retry strategies; preserve all edge cases when extracting.","status":"closed","priority":1,"issue_type":"epic","created_at":"2026-01-03T05:47:39.047203202Z","created_by":"cyou","updated_at":"2026-01-03T18:19:56.303981429Z","closed_at":"2026-01-03T18:19:56.303981429Z","close_reason":"Closed"}
{"id":"mala-brwf.1","title":"Extract pure command-rewrite helpers from _run_refresh","description":"## Overview\nExtract the complex command argument manipulation logic from `_run_refresh` into pure helper functions.\n\n## Current State\nLines 605-673 in `src/domain/validation/coverage.py` contain complex logic for:\n- Stripping xdist flags (`-n`, `--numprocesses`, `--dist`)\n- Removing `--cov-fail-under` arguments\n- Stripping/capturing `-m` marker expressions\n- Normalizing marker expressions (never include e2e)\n- Managing `--cov-report=xml` paths\n\nThis logic has CCN concerns and is difficult to test in isolation.\n\n## Implementation\n1. Create `src/domain/validation/coverage_args.py` with:\n   - `strip_xdist_flags(args: list[str]) -\u003e list[str]` - Remove parallel execution flags\n   - `strip_cov_fail_under(args: list[str]) -\u003e list[str]` - Remove fail-under threshold\n   - `extract_marker_expr(args: list[str]) -\u003e tuple[list[str], str | None]` - Extract -m marker\n   - `normalize_marker_expr(expr: str | None) -\u003e str` - Default to \"unit or integration\", filter e2e\n   - `strip_cov_report_xml(args: list[str]) -\u003e list[str]` - Remove existing xml report paths\n   - `rewrite_coverage_command(cmd: str, coverage_file: str) -\u003e list[str]` - Compose all the above\n\n2. Add unit tests in `tests/test_coverage_args.py`\n\n3. Update `_run_refresh` to call `rewrite_coverage_command()`\n\n## Files\n- `src/domain/validation/coverage_args.py` (new)\n- `tests/test_coverage_args.py` (new)\n- `src/domain/validation/coverage.py` (minor update to import and call)\n\n## Acceptance Criteria\n- All helpers are pure functions (no I/O, no side effects)\n- Each helper has comprehensive unit tests\n- `_run_refresh` command rewriting reduced to single function call","notes":"Quality gate failed: Quality gate failed: No commit with bd-mala-brwf.1 found after run baseline (stale commits from previous runs are rejected)\nLog: /home/cyou/.claude/projects/-home-cyou-mala/1de416c7-8aab-4c5a-bcb8-b53cb3a8ed39.jsonl","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T05:53:02.228749633Z","created_by":"cyou","updated_at":"2026-01-03T14:51:14.762521593Z","closed_at":"2026-01-03T14:51:14.762521593Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-brwf.1","depends_on_id":"mala-idx6.7","type":"blocks","created_at":"2026-01-03T05:59:32.221051997Z","created_by":"daemon"}]}
{"id":"mala-brwf.2","title":"Extract _run_coverage_with_fallback from _run_refresh","description":"## Overview\nExtract the coverage execution and fallback logic from `_run_refresh` into a dedicated method.\n\n## Current State\nLines 675-748 in `src/domain/validation/coverage.py` contain:\n- Running the coverage command (line 677)\n- Checking if XML was generated (lines 679-682)\n- Fallback: finding .coverage* data files (lines 684-688)\n- Determining coverage base command style (lines 693-708)\n- Running `coverage combine` + `coverage xml` (lines 710-718)\n- Building detailed error messages with command output tails (lines 720-748)\n\nThis is a cohesive unit that can be extracted into a method.\n\n## Implementation\n1. Add method `_run_coverage_with_fallback(self, runner, coverage_cmd, coverage_file, worktree_path, env) -\u003e Path | str`:\n   - Returns Path to coverage XML on success\n   - Returns error string on failure\n\n2. Inline helper `_infer_coverage_base_command(original_cmd: list[str]) -\u003e list[str]` for determining `coverage` vs `uv run coverage` vs `python -m coverage`\n\n3. Update `_run_refresh` to call new method and handle return value\n\n## Files\n- `src/domain/validation/coverage.py` (add method, update _run_refresh)\n\n## Acceptance Criteria\n- Fallback logic fully extracted to `_run_coverage_with_fallback`\n- Error messages preserved with full detail\n- `_run_refresh` reduced in complexity","notes":"Quality gate failed: Quality gate failed: No commit with bd-mala-brwf.2 found after run baseline (stale commits from previous runs are rejected)\nLog: /home/cyou/.claude/projects/-home-cyou-mala/7c4f3632-43df-4050-b820-6a7feff53928.jsonl","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T05:53:14.312052647Z","created_by":"cyou","updated_at":"2026-01-03T14:38:45.122512892Z","closed_at":"2026-01-03T14:38:45.122512892Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-brwf.2","depends_on_id":"mala-idx6.7","type":"blocks","created_at":"2026-01-03T06:00:07.42850325Z","created_by":"daemon"}]}
{"id":"mala-brwf.3","title":"Extract WorktreeRefreshContext for worktree lifecycle in baseline refresh","description":"## Overview\nExtract worktree creation, environment setup, and cleanup into a context manager for cleaner resource management.\n\n## Current State\n`_run_refresh` (lines 559-596, 768-773) handles:\n- Creating temp directory and worktree config (lines 559-565)\n- Creating worktree at HEAD (lines 569-583)\n- Building environment dict (lines 585-590)\n- Creating CommandRunner (lines 593-596)\n- Cleanup in finally block (lines 768-773)\n\nThis is worktree lifecycle management that can be encapsulated.\n\n## Implementation\n1. Create `WorktreeRefreshContext` context manager in `src/domain/validation/coverage.py`:\n   ```python\n   @dataclass\n   class WorktreeRefreshContext:\n       worktree_path: Path\n       runner: CommandRunner\n       env: dict[str, str]\n   \n   @contextmanager\n   def baseline_worktree(repo_path: Path, timeout: float) -\u003e Iterator[WorktreeRefreshContext]:\n       # Create temp dir, worktree, env, runner\n       # yield context\n       # Cleanup in finally\n   ```\n\n2. Update `_run_refresh` to use context manager:\n   ```python\n   with baseline_worktree(self.repo_path, timeout) as ctx:\n       # sync deps\n       # run coverage\n       # copy result\n   ```\n\n## Files\n- `src/domain/validation/coverage.py` (add context manager, update _run_refresh)\n\n## Acceptance Criteria\n- Resource cleanup guaranteed via context manager\n- `_run_refresh` has single indentation level for main logic\n- Error handling for worktree creation preserved","notes":"Quality gate failed: Quality gate failed: No commit with bd-mala-brwf.3 found after run baseline (stale commits from previous runs are rejected)\nLog: /home/cyou/.claude/projects/-home-cyou-mala/b1ba84fa-9234-4ab6-85f2-e753d266dde4.jsonl","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T05:53:26.60501604Z","created_by":"cyou","updated_at":"2026-01-03T14:50:20.111035484Z","closed_at":"2026-01-03T14:50:20.111035484Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-brwf.3","depends_on_id":"mala-idx6.7","type":"blocks","created_at":"2026-01-03T06:00:07.444600052Z","created_by":"daemon"}]}
{"id":"mala-brwf.4","title":"Refactor BaselineCoverageService to use extracted components","description":"## Overview\nFinal refactoring task to compose the extracted components and reduce `_run_refresh` CCN.\n\n## Dependencies\n- Depends on: mala-brwf.1 (command-rewrite helpers)\n- Depends on: mala-brwf.2 (coverage fallback extraction)\n- Depends on: mala-brwf.3 (worktree context manager)\n\n## Current State\nAfter the prerequisite tasks, `_run_refresh` should be simplified but may still have:\n- Remaining coordination logic\n- Baseline parsing and result creation\n\n## Implementation\n1. Compose the refactored components:\n   - Use `baseline_worktree()` context manager\n   - Use `rewrite_coverage_command()` for arg manipulation\n   - Use `_run_coverage_with_fallback()` for execution\n\n2. Extract `_parse_baseline_result(baseline_path, coverage_file) -\u003e BaselineRefreshResult`:\n   - Handle atomic file copy\n   - Parse coverage percentage\n   - Return appropriate result\n\n3. Final `_run_refresh` structure:\n   ```python\n   def _run_refresh(self, spec, baseline_path):\n       coverage_cmd = rewrite_coverage_command(\n           self.coverage_config.command,\n           self.coverage_config.file,\n       )\n       \n       with baseline_worktree(self.repo_path, timeout) as ctx:\n           if (sync_error := self._sync_deps(ctx)) is not None:\n               return sync_error\n           \n           result = self._run_coverage_with_fallback(ctx, coverage_cmd)\n           if isinstance(result, str):\n               return BaselineRefreshResult.fail(result)\n           \n           return self._parse_baseline_result(result, baseline_path)\n   ```\n\n4. Verify CCN of `_run_refresh` is now \u003c= 15\n\n## Files\n- `src/domain/validation/coverage.py` (final refactoring)\n\n## Acceptance Criteria\n- `_run_refresh` CCN \u003c= 15 (down from 54)\n- All tests pass\n- No behavior changes (same error messages, same logic)","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T05:53:39.394459483Z","created_by":"cyou","updated_at":"2026-01-03T15:14:53.967649084Z","closed_at":"2026-01-03T15:14:53.967649084Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-brwf.4","depends_on_id":"mala-brwf.1","type":"blocks","created_at":"2026-01-03T05:53:44.256497262Z","created_by":"daemon"},{"issue_id":"mala-brwf.4","depends_on_id":"mala-brwf.2","type":"blocks","created_at":"2026-01-03T05:53:44.270652073Z","created_by":"daemon"},{"issue_id":"mala-brwf.4","depends_on_id":"mala-brwf.3","type":"blocks","created_at":"2026-01-03T05:53:44.278550223Z","created_by":"daemon"}]}
{"id":"mala-brwf.5","title":"[Human Review] Epic mala-brwf requires manual verification","description":"## Context\nThis epic has been flagged for human review.\n\n## Reason\nLow confidence (0.00)\n\n## Epic ID\nmala-brwf\n\n## Verification Details\n- Confidence: 0.00\n- Reasoning: No child issues found for epic\n\n## Resolution\nReview the epic and its child issues manually. When satisfied, close this issue\nto unblock the epic, or use `--epic-override mala-brwf` to force closure.\n","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-03T17:50:46.154595602Z","created_by":"cyou","updated_at":"2026-01-03T17:51:27.962650345Z","closed_at":"2026-01-03T17:51:27.962650345Z","close_reason":"Closed","labels":["epic_human_review","epic_id:mala-brwf"],"dependencies":[{"issue_id":"mala-brwf.5","depends_on_id":"mala-brwf","type":"parent-child","created_at":"2026-01-03T17:50:46.162030121Z","created_by":"daemon"}]}
{"id":"mala-brwf.6","title":"[Human Review] Epic mala-brwf requires manual verification","description":"## Context\nThis epic has been flagged for human review.\n\n## Reason\nLow confidence (0.00)\n\n## Epic ID\nmala-brwf\n\n## Verification Details\n- Confidence: 0.00\n- Reasoning: No commits found for child issues\n\n## Resolution\nReview the epic and its child issues manually. When satisfied, close this issue\nto unblock the epic, or use `--epic-override mala-brwf` to force closure.\n","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-03T18:00:59.369191957Z","created_by":"cyou","updated_at":"2026-01-03T18:01:11.423017717Z","closed_at":"2026-01-03T18:01:11.423017717Z","close_reason":"Closed","labels":["epic_human_review","epic_id:mala-brwf"],"dependencies":[{"issue_id":"mala-brwf.6","depends_on_id":"mala-brwf","type":"parent-child","created_at":"2026-01-03T18:00:59.369587235Z","created_by":"daemon"}]}
{"id":"mala-buj","title":"Remove unused sync git functions","description":"## Context\n- `get_git_commit()` and `get_git_branch()` (sync versions) are defined but never called\n- Only async versions (`get_git_commit_async`, `get_git_branch_async`) are used by orchestrator\n- ~30 lines of dead code\n- Confidence: High - grep confirms no callers\n\n## Primary Files\n`src/git_utils.py:14-45`\n\n## Scope\n**In:** Remove `get_git_commit` and `get_git_branch` sync functions\n**Out:** Keep async versions and `DEFAULT_GIT_TIMEOUT` constant\n\n## Acceptance Criteria\n- Sync functions removed\n- No import errors\n- Tests pass\n\n## Test Plan\n- Run `uv run pytest`\n- Verify module imports cleanly: `python -c \"from src.git_utils import get_git_commit_async\"`\n\n## Notes for Agent\n- Keep the module docstring and async functions intact\n- The sync functions are lines 14-45, async are 51-100","status":"closed","priority":2,"issue_type":"chore","created_at":"2025-12-26T19:44:58.439537512Z","updated_at":"2025-12-26T19:52:03.649089915Z","closed_at":"2025-12-26T19:52:03.649089915Z","close_reason":"Closed","labels":["dead-code"],"dependencies":[{"issue_id":"mala-buj","depends_on_id":"mala-7zq","type":"parent-child","created_at":"2025-12-26T19:45:26.554545264Z","created_by":"daemon"}]}
{"id":"mala-bvd","title":"Other Improvements","description":"General improvements:\n- Deadlock detection (two agents waiting on each other)\n- Metrics/logging for multi-agent sessions\n- Graceful shutdown (signal all agents to wrap up)","status":"tombstone","priority":2,"issue_type":"epic","created_at":"2025-12-26T00:54:39.298081197Z","updated_at":"2025-12-26T00:55:27.989133028Z","deleted_at":"2025-12-26T00:55:27.989133028Z","deleted_by":"daemon","delete_reason":"delete","original_type":"epic"}
{"id":"mala-c01","title":"Epic: CLI module refactor (split cli.py)","description":"Parent epic for refactoring src/cli.py into focused modules without changing behavior. Children: hook extraction, git helper extraction, orchestrator extraction.","status":"closed","priority":3,"issue_type":"epic","created_at":"2025-12-25T21:41:59.788032839Z","updated_at":"2025-12-26T00:15:59.835213834Z","closed_at":"2025-12-26T00:15:59.835213834Z","close_reason":"All children completed"}
{"id":"mala-c01.1","title":"Refactor: extract beads client from cli.py","description":"Goal: Extract beads-related logic from MalaOrchestrator into a dedicated BeadsClient class (e.g., src/beads_client.py) that wraps bd CLI calls.\n\nScope/files: src/cli.py, new src/beads_client.py.\n\nWork:\n- Create BeadsClient class with methods: get_ready_issues(), claim_issue(), reset_issue(), commit_issues(), close_eligible_epics().\n- Move bd CLI subprocess calls from MalaOrchestrator to BeadsClient.\n- Have MalaOrchestrator use BeadsClient instance.\n- Keep CLI behavior unchanged.\n\nAcceptance criteria:\n- All bd CLI calls go through BeadsClient.\n- MalaOrchestrator behavior unchanged.\n- CLI still works.\n- Tests (if any) still pass.","status":"closed","priority":3,"issue_type":"task","created_at":"2025-12-25T22:15:22.761330846Z","updated_at":"2025-12-26T00:09:17.45761968Z","closed_at":"2025-12-26T00:09:17.45761968Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-c01.1","depends_on_id":"mala-c01","type":"parent-child","created_at":"2025-12-25T22:15:22.771293472Z","created_by":"daemon"}]}
{"id":"mala-c1ag","title":"[Review] 4 non-blocking findings from mala-v2w5","description":"## Review Findings\n\nThis issue consolidates 4 non-blocking findings from code review.\n\n**Source issue:** mala-v2w5\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Remove unused MagicMock fixture param in obsolete-resolution test\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** tests/unit/domain/test_quality_gate.py:1260-1261\n\n`test_obsolete_resolution_skips_all_commands` still requests `mock_command_runner: MagicMock` but never uses it after switching to `FakeCommandRunner`. This adds dead dependency/overhead (and counts toward remaining MagicMock usage) and makes it unclear what the test actually relies on; drop the parameter (and any related typing) or use it consistently.\n\n---\n\n### Finding 2: [P2] Replace private typing._get_protocol_attrs in contract tests\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** tests/contracts/__init__.py:30-33\n\nThe `tests/contracts/__init__.py` file still uses the private `typing._get_protocol_attrs` API for Python versions earlier than 3.13. As previously noted, using private internal APIs should be avoided when possible. Consider an alternative approach or document why this specific internal API is necessary despite the risks of relying on undocumented internals.\n\n---\n\n### Finding 3: [P2] Strengthen protocol compliance tests with runtime assertions\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** tests/contracts/test_command_runner_contract.py:21-24\n\nThe 'protocol compliance' tests (e.g., in `tests/contracts/test_command_runner_contract.py`) currently only assert that a fake instance is not `None`. Since the protocols are marked with `@runtime_checkable`, these tests should use `assert isinstance(instance, ProtocolClass)` to actually verify runtime conformance as suggested in previous feedback.\n\n---\n\n### Finding 4: [P2] Migrate test_quality_gate.py to FakeCommandRunner\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** tests/README.md:101\n\nThe `tests/unit/domain/test_quality_gate.py` file is documented as an exception for `MagicMock` usage on `CommandRunnerPort`, but a `FakeCommandRunner` already exists and is used elsewhere in the project. Using the fake in the `mock_command_runner` fixture would align this file with the 'fakes over mocks' philosophy without requiring a full refactor of the 3,500+ line file.\n\n---\n\n\u003c!-- fp:f097a791a00f5877 --\u003e\n\u003c!-- fp:e3f9750b809669f0 --\u003e\n\u003c!-- fp:6d9ecb6681883158 --\u003e\n\u003c!-- fp:34ed85fff500c844 --\u003e\n\u003c!-- review_finding:b605a34cfdfd --\u003e","notes":"Quality gate failed: Quality gate failed: No commit with bd-mala-c1ag found after run baseline (stale commits from previous runs are rejected)\nLog: /home/cyou/.claude/projects/-home-cyou-mala/92870b11-2999-476e-b447-bb5a8f4e9922.jsonl","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-05T16:25:30.830140356Z","created_by":"cyou","updated_at":"2026-01-05T17:47:35.613801159Z","closed_at":"2026-01-05T17:47:35.613801159Z","close_reason":"Closed","labels":["auto_generated","needs-followup","review_finding","source:mala-v2w5"]}
{"id":"mala-c1o","title":"Implement coverage parsing + threshold handling","description":"Context:\n- Coverage gate requires parsing pytest coverage output and enforcing thresholds.\n- Plan uses XML + min-percent thresholds.\n\nScope:\n- In: parse coverage XML and return CoverageResult; handle threshold comparison and error reporting.\n- Out: orchestrator gating logic (separate task).\n\nAcceptance Criteria:\n- CoverageResult includes percent, pass/fail, report path.\n- Parser handles missing/invalid report with clear failure reason.\n\nTest Plan:\n- TDD: add unit tests with fixture XMLs for pass/fail/invalid.\n- Run coverage module tests.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-26T23:00:01.67857076Z","updated_at":"2025-12-27T00:03:14.562228529Z","closed_at":"2025-12-27T00:03:14.562228529Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-c1o","depends_on_id":"mala-j6p","type":"blocks","created_at":"2025-12-26T23:00:26.478117705Z","created_by":"daemon"}]}
{"id":"mala-c3fl","title":"remove fail on empty cli command","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-06T03:22:28.728606963Z","created_by":"cyou","updated_at":"2026-01-06T03:29:34.72709038Z","closed_at":"2026-01-06T03:29:34.72709038Z","close_reason":"Closed"}
{"id":"mala-c5n","title":"Replace exponential backoff with simple 1s polling for lock waits","description":"## Problem\nThe implementer prompt instructs agents to use exponential backoff (10s, 20s, 40s, 80s, 160s, 320s, 640s) when waiting for locks. This has two issues:\n\n1. **Noisy logs**: Agents print a message every time they sleep, cluttering output\n2. **Over-engineering**: Exponential backoff adds complexity; a simple 1-second polling loop is simpler and equally effective for file locks\n\n## Current Behavior\nFrom src/prompts/implementer_prompt.md:53:\n```\n- Use exponential backoff: 10s, 20s, 40s, 80s, 160s, 320s, 640s (max 15 min total)\n```\n\n## Proposed Solution\n\nOption A: Add a helper script (e.g., `scripts/lock-wait.sh`) that:\n- Takes a file path and agent ID\n- Polls every 1 second until lock is available\n- Prints status only once at start and once when acquired\n- Agent calls this instead of implementing backoff loop\n\nOption B: Update prompt to use simpler guidance:\n- Replace exponential backoff with 'poll every 1 second'\n- Tell agent not to print on every poll, just when starting and when acquired\n\n## Acceptance Criteria\n- Agents no longer spam logs while waiting for locks\n- Lock waiting behavior is simple (1s fixed interval)\n- Either via helper script or updated prompt guidance","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-26T17:07:53.583339508Z","updated_at":"2025-12-26T17:16:00.837069817Z","closed_at":"2025-12-26T17:16:00.837069817Z","close_reason":"Closed"}
{"id":"mala-c7wb","title":"[Review] src/domain/validation/tool_name_extractor.py:255-257: [P2] Shell operator splitting doesn't respect quoted strings","description":"## Review Finding\n\nThis issue was auto-created from a P2 review finding.\n\n**Source issue:** mala-fdp1.5\n**Reviewer:** claude\n**Location:** src/domain/validation/tool_name_extractor.py:255-257\n\n## Details\n\nThe operator splitting at lines 255-271 uses simple `command.split(operator)` which doesn't respect quoted strings. For example, `eslint --rule \"semi \u0026\u0026 no-console\"` would incorrectly split on the `\u0026\u0026` inside quotes, potentially returning wrong results. Consider using shlex parsing first, then identifying operator boundaries, though this is an edge case for typical quality gate commands.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-02T03:17:47.83203058Z","created_by":"cyou","updated_at":"2026-01-02T03:55:38.83776056Z","closed_at":"2026-01-02T03:55:38.83776056Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_finding:0d4d7a0af016","source:mala-fdp1.5"]}
{"id":"mala-c90n","title":"[Review] 6 non-blocking findings from mala-xtcx","description":"## Review Findings\n\nThis issue consolidates 6 non-blocking findings from code review.\n\n**Source issue:** mala-xtcx\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Missing error handling for update_issue_description_async failure\n\n**Priority:** P2\n**Reviewer:** claude\n**Location:** src/orchestration/review_tracking.py:129-133\n\nThe `update_issue_description_async` call on line 129 returns a boolean indicating success/failure, but the result is silently discarded. If the update fails, the function proceeds to emit a warning claiming findings were appended when they weren't. Consider checking the return value and handling failures appropriately.\n\n---\n\n### Finding 2: [P3] String replacement for count update is fragile\n\n**Priority:** P3\n**Reviewer:** claude\n**Location:** src/orchestration/review_tracking.py:119-122\n\nThe `.replace()` on lines 119-122 assumes the description contains exactly `\"{count} non-blocking finding\"`. If the description was manually edited or has a different format, this replacement silently fails and leaves an incorrect count. The header will still show the old count even after appending findings. Consider using a regex or structured format for more robust updates.\n\n---\n\n### Finding 3: [P2] Update title/priority when appending findings\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/orchestration/review_tracking.py:125-133\n\nWhen an existing tracking issue is found, the code only updates the description and count but never updates the issue title (which encodes finding count and highest priority). If the new batch increases the count or includes a higher-priority finding, the title becomes stale (e.g., P3 title even after adding a P2). This can mislead triage and sorting. Consider updating the title (and highest priority) alongside the description update.\n\n---\n\n### Finding 4: [P2] Avoid overwriting description on fetch failure\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/orchestration/review_tracking.py:108-123\n\n`get_issue_description_async` returns `str | None`, but when it returns `None` the code builds `updated_desc` from an empty string and overwrites the issue with only the new findings. That can erase the existing description on transient API errors. Guard on `None` and skip the update (or retry) rather than replacing the description when the current content isn’t available.\n\n---\n\n### Finding 5: [P3] Incorrect pluralization when updating finding count\n\n**Priority:** P3\n**Reviewer:** gemini\n**Location:** src/orchestration/review_tracking.py:121-124\n\nWhen appending findings to an existing issue, the code replaces `f\"{existing_finding_count} non-blocking finding\"` with `f\"{total_count} non-blocking finding\"`. If the original count was 1 (\"1 non-blocking finding\") and the new count is 2, the result will be \"2 non-blocking finding\", missing the 's' for pluralization. Consider including the optional 's' in the replacement logic or using a more robust regex for updating the header.\n\n---\n\n### Finding 6: [P2] Batch-level deduplication allows individual finding duplicates\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/orchestration/review_tracking.py:105-108\n\nThe deduplication check `new_dedup_tag in existing_desc` works at the batch level. If a retry or subsequent review produces a set of findings that overlaps with but is not identical to a previously appended batch (e.g., findings {A, B} followed by {A, B, C}), the entire second batch will be appended because its hash differs. This results in duplicate findings (A and B appearing twice) within the same tracking issue. Checking for each finding's individual fingerprint in the description would be more robust.\n\n---\n","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T06:33:30.964727778Z","created_by":"cyou","updated_at":"2026-01-03T06:42:12.981704541Z","closed_at":"2026-01-03T06:42:12.981704541Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_findings:9b6823fe1e13","source:mala-xtcx"]}
{"id":"mala-cavk","title":"Epic: Architecture refactor - break up god classes","description":"# Architecture Refactor: High-Priority God Class Breakup\n\n**Spec**: `docs/2026-01-01-architecture-review.md`\n**Plan**: `docs/2026-01-01-architecture-refactor-plan.md`\n\n## Prerequisites\n\n**Depends on mala-iy6l** (Import-linter package restructure). The iy6l epic restructures the codebase into layer-based packages, and this refactor assumes those paths:\n- `src/orchestrator.py` → `src/orchestration/orchestrator.py`\n- `src/cli.py` → `src/cli/cli.py`\n\n## Goals\n- Reduce complexity and duplication in 3 high-priority areas identified in architecture review\n- Pure refactoring - no behavioral changes\n- Deliver as 3 incremental PRs that can be reviewed and merged independently\n\n## PRs (in order)\n1. **EventSink** - Create BaseEventSink to eliminate NullEventSink duplication (NullEventSink \u003c20 lines)\n2. **AgentSessionRunner** - Extract run_session into focused helpers (675 → \u003c100 lines)  \n3. **MalaOrchestrator** - Extract IssueExecutionCoordinator + remove legacy init (1565 → \u003c1000 lines)\n\n## Acceptance Criteria\n- [ ] NullEventSink reduced to \u003c20 lines\n- [ ] run_session reduced to \u003c100 lines\n- [ ] Legacy init removed from MalaOrchestrator\n- [ ] All existing tests pass\n- [ ] New unit tests for extracted helpers","status":"closed","priority":1,"issue_type":"epic","created_at":"2026-01-01T15:57:41.331463831Z","created_by":"cyou","updated_at":"2026-01-02T02:54:15.490652164Z","closed_at":"2026-01-02T02:54:15.490652164Z","close_reason":"All children completed","labels":["god-class"]}
{"id":"mala-cavk.1","title":"PR 1: EventSink - Create BaseEventSink and simplify NullEventSink","description":"# EventSink Refactor: Eliminate NullEventSink Duplication\n\n**Plan Task**: Task 1 from `docs/2026-01-01-architecture-refactor-plan.md`\n**PR**: 1 of 3 (safest, no behavioral impact - do first)\n\n## Context\n- `MalaEventSink` Protocol: lines 55-600 (~38 methods) in `src/infra/io/event_sink.py`\n- `NullEventSink` class: lines 601-835 (235 lines) - all methods are just `pass`\n- `ConsoleEventSink` class: lines 837-1396 (560 lines) - real implementations\n- **Problem**: Every new event requires updating 3 places (protocol, NullEventSink, ConsoleEventSink)\n- **Verified**: `MalaEventSink` is `@runtime_checkable` (line 55), so isinstance checks work\n\n## Scope\n**In**: \n- Create `BaseEventSink` class with explicit no-op `pass` implementations for all 38 protocol methods\n- Replace `NullEventSink` (235 lines) with `NullEventSink = BaseEventSink` or thin 3-line subclass\n- Update `ConsoleEventSink` to inherit from `BaseEventSink`, remove redundant `pass` methods\n\n**Out**: \n- Changing event method signatures\n- Modifying ConsoleEventSink logic (only structure)\n- Adding new events\n\n## Primary Files\n- `src/infra/io/event_sink.py` (main changes)\n- `tests/test_event_sink.py` (add BaseEventSink tests)\n\n## Acceptance Criteria\n- [ ] `BaseEventSink` class exists with all 38 protocol methods as no-ops\n- [ ] `NullEventSink` is \u003c20 lines (alias or thin subclass)\n- [ ] `ConsoleEventSink` inherits from `BaseEventSink`\n- [ ] Adding new event requires only: 1) protocol, 2) BaseEventSink, 3) ConsoleEventSink override if needed\n- [ ] `isinstance(BaseEventSink(), MalaEventSink)` returns True\n- [ ] All existing tests pass unchanged\n\n## Test Plan\n- Add test: `BaseEventSink` implements `MalaEventSink` protocol (isinstance check)\n- Add test: `NullEventSink` is usable as `MalaEventSink`\n- Run: `uvx ty check` - type checking passes\n- Run: `uv run pytest tests/test_event_sink.py -v`\n- Run: `uv run pytest -m \"unit or integration\"` - full suite passes\n- Verify line count: `wc -l` on NullEventSink class definition\n\n## Notes for Agent\n- BaseEventSink goes AFTER MalaEventSink protocol definition (around line 600)\n- Copy method signatures exactly from NullEventSink - just consolidate them\n- Don't change any method bodies in ConsoleEventSink, just remove inheritance boilerplate\n- This is the safest PR - pure structural change with no behavioral impact","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-01T15:58:09.767302696Z","created_by":"cyou","updated_at":"2026-01-01T21:02:01.945495114Z","closed_at":"2026-01-01T21:02:01.945495114Z","close_reason":"Closed","labels":["refactor"],"dependencies":[{"issue_id":"mala-cavk.1","depends_on_id":"mala-cavk","type":"parent-child","created_at":"2026-01-01T15:58:09.775130264Z","created_by":"daemon"}]}
{"id":"mala-cavk.2","title":"PR 2: AgentSessionRunner - Extract run_session into focused helpers","description":"# AgentSessionRunner.run_session Extraction\n\n**Plan Tasks**: Tasks 2-6 from `docs/2026-01-01-architecture-refactor-plan.md`\n**PR**: 2 of 3\n\n## Context\n- `run_session` method: lines 389-1064 (675 lines) with CCN 84 (extreme complexity)\n- Method handles: SDK setup, message streaming, idle timeout retry, log waiting, gate checks, review checks\n- Existing helper pattern to follow: `_build_hooks()`, `_build_agent_env()` already exist\n\n## Scope\n**In**: Extract 5 helper methods from run_session:\n1. `_build_sdk_options(agent_id: str) -\u003e ClaudeAgentOptions` - SDK options construction (lines 446-468)\n2. `_run_message_iteration(client, input, lifecycle_ctx, iter_ctx: MessageIterationContext) -\u003e bool` - Message streaming with idle timeout (lines 516-748)\n3. `_handle_log_waiting(session_id, lifecycle, lifecycle_ctx) -\u003e tuple[Path | None, TransitionResult]` - WAIT_FOR_LOG handling (lines 772-802)\n4. `_handle_gate_effect(input, log_path, lifecycle, lifecycle_ctx) -\u003e tuple[str | None, bool]` - RUN_GATE handling (lines 804-879)\n5. `_handle_review_effect(input, log_path, lifecycle, lifecycle_ctx) -\u003e tuple[str | None, bool, str | None]` - RUN_REVIEW handling (lines 881-1033)\n\nAlso create `MessageIterationContext` dataclass for mutable state:\n- `session_id: str | None`\n- `tool_calls_count: int`  \n- `pending_lint_commands: dict[str, tuple[str, str]]`\n\n**Out**:\n- Changing lifecycle state machine design\n- Modifying SDK client protocol\n- Any behavioral changes\n\n## Primary Files\n- `src/pipeline/agent_session_runner.py` (main refactor)\n- `tests/test_agent_session_runner.py` (add helper unit tests)\n\n## Acceptance Criteria\n- [ ] `run_session` reduced to \u003c100 lines orchestrating helpers\n- [ ] Each extracted helper has single responsibility\n- [ ] `MessageIterationContext` dataclass holds mutable iteration state\n- [ ] All existing agent session tests pass unchanged\n- [ ] New unit tests for each extracted helper method\n\n## Test Plan\n**TDD approach for each helper:**\n1. Add unit test for helper in isolation\n2. Extract helper from run_session\n3. Verify existing tests still pass\n\nRun after each extraction:\n- `uv run pytest tests/test_agent_session_runner.py -v`\n- `uv run pytest -m \"unit or integration\"` after all extractions\n\nVerify final line count: `run_session` \u003c100 lines\n\n## Notes for Agent\n- Follow existing pattern of `_build_hooks()` and `_build_agent_env()` for naming/style\n- `MessageIterationContext` is passed by reference to avoid complex return types\n- Helper methods should be private (`_` prefix)\n- Preserve exact behavior - use existing tests as behavioral specification\n- Handle IdleTimeoutError internally in `_run_message_iteration()`\n- Line numbers are approximate - use the code structure to identify extraction boundaries","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-01T15:58:44.82595465Z","created_by":"cyou","updated_at":"2026-01-01T21:33:48.173252332Z","closed_at":"2026-01-01T21:33:48.173252332Z","close_reason":"Closed","labels":["complexity"],"dependencies":[{"issue_id":"mala-cavk.2","depends_on_id":"mala-cavk","type":"parent-child","created_at":"2026-01-01T15:58:44.832897355Z","created_by":"daemon"},{"issue_id":"mala-cavk.2","depends_on_id":"mala-cavk.1","type":"blocks","created_at":"2026-01-01T15:59:38.845707389Z","created_by":"daemon"}]}
{"id":"mala-cavk.3","title":"PR 3: MalaOrchestrator - Extract IssueExecutionCoordinator and remove legacy init","description":"# MalaOrchestrator Split: IssueExecutionCoordinator + Legacy Init Removal\n\n**Plan Tasks**: Tasks 7-9 from `docs/2026-01-01-architecture-refactor-plan.md`\n**PR**: 3 of 3 (largest, do last)\n\n## Context\n- `MalaOrchestrator`: 1565 lines with dual init paths (legacy + factory)\n- `_run_main_loop` (lines 1351-1449): Main agent spawning loop\n- `spawn_agent`, `_finalize_issue_result`, `_abort_active_tasks`: Execution logic\n- 55+ test locations use legacy `MalaOrchestrator()` constructor across 5 test files\n\n## Scope\n**In** (3 subtasks):\n\n### Subtask 3a: Create IssueExecutionCoordinator\n- **New** `src/pipeline/issue_execution_coordinator.py`:\n  - `IssueExecutionCoordinator` class with protocol-based dependencies\n  - Constructor: `beads: IssueProvider`, `event_sink: MalaEventSink`, `config: CoordinatorConfig`\n  - `IssueProvider` imported from `src.core.protocols`\n  - `CoordinatorConfig` dataclass: `max_agents`, `max_issues`, `epic_id`, `only_ids`, `prioritize_wip`, `focus`\n  - `async def run_loop(spawn_callback, finalize_callback) -\u003e int`\n  - `async def abort_active_tasks(reason: str) -\u003e None`\n- Update `src/pipeline/__init__.py`: export `IssueExecutionCoordinator`, `CoordinatorConfig`\n- **New** `tests/test_issue_execution_coordinator.py`: unit tests with mock IssueProvider\n\n### Subtask 3b: Migrate MalaOrchestrator\n- Update `src/orchestrator.py` to use `IssueExecutionCoordinator`\n- `_run_main_loop` delegates to `coordinator.run_loop()`\n- Keep `spawn_agent` and `_finalize_issue_result` as callbacks\n\n### Subtask 3c: Remove legacy init\n- Remove legacy `__init__` overload (lines 312-340)\n- Remove `_init_legacy` method entirely\n- Update 55+ test locations across 5 files:\n  - `tests/test_orchestrator.py` (~45 locations)\n  - `tests/test_epic_verifier.py` (2 locations)\n  - `tests/test_run_level_validation.py` (4 locations)\n  - `tests/test_wip_prioritization.py` (4 locations)\n  - `tests/test_morph_integration.py` (1 location)\n- Create `make_orchestrator()` fixture in `tests/conftest.py`\n\n**Out**:\n- Changing factory pattern in `orchestrator_factory.py`\n- Breaking public `MalaOrchestrator` API for factory users\n- Line count target of \u003c400 (realistic target is \u003c1000)\n\n## Primary Files\n- `src/pipeline/issue_execution_coordinator.py` (**New**)\n- `src/pipeline/__init__.py` (add exports)\n- `src/orchestrator.py` (delegate + remove legacy)\n- `tests/test_issue_execution_coordinator.py` (**New**)\n- `tests/conftest.py` (add fixture)\n- `tests/test_orchestrator.py` (update ~45 locations)\n- `tests/test_epic_verifier.py`, `tests/test_run_level_validation.py`, `tests/test_wip_prioritization.py`, `tests/test_morph_integration.py` (update legacy calls)\n\n## Acceptance Criteria\n- [ ] `IssueExecutionCoordinator` exists and is testable without SDK dependencies\n- [ ] `src/pipeline/__init__.py` exports new coordinator\n- [ ] MalaOrchestrator delegates to coordinator\n- [ ] Legacy init path completely removed\n- [ ] No `MalaOrchestrator(` direct constructor calls remain in tests/src\n- [ ] `orchestrator.py` reduced to \u003c1000 lines\n- [ ] All 55+ test locations updated to use factory pattern\n- [ ] All existing tests pass\n\n## Test Plan\n1. Create coordinator with unit tests first (TDD)\n2. Migrate orchestrator to use coordinator\n3. Verify: `uv run pytest tests/test_orchestrator.py -v`\n4. Create `make_orchestrator()` fixture in conftest.py\n5. Update all test files to use fixture\n6. Final verification: `uv run pytest -m \"unit or integration\" -n auto`\n7. Verify no legacy usage: `grep -r \"MalaOrchestrator(\" tests/ src/`\n\n## Notes for Agent\n- This is the largest PR - consider doing subtasks incrementally with commits\n- `IssueProvider` comes from `src.core.protocols`\n- The \u003c400 line target is NOT achievable with this scope; \u003c1000 is realistic\n- Factory pattern is already used by `cli.py` via `create_orchestrator()`\n- When updating tests, use the new fixture rather than inline factory calls\n- Coordinator should NOT have SDK dependencies - use callbacks for agent spawning","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-01T15:59:24.862850525Z","created_by":"cyou","updated_at":"2026-01-01T22:21:12.192390898Z","closed_at":"2026-01-01T22:21:12.192390898Z","close_reason":"Closed","labels":["legacy-removal"],"dependencies":[{"issue_id":"mala-cavk.3","depends_on_id":"mala-cavk","type":"parent-child","created_at":"2026-01-01T15:59:24.870444296Z","created_by":"daemon"},{"issue_id":"mala-cavk.3","depends_on_id":"mala-cavk.2","type":"blocks","created_at":"2026-01-01T15:59:39.538985971Z","created_by":"daemon"}]}
{"id":"mala-cavk.4","title":"[Remediation] run_session reduced to \u003c100 lines","description":"## Context\nThis issue was auto-created by epic verification for epic `mala-cavk`.\n\n## Epic Description / Acceptance Criteria\nTitle: Epic: Architecture refactor - break up god classes\n\n# Architecture Refactor: High-Priority God Class Breakup\n\n**Spec**: `docs/2026-01-01-architecture-review.md`\n**Plan**: `docs/2026-01-01-architecture-refactor-plan.md`\n\n## Prerequisites\n\n**Depends on mala-iy6l** (Import-linter package restructure). The iy6l epic restructures the codebase into layer-based packages, and this refactor assumes those paths:\n- `src/orchestrator.py` → `src/orchestration/orchestrator.py`\n- `src/cli.py` → `src/cli/cli.py`\n\n## Goals\n- Reduce complexity and duplication in 3 high-priority areas identified in architecture review\n- Pure refactoring - no behavioral changes\n- Deliver as 3 incremental PRs that can be reviewed and merged independently\n\n## PRs (in order)\n1. **EventSink** - Create BaseEventSink to eliminate NullEventSink duplication (NullEventSink \u003c20 lines)\n2. **AgentSessionRunner** - Extract run_session into focused helpers (675 → \u003c100 lines)  \n3. **MalaOrchestrator** - Extract IssueExecutionCoordinator + remove legacy init (1565 → \u003c1000 lines)\n\n## Acceptance Criteria\n- [ ] NullEventSink reduced to \u003c20 lines\n- [ ] run_session reduced to \u003c100 lines\n- [ ] Legacy init removed from MalaOrchestrator\n- [ ] All existing tests pass\n- [ ] New unit tests for extracted helpers\n\n## Commit Scope\n- Range hint: b826537c90ea28c402742c605757be17820e0fc0^..6817b912b7e448c22748fb6778e5a7c3d5582992\n- Commits:\n- 942c6b2f8efd65b778f7f9c3036fbb05091d115e bd-mala-cavk.1: Fix protocol coverage tests to exclude ABCMeta helpers\n- b826537c90ea28c402742c605757be17820e0fc0 bd-mala-cavk.1: Create BaseEventSink, simplify NullEventSink\n- 46c0f46abccd2af19b1d68380e7957114133f76c bd-mala-cavk.2: Extract helpers from AgentSessionRunner.run_session\n- efb74872758e0863c8d0dd5b72ed0ff2f3a6fd0a bd-mala-cavk.2: Extract helpers from AgentSessionRunner.run_session\n- 6817b912b7e448c22748fb6778e5a7c3d5582992 bd-mala-cavk.3: Fix ruff TC003 type-checking import violations\n- 99e411ab4383d9e7d840a4678d98b9d1cfb2205c bd-mala-cavk.3: Fix type annotations and linting issues in test files\n- 977be4d0c6064476546a918b59f5ad49f0c5f63c bd-mala-cavk.3: Extract IssueExecutionCoordinator from MalaOrchestrator\n\n## Child Issues\n- mala-cavk.1\n- mala-cavk.2\n- mala-cavk.3\n- mala-dhry\n- mala-jnah\n\n## Unmet Criterion\nrun_session reduced to \u003c100 lines\n\n## Evidence\nAgentSessionRunner.run_session method spans lines 1114-1376 (262 lines total), significantly exceeding the \u003c100 line target despite helper extraction efforts in commits 46c0f46 and efb7487\n\n## Priority\nP1 (blocking)\n\n## Resolution\nAddress this criterion to unblock epic closure. When complete, close this issue.\n","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-01T22:23:07.379158233Z","created_by":"cyou","updated_at":"2026-01-01T22:35:35.169377921Z","closed_at":"2026-01-01T22:35:35.169377921Z","close_reason":"Closed","labels":["auto_generated","epic_remediation:mala-cavk:3526d7861e2fd47da405e92ba4b621f62fb96993da0f8ffe05424fa7df7b751a"],"dependencies":[{"issue_id":"mala-cavk.4","depends_on_id":"mala-cavk","type":"parent-child","created_at":"2026-01-01T22:23:07.379608422Z","created_by":"daemon"}]}
{"id":"mala-cavk.5","title":"[Remediation] MalaOrchestrator reduced to \u003c1000 lines","description":"## Context\nThis issue was auto-created by epic verification for epic `mala-cavk`.\n\n## Epic Description / Acceptance Criteria\nTitle: Epic: Architecture refactor - break up god classes\n\n# Architecture Refactor: High-Priority God Class Breakup\n\n**Spec**: `docs/2026-01-01-architecture-review.md`\n**Plan**: `docs/2026-01-01-architecture-refactor-plan.md`\n\n## Prerequisites\n\n**Depends on mala-iy6l** (Import-linter package restructure). The iy6l epic restructures the codebase into layer-based packages, and this refactor assumes those paths:\n- `src/orchestrator.py` → `src/orchestration/orchestrator.py`\n- `src/cli.py` → `src/cli/cli.py`\n\n## Goals\n- Reduce complexity and duplication in 3 high-priority areas identified in architecture review\n- Pure refactoring - no behavioral changes\n- Deliver as 3 incremental PRs that can be reviewed and merged independently\n\n## PRs (in order)\n1. **EventSink** - Create BaseEventSink to eliminate NullEventSink duplication (NullEventSink \u003c20 lines)\n2. **AgentSessionRunner** - Extract run_session into focused helpers (675 → \u003c100 lines)  \n3. **MalaOrchestrator** - Extract IssueExecutionCoordinator + remove legacy init (1565 → \u003c1000 lines)\n\n## Acceptance Criteria\n- [ ] NullEventSink reduced to \u003c20 lines\n- [ ] run_session reduced to \u003c100 lines\n- [ ] Legacy init removed from MalaOrchestrator\n- [ ] All existing tests pass\n- [ ] New unit tests for extracted helpers\n\n## Commit Scope\n- Range hint: b826537c90ea28c402742c605757be17820e0fc0^..6817b912b7e448c22748fb6778e5a7c3d5582992\n- Commits:\n- 942c6b2f8efd65b778f7f9c3036fbb05091d115e bd-mala-cavk.1: Fix protocol coverage tests to exclude ABCMeta helpers\n- b826537c90ea28c402742c605757be17820e0fc0 bd-mala-cavk.1: Create BaseEventSink, simplify NullEventSink\n- 46c0f46abccd2af19b1d68380e7957114133f76c bd-mala-cavk.2: Extract helpers from AgentSessionRunner.run_session\n- efb74872758e0863c8d0dd5b72ed0ff2f3a6fd0a bd-mala-cavk.2: Extract helpers from AgentSessionRunner.run_session\n- 6817b912b7e448c22748fb6778e5a7c3d5582992 bd-mala-cavk.3: Fix ruff TC003 type-checking import violations\n- 99e411ab4383d9e7d840a4678d98b9d1cfb2205c bd-mala-cavk.3: Fix type annotations and linting issues in test files\n- 977be4d0c6064476546a918b59f5ad49f0c5f63c bd-mala-cavk.3: Extract IssueExecutionCoordinator from MalaOrchestrator\n\n## Child Issues\n- mala-cavk.1\n- mala-cavk.2\n- mala-cavk.3\n- mala-dhry\n- mala-jnah\n\n## Unmet Criterion\nMalaOrchestrator reduced to \u003c1000 lines\n\n## Evidence\nsrc/orchestration/orchestrator.py contains 1368 lines, exceeding the \u003c1000 line target. While IssueExecutionCoordinator was extracted and ~170 lines were removed (1537→1367), it still exceeds the target by 368 lines\n\n## Priority\nP1 (blocking)\n\n## Resolution\nAddress this criterion to unblock epic closure. When complete, close this issue.\n","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-01T22:23:07.394553296Z","created_by":"cyou","updated_at":"2026-01-01T22:55:46.464306406Z","closed_at":"2026-01-01T22:55:46.464306406Z","close_reason":"Closed","labels":["auto_generated","epic_remediation:mala-cavk:c5e7cf724ec6ee8c9408df440ba125a8889c43ce33b386efc56b2b6dc5177fef"],"dependencies":[{"issue_id":"mala-cavk.5","depends_on_id":"mala-cavk","type":"parent-child","created_at":"2026-01-01T22:23:07.394908286Z","created_by":"daemon"}]}
{"id":"mala-ccll","title":"[Review] tests/test_issue_manager.py:408-460: [P3] Missing integration test for orphans_only filter in BeadsClient","description":"## Review Finding\n\nThis issue was auto-created from a P3 review finding.\n\n**Source issue:** mala-jnah\n**Reviewer:** claude\n**Location:** tests/test_issue_manager.py:408-460\n\n## Details\n\nThe `filter_orphans_only` method has unit tests, but there's no integration test that verifies `orphans_only=True` is properly passed through and applied in `BeadsClient.get_ready_async()` or `get_ready_issues_async()`. The mock `IssueProvider` implementations in test_orchestrator.py accept the parameter but don't actually test the filtering behavior.","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-01T23:18:02.915444141Z","created_by":"cyou","updated_at":"2026-01-01T23:40:16.474065099Z","closed_at":"2026-01-01T23:40:16.474065099Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_finding:96ba96eb5803","source:mala-jnah"]}
{"id":"mala-cdw","title":"Context Exhaustion Handling","description":"When agents run out of context mid-implementation:\n- Detect context exhaustion (agent returns incomplete)\n- Save progress state (files modified, locks held)\n- Spawn continuation agent with summary of prior work\n- Or: commit partial progress, mark issue as needs-continuation","status":"tombstone","priority":2,"issue_type":"epic","created_at":"2025-12-26T00:54:39.236980254Z","updated_at":"2025-12-26T00:55:27.98871018Z","deleted_at":"2025-12-26T00:55:27.98871018Z","deleted_by":"daemon","delete_reason":"delete","original_type":"epic"}
{"id":"mala-cee","title":"Fix issues from latest code review","description":"Address findings from the code review performed earlier in this chat on December 27, 2025. Use the review notes as source of truth; update tests/docs as needed.","status":"tombstone","priority":1,"issue_type":"task","created_at":"2025-12-27T22:37:11.106607746Z","updated_at":"2025-12-27T22:37:34.674838686Z","deleted_at":"2025-12-27T22:37:34.674838686Z","deleted_by":"daemon","delete_reason":"delete","original_type":"task"}
{"id":"mala-cf6s","title":"[Review] src/pipeline/agent_session_runner.py:992-1001: [P2] Duplicate default validation command logic","description":"## Review Finding\n\nThis issue was auto-created from a P2 review finding.\n\n**Source issue:** mala-vnzl\n**Reviewer:** gemini\n**Location:** src/pipeline/agent_session_runner.py:992-1001\n\n## Details\n\nThe default Python/uv validation commands (including the complex cache isolation environment variables) are hardcoded in both `src/domain/prompts.py` and `src/pipeline/agent_session_runner.py`. This creates a maintenance risk where updating the default toolchain configuration requires changes in multiple disconnected modules. Consider centralizing these defaults or having `AgentSessionRunner` utilize `build_prompt_validation_commands` to ensure consistency.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T00:31:02.168026534Z","created_by":"cyou","updated_at":"2026-01-03T00:39:12.445704194Z","closed_at":"2026-01-03T00:39:12.445704194Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_finding:ae145546d09e","source:mala-vnzl"]}
{"id":"mala-chf9","title":"Retry epic verification until passing (max_epic_verification_retries)","description":"# Epic Verification Retry Loop\n\n## Problem\nCurrently, when running `mala run --epic \u003cid\u003e`, epic verification only runs once after all child issues complete. If verification creates remediation issues (e.g., cavk.4, cavk.5), those issues are executed but the epic is not re-verified after they complete.\n\nThis was observed in run `9dfbf91e`:\n- Epic `mala-cavk` had children cavk.1-3, plus transitively connected dhry and jnah\n- After cavk.1-3 completed, epic verification found unmet criteria → created cavk.4 and cavk.5\n- cavk.4 and cavk.5 were completed successfully\n- But the epic remained **open** because verification didn't run again\n\n## Solution\nAdd a retry loop around epic verification:\n1. After all child issues complete, run epic verification\n2. If verification fails and creates remediation issues, execute them\n3. Re-verify the epic\n4. Repeat until verification passes OR max retries reached\n\n## API Design\n```python\n# CLI arg\nmala run --epic \u003cid\u003e --max-epic-verification-retries 3  # default: 3\n\n# Config/Env\nMALA_MAX_EPIC_VERIFICATION_RETRIES=3\n```\n\n## Acceptance Criteria\n- [ ] Add `max_epic_verification_retries` param to CLI (default: 3)\n- [ ] Add `MALA_MAX_EPIC_VERIFICATION_RETRIES` env var\n- [ ] Epic verification loops until passing or max retries\n- [ ] Each retry iteration executes any newly-created remediation issues\n- [ ] Clear logging of retry attempts: \"Epic verification attempt 2/3\"\n- [ ] If max retries exceeded, log failure reason and leave epic open\n- [ ] Unit test for retry loop behavior\n\n## Files to Modify\n- `src/cli/cli.py` - add CLI arg\n- `src/infra/io/config.py` - add env var\n- `src/orchestration/orchestrator.py` - implement retry loop in `_finalize_epic()`\n- `tests/test_epic_verification.py` - add retry tests","notes":"Quality gate failed: Timeout after 60 minutes\nLog: /home/cyou/.claude/projects/-home-cyou-mala/09592a4b-6d19-43ea-9c44-c9c006084844.jsonl","status":"closed","priority":2,"issue_type":"feature","created_at":"2026-01-01T23:26:35.992893304Z","created_by":"cyou","updated_at":"2026-01-03T02:34:56.435122753Z","closed_at":"2026-01-03T02:34:56.435122753Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-chf9","depends_on_id":"mala-0dsz","type":"blocks","created_at":"2026-01-01T23:32:33.264934808Z","created_by":"daemon"},{"issue_id":"mala-chf9","depends_on_id":"mala-i8ke","type":"blocks","created_at":"2026-01-01T23:32:33.296526802Z","created_by":"daemon"}]}
{"id":"mala-cij","title":"Extend ValidationRunner for spec + artifacts","description":"Context:\n- ValidationRunner must execute ValidationSpec commands, capture artifacts, and call coverage/e2e.\n- Needs to respect mutex usage and per-scope settings.\n\nScope:\n- In: extend runner to accept ValidationSpec + ValidationContext; execute commands with logging; integrate worktree, coverage, and e2e; produce ValidationResult and artifacts.\n- Out: orchestrator gating decisions (separate task).\n\nAcceptance Criteria:\n- Runner executes command list in order with proper env/mutex.\n- Artifacts (stdout/stderr paths, worktree_state, coverage_report) recorded.\n- Failures are reported with actionable reasons.\n\nTest Plan:\n- TDD: add unit tests with mocked command execution and failure paths.\n- Run validation runner tests.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-26T23:00:01.70404142Z","updated_at":"2025-12-27T00:56:15.341249871Z","closed_at":"2025-12-27T00:56:15.341249871Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-cij","depends_on_id":"mala-j6p","type":"blocks","created_at":"2025-12-26T23:00:26.500543494Z","created_by":"daemon"},{"issue_id":"mala-cij","depends_on_id":"mala-5us","type":"blocks","created_at":"2025-12-26T23:00:26.511968732Z","created_by":"daemon"},{"issue_id":"mala-cij","depends_on_id":"mala-252","type":"blocks","created_at":"2025-12-26T23:00:26.523608608Z","created_by":"daemon"},{"issue_id":"mala-cij","depends_on_id":"mala-c1o","type":"blocks","created_at":"2025-12-26T23:00:26.535378832Z","created_by":"daemon"},{"issue_id":"mala-cij","depends_on_id":"mala-4dh","type":"blocks","created_at":"2025-12-26T23:00:26.547116107Z","created_by":"daemon"}]}
{"id":"mala-cldd","title":"[P1] Avoid casting injected CommandRunnerPort to concrete CommandRunner","description":"## Review Finding\n\n**Priority:** P1\n**Trigger:** run_end\n**Baseline:** 700d3e3f..c1b90144\n**Location:** src/orchestration/factory.py:661-677\n\n---\n\n`_build_dependencies()` allows injecting `deps.command_runner` as a `CommandRunnerPort`, but when building `EpicVerifier` it force-casts that protocol back to the concrete infra type:\n\n```py\ncommand_runner=cast(\"CommandRunner\", command_runner)\n```\n\nIf a caller injects a fake or alternate implementation (e.g., tests or custom runner) that satisfies `CommandRunnerPort` but is not an instance of `src.infra.tools.command_runner.CommandRunner`, the cast will succeed at type-check time but fail at runtime when `EpicVerifier` expects concrete `CommandRunner` behavior/shape. This is a new failure mode introduced by wiring `command_runner` through the DI surface.\n\nConcrete scenario: a test or integration provides `OrchestratorDependencies(command_runner=FakeCommandRunner(...))`; when `issue_provider` is a real `BeadsClient` (the default when `deps.issue_provider` is not provided), the `EpicVerifier(...)` construction path runs and receives a non-`CommandRunner` object.\n\nFix options: change `EpicVerifier.__init__` to accept `CommandRunnerPort` instead of `CommandRunner`, or ensure the factory only passes a real `CommandRunner` (e.g., build a separate concrete runner for EpicVerifier) without casting DI-provided ports.","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-12T04:57:46.07714001Z","created_by":"cyou","updated_at":"2026-01-13T04:52:15.165678377Z","closed_at":"2026-01-13T04:52:15.165678377Z","close_reason":"Closed","labels":["auto_generated","cumulative-review","fp:834c8c5bd513f581","review_finding","trigger:run_end"]}
{"id":"mala-cw18","title":"[Review] 4 non-blocking findings from mala-d6x.7","description":"## Review Findings\n\nThis issue consolidates 4 non-blocking findings from code review.\n\n**Source issue:** mala-d6x.7\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P3] Inconsistent path resolution pattern in load_prompt\n\n**Priority:** P3\n**Reviewer:** claude\n**Location:** src/domain/prompts.py:120-121\n\nThe new `load_prompt()` hardcodes the path via `Path(__file__).parent.parent / \"prompts\"`, while the existing `load_prompts()` accepts `prompt_dir: Path` as a parameter. This inconsistency reduces testability - tests for `load_prompt` cannot inject a custom path and must use the real prompts directory. Consider accepting an optional `prompt_dir` parameter with a default, or making it consistent with the existing pattern.\n\n---\n\n### Finding 2: [P2] Avoid str.format on raw checkpoint text\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/domain/prompts.py:120-133\n\n`build_continuation_prompt` uses `template.format(checkpoint=checkpoint_text)`. If the checkpoint text includes `{` or `}` (e.g., code snippets, JSON, or placeholders from prior prompts), `str.format` will treat them as formatting tokens and either raise `KeyError` or alter the content. This can break continuation runs for perfectly valid checkpoints. Prefer a safe replacement (e.g., `template.replace(\"{checkpoint}\", checkpoint_text)`) or escape braces in `checkpoint_text` before formatting.\n\n---\n\n### Finding 3: [P2] Inconsistent prompt loading pattern\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/domain/prompts.py:108-136\n\nThe new `load_prompt` and `build_continuation_prompt` functions perform direct disk I/O on every call and hardcode the prompt directory logic. This deviates from the established pattern in `load_prompts`, which loads all templates into a `PromptProvider` data class at the startup boundary to avoid repeated I/O and allow for directory overrides (e.g., during testing).\n\n---\n\n### Finding 4: [P2] PromptProvider missing new checkpoint prompts\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/domain/prompts.py:20-51\n\nThe `PromptProvider` dataclass and the `load_prompts` function were not updated to include the new `checkpoint_request` and `continuation` prompts. This makes the new prompts unavailable to components that rely on the `PromptProvider` for centralized template management.\n\n---\n\n\u003c!-- fp:c802e72f1c3131c9 --\u003e\n\u003c!-- fp:1a4709275bfca65e --\u003e\n\u003c!-- fp:5fa737399c76eab6 --\u003e\n\u003c!-- fp:5df2900cdc3192b7 --\u003e\n\u003c!-- review_finding:ccf1a1469927 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T19:17:11.877674081Z","created_by":"cyou","updated_at":"2026-01-03T19:25:16.460236705Z","closed_at":"2026-01-03T19:25:16.460236705Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-d6x.7"]}
{"id":"mala-cw6h","title":"[Review] src/orchestration/run_config.py:121-131: [P2] Preserve debug_log when building RunMetadata","description":"## Review Finding\n\nThis issue was auto-created from a P2 review finding.\n\n**Source issue:** mala-cavk.5\n**Reviewer:** codex\n**Location:** src/orchestration/run_config.py:121-131\n\n## Details\n\n`RunMetadata` used to receive `debug_log` (via `RunMetadata(..., debug_log=self.debug_log)`), so the CLI/config debug log flag created a log file. After the refactor, `build_run_metadata` drops that argument and the orchestrator no longer stores `orch_config.debug_log`, so debug logging is silently disabled even when requested. Thread `debug_log` through `build_run_metadata` and pass it from `MalaOrchestrator.run()` to keep the prior behavior.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-01T22:55:46.494201629Z","created_by":"cyou","updated_at":"2026-01-02T01:05:24.852730144Z","closed_at":"2026-01-02T01:05:24.852730144Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_finding:d791f6300633","source:mala-cavk.5"],"dependencies":[{"issue_id":"mala-cw6h","depends_on_id":"mala-i8ke","type":"blocks","created_at":"2026-01-01T23:33:37.599387241Z","created_by":"daemon"}]}
{"id":"mala-cwzb","title":"Add epic_verify_lock_timeout_seconds to mala.yaml","description":"Currently EPIC_VERIFY_LOCK_TIMEOUT_SECONDS=300 is hardcoded in epic_verifier.py. Should be configurable in mala.yaml epic verification config.","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-10T21:25:01.354747024Z","created_by":"cyou","updated_at":"2026-01-11T00:18:22.934227576Z","closed_at":"2026-01-11T00:18:22.934227576Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-cwzb","depends_on_id":"mala-ejeq","type":"blocks","created_at":"2026-01-10T21:26:48.8315788Z","created_by":"cyou"}]}
{"id":"mala-cy0b","title":"Add comprehensive logging for deadlock detection debugging","description":"## Problem\n\nDuring a real deadlock between agents mala-skxg.2 and mala-skxg.13, the deadlock detection system failed to trigger. Diagnosis was impossible due to insufficient logging across the codebase.\n\n### Gaps Identified\n1. Whether PreToolUse hooks were being called\n2. Whether lock events were being emitted to DeadlockMonitor\n3. Whether WaitForGraph state was being updated correctly\n4. Whether cycle detection was running but not finding cycles\n5. Agent lifecycle state transitions\n6. Session execution flow\n7. Gate/review coordination\n\n## Solution\n\nAdd ~125 LOC of structured logging across 13 files to answer key debugging questions:\n- Is the system active? (config/wiring logs)\n- Are events flowing? (hook emission + monitor ingestion)\n- What changed in the graph? Were there anomalies? (graph mutations + invariant violations)\n- Did resolution succeed or fail? (resolution path)\n\n## Implementation\n\nSee full plan: `plans/deadlock-logging-plan.md`\n\n### Key Design Decisions\n- Use `AgentLoggerAdapter` for agent_id propagation (not contextvars)\n- Keep high-frequency events at DEBUG level\n- Add WARNING logs for invariant violations and anomalies\n- Add INFO logs for configuration and lifecycle boundaries\n\n### Files to Modify (13 total)\n\n**Phase 1-4: Critical Path**\n- `src/orchestration/orchestrator.py` - Config + resolution logging (+12 LOC)\n- `src/infra/agent_runtime.py` - Hook wiring logs (+8 LOC)\n- `src/infra/hooks/deadlock.py` - Event emission + multi-wait warning (+10 LOC)\n- `src/domain/deadlock.py` - Ingestion + invariants + graph state (+25 LOC)\n\n**Phase 5: Lock Operations**\n- `src/infra/tools/locking.py` - Contention + timeout logging (+10 LOC)\n\n**Phase 6: Lifecycle**\n- `src/domain/lifecycle.py` - State transition logs (+12 LOC)\n\n**Phase 7: Pipeline**\n- `src/pipeline/issue_execution_coordinator.py` - Task tracking (+8 LOC)\n- `src/pipeline/run_coordinator.py` - Lock acquisition logs (+8 LOC)\n- `src/pipeline/gate_runner.py` - Gate check logs (+6 LOC)\n- `src/pipeline/review_runner.py` - Review lifecycle logs (+6 LOC)\n\n**Phase 8: Orchestration \u0026 Infra**\n- `src/orchestration/factory.py` - Config logs (+6 LOC)\n- `src/infra/git_utils.py` - Baseline resolution logs (+6 LOC)\n- `src/infra/clients/cerberus_review.py` - Review outcome logs (+8 LOC)\n\n## Expected Debugging Workflow After Implementation\n\n1. Was detection enabled? → `grep \"Deadlock detection\"`\n2. Were hooks wired? → `grep \"Wiring deadlock monitor\"`\n3. Were events emitted? → `grep \"Lock event emitted\"`\n4. Were events received? → `grep \"Event received\"`\n5. Were there overwrites? → `grep \"Wait edge overwritten\"`\n6. Were there invariant violations? → `grep \"Invariant:\"`\n7. Was cycle detected? → `grep \"Cycle detected\"`\n8. Was resolution attempted? → `grep \"Deadlock resolution\"`\n9. Did resolution succeed? → `grep \"Victim killed\"\" or `grep \"resolution failed\"`\n\n## Testing\n\n- Unit test for `AgentLoggerAdapter` (if centralized)\n\n## Rollback\n\n1. Disable via log level (set to WARNING to suppress DEBUG/INFO)\n2. git revert of logging commits\n3. Per-module rollback (phases are independent)","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-04T03:13:26.73785268Z","created_by":"cyou","updated_at":"2026-01-04T03:48:11.349641234Z","closed_at":"2026-01-04T03:48:11.349641234Z","close_reason":"Closed","labels":["deadlock","logging","observability"]}
{"id":"mala-cygs","title":"Trigger tests use asyncio.run() instead of pytest-asyncio","description":"In tests/unit/pipeline/test_trigger_execution.py (lines L846-1146), the unit tests call asyncio.run(...) directly.\n\nIn pytest suites using pytest-asyncio (or any fixture/plugin with an event loop), this fails with:\n```\nRuntimeError: asyncio.run() cannot be called from a running event loop\n```\n\n## Fix\nMark tests as `async def` and use `await coordinator.check_epic_closure(...)` with pytest.mark.asyncio, matching the existing async testing approach in this repo.\n\n## Context\nBlocked mala-smaj.10 from passing review.","status":"closed","priority":1,"issue_type":"bug","created_at":"2026-01-08T21:02:46.860009244Z","created_by":"cyou","updated_at":"2026-01-08T21:07:29.464975396Z","closed_at":"2026-01-08T21:07:29.464975396Z","close_reason":"Already fixed in commit 7bcb7b6 (post-review fix, just not re-reviewed)"}
{"id":"mala-d12n","title":"[P2] Avoid non-strict xfail on critical E2E lock tests","description":"## Review Finding\n\n**Priority:** P2\n**Trigger:** run_end\n**Baseline:** 45428cf2..86160730\n**Location:** tests/e2e/test_lock_sdk.py:219-227\n\n---\n\nThese tests validate core lock ownership/release and stop-hook cleanup behavior, but they’re now marked `xfail(strict=False)`. This combination can mask regressions:\n- If the implementation breaks and the test starts failing, CI will still report an expected failure.\n- If the test actually passes, `strict=False` treats XPASS as non-failing, so you lose signal that the test is stable again.\n\nIf the intent is to quarantine flakiness, prefer `strict=True` (so XPASS alerts you) and/or a narrower skip/xfail condition tied to a real environment marker (e.g., only xfail under known flaky CI configs), rather than a blanket “LLM may not reliably execute shell scripts verbatim.”","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-11T07:20:32.237481372Z","created_by":"cyou","updated_at":"2026-01-11T14:55:45.294112452Z","closed_at":"2026-01-11T14:55:45.294112452Z","close_reason":"Closed","labels":["auto_generated","cumulative-review","fp:ede65d07c9ca868c","review_finding","trigger:run_end"]}
{"id":"mala-d1hs","title":"[Review] src/orchestration/gate_metadata.py:85-88: [P3] Align ignored setup command names with extracted tool names","description":"## Review Finding\n\nThis issue was auto-created from a P3 review finding.\n\n**Source issue:** mala-fdp1.11\n**Reviewer:** codex\n**Location:** src/orchestration/gate_metadata.py:85-88\n\n## Details\n\n`failed_commands` now stores extracted tool names (e.g., `uv sync` → `uv`), but metadata filtering still checks for `uv sync`. This means setup failures will leak into `commands_failed` even though they’re intended to be ignored. Update the ignore list (or filter by CommandKind) to match the new representation.","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-02T15:51:09.52520912Z","created_by":"cyou","updated_at":"2026-01-02T17:09:23.379371769Z","closed_at":"2026-01-02T17:09:23.379371769Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_finding:132509cf0bb3","source:mala-fdp1.11"]}
{"id":"mala-d3t","title":"Update orchestrator coverage_threshold type","description":"Context:\n- `MalaOrchestrator.__init__` accepts `coverage_threshold: float = 85.0`\n- Need to update type to `float | None = None` to match CLI\n\nScope:\n- In: Change `coverage_threshold` parameter type to `float | None = None`\n- Out: Any logic changes (just type signature update)\n\nAcceptance Criteria:\n- Orchestrator accepts None for coverage_threshold\n- Type hints updated\n- Passes value through to ValidationSpec correctly\n\nTest Plan:\n- Verify orchestrator instantiation with None works\n- Verify orchestrator instantiation with float works\n\nNotes for Agent:\n- Simple signature change, no logic changes needed\n- Primary file: src/orchestrator.py","status":"tombstone","priority":1,"issue_type":"task","created_at":"2025-12-27T18:14:32.921915115Z","updated_at":"2025-12-27T18:20:40.635021365Z","labels":["orchestrator"],"dependencies":[{"issue_id":"mala-d3t","depends_on_id":"mala-kwx","type":"blocks","created_at":"2025-12-27T18:15:02.051461062Z","created_by":"daemon"}],"deleted_at":"2025-12-27T18:20:40.635021365Z","deleted_by":"daemon","delete_reason":"delete","original_type":"task"}
{"id":"mala-d6x","title":"Context exhaustion handling","description":"**Epic: Context Exhaustion Handling**\n\nHandle the 200K token context limit gracefully by treating context exhaustion as a session boundary, not a process boundary.\n\n**Key Insight**: Instead of creating continuation issues and waiting for them to be picked up, immediately start a new SDK session with checkpoint context in the same process.\n\n**Flow**:\n```\nSession 1: Working on task...\n  │\n  ├─ Hit 90% context threshold\n  ├─ Inject checkpoint prompt\n  ├─ Agent outputs \u003ccheckpoint\u003e block\n  ├─ Parse checkpoint (in-memory only)\n  │\n  └─ Immediately start Session 2\n       ├─ Initial prompt = checkpoint context\n       ├─ Same process, same locks, same repo state\n       └─ Continue working...\n```\n\n**Benefits over continuation-issue approach**:\n- No latency: No waiting for orchestrator to pick up continuation\n- No lock churn: Same process keeps locks\n- No persistence overhead: No checkpoint files, no issue creation\n- Simpler implementation: ~50% fewer tasks\n\n**File Impact**:\n- src/domain/lifecycle.py - ContextUsage dataclass, context_usage field\n- src/orchestration/types.py - threshold/limit config\n- src/pipeline/agent_session_runner.py - restart loop, checkpoint handling\n- src/domain/prompts.py - checkpoint extraction, continuation prompt\n- src/prompts/checkpoint_request.md - NEW: checkpoint prompt\n- src/prompts/continuation.md - NEW: continuation template\n\n**Acceptance Criteria**:\n- Context exhaustion is detected before agent crashes\n- Checkpoint is captured from agent before restart\n- New session starts immediately with continuation context\n- Same process keeps locks, no work is lost\n- Works across multiple restarts (continuation_count tracked)\n\n**Plan**: plans/2026-01-03-context-exhaustion-handling.md\n**Spec**: docs/2026-01-03-context-exhaustion-skeleton.md","status":"closed","priority":1,"issue_type":"epic","created_at":"2025-12-26T00:56:35.644475789Z","updated_at":"2026-01-03T22:02:58.939328445Z","closed_at":"2026-01-03T22:02:58.939328445Z","close_reason":"Closed"}
{"id":"mala-d6x.1","title":"Detect context exhaustion in agent runs","description":"Detect when an agent run exhausts context or returns incomplete output. Emit a structured reason (e.g., context_exhausted) so the orchestrator can trigger follow-up handling without treating it as a generic failure.","status":"tombstone","priority":2,"issue_type":"task","created_at":"2025-12-26T01:14:09.957270657Z","updated_at":"2025-12-26T15:33:44.086569066Z","deleted_at":"2025-12-26T15:33:44.086569066Z","deleted_by":"batch delete","delete_reason":"batch delete","original_type":"task"}
{"id":"mala-d6x.10","title":"[T006] Integration tests and cleanup","description":"**Goal**: Verify end-to-end flow with comprehensive tests, run quality gates\n\n**Context**:\n- All implementation tasks complete; this task adds thorough test coverage\n- Tests use mocked SDK to simulate context pressure scenarios\n- Must pass ruff, ty, and existing test suite\n\n**Scope**:\n- In: Integration tests for restart flow, edge case tests, quality gate checks\n- Out: Production code changes (all done in T001-T005)\n\n**Changes**:\n- `tests/test_lifecycle.py` — Exists — Add tests for ContextUsage dataclass: pressure_ratio at 0%, 50%, 100%, edge cases (limit=0)\n- `tests/test_agent_session_runner.py` — Exists — Add integration tests: 90% threshold triggers restart, multiple restarts, SDK missing usage field, checkpoint without tags, fresh lifecycle per restart\n- `tests/test_domain_prompts.py` — Exists — Add tests for load_prompt, build_continuation_prompt, extract_checkpoint\n\n**Test Cases**:\n1. Session hits 90% → ContextPressureError → checkpoint → restart with continuation\n2. Multiple restarts in sequence (continuation_count = 1, 2, 3...)\n3. SDK missing usage field → warning logged, session continues without tracking\n4. Checkpoint without \u003ccheckpoint\u003e tags → full response used as fallback\n5. Fresh lifecycle created each restart (no \"Cannot start from state X\" error)\n6. pressure_ratio with limit=0 → should not raise ZeroDivisionError\n7. load_prompt loads markdown files correctly\n8. extract_checkpoint handles code block wrappers\n\n**Acceptance Criteria**:\n- All new tests pass\n- All existing tests pass\n- uvx ruff check . passes (no lint errors)\n- uvx ty check passes (no type errors)\n- Coverage threshold maintained (85%)\n\n**Verification**:\n- uv run pytest -m \"unit or integration\"\n- uvx ruff check . \u0026\u0026 uvx ruff format --check .\n- uvx ty check\n\n**Rollback**:\n- Revert test additions (no production impact)\n\n**Notes for Agent**:\n- Tests are in flat tests/ directory, not subdirectories\n- Use pytest fixtures for mocked SDK client\n- Mock ResultMessage.usage to control token counts\n- Existing test files are large - add new test classes/functions at end","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T18:59:55.748323305Z","created_by":"cyou","updated_at":"2026-01-03T21:54:42.526639628Z","closed_at":"2026-01-03T21:54:42.526639628Z","close_reason":"Closed","labels":["tests,quality-gate"],"dependencies":[{"issue_id":"mala-d6x.10","depends_on_id":"mala-d6x","type":"parent-child","created_at":"2026-01-03T18:59:55.748798493Z","created_by":"daemon"},{"issue_id":"mala-d6x.10","depends_on_id":"mala-d6x.9","type":"blocks","created_at":"2026-01-03T19:00:26.860937136Z","created_by":"daemon"}]}
{"id":"mala-d6x.11","title":"[Review] 3 non-blocking findings from mala-d6x.9","description":"## Review Findings\n\nThis issue consolidates 3 non-blocking findings from code review.\n\n**Source issue:** mala-d6x.9\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Timeout resets on each context restart, allowing unbounded session duration\n\n**Priority:** P2\n**Reviewer:** claude\n**Location:** src/pipeline/agent_session_runner.py:1914-1918\n\nThe `asyncio.timeout(self.config.timeout_seconds)` is inside the `while True` loop, so each iteration gets a fresh timeout. If the agent repeatedly hits the context pressure threshold (e.g., every 45 minutes in a 1-hour timeout), the session can run indefinitely. Consider using `start_time` to enforce a global session timeout across all restarts.\n\n---\n\n### Finding 2: [P2] Checkpoint fetch has no timeout protection\n\n**Priority:** P2\n**Reviewer:** claude\n**Location:** src/pipeline/agent_session_runner.py:1921-1927\n\nThe `_get_checkpoint_from_agent()` call at line 1923-1927 happens after exiting the `asyncio.timeout` context manager. If the checkpoint query hangs (e.g., SDK connection issue), the session will block indefinitely. The checkpoint fetch should have its own timeout, or be wrapped in the same timeout context.\n\n---\n\n### Finding 3: [P2] Enforce overall timeout across restarts\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/pipeline/agent_session_runner.py:1912-1915\n\nThe timeout context is created inside the restart loop, so each ContextPressureError grants a fresh full timeout. This regresses the previous behavior (one timeout per run_session) and can let a session run far longer than config.timeout_seconds if multiple restarts occur. Consider moving the timeout outside the loop or tracking remaining time and using a shortened timeout per iteration.\n\n---\n\n\u003c!-- fp:603025c2ad1d43dc --\u003e\n\u003c!-- fp:8afcf91fe942be7b --\u003e\n\u003c!-- fp:eb84f32a06e5601c --\u003e\n\u003c!-- review_finding:ad252efaa972 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T21:40:46.096147107Z","created_by":"cyou","updated_at":"2026-01-03T21:46:34.252110945Z","closed_at":"2026-01-03T21:46:34.252110945Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-d6x.9"],"dependencies":[{"issue_id":"mala-d6x.11","depends_on_id":"mala-d6x","type":"parent-child","created_at":"2026-01-03T21:40:46.096559336Z","created_by":"daemon"}]}
{"id":"mala-d6x.12","title":"[Review] 2 non-blocking findings from mala-d6x.11","description":"## Review Findings\n\nThis issue consolidates 2 non-blocking findings from code review.\n\n**Source issue:** mala-d6x.11\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Early timeout raises uncaught TimeoutError, bypasses lifecycle cleanup\n\n**Priority:** P2\n**Reviewer:** claude\n**Location:** src/pipeline/agent_session_runner.py:1911-1912\n\nThe `TimeoutError` raised at line 1912 (when `remaining \u003c= 0` at loop start) is outside the try block, so it propagates uncaught rather than being handled by the `except TimeoutError` at line 1968. This means `state.lifecycle.on_timeout()` won't be called, bypassing any cleanup or state finalization. On the first iteration, this also means `state` was never initialized. Consider either moving the check inside the try block after `_initialize_session`, or handling this case separately to ensure proper cleanup.\n\n---\n\n### Finding 2: [P2] Bound checkpoint timeout by remaining session time\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/pipeline/agent_session_runner.py:1931-1939\n\nThe dedicated 30s timeout for checkpoint fetch can overrun the overall session timeout. If `remaining` is small (e.g., 5s), this block can still wait up to 30s before the next loop iteration checks elapsed time, so the session can exceed `config.timeout_seconds` by a large margin. Recompute `remaining` before the checkpoint fetch and use `min(remaining, checkpoint_timeout_seconds)`, or skip fetch when `remaining \u003c= 0` to keep the global timeout strict.\n\n---\n\n\u003c!-- fp:c993b5e57545cc2a --\u003e\n\u003c!-- fp:2082759ef9833d10 --\u003e\n\u003c!-- review_finding:bb9d80c2e124 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T21:46:34.281827613Z","created_by":"cyou","updated_at":"2026-01-03T21:48:47.749387093Z","closed_at":"2026-01-03T21:48:47.749387093Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-d6x.11"],"dependencies":[{"issue_id":"mala-d6x.12","depends_on_id":"mala-d6x","type":"parent-child","created_at":"2026-01-03T21:46:34.282255382Z","created_by":"daemon"}]}
{"id":"mala-d6x.13","title":"[Remediation] Same process keeps locks, no work is lost","description":"## Context\nThis issue was auto-created by epic verification for epic `mala-d6x`.\n\n## Epic Description / Acceptance Criteria\nTitle: Context exhaustion handling\n\n**Epic: Context Exhaustion Handling**\n\nHandle the 200K token context limit gracefully by treating context exhaustion as a session boundary, not a process boundary.\n\n**Key Insight**: Instead of creating continuation issues and waiting for them to be picked up, immediately start a new SDK session with checkpoint context in the same process.\n\n**Flow**:\n```\nSession 1: Working on task...\n  │\n  ├─ Hit 90% context threshold\n  ├─ Inject checkpoint prompt\n  ├─ Agent outputs \u003ccheckpoint\u003e block\n  ├─ Parse checkpoint (in-memory only)\n  │\n  └─ Immediately start Session 2\n       ├─ Initial prompt = checkpoint context\n       ├─ Same process, same locks, same repo state\n       └─ Continue working...\n```\n\n**Benefits over continuation-issue approach**:\n- No latency: No waiting for orchestrator to pick up continuation\n- No lock churn: Same process keeps locks\n- No persistence overhead: No checkpoint files, no issue creation\n- Simpler implementation: ~50% fewer tasks\n\n**File Impact**:\n- src/domain/lifecycle.py - ContextUsage dataclass, context_usage field\n- src/orchestration/types.py - threshold/limit config\n- src/pipeline/agent_session_runner.py - restart loop, checkpoint handling\n- src/domain/prompts.py - checkpoint extraction, continuation prompt\n- src/prompts/checkpoint_request.md - NEW: checkpoint prompt\n- src/prompts/continuation.md - NEW: continuation template\n\n**Acceptance Criteria**:\n- Context exhaustion is detected before agent crashes\n- Checkpoint is captured from agent before restart\n- New session starts immediately with continuation context\n- Same process keeps locks, no work is lost\n- Works across multiple restarts (continuation_count tracked)\n\n**Plan**: plans/2026-01-03-context-exhaustion-handling.md\n**Spec**: docs/2026-01-03-context-exhaustion-skeleton.md\n\n## Commit Scope\n- Range hint: 24c08ad65921976e42fbf28d512a44e71c8a445a^..6bc0fd25989f431ae9f27cba7cbfadec13220ef1\n- Commits:\n- 6bc0fd25989f431ae9f27cba7cbfadec13220ef1 bd-mala-d6x.10: Add integration tests for session restart loop\n- fc282ab0d2a31f18230c5db5e2dbb0ff0ea637b9 bd-mala-d6x.12: Fix timeout handling and checkpoint timeout bounding\n- bc28305dcd1eafeb238753b75dd3bd1c128fb5cb bd-mala-d6x.11: Enforce overall session timeout across context restarts\n- 25b99f63971f12c6b83c520c90bcd5f954181edf bd-mala-d6x.9: Pass continuation prompt to lifecycle loop after context restart\n- 979b05fc2f9616e3f364af6a62130ff8ce6fdefa bd-mala-d6x.9: Implement session restart loop for context exhaustion\n- 909a1d0e18b67f2e1c54582daac658473ce7aab5 bd-mala-d6x.6: Fix deadlock handling plan review issues\n- f54004523c65f4b168cdbd2d954e20f8e66951c1 bd-mala-d6x.6: Update deadlock handling plan with implementation details\n- 3de860f4ca899c3c519d5dfc6c28f2b25736ea6d bd-mala-d6x.8: Fix fallback code block stripping in extract_checkpoint\n- 89798a13695a14394c97c4b327134a7312e4dc89 bd-mala-d6x.8: Fix fallback code block stripping in extract_checkpoint\n- 5af0f5e085421531dd1ca6f82f08ab6ed86506f4 bd-mala-d6x.5: Fix test failures after signature changes\n- 8ab77edf3fe6b1a01ef10879ded1f035289e5d94 bd-mala-d6x.8: Fix extract_checkpoint nested tags and docstrings\n- a00994d879ad25745b12dfe79d9b93109fc44b01 bd-mala-d6x.8: Fix greedy regex in extract_checkpoint\n- eddd52f333fa79f51b43f8e38a4c73af01e80f87 bd-mala-d6x.8: Add extract_checkpoint function for parsing agent response\n- 6aad9182c9a4e191f7f3071e6ba6da318f7371f8 bd-mala-d6x.7: Add checkpoint prompt files\n- 1f2221da78840ebbb044f7714930cd6c6abbcc33 bd-mala-d6x.5: Fix pressure_ratio to not double-count cache_read_tokens\n- 5ac015945f3739d751c19c3eb76c6953155a0188 bd-mala-d6x.5: Fix pressure_ratio to include all token types\n- 24c08ad65921976e42fbf28d512a44e71c8a445a bd-mala-d6x.5: Add ContextUsage dataclass and config fields\n\n## Child Issues\n- mala-d6x.10\n- mala-d6x.11\n- mala-d6x.12\n- mala-d6x.5\n- mala-d6x.6\n- mala-d6x.7\n- mala-d6x.8\n- mala-d6x.9\n\n## Unmet Criterion\nSame process keeps locks, no work is lost\n\n## Evidence\nIn src/pipeline/agent_session_runner.py:817, _initialize_session() generates a new agent_id = f\"{input.issue_id}-{uuid.uuid4().hex[:8]}\" on each call. Since this is called for every context restart in the run_session() loop (line 1919), each restart gets a different agent_id and AGENT_ID environment variable, breaking lock continuity.\n\n## Priority\nP1 (blocking)\n\n## Resolution\nAddress this criterion to unblock epic closure. When complete, close this issue.\n","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-03T21:57:07.197781636Z","created_by":"cyou","updated_at":"2026-01-03T22:01:03.590408806Z","closed_at":"2026-01-03T22:01:03.590408806Z","close_reason":"Closed","labels":["auto_generated","epic_remediation:mala-d6x:d2b771661435595e95a7aedfd7527c63f94317f75fd4f6842667c0ad081d8d44"],"dependencies":[{"issue_id":"mala-d6x.13","depends_on_id":"mala-d6x","type":"parent-child","created_at":"2026-01-03T21:57:07.198303694Z","created_by":"daemon"}]}
{"id":"mala-d6x.14","title":"[Review] 2 non-blocking findings from mala-d6x.10","description":"## Review Findings\n\nThis issue consolidates 2 non-blocking findings from code review.\n\n**Source issue:** mala-d6x.10\n**Highest priority:** P3\n\n---\n\n### Finding 1: [P3] Unused `limit` parameter in make_high_pressure_result\n\n**Priority:** P3\n**Reviewer:** claude\n**Location:** tests/test_agent_session_runner.py:4326-4328\n\nThe `limit` parameter is declared but never used in the function body. While the default value of `200000` happens to match the `context_limit=200_000` used in tests, the parameter serves no purpose and could mislead readers into thinking it affects the result.\n\n```python\ndef make_high_pressure_result(\n    self, session_id: str, input_tokens: int = 180000, limit: int = 200000\n) -\u003e ResultMessage:\n```\n\n---\n\n### Finding 2: [P3] Docstring claims continuation_count verification but test doesn't assert it\n\n**Priority:** P3\n**Reviewer:** claude\n**Location:** tests/test_agent_session_runner.py:4464-4467\n\nThe docstring states \"Verifies that continuation_count increments on each restart\" but the test only checks that checkpoint content is passed correctly (\"checkpoint-2\" in the final prompt). If you want to verify `continuation_count`, add an assertion for it; otherwise update the docstring to reflect what's actually tested.\n\n---\n\n\u003c!-- fp:11b78966c68f5ab5 --\u003e\n\u003c!-- fp:bd1468dbac8732e3 --\u003e\n\u003c!-- review_finding:12ebaf44ec17 --\u003e","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-03T22:02:58.962055708Z","created_by":"cyou","updated_at":"2026-01-03T22:04:56.949009982Z","closed_at":"2026-01-03T22:04:56.949009982Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-d6x.10"],"dependencies":[{"issue_id":"mala-d6x.14","depends_on_id":"mala-d6x","type":"parent-child","created_at":"2026-01-03T22:02:58.962447226Z","created_by":"daemon"}]}
{"id":"mala-d6x.2","title":"Persist progress snapshot on context exhaustion","description":"When context exhaustion is detected, capture a progress snapshot (modified files, locks held, agent summary, and any partial results) in a durable location so continuation can resume safely.","status":"tombstone","priority":2,"issue_type":"task","created_at":"2025-12-26T01:14:15.412064244Z","updated_at":"2025-12-26T15:33:44.086569066Z","deleted_at":"2025-12-26T15:33:44.086569066Z","deleted_by":"batch delete","delete_reason":"batch delete","original_type":"task"}
{"id":"mala-d6x.3","title":"Spawn continuation agent with prior summary","description":"After persisting a progress snapshot, spawn a continuation agent and pass a concise summary + snapshot path so it can resume work without losing context.","status":"tombstone","priority":2,"issue_type":"task","created_at":"2025-12-26T01:14:19.459225253Z","updated_at":"2025-12-26T15:33:44.086569066Z","deleted_at":"2025-12-26T15:33:44.086569066Z","deleted_by":"batch delete","delete_reason":"batch delete","original_type":"task"}
{"id":"mala-d6x.4","title":"Commit partial progress and mark needs-continuation","description":"Provide a fallback path to commit partial work on context exhaustion and mark the issue as needs-continuation (status/notes) so a future run can pick it up intentionally.","status":"tombstone","priority":2,"issue_type":"task","created_at":"2025-12-26T01:14:24.562096252Z","updated_at":"2025-12-26T15:33:44.086569066Z","deleted_at":"2025-12-26T15:33:44.086569066Z","deleted_by":"batch delete","delete_reason":"batch delete","original_type":"task"}
{"id":"mala-d6x.5","title":"[T001] Add ContextUsage dataclass and config fields","description":"**Goal**: Define data structures for token tracking and thresholds\n\n**Context**:\n- The SDK provides cumulative input_tokens in ResultMessage.usage\n- We need to track context usage to detect when approaching the 200K limit\n- A 90% threshold (180K) triggers session restart to preserve work\n\n**Scope**:\n- In: ContextUsage dataclass, pressure_ratio method, config fields\n- Out: Token extraction logic, checkpoint handling\n\n**Changes**:\n- `src/domain/lifecycle.py` — Exists — Add ContextUsage dataclass with input_tokens, output_tokens, cache_read_tokens; add pressure_ratio(limit: int) -\u003e float method; add context_usage field to LifecycleContext\n- `src/orchestration/types.py` — Exists — Add context_restart_threshold: float = 0.90 and context_limit: int = 200_000 to OrchestratorConfig\n\n**Acceptance Criteria**:\n- ContextUsage dataclass exists with three token fields\n- pressure_ratio() returns correct ratio (e.g., 90000/200000 = 0.45)\n- LifecycleContext has context_usage field with default\n- OrchestratorConfig has threshold (0.90) and limit (200000) defaults\n\n**Verification**:\n- Unit test pressure_ratio() at 0%, 50%, 100%\n- Unit test config defaults\n- Run: uvx ty check \u0026\u0026 uvx ruff check .\n\n**Rollback**:\n- Revert changes to lifecycle.py and types.py\n\n**Notes for Agent**:\n- pressure_ratio should handle division safely (limit=0 edge case)\n- Use field(default_factory=ContextUsage) for the context_usage field","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-03T18:58:24.01884241Z","created_by":"cyou","updated_at":"2026-01-03T19:55:27.546058407Z","closed_at":"2026-01-03T19:55:27.546058407Z","close_reason":"Closed","labels":["domain,lifecycle"],"dependencies":[{"issue_id":"mala-d6x.5","depends_on_id":"mala-d6x","type":"parent-child","created_at":"2026-01-03T18:58:24.025784959Z","created_by":"daemon"}]}
{"id":"mala-d6x.6","title":"[T002] Token usage extraction and context pressure detection","description":"**Goal**: Extract token usage from SDK and raise exception on threshold breach\n\n**Context**:\n- SDK ResultMessage includes usage field with cumulative token counts\n- When context hits 90% (180K tokens), we need to signal restart\n- Using exception-based signaling (ContextPressureError) to cleanly exit the stream processing\n\n**Scope**:\n- In: ContextPressureError exception, token extraction in _process_message_stream\n- Out: Checkpoint handling, restart loop logic\n\n**Changes**:\n- `src/pipeline/agent_session_runner.py` — Exists — Add ContextPressureError exception class with session_id and usage fields; in _process_message_stream(), after ResultMessage: extract usage.input_tokens/output_tokens/cache_read_input_tokens, update lifecycle_ctx.context_usage, check threshold, raise ContextPressureError if exceeded\n\n**Acceptance Criteria**:\n- ContextPressureError is defined with session_id and usage fields\n- Token usage extracted from ResultMessage.usage when available\n- Warning logged if SDK doesn't provide usage (graceful fallback)\n- Exception raised when pressure_ratio \u003e= threshold\n- Exception includes session_id needed for checkpoint query\n\n**Verification**:\n- Unit test: mocked ResultMessage with usage → verify extraction\n- Unit test: ResultMessage without usage → verify warning logged, no crash\n- Unit test: at exactly 90% threshold → verify ContextPressureError raised\n- Run: uvx ty check \u0026\u0026 uvx ruff check .\n\n**Rollback**:\n- Revert changes to agent_session_runner.py\n\n**Notes for Agent**:\n- Use getattr for safe attribute access on SDK types\n- Sentinel value -1 for context_usage.input_tokens when tracking disabled\n- ContextPressureError should inherit from Exception, not BaseException","notes":"Quality gate failed: External review failed: src/pipeline/agent_session_runner.py:1: [P0] PR content does not match assigned task [T002]; src/core/tool_names.py:1: [P1] Redundant and buggy implementation of tool_names.py; src/core/tool_name_extractor.py:1: [P1] Incomplete refactor: stale file remaining\nLog: /home/cyou/.claude/projects/-home-cyou-mala/a4d73708-a5a5-4146-ada9-81a34b6e89d4.jsonl","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-03T18:59:11.291088969Z","created_by":"cyou","updated_at":"2026-01-03T21:25:12.372403704Z","closed_at":"2026-01-03T21:25:12.372403704Z","close_reason":"Closed","labels":["needs-followup","pipeline,agent-session"],"dependencies":[{"issue_id":"mala-d6x.6","depends_on_id":"mala-d6x","type":"parent-child","created_at":"2026-01-03T18:59:11.291572137Z","created_by":"daemon"},{"issue_id":"mala-d6x.6","depends_on_id":"mala-d6x.5","type":"blocks","created_at":"2026-01-03T19:00:19.454412001Z","created_by":"daemon"}]}
{"id":"mala-d6x.7","title":"[T003] Add checkpoint prompt files","description":"**Goal**: Define prompts for checkpoint generation and continuation as markdown files\n\n**Context**:\n- When context pressure is detected, we ask the agent to output a structured checkpoint\n- The continuation prompt injects the checkpoint into a new session\n- Prompts are loaded from src/prompts/*.md using existing patterns\n\n**Scope**:\n- In: Two new markdown prompt files, load_prompt function, build_continuation_prompt function\n- Out: Checkpoint parsing logic (separate task)\n\n**Changes**:\n- `src/prompts/checkpoint_request.md` — New — Prompt asking agent to output \u003ccheckpoint\u003e block with Goal, Completed Work, Remaining Tasks, Do Not Redo, Key Decisions sections\n- `src/prompts/continuation.md` — New — Template with {checkpoint} placeholder for continuation context\n- `src/domain/prompts.py` — Exists — Add load_prompt(name: str) -\u003e str to load from src/prompts/{name}.md; add build_continuation_prompt(checkpoint_text: str) -\u003e str\n\n**Acceptance Criteria**:\n- checkpoint_request.md instructs agent to output structured \u003ccheckpoint\u003e block\n- continuation.md contains {checkpoint} placeholder\n- load_prompt(\"checkpoint_request\") returns the markdown content\n- build_continuation_prompt formats checkpoint into continuation template\n\n**Verification**:\n- Unit test: load_prompt loads markdown files correctly\n- Unit test: build_continuation_prompt formats checkpoint into template\n- Verify prompt files exist with correct content\n- Run: uvx ty check \u0026\u0026 uvx ruff check .\n\n**Rollback**:\n- Delete new .md files, revert prompts.py changes\n\n**Notes for Agent**:\n- Follow existing prompt file patterns in src/prompts/\n- load_prompt should use Path(__file__).parent / \"prompts\" / f\"{name}.md\" or similar\n- Consider using importlib.resources for package-safe loading","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-03T18:59:11.349114425Z","created_by":"cyou","updated_at":"2026-01-03T19:17:11.847996274Z","closed_at":"2026-01-03T19:17:11.847996274Z","close_reason":"Closed","labels":["domain,prompts"],"dependencies":[{"issue_id":"mala-d6x.7","depends_on_id":"mala-d6x","type":"parent-child","created_at":"2026-01-03T18:59:11.349566793Z","created_by":"daemon"}]}
{"id":"mala-d6x.8","title":"[T004] Checkpoint parsing","description":"**Goal**: Extract checkpoint block from agent response text\n\n**Context**:\n- Agent outputs checkpoint within \u003ccheckpoint\u003e...\u003c/checkpoint\u003e tags\n- May be wrapped in markdown code blocks (```xml, ```markdown, etc.)\n- If tags not found, use full response as fallback\n\n**Scope**:\n- In: extract_checkpoint function in prompts.py\n- Out: Prompt files (T003), session restart logic (T005)\n\n**Changes**:\n- `src/domain/prompts.py` — Exists — Add extract_checkpoint(text: str) -\u003e str that: strips markdown code block wrappers, extracts content between \u003ccheckpoint\u003e and \u003c/checkpoint\u003e tags, returns full text as fallback if no tags found\n\n**Acceptance Criteria**:\n- Well-formed \u003ccheckpoint\u003econtent\u003c/checkpoint\u003e → returns \"content\"\n- Checkpoint wrapped in ```xml block → strips wrapper, returns content\n- No checkpoint tags → returns full response text as fallback\n- Handles nested tags gracefully (returns outermost checkpoint content)\n\n**Verification**:\n- Unit test: extract well-formed checkpoint block\n- Unit test: extract checkpoint wrapped in ```xml code block\n- Unit test: fallback to full text when no tags found\n- Unit test: handle edge cases (empty, whitespace-only, malformed)\n- Run: uvx ty check \u0026\u0026 uvx ruff check .\n\n**Rollback**:\n- Revert changes to prompts.py\n\n**Notes for Agent**:\n- Use regex for tag extraction: re.search(r'\u003ccheckpoint\u003e(.*?)\u003c/checkpoint\u003e', text, re.DOTALL)\n- Strip code block patterns: ^\\s*```\\w*\\s* and \\s*```\\s*$\n- Preserve whitespace inside checkpoint content","notes":"Quality gate failed: External review failed: src/domain/prompts.py:143: [P1] Fallback code block stripping is too restrictive\nLog: /home/cyou/.claude/projects/-home-cyou-mala/59c15637-8469-426e-8c6d-174b67ef584a.jsonl","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-03T18:59:11.408170166Z","created_by":"cyou","updated_at":"2026-01-03T20:27:52.621942858Z","closed_at":"2026-01-03T20:27:52.621942858Z","close_reason":"Closed","labels":["domain,prompts","needs-followup"],"dependencies":[{"issue_id":"mala-d6x.8","depends_on_id":"mala-d6x","type":"parent-child","created_at":"2026-01-03T18:59:11.408580774Z","created_by":"daemon"},{"issue_id":"mala-d6x.8","depends_on_id":"mala-d6x.7","type":"blocks","created_at":"2026-01-03T19:00:20.754251584Z","created_by":"daemon"}]}
{"id":"mala-d6x.9","title":"[T005] Session restart loop in run_session()","description":"**Goal**: Implement restart loop with fresh lifecycle per iteration and checkpoint handling\n\n**Context**:\n- This is the core integration task that ties everything together\n- run_session() wraps execution in a while loop that catches ContextPressureError\n- On exception: get checkpoint from agent, build continuation prompt, restart with fresh lifecycle\n- Fresh LifecycleContext + ImplementerLifecycle created each iteration (lifecycle.start() requires INITIAL state)\n\n**Scope**:\n- In: Restart loop in run_session(), _get_checkpoint_from_agent() method, continuation prompt building\n- Out: Unit tests (covered in T006)\n\n**Changes**:\n- `src/pipeline/agent_session_runner.py` — Exists — Modify run_session(): wrap in while True loop, create fresh lifecycle_ctx and lifecycle each iteration, catch ContextPressureError, call _get_checkpoint_from_agent(), build continuation prompt, increment continuation_count, continue loop. Add _get_checkpoint_from_agent(session_id, issue_id) method that sends checkpoint_request prompt and extracts response.\n\n**Acceptance Criteria**:\n- run_session() has outer while loop that catches ContextPressureError\n- Fresh LifecycleContext and ImplementerLifecycle created each iteration\n- _get_checkpoint_from_agent() sends checkpoint prompt to current session\n- Continuation prompt built with extracted checkpoint\n- continuation_count incremented and logged on each restart\n- Normal completion returns AgentSessionOutput, exits loop\n\n**Verification**:\n- Integration test: simulate 90% threshold, verify restart occurs\n- Integration test: verify fresh lifecycle created each iteration\n- Integration test: verify continuation prompt contains checkpoint\n- Integration test: verify continuation_count increments\n- Run: uvx ty check \u0026\u0026 uvx ruff check .\n\n**Rollback**:\n- Revert changes to agent_session_runner.py\n\n**Notes for Agent**:\n- The checkpoint query uses the SAME session_id from the ContextPressureError\n- After checkpoint retrieved, next iteration starts with session_id=None (fresh SDK session)\n- Use existing sdk_client_factory pattern for checkpoint query\n- Log: f\"Session {issue_id}: context restart #{continuation_count} at {ratio:.1%}\"","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-03T18:59:55.67881109Z","created_by":"cyou","updated_at":"2026-01-03T21:40:46.065476441Z","closed_at":"2026-01-03T21:40:46.065476441Z","close_reason":"Closed","labels":["pipeline,agent-session,integration"],"dependencies":[{"issue_id":"mala-d6x.9","depends_on_id":"mala-d6x","type":"parent-child","created_at":"2026-01-03T18:59:55.685908099Z","created_by":"daemon"},{"issue_id":"mala-d6x.9","depends_on_id":"mala-d6x.5","type":"blocks","created_at":"2026-01-03T19:00:22.059632784Z","created_by":"daemon"},{"issue_id":"mala-d6x.9","depends_on_id":"mala-d6x.6","type":"blocks","created_at":"2026-01-03T19:00:23.482767485Z","created_by":"daemon"},{"issue_id":"mala-d6x.9","depends_on_id":"mala-d6x.7","type":"blocks","created_at":"2026-01-03T19:00:24.586869469Z","created_by":"daemon"},{"issue_id":"mala-d6x.9","depends_on_id":"mala-d6x.8","type":"blocks","created_at":"2026-01-03T19:00:25.942159657Z","created_by":"daemon"}]}
{"id":"mala-d6xi","title":"[Review] 4 non-blocking findings from mala-e17r.1","description":"## Review Findings\n\nThis issue consolidates 4 non-blocking findings from code review.\n\n**Source issue:** mala-e17r.1\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Remove re-exports and update all imports directly\n\n**Priority:** P2\n**Reviewer:** claude\n**Location:** src/infra/io/event_sink.py:26-33\n\nCLAUDE.md explicitly states \"No re-exports: Don't create modules that just import and re-export from elsewhere. If code moves, fix the imports.\" The `__all__` list re-exports `EventRunConfig` and `MalaEventSink` from `event_protocol.py` for backward compatibility. Instead, all 15+ files that import these from `event_sink.py` should be updated to import from `event_protocol.py` directly.\n\n---\n\n### Finding 2: [P2] Remove backward-compatibility re-export shim\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/infra/io/event_sink.py:23-33\n\nThe added `__all__` explicitly re-exports `EventRunConfig` and `MalaEventSink` for compatibility. The project’s Code Migration Rules forbid backward-compatibility shims and re-exports when moving/renaming modules. This should be fixed by updating all imports to `event_protocol` and dropping the re-export to avoid maintaining duplicate import paths.\n\n---\n\n### Finding 3: [P3] Outdated docstring in event_protocol.py\n\n**Priority:** P3\n**Reviewer:** gemini\n**Location:** src/infra/io/event_protocol.py:6\n\nThe docstring in `event_protocol.py` refers to `ConsoleEventSink` as a \"follow-up task\", but it is already implemented in the sibling `event_sink.py` file.\n\n---\n\n### Finding 4: [P3] Incorrect method count in BaseEventSink docstring\n\n**Priority:** P3\n**Reviewer:** gemini\n**Location:** src/infra/io/event_sink.py:36\n\nThe docstring for `BaseEventSink` claims there are \"all 46 methods\", but the `MalaEventSink` protocol now defines 51 methods.\n\n---\n","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T14:57:27.518804133Z","created_by":"cyou","updated_at":"2026-01-03T15:14:49.365423719Z","closed_at":"2026-01-03T15:14:49.365423719Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_findings:f3032cf73087","source:mala-e17r.1"]}
{"id":"mala-d9ph","title":"[Review] 1 non-blocking finding from mala-idx6.2","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-idx6.2\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Remove unused dataclasses.field import\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/domain/validation/spec_executor.py:18\n\n`field` is no longer used after switching to `kw_only=True`, so `uvx ruff check .` will raise F401 and likely fail CI. Dropping the unused import fixes the lint regression.\n\n---\n","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T15:01:35.402562123Z","created_by":"cyou","updated_at":"2026-01-03T15:03:24.715963695Z","closed_at":"2026-01-03T15:03:24.715963695Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_findings:80f642b3bd2d","source:mala-idx6.2"]}
{"id":"mala-da4","title":"Update CoverageConfig and pytest command in spec.py","description":"Context:\n- `CoverageConfig.min_percent` currently defaults to 85.0\n- Need to change default to None (meaning \"no decrease\" mode)\n- When `coverage_threshold is None`, must add `--cov-fail-under=0` to pytest command to override pyproject.toml\n\nScope:\n- In: Change `CoverageConfig.min_percent: float = 85.0` to `min_percent: float | None = None`\n- In: Update docstring to explain None = \"no decrease\" mode\n- In: In `build_validation_spec`, when `coverage_threshold is None`, add `--cov-fail-under=0` to pytest command\n- Out: Actual baseline comparison logic (that's in spec_runner.py)\n\nAcceptance Criteria:\n- `CoverageConfig(enabled=True)` has `min_percent=None` by default\n- Pytest command includes `--cov-fail-under=0` when threshold is None\n- Pytest command includes explicit threshold when provided\n- Docstrings updated\n\nTest Plan:\n- TDD: Test default CoverageConfig has min_percent=None\n- Test `build_validation_spec()` with no threshold → pytest includes `--cov-fail-under=0`\n- Test `build_validation_spec(coverage_threshold=80)` → pytest includes `--cov-fail-under=80`\n\nNotes for Agent:\n- The `--cov-fail-under=0` is needed to override pyproject.toml's `--cov-fail-under=85`\n- Keep existing coverage-related pytest flags (`--cov=src`, etc.)\n- Primary files: src/validation/spec.py, tests/test_spec.py","status":"tombstone","priority":1,"issue_type":"task","created_at":"2025-12-27T18:13:57.436716865Z","updated_at":"2025-12-27T18:20:40.633815335Z","labels":["validation"],"dependencies":[{"issue_id":"mala-da4","depends_on_id":"mala-wg1","type":"blocks","created_at":"2025-12-27T18:15:01.997067804Z","created_by":"daemon"}],"deleted_at":"2025-12-27T18:20:40.633815335Z","deleted_by":"daemon","delete_reason":"delete","original_type":"task"}
{"id":"mala-db2","title":"Segment run logs into per-repo directories","description":"## Problem\n\nCurrently all mala run metadata is stored in a flat directory (`~/.config/mala/runs/*.json`). As the tool is used across multiple repositories, this becomes hard to navigate and correlate.\n\n## Desired Behavior\n\nSegment logs into directories by repo, similar to how `.claude/projects/` segments by project path:\n\n```\n~/.config/mala/runs/\n├── -home-cyou-mala/\n│   ├── 2024-12-27T10-30-00_abc123.json\n│   └── 2024-12-27T11-45-00_def456.json\n├── -home-cyou-other-repo/\n│   ├── 2024-12-28T09-00-00_ghi789.json\n│   └── ...\n└── ...\n```\n\n### Key changes:\n1. **Directory naming**: Convert repo path to safe directory name (replace `/` with `-`, similar to `.claude`)\n2. **File naming**: Use `{iso-timestamp}_{short-uuid}.json` for easier sorting and identification\n3. **Backward compat**: On startup, optionally migrate existing flat files to new structure (or leave them)\n\n## Implementation\n\n**Files:**\n- `src/tools/env.py`: Add helper to compute repo-specific runs directory\n- `src/logging/run_metadata.py`: Update `save()` to use repo-segmented path\n\n### 1. Add path helper in `src/tools/env.py`:\n\n```python\ndef get_repo_runs_dir(repo_path: Path) -\u003e Path:\n    \"\"\"Get runs directory segmented by repo path.\n    \n    Converts /home/cyou/mala -\u003e -home-cyou-mala\n    \"\"\"\n    # Resolve and convert to safe directory name\n    safe_name = str(repo_path.resolve()).replace('/', '-').lstrip('-')\n    return get_runs_dir() / safe_name\n```\n\n### 2. Update RunMetadata.save() in `src/logging/run_metadata.py`:\n\n```python\ndef save(self) -\u003e Path:\n    self.completed_at = datetime.now(UTC)\n    # Use repo-specific subdirectory\n    runs_dir = get_repo_runs_dir(self.repo_path)\n    runs_dir.mkdir(parents=True, exist_ok=True)\n    \n    # Use timestamp + short UUID for filename\n    timestamp = self.started_at.strftime('%Y-%m-%dT%H-%M-%S')\n    short_id = self.run_id[:8]\n    path = runs_dir / f\"{timestamp}_{short_id}.json\"\n    \n    with open(path, 'w') as f:\n        json.dump(self._to_dict(), f, indent=2)\n    return path\n```\n\n## Acceptance Criteria\n\n- [ ] Run logs are saved to `~/.config/mala/runs/{repo-safe-name}/` directories\n- [ ] Filenames include timestamp for easy sorting\n- [ ] Existing flat-file runs continue to work (no migration required initially)\n- [ ] Tests updated for new path structure\n\n## Test Plan\n\n- Add unit tests for `get_repo_runs_dir` path conversion\n- Update RunMetadata tests to verify new save location\n- Manual: run mala in two different repos, verify separate directories created","status":"closed","priority":3,"issue_type":"task","created_at":"2025-12-27T22:51:29.556789345Z","updated_at":"2025-12-28T05:26:56.324190971Z","closed_at":"2025-12-28T05:26:56.324190971Z","close_reason":"Closed"}
{"id":"mala-dhay","title":"[Review] 2 non-blocking findings from mala-pnil","description":"## Review Findings\n\nThis issue consolidates 2 non-blocking findings from code review.\n\n**Source issue:** mala-pnil\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Re-export in domain/deadlock.py violates codebase conventions\n\n**Priority:** P2\n**Reviewer:** claude\n**Location:** src/domain/deadlock.py:28-37\n\nThe `__all__` list with re-exports for `LockEvent` and `LockEventType` violates the CLAUDE.md rule: **\"No re-exports: Don't create modules that just import and re-export from elsewhere.\"** While the module does more than just re-export, the explicit re-export shim pattern here (`from src.core.models import LockEvent, LockEventType` followed by `__all__ = [..., \"LockEvent\", \"LockEventType\", ...]`) exists solely for backward compatibility. The tests at `tests/unit/domain/test_deadlock.py:6-7` and `tests/integration/domain/test_deadlock.py:20-21` import these from `src.domain.deadlock`—these should be updated to import directly from `src.core.models` instead.\n\n---\n\n### Finding 2: [P2] Remove re-export shim from domain.deadlock\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/domain/deadlock.py:23-33\n\nThe new `__all__` includes `LockEvent` and `LockEventType` re-exported from `core.models` specifically for backward compatibility. That violates the repo’s migration rules (no backward-compat shims / no re-exports) and keeps the old import path available, which can silently reintroduce forbidden `infra -\u003e domain` dependencies. Update any remaining call sites to import from `src.core.models` directly and drop the re-export block.\n\n---\n\n\u003c!-- fp:69d4bc4562b04f90 --\u003e\n\u003c!-- fp:407c73ab8ab2e243 --\u003e\n\u003c!-- review_finding:96ce06940035 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-04T04:39:37.466627129Z","created_by":"cyou","updated_at":"2026-01-04T04:42:22.400945073Z","closed_at":"2026-01-04T04:42:22.400945073Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-pnil"]}
{"id":"mala-dhry","title":"remove debug log cli arg, should always run","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-01T20:52:38.283130553Z","created_by":"cyou","updated_at":"2026-01-01T22:54:29.928386122Z","closed_at":"2026-01-01T22:54:29.928386122Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-dhry","depends_on_id":"mala-cavk.3","type":"blocks","created_at":"2026-01-01T21:29:33.013492786Z","created_by":"daemon"}]}
{"id":"mala-dqp","title":"Fix lock enforcement canonicalization","description":"Context:\n- Hook lock checks use repo-relative hashing, but current canonicalization can diverge from lock scripts when mala is launched outside the repo.\n- This can cause false \"not locked\" blocks or allow writes without a matching lock.\n- The unused lock hook duplicates logic and risks drift.\n- Needs verification: reproduce mismatch by running from a non-repo cwd.\n\nScope:\n- In: unify path canonicalization between shell scripts and Python hook (use repo namespace consistently), pass repo path into lock enforcement, remove redundant hook, and ensure LOCK_DIR mkdir uses parents=True.\n- Out: redesigning the locking model or changing lock script behavior beyond canonicalization.\n\nAcceptance Criteria:\n- A lock acquired via lock-try.sh is recognized by the PreToolUse hook even when mala is launched from outside the repo.\n- Hook blocks writes when another agent holds the lock and allows when the current agent holds it.\n- Redundant lock hook removed (single source of truth).\n- Nested MALA_LOCK_DIR paths start without error.\n\nTest Plan:\n- TDD: add failing test for cross-cwd lock enforcement, implement fix, then run lock integration tests.\n- Run relevant tests in tests/test_lock_sdk_integration.py and tests/test_lock_scripts.py.\n\nNotes for Agent:\n- Keep canonicalization consistent with lock-try.sh hashing; avoid changing lock key format.","status":"closed","priority":1,"issue_type":"bug","created_at":"2025-12-26T17:35:26.214219228Z","updated_at":"2025-12-26T18:45:48.051480297Z","closed_at":"2025-12-26T18:45:48.051480297Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-dqp","depends_on_id":"mala-jnq","type":"parent-child","created_at":"2025-12-26T17:36:15.673575659Z","created_by":"daemon"}]}
{"id":"mala-dqt","title":"Refactor: extract git helper functions","description":"Goal: Move git helper(s) from src/cli.py (e.g., get_git_commit) into a small utility module (e.g., src/git_utils.py).\\n\\nScope/files: src/cli.py, new src/git_utils.py.\\n\\nWork:\\n- Move get_git_commit (and any related helpers if needed).\\n- Update imports in cli.py.\\n- Keep behavior unchanged.\\n\\nAcceptance criteria:\\n- Same git commit info used in metadata.\\n- No behavior changes to CLI.","status":"closed","priority":3,"issue_type":"task","created_at":"2025-12-25T21:41:18.022578128Z","updated_at":"2025-12-26T00:11:10.071338973Z","closed_at":"2025-12-26T00:11:10.071338973Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-dqt","depends_on_id":"mala-9qr","type":"blocks","created_at":"2025-12-25T21:47:31.302567088Z","created_by":"daemon"},{"issue_id":"mala-dqt","depends_on_id":"mala-c01","type":"parent-child","created_at":"2025-12-25T22:05:59.473063215Z","created_by":"daemon"}]}
{"id":"mala-dr9k","title":"Epic: SIGINT Escalation (3-Stage Ctrl-C)","description":"## Goal\nImplement three-stage Ctrl-C behavior for predictable, user-controlled shutdown:\n1. **Drain (1st Ctrl-C)**: Stop spawning new agents, let active agents finish\n2. **Graceful Abort (2nd Ctrl-C)**: Cancel active tasks, forward SIGINT to subprocesses, 10s grace period\n3. **Hard Abort (3rd Ctrl-C)**: SIGKILL all subprocess groups, exit immediately\n\n## Spec\n`plans/2026-01-06-sigint-escalation-spec.md`\n\n## Plan\n`plans/2026-01-06-sigint-escalation-plan-v2.md`\n\n## Exit Code Precedence\n- Stage 3 → 130\n- Stage 2 → 1 if validation already failed, else 130\n- Stage 1 → normal semantics (0 or 1)\n\n## Key Components\n- `MalaOrchestrator`: SIGINT state machine with `_handle_sigint()` method\n- `IssueExecutionCoordinator`: `drain_event` parameter for drain mode\n- `CommandRunner`: `kill_active_process_groups()` static method\n- `MalaEventSink`: `on_drain_started`, `on_abort_started`, `on_force_abort` methods\n\n## Acceptance Criteria\n- First Ctrl-C shows \"draining\" message, stops spawning, lets active tasks finish\n- Second Ctrl-C shows \"aborting\" message, cancels tasks, forwards SIGINT\n- Third Ctrl-C shows \"force killing\" message, SIGKILLs all processes\n- Exit codes follow precedence rules\n- 5s escalation window between Ctrl-C presses\n- 10s grace period for Stage 2\n\n## Related\n- mala-b0m: Graceful shutdown with agent wrap-up (different scope: git cleanup vs signal escalation)","status":"closed","priority":1,"issue_type":"epic","created_at":"2026-01-06T04:42:15.626803312Z","created_by":"cyou","updated_at":"2026-01-06T15:36:20.803259586Z","closed_at":"2026-01-06T15:36:20.803259586Z","close_reason":"Closed"}
{"id":"mala-dr9k.1","title":"[T001] Add event sink lifecycle methods for SIGINT escalation","description":"### Type: task\n### Priority: P2\n### Story: Foundation\n### Parallel: [P]\n### Primary Files: src/core/protocols.py, src/infra/io/base_sink.py, src/infra/io/console_sink.py, tests/fakes/event_sink.py\n### Subsystems: core, infra\n\n## Goal\nAdd three new lifecycle methods to `MalaEventSink` protocol for observability of SIGINT escalation stages.\n\n## Context\nThe three-stage SIGINT escalation needs user-visible feedback:\n- Stage 1: \"Ctrl-C: draining N active tasks...\"\n- Stage 2: \"Ctrl-C: aborting...\"\n- Stage 3: \"Ctrl-C: force killing...\"\n\nThese methods differ from existing `on_abort_requested` (internal error) and `on_tasks_aborting` (task cancellation) by being user-initiated escalation events.\n\n## Scope\n- In: Protocol definition, base sink stubs, console sink messages, fake sink recording\n- Out: Calling these methods (done in orchestrator task)\n\n## Changes\n- `src/core/protocols.py` — [Exists] — Add to `MalaEventSink` protocol:\n  ```python\n  def on_drain_started(self, active_task_count: int) -\u003e None:\n      \"\"\"Called when drain mode starts (1st Ctrl-C).\"\"\"\n  \n  def on_abort_started(self) -\u003e None:\n      \"\"\"Called when graceful abort starts (2nd Ctrl-C).\"\"\"\n  \n  def on_force_abort(self) -\u003e None:\n      \"\"\"Called when hard abort starts (3rd Ctrl-C).\"\"\"\n  ```\n\n- `src/infra/io/base_sink.py` — [Exists] — Add no-op implementations\n\n- `src/infra/io/console_sink.py` — [Exists] — Add user-facing messages:\n  - `on_drain_started`: Print \"Ctrl-C: draining {count} active task(s)...\"\n  - `on_abort_started`: Print \"Ctrl-C: aborting...\"\n  - `on_force_abort`: Print \"Ctrl-C: force killing...\"\n\n- `tests/fakes/event_sink.py` — [Exists] — Add recording:\n  - `drain_started_events: list[int]` \n  - `abort_started_events: list[None]`\n  - `force_abort_events: list[None]`\n\n## Acceptance Criteria\n- `MalaEventSink` protocol includes all three new methods with correct signatures\n- `BaseEventSink` provides no-op implementations (no exceptions)\n- `ConsoleEventSink` prints user-friendly messages to console\n- `FakeEventSink` records all calls for test assertions\n- Existing code still compiles (no missing implementations)\n\n## Verification\n- `uvx ruff check src/core/protocols.py src/infra/io/base_sink.py src/infra/io/console_sink.py tests/fakes/event_sink.py`\n- `uvx ty check`\n- `uv run pytest tests/unit/infra/io/ -v` (existing tests still pass)\n\n## Notes for Agent\n- Follow existing sink method patterns for consistency\n- Console messages should be concise - they appear during Ctrl-C when user wants quick feedback\n- The `active_task_count` param for `on_drain_started` helps user know how many tasks are finishing","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-06T04:44:01.988290012Z","created_by":"cyou","updated_at":"2026-01-06T06:06:01.635580475Z","closed_at":"2026-01-06T06:06:01.635580475Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-dr9k.1","depends_on_id":"mala-dr9k","type":"parent-child","created_at":"2026-01-06T04:44:01.988910857Z","created_by":"cyou"}]}
{"id":"mala-dr9k.10","title":"Resolve Stage-2 SIGINT exit code precedence","description":"Spec says Stage 2 exit code snapshots _validation_failed at 2nd Ctrl-C; current _handle_interrupt runs validation after abort and can set exit_code=1 (src/pipeline/issue_execution_coordinator.py:205). Decide behavior, align code/spec/tests accordingly.","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-06T16:18:22.958271775Z","created_by":"cyou","updated_at":"2026-01-06T17:59:44.164295433Z","closed_at":"2026-01-06T17:59:44.164295433Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-dr9k.10","depends_on_id":"mala-dr9k","type":"parent-child","created_at":"2026-01-06T16:18:22.968017941Z","created_by":"cyou"}]}
{"id":"mala-dr9k.11","title":"Schedule SIGINT forwarding/kill on event loop","description":"Spec requires handler to only update state and schedule work; currently CommandRunner.forward_sigint/kill_active_process_groups are called directly in handler (src/orchestration/orchestrator.py:946,956). Move to loop.call_soon_threadsafe or equivalent; adjust tests if needed.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-06T16:18:34.219822661Z","created_by":"cyou","updated_at":"2026-01-06T17:40:14.820005318Z","closed_at":"2026-01-06T17:40:14.820005318Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-dr9k.11","depends_on_id":"mala-dr9k","type":"parent-child","created_at":"2026-01-06T16:18:34.229481217Z","created_by":"cyou"}]}
{"id":"mala-dr9k.12","title":"Add integration test for real MalaOrchestrator SIGINT handler","description":"Current integration tests embed custom handlers and don't exercise MalaOrchestrator wiring. Add subprocess test that runs real orchestrator and sends 1/2/3 SIGINTs; assert exit codes/messages; skip Windows.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-06T16:18:43.737093035Z","created_by":"cyou","updated_at":"2026-01-06T18:33:13.871176493Z","closed_at":"2026-01-06T18:33:13.871176493Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-dr9k.12","depends_on_id":"mala-dr9k","type":"parent-child","created_at":"2026-01-06T16:18:43.753781049Z","created_by":"cyou"}]}
{"id":"mala-dr9k.13","title":"[Review] 1 non-blocking finding from mala-dr9k.9","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-dr9k.9\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Avoid potentially blocking `proc.stdout.read()` in test\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** tests/integration/orchestration/test_sigint_escalation.py:663-670\n\n`proc.stdout.read()` can hang if the pipe never reaches EOF (e.g., if the launched program spawns a child that inherits and keeps stdout open after the parent exits). This is a new failure mode introduced by the change and can turn this test into a timeout/hang. Prefer draining output via `proc.communicate(timeout=...)` (or another bounded read) and asserting on the combined stdout.\n\n```python\nstdout, _ = proc.communicate(timeout=5)\nassert \"VALIDATION_FAILED\" in (output + stdout)\n```\n\n---\n\n\u003c!-- fp:98b1d72801d0f2af --\u003e\n\u003c!-- review_finding:a32129bb4412 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-06T17:39:05.434037204Z","created_by":"cyou","updated_at":"2026-01-06T18:00:50.307164344Z","closed_at":"2026-01-06T18:00:50.307164344Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-dr9k.9"],"dependencies":[{"issue_id":"mala-dr9k.13","depends_on_id":"mala-dr9k","type":"parent-child","created_at":"2026-01-06T17:39:05.434449091Z","created_by":"cyou"}]}
{"id":"mala-dr9k.14","title":"[Review] 1 non-blocking finding from mala-dr9k.13","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-dr9k.13\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Keep communicate() bounded after kill\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** tests/integration/orchestration/test_sigint_escalation.py:657-660\n\nIn the `TimeoutExpired` fallback, `proc.kill()` is followed by `proc.communicate()` **without** a timeout. If the subprocess spawned a child that inherited and kept stdout/stderr open, `communicate()` can still block indefinitely waiting for EOF (even though the parent was killed). This reintroduces the hang risk the change is addressing. Consider using a second bounded `communicate(timeout=...)` (and, if it times out again, close pipes / fail with a clear message) to keep the test suite from hanging.\n\nExample:\n`remaining_stdout, stderr = proc.communicate(timeout=5.0)`\n\n---\n\n\u003c!-- fp:86f82a60d68e1c93 --\u003e\n\u003c!-- review_finding:39bb66d8380c --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-06T18:00:50.33770595Z","created_by":"cyou","updated_at":"2026-01-06T18:41:04.55659534Z","closed_at":"2026-01-06T18:41:04.55659534Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-dr9k.13"],"dependencies":[{"issue_id":"mala-dr9k.14","depends_on_id":"mala-dr9k","type":"parent-child","created_at":"2026-01-06T18:00:50.338194607Z","created_by":"cyou"}]}
{"id":"mala-dr9k.15","title":"[Review] 2 non-blocking findings from mala-dr9k.12","description":"## Review Findings\n\nThis issue consolidates 2 non-blocking findings from code review.\n\n**Source issue:** mala-dr9k.12\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Add timeout/run_task failure handling for handler-install wait loop\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** tests/integration/orchestration/test_sigint_escalation.py:745-751\n\nEach subprocess script spins in `while signal.getsignal(SIGINT) is signal.default_int_handler` with no timeout and without checking whether `run_task` already finished/errored. If `orchestrator.run()` fails before installing the handler (import error, config error, etc.), the subprocess will sit in that loop until the parent `_wait_for_ready` times out, often losing the original traceback and making failures slow/hard to debug. Add a monotonic deadline and bail out early if `run_task.done()` (printing `run_task.exception()`), e.g. `if run_task.done(): raise SystemExit(1)` and `if time.monotonic() \u003e deadline: raise SystemExit(1)`.\n\n---\n\n### Finding 2: [P2] Stage 2/3 tests rely on timing/coordinator sleep behavior to avoid draining\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** tests/integration/orchestration/test_sigint_escalation.py:901-910\n\n`test_orchestrator_double_sigint_abort_mode` and `test_orchestrator_triple_sigint_force_abort` use an empty provider and a long `poll_interval_seconds=10.0` to keep the process alive long enough for additional SIGINTs. This implicitly relies on the coordinator not immediately exiting on Stage 1 drain (and on where the loop happens to be when SIGINT arrives). If the coordinator behavior changes (e.g., drain wakes idle sleep / drains immediately when no tasks), these tests can start exiting after the first SIGINT and never actually exercise Stage 2/3. A more robust approach is to ensure there is always at least one controlled “active task” (dummy long-running task) when the first SIGINT arrives, so drain mode can’t complete before the second/third SIGINT is delivered.\n\n---\n\n\u003c!-- fp:64c7a6117fdb6def --\u003e\n\u003c!-- fp:4419312e82772cb8 --\u003e\n\u003c!-- review_finding:a66f7cdb3961 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-06T18:33:13.903051128Z","created_by":"cyou","updated_at":"2026-01-06T18:43:12.600385253Z","closed_at":"2026-01-06T18:43:12.600385253Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-dr9k.12"],"dependencies":[{"issue_id":"mala-dr9k.15","depends_on_id":"mala-dr9k","type":"parent-child","created_at":"2026-01-06T18:33:13.903570144Z","created_by":"cyou"}]}
{"id":"mala-dr9k.16","title":"[Review] 2 non-blocking findings from mala-dr9k.14","description":"## Review Findings\n\nThis issue consolidates 2 non-blocking findings from code review.\n\n**Source issue:** mala-dr9k.14\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Handle TimeoutExpired after kill-time communicate()\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** tests/integration/orchestration/test_sigint_escalation.py:658-663\n\nAfter `proc.kill()`, you now call `proc.communicate(timeout=5.0)`. If stdout/stderr are still held open (e.g., a child inherited the pipe fds), this second `communicate()` can also hit `subprocess.TimeoutExpired`, which will currently bubble up as a pytest error and can leave the subprocess/pipes in an uncertain state.\n\nConsider explicitly handling the second timeout and failing with a clear message (and best-effort cleanup like closing pipes / `wait(timeout=...)`). For example:\n`try: proc.communicate(timeout=5.0)`\n`except subprocess.TimeoutExpired: pytest.fail(\"...still did not exit after kill...\")`\n\nThis pattern appears in the same file at ~lines 661, 826, 977, and 1154.\n\n---\n\n### Finding 2: [P2] Concatenate partial output on TimeoutExpired in fallback handlers\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** tests/integration/orchestration/test_sigint_escalation.py:658-662\n\nWhen `subprocess.communicate()` times out, the output read up to that point is available in the `TimeoutExpired` exception's `stdout` and `stderr` attributes. The current implementation reassigns these variables in the `except` block, which discards the initial diagnostic output from the first 5-10 second timeout period. To ensure the `pytest.fail` message contains complete logs for debugging, these partial results should be captured and concatenated with the results from the second `communicate()` call. Additionally, the second call itself remains vulnerable to an unhandled `TimeoutExpired` (e.g., if child processes keep pipes open), which would obscure the intended failure message.\n\n---\n\n\u003c!-- fp:950e4da12ff6c47e --\u003e\n\u003c!-- fp:bc115982d179050a --\u003e\n\u003c!-- review_finding:3a5cb733b72d --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-06T18:41:04.588111338Z","created_by":"cyou","updated_at":"2026-01-06T18:48:02.675973617Z","closed_at":"2026-01-06T18:48:02.675973617Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-dr9k.14"],"dependencies":[{"issue_id":"mala-dr9k.16","depends_on_id":"mala-dr9k","type":"parent-child","created_at":"2026-01-06T18:41:04.588532455Z","created_by":"cyou"}]}
{"id":"mala-dr9k.17","title":"[Review] 4 non-blocking findings from mala-dr9k.15","description":"## Review Findings\n\nThis issue consolidates 4 non-blocking findings from code review.\n\n**Source issue:** mala-dr9k.15\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P3] task.exception() raises CancelledError if task was cancelled\n\n**Priority:** P3\n**Reviewer:** claude\n**Location:** tests/integration/orchestration/test_sigint_escalation.py:754-755\n\nThe pattern `if run_task.exception():` will raise `CancelledError` rather than returning falsy if the task was cancelled (not just errored). While cancellation during the brief handler-installation window is extremely unlikely, a more defensive pattern would use a try-except: `try: exc = run_task.exception() except asyncio.CancelledError: exc = None`. This is minor since it's test code and unlikely to occur in practice.\n\n---\n\n### Finding 2: Use a less flaky deadline than fixed 5s\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** tests/integration/orchestration/test_sigint_escalation.py:747-756\n\n`deadline = time.monotonic() + 5.0` can make these integration tests flaky on slower/loaded CI or cold starts: the orchestrator may legitimately take \u003e5s to install the SIGINT handler (imports, filesystem, scheduling), causing an artificial failure even though it would become ready. Consider aligning the deadline with the parent-side readiness timeout (or using a more conservative value / env override) so this change reduces hangs without introducing spurious timeouts.\n\n---\n\n### Finding 3: Preserve diagnostics when run_task finishes without exception\n\n**Priority:** P3\n**Reviewer:** codex\n**Location:** tests/integration/orchestration/test_sigint_escalation.py:749-754\n\nIn the early-failure branch, `run_task.done()` followed by `if run_task.exception(): ...` prints nothing if the task finishes “cleanly” (or is cancelled), then exits 1. That can still be hard to debug compared to emitting an explicit message like `STARTUP_EXITED`/`STARTUP_CANCELLED` and including the task state. Also, calling `run_task.exception()` multiple times is avoidable—capture once and report based on that value.\n\n---\n\n### Finding 4: [P2] Finding 2 addressed via comments instead of robust fix\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** tests/integration/orchestration/test_sigint_escalation.py:894-915\n\nThe commit adds explanatory comments regarding the use of `poll_interval_seconds=10.0` to keep the process alive, but does not implement the 'more robust approach' suggested in the review findings (using a controlled active task). The tests remain potentially brittle if the orchestrator's idle behavior changes (e.g., if it starts waking up and exiting immediately upon entering drain mode).\n\n---\n\n\u003c!-- fp:591baafeeb092c7a --\u003e\n\u003c!-- fp:901eb0916573049f --\u003e\n\u003c!-- fp:dad201673dcfb7b2 --\u003e\n\u003c!-- fp:6f5943132b523232 --\u003e\n\u003c!-- review_finding:4d3024228e1c --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-06T18:43:12.632851546Z","created_by":"cyou","updated_at":"2026-01-06T18:53:52.434511546Z","closed_at":"2026-01-06T18:53:52.434511546Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-dr9k.15"],"dependencies":[{"issue_id":"mala-dr9k.17","depends_on_id":"mala-dr9k","type":"parent-child","created_at":"2026-01-06T18:43:12.633296574Z","created_by":"cyou"}]}
{"id":"mala-dr9k.18","title":"[Review] 3 non-blocking findings from mala-dr9k.16","description":"## Review Findings\n\nThis issue consolidates 3 non-blocking findings from code review.\n\n**Source issue:** mala-dr9k.16\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Include prior stdout in post-kill timeout failure\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** tests/integration/orchestration/test_sigint_escalation.py:666-672\n\nIn the first updated block, the nested `except subprocess.TimeoutExpired:` after `proc.kill()` fails with `Partial stdout: {partial_stdout}` but omits the already-captured `output` prefix. This is inconsistent with the other updated blocks and defeats the goal of “complete logs” specifically in the worst-case scenario (process still not exiting after kill). It can hide the most relevant stdout leading up to the hang.\n\nSuggested fix: include `output + partial_stdout` (and/or the same stdout composition you use in the other blocks) in that failure message.\n\n---\n\n### Finding 2: [P2] Inconsistent stdout concatenation in test_issue_execution_coordinator_sigint_no_validation\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** tests/integration/orchestration/test_sigint_escalation.py:673-677\n\nIn the first test case modified, the failure message in the inner `TimeoutExpired` block (line 673) omits the initial `output` from the `READY` phase, whereas the other three test cases in the same file correctly concatenate `output + partial_stdout`. This results in incomplete diagnostic logs if the process fails to exit after `kill()` in this specific test.\n\n---\n\n### Finding 3: [P2] Second TimeoutExpired handler discards partial output\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** tests/integration/orchestration/test_sigint_escalation.py:671-678\n\nThe newly added `try/except` blocks for the second `communicate()` call (after `kill()`) do not capture partial output from the `TimeoutExpired` exception. If the second call also times out (e.g., due to child processes holding pipes open), any output produced during that 5-second window is lost, which is inconsistent with the PR's goal of preserving partial output for debugging. This occurs at lines 671, 859, 1036, and 1236.\n\n---\n\n\u003c!-- fp:91306b23d4101eb2 --\u003e\n\u003c!-- fp:b67636ba70e165ca --\u003e\n\u003c!-- fp:af70d7e89ef18465 --\u003e\n\u003c!-- review_finding:af9ee12c73e3 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-06T18:48:02.710167742Z","created_by":"cyou","updated_at":"2026-01-06T18:52:48.510162188Z","closed_at":"2026-01-06T18:52:48.510162188Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-dr9k.16"],"dependencies":[{"issue_id":"mala-dr9k.18","depends_on_id":"mala-dr9k","type":"parent-child","created_at":"2026-01-06T18:48:02.710634819Z","created_by":"cyou"}]}
{"id":"mala-dr9k.19","title":"[Review] 2 non-blocking findings from mala-dr9k.17","description":"## Review Findings\n\nThis issue consolidates 2 non-blocking findings from code review.\n\n**Source issue:** mala-dr9k.17\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Avoid spawning real agent work in SIGINT escalation subprocess tests\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** tests/integration/orchestration/test_sigint_escalation.py:939-950\n\nIn Stage 2/3, adding an `open` `FakeIssue` means `MalaOrchestrator` will immediately `claim_async()` and spawn `run_implementer()`, which can invoke real SDK/review/gate behavior (non-deterministic, possibly requiring credentials, and potentially fast-failing).\n\nThis undermines the stated goal (“long-running task”) and can make the test flaky: if the issue completes/fails quickly (or `max_issues=1` is reached), the subprocess can exit before the SIGINTs arrive, so Stage 2/3 never triggers.\n\nA more deterministic approach is to keep the provider empty and use a poll interval long enough to allow the second SIGINT, or explicitly monkey-patch `spawn_agent`/`run_implementer` in the subprocess to a controlled long-running coroutine (e.g. `await asyncio.Event().wait()`) that only ends via cancellation/SIGINT.\n\n---\n\n### Finding 2: [P3] Align parent READY timeout with new 15s child startup deadline\n\n**Priority:** P3\n**Reviewer:** codex\n**Location:** tests/integration/orchestration/test_sigint_escalation.py:29-58\n\nThe subprocess scripts now allow up to 15s for SIGINT handler installation, but the parent still calls `_wait_for_ready(..., timeout=10.0)`. On very slow/loaded CI, the parent can kill/fail the test before the subprocess reaches its own timeout/diagnostics, reducing the value of the 15s increase.\n\nConsider bumping the parent timeout to \u003e= 15s (or centralizing a shared constant) so the readiness and in-subprocess diagnostics are consistent.\n\n---\n\n\u003c!-- fp:4ce408a9481ce7a4 --\u003e\n\u003c!-- fp:5715e8869da89e45 --\u003e\n\u003c!-- review_finding:daefa4bc9309 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-06T18:53:52.471492785Z","created_by":"cyou","updated_at":"2026-01-06T19:05:42.846383185Z","closed_at":"2026-01-06T19:05:42.846383185Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-dr9k.17"],"dependencies":[{"issue_id":"mala-dr9k.19","depends_on_id":"mala-dr9k","type":"parent-child","created_at":"2026-01-06T18:53:52.472088782Z","created_by":"cyou"}]}
{"id":"mala-dr9k.2","title":"[T002] Add CommandRunner.kill_active_process_groups() method","description":"### Type: task\n### Priority: P2\n### Story: Foundation\n### Parallel: [P]\n### Primary Files: src/infra/tools/command_runner.py, tests/unit/infra/tools/test_command_runner.py\n### Subsystems: infra\n\n## Goal\nAdd static method to SIGKILL all tracked process groups for Stage 3 hard abort.\n\n## Context\nStage 3 (third Ctrl-C) needs to immediately kill all subprocess groups. `CommandRunner` already tracks process groups in `_SIGINT_FORWARD_PGIDS` for SIGINT forwarding. This task adds a method to SIGKILL them all.\n\n## Scope\n- In: `kill_active_process_groups()` static method, unit tests\n- Out: Calling the method (done in orchestrator task)\n\n## Changes\n- `src/infra/tools/command_runner.py` — [Exists] — Add:\n  ```python\n  @staticmethod\n  def kill_active_process_groups() -\u003e None:\n      \"\"\"Send SIGKILL to all tracked process groups.\n      \n      Safe to call multiple times - clears pgid set after kill.\n      No-op on Windows. Silently handles ProcessLookupError/PermissionError.\n      \"\"\"\n      if sys.platform == \"win32\":\n          return\n      pgids = _SIGINT_FORWARD_PGIDS.copy()\n      _SIGINT_FORWARD_PGIDS.clear()\n      for pgid in pgids:\n          try:\n              os.killpg(pgid, signal.SIGKILL)\n          except (ProcessLookupError, PermissionError):\n              pass  # Process already dead or permission issue - expected during shutdown\n  ```\n\n- `tests/unit/infra/tools/test_command_runner.py` — [New] — Unit tests:\n  - Test sends SIGKILL to tracked pgids\n  - Test no-op on Windows (mock `sys.platform`)\n  - Test clears `_SIGINT_FORWARD_PGIDS` after kill\n  - Test handles empty pgid set gracefully\n  - Test silently handles ProcessLookupError\n  - Test silently handles PermissionError\n\n## Acceptance Criteria\n- `kill_active_process_groups()` sends SIGKILL to all tracked process groups\n- Method clears the pgid set to avoid redundant kills on repeated calls\n- No-op on Windows (no exceptions, no side effects)\n- Silently handles expected errors (ProcessLookupError, PermissionError)\n- Unit tests cover all edge cases\n\n## Verification\n- `uvx ruff check src/infra/tools/command_runner.py tests/unit/infra/tools/test_command_runner.py`\n- `uvx ty check`\n- `uv run pytest tests/unit/infra/tools/test_command_runner.py -v`\n\n## Notes for Agent\n- Use `_SIGINT_FORWARD_PGIDS.copy()` then clear to avoid modification during iteration\n- The existing `forward_sigint()` method is a good reference for error handling patterns\n- Test file is new - follow existing test file patterns in `tests/unit/infra/`","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-06T04:45:35.978637246Z","created_by":"cyou","updated_at":"2026-01-06T06:22:15.640659762Z","closed_at":"2026-01-06T06:22:15.640659762Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-dr9k.2","depends_on_id":"mala-dr9k","type":"parent-child","created_at":"2026-01-06T04:45:35.979276061Z","created_by":"cyou"}]}
{"id":"mala-dr9k.20","title":"[Review] 2 non-blocking findings from mala-dr9k.19","description":"## Review Findings\n\nThis issue consolidates 2 non-blocking findings from code review.\n\n**Source issue:** mala-dr9k.19\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] 60s poll interval blocks Stage 1 drain completion\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** tests/integration/orchestration/test_sigint_escalation.py:956\n\nIn Stage 2/3 tests, setting `poll_interval_seconds=60.0` prevents the orchestrator from responding to the first SIGINT (Stage 1) until the sleep finishes. Since `MalaOrchestrator._poll_loop` only checks `self._drain_mode_active` after `await asyncio.sleep()`, the test may timeout or wait much longer than necessary while the process is stuck in Stage 1 sleep.\n\n---\n\n### Finding 2: [P2] Missing poll interval update in force abort test\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** tests/integration/orchestration/test_sigint_escalation.py:1161\n\nWhile `test_orchestrator_double_sigint_abort_mode` was updated to 60s poll interval, `test_orchestrator_triple_sigint_force_abort` (Stage 3) still shows `poll_interval_seconds=60.0` in the diff but the surrounding code suggests it was meant to be aligned with the change in Finding 1. If the goal was to avoid fast poll flakiness, both tests should be consistent.\n\n---\n\n\u003c!-- fp:8e547248adc31648 --\u003e\n\u003c!-- fp:a70248e9c8c68cb0 --\u003e\n\u003c!-- review_finding:b856c6b9a0a7 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-06T19:05:42.877595726Z","created_by":"cyou","updated_at":"2026-01-06T19:39:15.647900075Z","closed_at":"2026-01-06T19:39:15.647900075Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-dr9k.19"],"dependencies":[{"issue_id":"mala-dr9k.20","depends_on_id":"mala-dr9k","type":"parent-child","created_at":"2026-01-06T19:05:42.878112533Z","created_by":"cyou"}]}
{"id":"mala-dr9k.21","title":"[Review] 1 non-blocking finding from mala-dr9k.20","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-dr9k.20\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Reset agent-spawn deadline after SIGINT handler installs\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** tests/integration/orchestration/test_sigint_escalation.py:980-1015\n\nThe agent-spawn wait loop reuses `deadline` that was set for waiting on SIGINT handler installation. If handler installation takes most of the 15s budget (slow/loaded CI), the spawn loop can time out almost immediately even though the agent would have spawned shortly after, making the test flaky.\n\nFix: use a separate deadline (or reset `deadline`) before the `active_tasks` loop, e.g.:\n`spawn_deadline = time.monotonic() + 15.0`\n`while not _orchestrator.issue_coordinator.active_tasks:`\n`    if time.monotonic() \u003e spawn_deadline: ...`\n\n---\n\n\u003c!-- fp:78e6d801f1dd8236 --\u003e\n\u003c!-- review_finding:13bff71b4029 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-06T19:39:15.679482631Z","created_by":"cyou","updated_at":"2026-01-06T19:41:05.75653768Z","closed_at":"2026-01-06T19:41:05.75653768Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-dr9k.20"],"dependencies":[{"issue_id":"mala-dr9k.21","depends_on_id":"mala-dr9k","type":"parent-child","created_at":"2026-01-06T19:39:15.679920079Z","created_by":"cyou"}]}
{"id":"mala-dr9k.3","title":"[T003] Implement SIGINT state machine in MalaOrchestrator","description":"### Type: task\n### Priority: P2\n### Story: US1 - SIGINT Escalation\n### Parallel: (depends on T001, T002, T004)\n### Primary Files: src/orchestration/orchestrator.py, src/orchestration/deadlock_handler.py, tests/unit/orchestration/test_orchestrator.py\n### Subsystems: orchestration\n\n## Goal\nImplement the three-stage SIGINT escalation state machine in `MalaOrchestrator` with testable `_handle_sigint()` method, and add 10s grace period to Stage 2 abort.\n\n## Context\nThis is the core implementation of the SIGINT escalation feature. The orchestrator needs to:\n1. Track SIGINT count and escalation state\n2. Handle drain mode (1st Ctrl-C) - set drain_event\n3. Handle abort mode (2nd Ctrl-C) - set interrupt_event, forward SIGINT, snapshot exit code\n4. Handle force kill (3rd Ctrl-C) - SIGKILL all processes, cancel run task\n\n## Scope\n- In: State machine, `_handle_sigint()` method, drain_event creation, exit code logic, calling event sink methods, 10s grace period for Stage 2\n- Out: Coordinator drain handling (T004), integration tests (T005)\n\n## Changes\n- `src/orchestration/orchestrator.py` — [Exists] — Add:\n  1. Module constants:\n     ```python\n     ESCALATION_WINDOW_SECONDS = 5.0\n     ABORT_GRACE_SECONDS = 10.0\n     ```\n  \n  2. New instance attributes (reset per-run in `run()`):\n     ```python\n     self._sigint_count: int = 0\n     self._sigint_last_at: float = 0.0\n     self._drain_mode_active: bool = False\n     self._abort_mode_active: bool = False\n     self._abort_exit_code: int = 130\n     self._validation_failed: bool = False\n     self._shutdown_requested: bool = False\n     self._run_task: asyncio.Task[Any] | None = None\n     ```\n  \n  3. Private `_handle_sigint()` method:\n     ```python\n     def _handle_sigint(\n         self,\n         loop: asyncio.AbstractEventLoop,\n         drain_event: asyncio.Event,\n         interrupt_event: asyncio.Event,\n     ) -\u003e None:\n         \"\"\"Handle SIGINT with three-stage escalation.\"\"\"\n         now = time.monotonic()\n         # Reset escalation window if idle (not in drain/abort mode)\n         if not self._drain_mode_active and not self._abort_mode_active:\n             if now - self._sigint_last_at \u003e ESCALATION_WINDOW_SECONDS:\n                 self._sigint_count = 0\n         \n         self._sigint_count += 1\n         self._sigint_last_at = now\n         \n         if self._sigint_count == 1:\n             # Stage 1: Drain\n             self._drain_mode_active = True\n             loop.call_soon_threadsafe(drain_event.set)\n             # Get active task count from coordinator\n             active_count = len(self.issue_coordinator.active_tasks)\n             loop.call_soon_threadsafe(\n                 lambda: self._event_sink.on_drain_started(active_count)\n             )\n         elif self._sigint_count == 2:\n             # Stage 2: Graceful Abort\n             self._abort_mode_active = True\n             self._abort_exit_code = 1 if self._validation_failed else 130\n             loop.call_soon_threadsafe(interrupt_event.set)\n             CommandRunner.forward_sigint()\n             loop.call_soon_threadsafe(self._event_sink.on_abort_started)\n         else:\n             # Stage 3: Hard Abort\n             self._shutdown_requested = True\n             CommandRunner.kill_active_process_groups()\n             loop.call_soon_threadsafe(self._event_sink.on_force_abort)\n             if self._run_task:\n                 loop.call_soon_threadsafe(self._run_task.cancel)\n     ```\n  \n  4. Update `run()` to:\n     - Reset SIGINT state attributes at start\n     - Create `drain_event` alongside `interrupt_event`\n     - Install signal handler using `_handle_sigint()`\n     - Pass `drain_event` to coordinator (signature prepared in T004)\n     - Handle `_shutdown_requested` flag for early exit\n     - Use `_abort_exit_code` for Stage 2 exit code\n\n- `src/orchestration/deadlock_handler.py` — [Exists] — Update `abort_active_tasks()`:\n  ```python\n  from .orchestrator import ABORT_GRACE_SECONDS\n  \n  async def abort_active_tasks(...) -\u003e int:\n      # ... existing cancel logic ...\n      \n      # Await with bounded timeout (Stage 2 grace period)\n      try:\n          await asyncio.wait_for(\n              asyncio.gather(*[task for _, task in tasks_snapshot], return_exceptions=True),\n              timeout=ABORT_GRACE_SECONDS,  # 10.0\n          )\n      except TimeoutError:\n          # Tasks still running after grace period - they remain cancelled\n          # but we don't wait further. Stage 3 (force kill) available via Ctrl-C.\n          pass\n      \n      # ... existing finalization logic ...\n  ```\n\n- `tests/unit/orchestration/test_orchestrator.py` — [Exists] — Add unit tests:\n  - Test `_handle_sigint()` directly with mock loop and events\n  - Test escalation timing: 5s window reset only when idle\n  - Test state transitions: idle → drain → abort → hard abort\n  - Test exit code precedence (Stage 3: 130, Stage 2: 130/1, Stage 1: 0/1)\n  - Test `_validation_failed` snapshot at Stage 2 entry\n  - Test event sink methods are called correctly\n  - Test `on_drain_started` receives correct active task count\n\n## Wiring Map\n- `_handle_sigint()` → signal.signal(SIGINT) handler closure\n- `drain_event` → orchestrator creates → passes to coordinator `run_loop()`\n- `_validation_failed` → set by validation callback → read by signal handler at Stage 2 entry\n- `self.issue_coordinator.active_tasks` → read in signal handler → count passed to `on_drain_started()`\n- `ABORT_GRACE_SECONDS` → defined in orchestrator.py → imported by deadlock_handler.py\n\n## Acceptance Criteria\n- First SIGINT sets `drain_event`, calls `on_drain_started()` with correct active task count\n- Second SIGINT sets `interrupt_event`, forwards SIGINT, calls `on_abort_started()`, snapshots exit code\n- Third SIGINT calls `kill_active_process_groups()`, cancels run task, calls `on_force_abort()`\n- Stage 2 abort waits at most 10 seconds for tasks to finish (grace period)\n- Exit code is 130 for Stage 3, 130 or 1 for Stage 2 (based on `_validation_failed`), normal for Stage 1\n- 5s escalation window resets only when idle (not in drain/abort mode)\n- State is reset per-run (not per-instance)\n\n## Verification\n- `uvx ruff check src/orchestration/orchestrator.py src/orchestration/deadlock_handler.py`\n- `uvx ty check`\n- `uv run pytest tests/unit/orchestration/test_orchestrator.py -v -k sigint`\n- `uv run pytest tests/unit/orchestration/ -v` (all existing tests pass)\n\n## Notes for Agent\n- Keep SIGINT state as instance attributes (not in OrchestratorState) for signal handler closure access\n- Use `loop.call_soon_threadsafe()` for all asyncio operations from signal handler\n- Signal handler must only update simple Python types (GIL guarantees atomicity)\n- `_validation_failed` snapshot at Stage 2 entry ensures deterministic exit codes\n- Reference existing SIGINT handling in `run()` for patterns\n- Active task count comes from `self.issue_coordinator.active_tasks` - capture the value before the lambda to avoid race conditions\n- The 10s grace period in deadlock_handler ensures Stage 2 doesn't wait forever","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-06T04:46:13.450201751Z","created_by":"cyou","updated_at":"2026-01-06T07:00:42.421896056Z","closed_at":"2026-01-06T07:00:42.421896056Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-dr9k.3","depends_on_id":"mala-dr9k","type":"parent-child","created_at":"2026-01-06T04:46:13.450717897Z","created_by":"cyou"},{"issue_id":"mala-dr9k.3","depends_on_id":"mala-dr9k.1","type":"blocks","created_at":"2026-01-06T04:47:18.853550865Z","created_by":"cyou"},{"issue_id":"mala-dr9k.3","depends_on_id":"mala-dr9k.2","type":"blocks","created_at":"2026-01-06T04:47:18.915673526Z","created_by":"cyou"},{"issue_id":"mala-dr9k.3","depends_on_id":"mala-dr9k.4","type":"blocks","created_at":"2026-01-06T04:50:03.342074397Z","created_by":"cyou"}]}
{"id":"mala-dr9k.4","title":"[T004] Add drain_event handling to IssueExecutionCoordinator","description":"### Type: task\n### Priority: P2\n### Story: US1 - SIGINT Escalation\n### Parallel: [P]\n### Primary Files: src/pipeline/issue_execution_coordinator.py, tests/unit/pipeline/test_issue_execution_coordinator.py\n### Subsystems: pipeline\n\n## Goal\nAdd `drain_event` parameter to `IssueExecutionCoordinator.run_loop()` to support Stage 1 drain mode.\n\n## Context\nWhen drain mode is activated (1st Ctrl-C), the coordinator should:\n1. Stop spawning new issues\n2. Let active tasks complete their full flow\n3. Trigger final validation in watch mode when drain completes\n\n## Scope\n- In: `drain_event` parameter, drain mode handling in loop, validation trigger on drain completion\n- Out: Calling drain_event.set() (done in orchestrator T003)\n\n## Changes\n- `src/pipeline/issue_execution_coordinator.py` — [Exists] — Update:\n  1. Add `drain_event` parameter at end of `run_loop()` signature:\n     ```python\n     async def run_loop(\n         self,\n         spawn_callback: SpawnCallback,\n         finalize_callback: FinalizeCallback,\n         abort_callback: AbortCallback,\n         watch_config: WatchConfig | None = None,\n         interrupt_event: asyncio.Event | None = None,\n         validation_callback: Callable[[], Awaitable[bool]] | None = None,\n         sleep_fn: Callable[[float], Awaitable[None]] = asyncio.sleep,\n         drain_event: asyncio.Event | None = None,  # NEW\n     ) -\u003e RunResult:\n     ```\n  \n  2. Add drain mode check in spawn loop:\n     ```python\n     # Check drain mode before spawning new issues\n     if drain_event and drain_event.is_set():\n         # Don't spawn new issues, wait for active to complete\n         if not self.active_tasks:  # Note: self.active_tasks, not self._active_tasks\n             # All drained - trigger validation if in watch mode\n             if watch_config and validation_callback:\n                 await validation_callback()\n             return RunResult(...)\n         continue  # Wait for active tasks without spawning\n     ```\n  \n  3. Ensure drain respects existing interrupt_event (Stage 2 escalation):\n     - If both drain_event and interrupt_event are set, interrupt_event takes precedence\n     - Drain only stops spawning; interrupt cancels active tasks\n\n- `tests/unit/pipeline/test_issue_execution_coordinator.py` — [Exists] — Add unit tests:\n  - Test drain mode: no new spawns when `drain_event` set\n  - Test drain completion: returns when active tasks finish (no cancellation)\n  - Test drain + interrupt: `interrupt_event` after `drain_event` escalates correctly\n  - Test drain completion triggers validation callback in watch mode\n  - Test drain with no active tasks returns immediately\n  - Test existing interrupt_event behavior unchanged when drain_event is None\n\n## Acceptance Criteria\n- `drain_event` parameter added at end of signature (backward compatible)\n- Setting `drain_event` stops spawning new issues\n- Active tasks complete normally (not cancelled)\n- Drain completion triggers validation in watch mode\n- Existing behavior unchanged when `drain_event` is None\n- `interrupt_event` takes precedence over `drain_event`\n\n## Verification\n- `uvx ruff check src/pipeline/issue_execution_coordinator.py`\n- `uvx ty check`\n- `uv run pytest tests/unit/pipeline/test_issue_execution_coordinator.py -v -k drain`\n- `uv run pytest tests/unit/pipeline/test_issue_execution_coordinator.py -v` (all existing tests pass)\n\n## Notes for Agent\n- Parameter at end of signature for backward compatibility with positional callers\n- Drain mode is about stopping *new* work, not cancelling *active* work\n- In watch mode, drain completion should trigger final validation before exit\n- Be careful not to break existing interrupt_event semantics\n- Use `self.active_tasks` (without underscore) - this is the coordinator's task dict","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-06T04:46:36.622189719Z","created_by":"cyou","updated_at":"2026-01-06T06:20:09.465171096Z","closed_at":"2026-01-06T06:20:09.465171096Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-dr9k.4","depends_on_id":"mala-dr9k","type":"parent-child","created_at":"2026-01-06T04:46:36.622698305Z","created_by":"cyou"}]}
{"id":"mala-dr9k.5","title":"[T005] [integration-path-test] Add SIGINT escalation integration tests","description":"### Type: task\n### Priority: P2\n### Story: Verification\n### Parallel: (depends on T003, T004)\n### Primary Files: tests/integration/orchestration/test_sigint_escalation.py\n### Subsystems: tests\n\n## Goal\nAdd subprocess-based integration tests verifying real SIGINT handling across all three stages.\n\n## Context\nWhile unit tests cover the `_handle_sigint()` logic with mocked components, integration tests need to verify actual signal delivery and subprocess behavior. These tests spawn real processes and send real SIGINT signals.\n\n## Scope\n- In: Integration tests for all three SIGINT stages, POSIX-only\n- Out: Windows support (tests skipped on Windows)\n\n## Changes\n- `tests/integration/orchestration/test_sigint_escalation.py` — [New] — Add integration tests:\n  1. Test infrastructure:\n     ```python\n     @pytest.fixture\n     def slow_task_script(tmp_path):\n         \"\"\"Create a script that runs slowly and handles signals.\"\"\"\n         script = tmp_path / \"slow_task.py\"\n         script.write_text('''\n         import signal\n         import time\n         import sys\n         \n         def handler(signum, frame):\n             print(\"SIGINT received\", flush=True)\n             sys.exit(130)\n         \n         signal.signal(signal.SIGINT, handler)\n         print(\"started\", flush=True)\n         time.sleep(60)  # Long enough to receive signals\n         ''')\n         return script\n     ```\n  \n  2. Test scenarios:\n     - `test_single_sigint_drain`: Send one SIGINT, verify drain message, task completes\n     - `test_double_sigint_abort`: Send two SIGINTs, verify abort message, exit 130\n     - `test_triple_sigint_force_kill`: Send three SIGINTs, verify force kill, exit 130\n     - `test_abort_with_validation_failed`: Verify exit 1 when validation failed before Stage 2\n     - `test_escalation_window`: Verify 5s window resets when idle\n  \n  3. Test markers:\n     ```python\n     @pytest.mark.skipif(sys.platform == \"win32\", reason=\"POSIX-only\")\n     @pytest.mark.integration\n     ```\n  \n  4. Consider CI reliability:\n     ```python\n     @pytest.mark.skipif(\n         os.environ.get(\"CI\") and os.environ.get(\"SKIP_SIGNAL_TESTS\"),\n         reason=\"Signal tests may be flaky in CI\"\n     )\n     ```\n\n## Acceptance Criteria\n- Single SIGINT → drain mode → task completes → exit 0 (or 1 if validation fails)\n- Double SIGINT → abort mode → tasks cancelled → exit 130 (or 1 if validation already failed)\n- Triple SIGINT → force kill → immediate exit 130\n- Tests pass on Linux/macOS\n- Tests skipped on Windows (no failures)\n- Tests use generous timeouts for timing stability\n\n## Verification\n- `uv run pytest tests/integration/orchestration/test_sigint_escalation.py -v` (POSIX)\n- Verify tests are skipped on Windows\n- All integration tests should pass: `uv run pytest -m integration -v`\n- Final verification that all tests pass:\n  - `uv run pytest tests/unit/orchestration/test_orchestrator.py tests/unit/pipeline/test_issue_execution_coordinator.py tests/unit/infra/tools/test_command_runner.py tests/integration/orchestration/test_sigint_escalation.py -v`\n\n## Notes for Agent\n- Use `subprocess.Popen` to spawn test processes\n- Use `os.kill(pid, signal.SIGINT)` to send signals\n- Add generous sleeps between signals (e.g., 0.5s) for stability\n- Capture stdout/stderr to verify messages\n- Test file is new - create `tests/integration/orchestration/` if needed\n- Follow existing integration test patterns in `tests/integration/`","notes":"Quality gate failed: Quality gate failed: Missing validation evidence for: typecheck, lint, format, test\nLog: /home/cyou/.claude/projects/-home-cyou-mala/82978ec7-62ad-48ae-a9ff-688a631c0e88.jsonl","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-06T04:47:05.793968338Z","created_by":"cyou","updated_at":"2026-01-06T15:33:14.810417786Z","closed_at":"2026-01-06T15:33:14.810417786Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-dr9k.5","depends_on_id":"mala-dr9k","type":"parent-child","created_at":"2026-01-06T04:47:05.794496284Z","created_by":"cyou"},{"issue_id":"mala-dr9k.5","depends_on_id":"mala-dr9k.3","type":"blocks","created_at":"2026-01-06T04:47:18.98016933Z","created_by":"cyou"},{"issue_id":"mala-dr9k.5","depends_on_id":"mala-dr9k.4","type":"blocks","created_at":"2026-01-06T04:47:19.050650351Z","created_by":"cyou"}]}
{"id":"mala-dr9k.6","title":"[Review] 3 non-blocking findings from mala-dr9k.4","description":"## Review Findings\n\nThis issue consolidates 3 non-blocking findings from code review.\n\n**Source issue:** mala-dr9k.4\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Await cancelled tasks in interrupt test abort_callback\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** tests/unit/pipeline/test_issue_execution_coordinator.py:703-709\n\nIn `test_interrupt_takes_precedence_over_drain`, the local `abort_callback` cancels active tasks and clears `coord.active_tasks` but never awaits those cancelled tasks. The coordinator’s own contract/docstring for `abort_callback` says it should await all tasks before returning; not doing so can leave pending tasks at test teardown (warnings like “Task was destroyed but it is pending!”) and make the test flaky depending on the event loop/pytest-asyncio configuration. Capture the tasks, cancel them, `await asyncio.gather(..., return_exceptions=True)`, then clear the dict.\n\n---\n\n### Finding 2: [P2] Avoid significant code duplication in task waiting logic\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/pipeline/issue_execution_coordinator.py:296-324\n\nThe `drain_event` block (lines 275-324) duplicates approximately 30 lines of complex asynchronous logic for waiting and finalizing tasks that already exists at the end of the `run_loop` method (lines 476-502). This duplication increases maintenance burden and the risk of bugs if the two sections diverge. The loop can be simplified by making the fetching and spawning sections conditional on the drain state, allowing the existing wait and finalization logic at the end of the loop to be reused.\n\n---\n\n### Finding 3: [P3] Use 'validation_failed' exit reason when validation fails during drain\n\n**Priority:** P3\n**Reviewer:** gemini\n**Location:** src/pipeline/issue_execution_coordinator.py:291-294\n\nWhen `drain_event` is set and the final validation fails, the `RunResult` exit reason is set to `\"drained\"`. To maintain consistency with the main loop's exit logic, it should use `\"validation_failed\"` when the exit code is 1, providing better clarity for telemetry and logging.\n\n---\n\n\u003c!-- fp:bf4c39b8bc6e3646 --\u003e\n\u003c!-- fp:44eda980b264e18b --\u003e\n\u003c!-- fp:d033d9ddc663a644 --\u003e\n\u003c!-- review_finding:4b8bdf9440da --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-06T06:20:09.508274Z","created_by":"cyou","updated_at":"2026-01-06T06:29:19.500979146Z","closed_at":"2026-01-06T06:29:19.500979146Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-dr9k.4"],"dependencies":[{"issue_id":"mala-dr9k.6","depends_on_id":"mala-dr9k","type":"parent-child","created_at":"2026-01-06T06:20:09.508716879Z","created_by":"cyou"}]}
{"id":"mala-dr9k.7","title":"[Review] 1 non-blocking finding from mala-dr9k.2","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-dr9k.2\n**Highest priority:** P3\n\n---\n\n### Finding 1: [P3] Fix docstring: pgids removed even when kill fails\n\n**Priority:** P3\n**Reviewer:** codex\n**Location:** src/infra/tools/command_runner.py:177-186\n\n`kill_active_process_groups()` removes the snapshot pgids from `_SIGINT_FORWARD_PGIDS` *before* attempting `os.killpg()`, and it removes them even if `os.killpg()` raises `PermissionError` (or `ProcessLookupError`). That means the removed pgids are not necessarily “killed” by this call, and repeated calls won’t retry. If that’s intended, the docstring should reflect “removes attempted/snapshotted pgids” rather than “removes killed pgids”.\n\nExample wording:\n`Safe to call multiple times - removes attempted pgids from set.`\n\n---\n\n\u003c!-- fp:1cd863b3e40f5abf --\u003e\n\u003c!-- review_finding:2d75b6820e21 --\u003e","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-06T06:22:15.683652095Z","created_by":"cyou","updated_at":"2026-01-06T06:32:07.218617189Z","closed_at":"2026-01-06T06:32:07.218617189Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-dr9k.2"],"dependencies":[{"issue_id":"mala-dr9k.7","depends_on_id":"mala-dr9k","type":"parent-child","created_at":"2026-01-06T06:22:15.684071344Z","created_by":"cyou"}]}
{"id":"mala-dr9k.8","title":"[Review] 1 non-blocking finding from mala-dr9k.3","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-dr9k.3\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Skip interrupt-time validation if abort left tasks running\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/pipeline/issue_execution_coordinator.py:176-185\n\n`DeadlockHandler.abort_active_tasks()` can now return after `ABORT_GRACE_SECONDS` with still-pending (\"unresponsive\") tasks, but `_handle_interrupt()` may immediately call `validation_callback()` based on `completed_count`. In that scenario, validation can run concurrently with still-running agent tasks (which may still be mutating the repo), producing non-deterministic validation outcomes and potentially returning exit code `1` on what was intended as an interrupt. Consider having `abort_callback` report whether any tasks were still pending after the grace period and, if so, skip validation and force `exit_code=130` (or trigger a Stage 3-style hard stop before validating).\n\n---\n\n\u003c!-- fp:3b1b7f42369a6124 --\u003e\n\u003c!-- review_finding:1b9b0aec5ae6 --\u003e","notes":"Quality gate failed: Aborted due to unrecoverable error","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-06T07:00:42.469303876Z","created_by":"cyou","updated_at":"2026-01-06T15:17:06.057792256Z","closed_at":"2026-01-06T15:17:06.057792256Z","close_reason":"Closed","labels":["auto_generated","needs-followup","review_finding","source:mala-dr9k.3"],"dependencies":[{"issue_id":"mala-dr9k.8","depends_on_id":"mala-dr9k","type":"parent-child","created_at":"2026-01-06T07:00:42.469763991Z","created_by":"cyou"},{"issue_id":"mala-dr9k.8","depends_on_id":"mala-n1y1.8","type":"blocks","created_at":"2026-01-06T07:17:25.403825679Z","created_by":"cyou"}]}
{"id":"mala-dr9k.9","title":"[Review] 1 non-blocking finding from mala-dr9k.5","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-dr9k.5\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Assert validation actually ran (avoid false-positive pass)\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** tests/integration/orchestration/test_sigint_escalation.py:658-664\n\nAfter `READY`, the test only checks `proc.returncode == 1` and never asserts that `validation_callback()` executed. A regression that raises an exception during/after interrupt handling could still exit with code 1 and incorrectly satisfy the test.\n\nConsider asserting stdout contains the sentinel print from `validation_callback`, e.g.:\n\n```python\nstdout = output + (proc.stdout.read() if proc.stdout else \"\")\nassert \"VALIDATION_FAILED\" in stdout\n```\n\nThis makes the test validate the intended behavior (exit 1 *because* validation failed) rather than any generic failure that happens to return 1.\n\n---\n\n\u003c!-- fp:425415ce85b7a055 --\u003e\n\u003c!-- review_finding:950a7789de39 --\u003e","notes":"Confirmed: this tracks the missing validation assertion in tests/integration/orchestration/test_sigint_escalation.py:658 (assert validation actually ran).","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-06T15:36:20.840321396Z","created_by":"cyou","updated_at":"2026-01-06T17:39:05.400874518Z","closed_at":"2026-01-06T17:39:05.400874518Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-dr9k.5"],"dependencies":[{"issue_id":"mala-dr9k.9","depends_on_id":"mala-dr9k","type":"parent-child","created_at":"2026-01-06T15:36:20.840789745Z","created_by":"cyou"}]}
{"id":"mala-drqh","title":"[Review] src/domain/validation/tool_name_extractor.py:140-149: [P2] Skip flags after multi-token wrappers (uv/poetry/pipx run)","description":"## Review Finding\n\nThis issue was auto-created from a P2 review finding.\n\n**Source issue:** mala-fdp1.5\n**Reviewer:** codex\n**Location:** src/domain/validation/tool_name_extractor.py:140-149\n\n## Details\n\nAfter detecting a multi-token wrapper, the code sets `first` to the next token without skipping flags. Commands like `uv run --group dev pytest` or `poetry run --quiet pytest` will return `--group`/`--quiet` as the tool name. This is a common pattern for these wrappers and leads to incorrect extraction. The single-token wrapper path already skips leading flags; the multi-token wrapper path should do the same before selecting the tool.","notes":"Quality gate failed: Timeout after 60 minutes","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-02T03:17:47.873053898Z","created_by":"cyou","updated_at":"2026-01-03T02:38:24.667413431Z","closed_at":"2026-01-03T02:38:24.667413431Z","close_reason":"Closed","labels":["auto_generated","needs-followup","review_finding","review_finding:c6d6d26311e1","source:mala-fdp1.5"]}
{"id":"mala-dryi","title":"[Review] 1 non-blocking finding from mala-pd8v","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-pd8v\n**Highest priority:** P3\n\n---\n\n### Finding 1: [P3] Update _prepare_idle_retry docstring to mention pending_lint_commands\n\n**Priority:** P3\n**Reviewer:** claude\n**Location:** src/pipeline/idle_retry_policy.py:285-286\n\nThe docstring at line 285-286 says the method \"clears state.pending_tool_ids\" but now it also clears `pending_lint_commands`. Consider updating to: \"...and clears state.pending_tool_ids and state.pending_lint_commands.\"\n\n---\n\n\u003c!-- fp:6f9492950a9131aa --\u003e\n\u003c!-- review_finding:fbff85be315d --\u003e","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-05T01:31:56.1296586Z","created_by":"cyou","updated_at":"2026-01-05T01:38:06.891263934Z","closed_at":"2026-01-05T01:38:06.891263934Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-pd8v"]}
{"id":"mala-dxrk","title":"remove deadlock detection cli options, it should always be on","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-06T03:22:04.966395455Z","created_by":"cyou","updated_at":"2026-01-06T03:33:18.905006267Z","closed_at":"2026-01-06T03:33:18.905006267Z","close_reason":"Closed"}
{"id":"mala-dyt","title":"Improve tool call formatting with better colors and code pretty-printing","description":"## Goal\n1. **All tools**: Skip \"args:\" prefix and JSON brackets, use `key: value` format with distinct colors\n2. **Edit tools**: Pretty-print code fields with actual newlines and distinct coloring\n3. **CLI flag**: Change `--no-truncate` to `--verbose` / `-v`\n\n## Current output\n```\n  [mala-e47] ⚙ mcp__morphllm__edit_file\n    args:\n    {\n      \"path\": \"/home/cyou/mala/tests/test_orchestrator.py\",\n      \"code_edit\": \"    @pytest.mark.asyncio\\n    async def...\"\n    }\n```\n\n## Desired output\n```\n  [mala-e47] ⚙ mcp__morphllm__edit_file\n    path: /home/cyou/mala/tests/test_orchestrator.py     \u003c- key=CYAN, value=WHITE\n    instruction: Update mock_get_ready signature...\n    code_edit:                                           \u003c- key=CYAN\n        @pytest.mark.asyncio                             \u003c- code=GRAY/dim\n        async def test_success...\n```\n\n## Files to modify\n- `src/cli.py` - rename flag from --no-truncate to --verbose/-v\n- `src/logging/console.py` - formatting logic\n\n## Implementation\n1. Rename CLI flag to --verbose/-v\n2. Define constants for edit tool detection and code fields\n3. Rewrite _format_arguments() for key:value format with colors\n4. For edit tools, expand code fields with actual newlines\n5. Remove \"args:\" prefix from output","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-26T19:08:00.801228302Z","updated_at":"2025-12-26T19:14:26.272944778Z","closed_at":"2025-12-26T19:14:26.272944778Z","close_reason":"Closed"}
{"id":"mala-e0i","title":"Update RunMetadata for validation results + IssueResolution","description":"Context:\n- Run metadata should record per-issue validation outcomes and artifacts for observability.\n- Plan adds IssueResolution and validation results to metadata.\n\nScope:\n- In: extend RunMetadata schema to include per-issue ValidationResult/Artifacts and IssueResolution; add run-level validation result fields.\n- Out: CLI output formatting (separate concern).\n\nAcceptance Criteria:\n- RunMetadata persists new fields without breaking existing readers.\n- Serialization handles optional fields cleanly.\n\nTest Plan:\n- Add unit tests for metadata serialization/deserialization.\n- Run logging module tests.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-26T23:00:01.729378301Z","updated_at":"2025-12-27T00:31:03.493814554Z","closed_at":"2025-12-27T00:31:03.493814554Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-e0i","depends_on_id":"mala-5us","type":"blocks","created_at":"2025-12-26T23:00:26.569756144Z","created_by":"daemon"}]}
{"id":"mala-e0sn","title":"[Review] src/domain/quality_gate.py:63: [P3] Update QUALITY_GATE_IGNORED_COMMANDS to match extracted names","description":"## Review Finding\n\nThis issue was auto-created from a P3 review finding.\n\n**Source issue:** mala-fdp1.11\n**Reviewer:** claude\n**Location:** src/domain/quality_gate.py:63\n\n## Details\n\n`QUALITY_GATE_IGNORED_COMMANDS` contains `\"uv sync\"` but `extract_tool_name(\"uv sync\")` returns `\"uv\"`. This causes a mismatch in metadata filtering: if a SETUP command fails, the extracted name `\"uv\"` won't be filtered out of `validation_result.commands_failed`. While this only affects metadata display (not gate decisions), it's inconsistent with the documented intent. Consider updating to `{\"uv\"}` or using `QUALITY_GATE_IGNORED_KINDS` for filtering instead.","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-02T15:51:09.496999415Z","created_by":"cyou","updated_at":"2026-01-02T17:00:15.188210038Z","closed_at":"2026-01-02T17:00:15.188210038Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_finding:901f732470ec","source:mala-fdp1.11"],"dependencies":[{"issue_id":"mala-e0sn","depends_on_id":"mala-moty","type":"blocks","created_at":"2026-01-02T16:44:12.078919278Z","created_by":"daemon"}]}
{"id":"mala-e17r","title":"Epic: Split event_sink.py into protocol, base, and console modules","description":"## Summary\n`event_sink.py` defines the protocol (46 methods), base sink, and full console rendering in one 1,587-line file. This makes changes risky and discourages adding new sinks.\n\n## Primary Files\n- `src/infra/io/event_sink.py:1-1587`\n\n## Category\nCohesion\n\n## Source\ncodex\n\n## Fix Approach\nSplit into:\n- `event_protocol.py` (protocol only)\n- `base_sink.py` (BaseEventSink)\n- `console_sink.py` (ConsoleEventSink + helpers)\n\n## Non-Goals\n- Reducing the number of event sink methods\n- Breaking existing imports\n\n## Acceptance Criteria\n- [ ] Protocol, base class, and console sink live in separate modules\n- [ ] New sinks can be added without importing console formatting\n- [ ] Existing `from src.infra.io.event_sink import ...` continues to work via re-exports\n\n## Test Plan\n- Verify existing tests pass after split\n- Add new test sink using only protocol module","status":"closed","priority":3,"issue_type":"epic","created_at":"2026-01-03T05:50:22.389356123Z","created_by":"cyou","updated_at":"2026-01-03T18:27:40.219294236Z","closed_at":"2026-01-03T18:27:40.219294236Z","close_reason":"Closed"}
{"id":"mala-e17r.1","title":"Extract event_protocol.py with MalaEventSink protocol","description":"Extract the MalaEventSink protocol and EventRunConfig dataclass into a new file.\n\n## Files to create/modify\n- **CREATE**: `src/infra/io/event_protocol.py`\n- **MODIFY**: `src/infra/io/event_sink.py` (remove protocol, add import from new file)\n\n## Implementation\n1. Create `src/infra/io/event_protocol.py` containing:\n   - `EventRunConfig` dataclass (lines 27-54 of event_sink.py)\n   - `MalaEventSink` protocol class (lines 56-637 of event_sink.py)\n   - Required imports: `dataclass`, `Protocol`, `runtime_checkable`, `Any`\n\n2. Update `src/infra/io/event_sink.py`:\n   - Remove the EventRunConfig and MalaEventSink definitions\n   - Add: `from src.infra.io.event_protocol import EventRunConfig, MalaEventSink`\n   - Keep the re-export for backward compatibility\n\n## Validation\n- Run `uvx ty check` to verify type checking passes\n- Run `uv run pytest tests/test_event_sink.py -v` to verify tests pass\n- Existing imports like `from src.infra.io.event_sink import MalaEventSink` must continue to work","notes":"Quality gate failed: Quality gate failed: No commit with bd-mala-e17r.1 found after run baseline (stale commits from previous runs are rejected)\nLog: /home/cyou/.claude/projects/-home-cyou-mala/1299478c-af23-40ab-9f33-011327a8b7ae.jsonl","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-03T05:54:01.80632294Z","created_by":"cyou","updated_at":"2026-01-03T14:57:27.496204578Z","closed_at":"2026-01-03T14:57:27.496204578Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-e17r.1","depends_on_id":"mala-b79o.1","type":"blocks","created_at":"2026-01-03T05:56:14.229425946Z","created_by":"daemon"},{"issue_id":"mala-e17r.1","depends_on_id":"mala-jszz.1","type":"blocks","created_at":"2026-01-03T05:56:14.244659618Z","created_by":"daemon"},{"issue_id":"mala-e17r.1","depends_on_id":"mala-e17r","type":"parent-child","created_at":"2026-01-03T17:57:22.379804002Z","created_by":"daemon"}]}
{"id":"mala-e17r.2","title":"Extract base_sink.py with BaseEventSink and NullEventSink","description":"Extract BaseEventSink and NullEventSink classes into a new file.\n\n## Prerequisites\n- Task mala-e17r.1 must be completed first (event_protocol.py exists)\n\n## Files to create/modify\n- **CREATE**: `src/infra/io/base_sink.py`\n- **MODIFY**: `src/infra/io/event_sink.py` (remove classes, add import from new file)\n\n## Implementation\n1. Create `src/infra/io/base_sink.py` containing:\n   - Import from event_protocol: `from src.infra.io.event_protocol import EventRunConfig, MalaEventSink`\n   - `BaseEventSink` class (lines 639-937 of original event_sink.py)\n   - `NullEventSink` class (lines 940-956 of original event_sink.py)\n   - Protocol assertion: `assert isinstance(BaseEventSink(), MalaEventSink)`\n   - Protocol assertion: `assert isinstance(NullEventSink(), MalaEventSink)`\n\n2. Update `src/infra/io/event_sink.py`:\n   - Remove BaseEventSink and NullEventSink definitions\n   - Add: `from src.infra.io.base_sink import BaseEventSink, NullEventSink`\n   - Keep re-exports for backward compatibility\n\n## Validation\n- Run `uvx ty check` to verify type checking passes\n- Run `uv run pytest tests/test_event_sink.py -v` to verify tests pass\n- Existing imports like `from src.infra.io.event_sink import NullEventSink` must continue to work","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-03T05:54:16.064648448Z","created_by":"cyou","updated_at":"2026-01-03T16:38:02.64432441Z","closed_at":"2026-01-03T16:38:02.64432441Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-e17r.2","depends_on_id":"mala-e17r.1","type":"blocks","created_at":"2026-01-03T05:55:01.581157833Z","created_by":"daemon"},{"issue_id":"mala-e17r.2","depends_on_id":"mala-e17r","type":"parent-child","created_at":"2026-01-03T17:57:22.418409481Z","created_by":"daemon"}]}
{"id":"mala-e17r.3","title":"Extract console_sink.py with ConsoleEventSink","description":"Extract ConsoleEventSink class into a new file.\n\n## Prerequisites\n- Task mala-e17r.2 must be completed first (base_sink.py exists with BaseEventSink)\n\n## Files to create/modify\n- **CREATE**: `src/infra/io/console_sink.py`\n- **MODIFY**: `src/infra/io/event_sink.py` (remove class, add import from new file)\n\n## Implementation\n1. Create `src/infra/io/console_sink.py` containing:\n   - Import: `import re`\n   - Import: `from typing import Any`\n   - Import from base_sink: `from src.infra.io.base_sink import BaseEventSink`\n   - Import from event_protocol: `from src.infra.io.event_protocol import EventRunConfig`\n   - Import log helpers: `from src.infra.io.log_output.console import Colors, log, log_agent_text, log_tool, log_verbose, truncate_text`\n   - `ConsoleEventSink` class (lines 959-1582 of original event_sink.py)\n   - Protocol assertion: `from src.infra.io.event_protocol import MalaEventSink` and `assert isinstance(ConsoleEventSink(), MalaEventSink)`\n\n2. Update `src/infra/io/event_sink.py`:\n   - Remove ConsoleEventSink definition\n   - Remove the re import (no longer needed in event_sink.py)\n   - Add: `from src.infra.io.console_sink import ConsoleEventSink`\n   - Keep re-export for backward compatibility\n\n## Validation\n- Run `uvx ty check` to verify type checking passes\n- Run `uv run pytest tests/test_event_sink.py -v` to verify tests pass\n- Existing imports like `from src.infra.io.event_sink import ConsoleEventSink` must continue to work","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-03T05:54:32.282066759Z","created_by":"cyou","updated_at":"2026-01-03T16:43:50.715162274Z","closed_at":"2026-01-03T16:43:50.715162274Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-e17r.3","depends_on_id":"mala-e17r.2","type":"blocks","created_at":"2026-01-03T05:55:01.644459669Z","created_by":"daemon"},{"issue_id":"mala-e17r.3","depends_on_id":"mala-e17r","type":"parent-child","created_at":"2026-01-03T17:57:22.453676734Z","created_by":"daemon"}]}
{"id":"mala-e17r.4","title":"Update __init__.py imports for new event sink module structure","description":"Update package __init__.py to import from new module structure and add BaseEventSink export.\n\n## Prerequisites\n- Tasks mala-e17r.1, mala-e17r.2, mala-e17r.3 must be completed first\n\n## Files to modify\n- **MODIFY**: `src/infra/io/__init__.py`\n\n## Implementation\nUpdate `src/infra/io/__init__.py`:\n\n1. Change the import block from:\n   ```python\n   from src.infra.io.event_sink import (\n       ConsoleEventSink,\n       EventRunConfig,\n       MalaEventSink,\n       NullEventSink,\n   )\n   ```\n   \n   To import from specific modules (optional, for clarity):\n   ```python\n   from src.infra.io.event_protocol import EventRunConfig, MalaEventSink\n   from src.infra.io.base_sink import BaseEventSink, NullEventSink\n   from src.infra.io.console_sink import ConsoleEventSink\n   ```\n\n2. Add `BaseEventSink` to `__all__` list (new export)\n\nNote: Importing from event_sink.py will still work via re-exports, but importing directly from the specific modules is cleaner and more explicit.\n\n## Validation\n- Run `uvx ty check` to verify type checking passes\n- Run `uv run pytest tests/test_event_sink.py -v` to verify tests pass\n- Verify `from src.infra.io import BaseEventSink` works","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-03T05:54:49.326502685Z","created_by":"cyou","updated_at":"2026-01-03T16:50:34.071858465Z","closed_at":"2026-01-03T16:50:34.071858465Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-e17r.4","depends_on_id":"mala-e17r.3","type":"blocks","created_at":"2026-01-03T05:55:01.700295783Z","created_by":"daemon"},{"issue_id":"mala-e17r.4","depends_on_id":"mala-e17r","type":"parent-child","created_at":"2026-01-03T17:57:22.515671083Z","created_by":"daemon"}]}
{"id":"mala-e37n","title":"[Review] src/domain/validation/config_merger.py:190-193: [P2] Treat explicit null coverage as disable override","description":"## Review Finding\n\nThis issue was auto-created from a P2 review finding.\n\n**Source issue:** mala-c7wb\n**Reviewer:** codex\n**Location:** src/domain/validation/config_merger.py:190-193\n\n## Details\n\n`ValidationConfig` docs state that `coverage` can be set to null to disable it. When a preset defines coverage and a user config sets `coverage: null`, the parser yields `user_cov=None`, but `_merge_coverage` treats `None` as “inherit” and keeps the preset coverage. This makes it impossible to disable coverage when extending a preset. Consider tracking whether `coverage` was explicitly provided (e.g., a sentinel like `DISABLED`) so explicit null can override the preset.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-02T03:55:38.858200445Z","created_by":"cyou","updated_at":"2026-01-02T04:14:26.247852074Z","closed_at":"2026-01-02T04:14:26.247852074Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_finding:9c69e7e9be86","source:mala-c7wb"]}
{"id":"mala-e47","title":"Update orchestrator tests for get_ready signature and max_agents default","description":"Tests currently mock get_ready without the new suppress_warn_ids param and still assert max_agents==3 even though default is now unlimited (None). Update tests to match current behavior and avoid TypeError.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-26T15:34:00.738271737Z","updated_at":"2025-12-26T18:42:41.370094167Z","closed_at":"2025-12-26T18:42:41.370094167Z","close_reason":"Closed"}
{"id":"mala-e8f","title":"CLI commands for periodic checks: healthcheck, architecture review, integration tests + coverage","description":"Context:\n- Want automated periodic quality checks during long runs\n- Currently manual: run healthcheck, architecture review separately\n- Related to mala-0hn (health monitoring)\n\nLocation:\n- src/cli/cli.py (CLI entry point)\n- src/orchestration/orchestrator.py (main loop where checks would trigger)\n\nScope:\n- In: CLI flags for periodic check configuration (--healthcheck-every N)\n- In: Healthcheck runs after N issues complete or T minutes\n- In: Architecture review triggered on significant changes\n- In: Integration tests + coverage check at intervals\n- Out: Full CI pipeline; blocking checks (these are advisory)\n\nAcceptance Criteria:\n- `mala run --healthcheck-every 5` runs healthcheck every 5 completed issues\n- `mala run --review-every 10` runs architecture review every 10 issues\n- `mala run --test-every 15` runs integration tests every 15 issues\n- Results logged to run metadata; failures don't block but are flagged\n\nTest Plan:\n- Unit: Test interval calculation and trigger logic\n- Integration: Mock check commands, verify they're called at correct intervals\n- Manual: Run mala with periodic checks, observe execution\n\nNotes for Agent:\n- Healthcheck = uvx ty check + uvx ruff check\n- Architecture review = cerberus:architecture-review skill\n- Consider making checks async to not block main loop","status":"closed","priority":3,"issue_type":"feature","created_at":"2025-12-28T06:21:39.079301328Z","updated_at":"2026-01-03T04:52:36.654744213Z","closed_at":"2026-01-03T04:52:36.654744213Z","close_reason":"Closed"}
{"id":"mala-e8ks","title":"[Review] tests/test_run_metadata.py:1077-1089: [P3] Test risks leaking log handlers on assertion failure","description":"## Review Finding\n\nThis issue was auto-created from a P3 review finding.\n\n**Source issue:** mala-nzk6\n**Reviewer:** gemini\n**Location:** tests/test_run_metadata.py:1077-1089\n\n## Details\n\nThe test attaches a handler to the global `src` logger via `RunMetadata`. If the assertion checks fail before `metadata.cleanup()` is called, the handler will remain attached, potentially polluting subsequent tests. Wrap the logic in a `try...finally` block to ensure cleanup always runs.","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-02T01:08:22.751771117Z","created_by":"cyou","updated_at":"2026-01-02T01:10:53.696239305Z","closed_at":"2026-01-02T01:10:53.696239305Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_finding:552a7b6c2c65","source:mala-nzk6"]}
{"id":"mala-ednv","title":"Replace commit_issues_async with bd sync to ensure beads changes are persisted","description":"## Problem\nWhen mala closes an issue and the EpicVerifier closes eligible epics, the epic closure may not be persisted to git because:\n\n1. `commit_issues_async()` only does `git add .beads/issues.jsonl \u0026\u0026 git commit`\n2. It relies on the bd daemon to auto-export SQLite → JSONL\n3. If daemon is slow/not running, the JSONL may not have the latest epic closure\n\n**Evidence:** mala-aqp4 epic stayed open despite all 4 children being closed. No 'beads: close completed issues' commit appeared after the final child closed.\n\n## Root Cause\n`_finalize_run()` in orchestrator.py:1041 calls `commit_issues_async()` without first ensuring the JSONL is up-to-date.\n\n## Fix\nAdd `bd export` call before `git add` in `commit_issues_async()`, or add a new `export_async()` method and call it in `_finalize_run()` before committing.\n\n## Primary Files\n- src/beads_client.py (commit_issues_async)\n- src/orchestrator.py (_finalize_run)\n\n## Acceptance Criteria\n- Epic closures are persisted to git even if bd daemon is not running\n- `bd export` (or equivalent) is called before committing .beads/issues.jsonl\n- Test: close all children of an epic, verify epic closure appears in git log","notes":"Correction: Instead of adding bd export before git commit, replace the entire commit_issues_async() implementation with a single `bd sync` call. bd sync handles export + commit + pull + push in one atomic operation.","status":"closed","priority":1,"issue_type":"bug","created_at":"2025-12-30T22:50:09.159491419Z","created_by":"cyou","updated_at":"2025-12-30T23:51:34.442312116Z","closed_at":"2025-12-30T23:51:34.442312116Z","close_reason":"Closed"}
{"id":"mala-eeof","title":"[Review] 5 non-blocking findings from mala-e17r.2","description":"## Review Findings\n\nThis issue consolidates 5 non-blocking findings from code review.\n\n**Source issue:** mala-e17r.2\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Remove backward-compat re-exports from event_sink\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/infra/io/event_sink.py:12-26\n\nProject rules explicitly forbid backward-compatibility shims and re-exports when moving modules. This change re-exports `BaseEventSink`/`NullEventSink` from `event_sink.py` (e.g. `from .base_sink import BaseEventSink, NullEventSink` and the `__all__` entries), which keeps two public entry points and violates the migration rules. Update imports across the codebase to `src.infra.io.base_sink` and remove these re-exports instead.\n\n---\n\n### Finding 2: [P2] Redundant logging for quality gates\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/infra/io/event_sink.py:218-230\n\nThe `on_gate_result` method logs a success message even if `on_gate_passed` has already logged one, leading to duplicate output in the console. For failures, it also logs an additional red status line that is redundant with `on_gate_failed`.\n\n---\n\n### Finding 3: [P2] Regression: Missing run configuration in on_run_started\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/infra/io/event_sink.py:49-56\n\nThe updated `on_run_started` implementation no longer logs critical configuration details such as `repo_path`, `epic_id`, `only_ids`, and other filters. This makes it difficult for users to verify what the orchestrator is processing at startup.\n\n---\n\n### Finding 4: [P2] Redundant logging for agent and issue completion\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/infra/io/event_sink.py:313-325\n\nBoth `on_agent_completed` and `on_issue_completed` log nearly identical messages with the same icons. Since these often trigger at the same time for the same issue, it creates cluttered and repetitive console output.\n\n---\n\n### Finding 5: [P2] Regression: Metadata save path is now verbose\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/infra/io/event_sink.py:395-396\n\nLogging of the run metadata path was moved to `log_verbose`, meaning it no longer appears in the standard output. This makes it harder for users to locate the summary artifacts of their run without enabling verbose mode.\n\n---\n","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T16:38:02.665598504Z","created_by":"cyou","updated_at":"2026-01-03T16:48:01.279529045Z","closed_at":"2026-01-03T16:48:01.279529045Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_findings:012a82925ce4","source:mala-e17r.2"]}
{"id":"mala-ejeq","title":"Move track_review_issues from env var to mala.yaml","description":"Currently configured via MALA_TRACK_REVIEW_ISSUES env var. Controls whether to create beads issues for P2/P3 review findings. Should be in mala.yaml code_review config for consistency.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-10T21:24:53.377937656Z","created_by":"cyou","updated_at":"2026-01-11T00:09:22.008294132Z","closed_at":"2026-01-11T00:09:22.008294132Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-ejeq","depends_on_id":"mala-zgl0","type":"blocks","created_at":"2026-01-10T21:26:38.068633513Z","created_by":"cyou"}]}
{"id":"mala-elat","title":"[Review] .beads/issues.jsonl:303: [P3] Issue mala-sp2s closed while implementation remains","description":"## Review Finding\n\nThis issue was auto-created from a P3 review finding.\n\n**Source issue:** mala-apsz\n**Reviewer:** gemini\n**Location:** .beads/issues.jsonl:303\n\n## Details\n\nCommit `2b98c2d` marks issue `mala-sp2s` (\"Legacy init removed from MalaOrchestrator\") as closed in `.beads/issues.jsonl`. However, the legacy `__init__` method and its ~100 lines of compatibility logic still exist in `src/orchestration/orchestrator.py` (lines 121-220). While the issue is marked as advisory (P3), closing it creates a mismatch between the tracking system and the actual codebase state.","notes":"Quality gate failed: External review failed: spawn failed: Artifact written: /home/cyou/.claude/projects/-home-cyou-mala/cerberus/d45a6cb8-9bf5-4ed0-b8a7-5887ac4be6d7/latest.md\nReview gate already active (status: pending, age: 51s)\nUse /home/cyou/.claude/plugins/cache/cerberus/cerberus/1.10.6/bin/review-gate resolve to complete the existing gate first.\nLog: /home/cyou/.claude/projects/-home-cyou-mala/d45a6cb8-9bf5-4ed0-b8a7-5887ac4be6d7.jsonl","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-01T23:49:33.666901533Z","created_by":"cyou","updated_at":"2026-01-02T00:48:11.900477583Z","closed_at":"2026-01-02T00:48:11.900477583Z","close_reason":"Closed","labels":["auto_generated","needs-followup","review_finding","review_finding:c6342fdbce63","source:mala-apsz"]}
{"id":"mala-esc8","title":"[Advisory] Periodic triggers should parse code_review configuration con...","description":"## Context\nThis issue was auto-created by epic verification for epic `mala-92r6`.\n\n## Epic Description / Acceptance Criteria\nTitle: Epic: Unified Code Review Configuration\n\n## Summary\nConsolidate all code review settings into a unified `code_review` config block in `mala.yaml`, supporting:\n- Per-issue reviews via `session_end` trigger\n- Cumulative reviews via `epic_completion` trigger  \n- Cumulative reviews via `run_end` trigger (NEW)\n\n## Goals\n- Same `CodeReviewConfig` YAML shape at all trigger levels\n- Move CLI options (reviewer_type, max_retries, cerberus args) into config\n- Baseline tracking for cumulative reviews (`since_run_start` or `since_last_review`)\n- Explicit opt-in (`enabled: true`) - breaking change from implicit reviews\n\n## Key Decisions\n1. Code review is a trigger action, not a CommandKind\n2. No config inheritance - each trigger has full config\n3. `failure_mode` for execution_error, `finding_threshold` for findings\n4. Per-trigger-instance baseline tracking (e.g., `epic_completion:\u003cepic_id\u003e`)\n5. Baseline advances on review completion, not on execution_error\n\n## Plan\nSee: plans/2026-01-09-cumulative-code-review-plan.md\n\n## Acceptance Criteria\n- [ ] Unified `code_review` config block in triggers\n- [ ] Config loading with validation rules\n- [ ] CumulativeReviewRunner with baseline tracking\n- [ ] run_end trigger implemented\n- [ ] CLI flags deprecated with warnings\n- [ ] Documentation updated\n\n## Commit Scope\n- Range hint: 6d0b6ddde14f1299dbc98fcc3fb40a8978deb1cd^..27ddf215aa1c99a5ffe8426bf739ed25d8baf612\n- Commits:\n- 27ddf215aa1c99a5ffe8426bf739ed25d8baf612 bd-mala-92r6.25: Add dedup + metadata for cumulative review findings\n- 2a15d1057b6f9f0319fe35246a4e31dec40bfc42 bd-mala-92r6.30: Gate validation_passed event on no prior failure\n- 4b3b2f605eb7e07c5bf60ad13bbb744c1ba7648b bd-mala-92r6.24: Clear last_failure when findings remediation succeeds\n- bc33179c8db37d0fe97b4f7c200d7c684ee64e1c bd-mala-92r6.24: Fix findings remediation exhaustion to record failure not abort\n- 19b1f07a90c01dcfde3600316ba40f6f82cfe326 bd-mala-92r6.29: Add agent_sdk_timeout/model to code_review config\n- 28e790ddc6dbdf7980cdcfdfd5513419c441af2f bd-mala-92r6.29: Remove deprecated top-level reviewer_type config and CLI flag\n- 379bf9f82c8f5dd1294210ccc2f62648d7989fbf bd-mala-92r6.14: Add code_review config documentation to validation.md\n- 1acb59cdfab996ca9e57ab077cb0527588a92269 bd-mala-92r6.24: Respect failure_mode for findings exceeding threshold\n- 321411f68c0681a95a815e2fdc035d42a0067f65 bd-mala-92r6.14: Fix test and lint issues\n- 203bf90d19765adb5638c886587bf43dabc28554 bd-mala-92r6.27: Fix lint errors in new test files\n- a69c1c348196c535f3336fe5e7c73a4a8bd59f4b bd-mala-92r6.24: Add finding_threshold + remediate handling for code_review\n- b5fab655aedd8871237f1b202bec7e8e172f0df7 bd-mala-92r6.14: Add deprecated CLI flags section to cli-reference.md\n- 55d404f2b06640a2b23eabe7d889eb61d773d03a bd-mala-92r6.28: Add integration tests for run_end YAML + code_review thresholds\n- a37513be42564ebd6fbc0f8d11c3f5b3c5b0b44f bd-mala-92r6.26: Capture run_start_commit for epic_completion code_review\n- 437235b783eed1ade3914062088b72587deff675 bd-mala-92r6.27: Update docs for unified code_review config and deprecations\n- 9f903384d59adab31bb6b8f808ca830a041f5ab3 Revert \"bd-mala-92r6.22: Remove out-of-scope run_end trigger parsing\"\n- 9a097cf6aea724dcb593e76ab5913ac3657222cf bd-mala-92r6.23: Fix code_review config validation per spec\n- 0eaf0bf92e694b63fd23fc0fb14246dd72aa5a4f bd-mala-92r6.13: Use UserWarning for visible deprecation, clarify default\n- bfeaf4108eff24e7895cd54a4a4647678460d620 bd-mala-92r6.13: Fix trigger code_review.reviewer_type precedence\n- 7567c31ba59954373d7c3b6a441f8e36b70d17a5 bd-mala-92r6.13: Wire --reviewer-type CLI flag into precedence logic\n- 546280d39ac07a4fb52025c81663b27defdf8c80 bd-mala-92r6.22: Add type ignore for invalid-arg test cases\n- 7390ea3db38b49791f78209042eca7d261ede38d bd-mala-92r6.22: Remove out-of-scope run_end trigger parsing\n- a879cb11c7533cd860a1010c3cdecfb4342ff4f6 bd-mala-92r6.13: Add CLI deprecation warnings + versioned behavior detection\n- a32bef86e2ed9ab8bd74baab9b71f139bc454ee4 bd-mala-92r6.3: Implement config_loader code_review parsing + validation\n- 19b9c7755207f26d1ef5e7361b1ce4d9cebeb866 bd-mala-92r6.21: Move FailureMode import to module level\n- cd37a1ab95012118b7ee29a1d6e8030b5706b237 bd-mala-92r6.20: Respect failure_mode on review execution errors\n- 3ba424a7125bed11f30558ebc9da4f73948cb629 bd-mala-92r6.8: Add diff_content to ReviewInput and pass from CumulativeReviewRunner\n- 17097e5c34bcbf3854cdadaf518080a02be09608 bd-mala-92r6.8: Address review feedback - fix race condition and clarify interface\n- d308a9b1703ed51f14aec467e64bd54a856d8b80 bd-mala-92r6.19: Fix get_head_commit docstring to reflect actual git behavior\n- 753d67e2f0df2acf3bb37121990834be2d01b457 bd-mala-92r6.8: Implement CumulativeReviewRunner diff handling + review execution\n- 31f415b486bc71f07dbb6eb93a9f6901868985b6 bd-mala-92r6.12: Address review feedback - guard code_review, inject run_metadata, add fixer guard\n- 637f8ec79834dc5a168d325ab2c09e168656b5c4 bd-mala-92r6.18: Add baseline commit reachability check for shallow clones\n- 904823fa183f0198f59fb30cd6f8b8dde171a61c bd-mala-92r6.2: Reduce scope to single session_end integration test\n- b526bb0dd593d2f31ff782c53638147cb897bd87 bd-mala-92r6.12: Integrate CumulativeReviewRunner with RunCoordinator trigger handling\n- 3264c226510953e546267dbdb7c18903bf63938b bd-mala-92r6.7: Fix docstrings for GitUtils baseline/HEAD methods\n- aa72b26529e3124a8db793d429364e8e94ba26ae bd-mala-92r6.7: Implement CumulativeReviewRunner baseline determination\n- 6b975db687021e7cb5d28b5582411a07a7f926de bd-mala-92r6.11: Fix fire_on guard to skip only when total_count==0\n- 452845ba3d61a8cdef534daa9adee0461f14421e bd-mala-92r6.11: Add type ignore comments for mock assignments\n- c8f1fecae0869ede66e7bc987a19ffc70de84230 bd-mala-92r6.11: Implement run_start_commit capture + run_end trigger firing\n- d59d34f7e9884ebe2430a7a4cb5c8465dcac20d3 bd-mala-92r6.6: Fix ty type error in test_validation_config\n- b3e59e289ebfe92a8a68a1d758424a8497bdc5ef bd-mala-92r6.6: Add git_utils dependency and RunCoordinator wiring\n- f34386159192795fcdeecdd0d7e9bd925aa5b784 bd-mala-92r6.17: Fix ty type error in test_validation_config\n- 25e82762e0b7eb5859d5a489704b4ef12103de38 bd-mala-92r6.2: Remove code_review from periodic trigger (scope fix)\n- 4e5baa5c5cf1864c95661251f6e0e05c150ce2fb bd-mala-92r6.10: Remove mocked skeleton methods from integration test\n- 34ffec3524ef5e1d6de807c6caef18de000306bf bd-mala-92r6.17: Unescape special characters in git-quoted paths\n- 50b0d4ab4c611a14a537dc0e542c6ae80ca5e44f bd-mala-92r6.6: Add CumulativeReviewRunner skeleton + failing integration test\n- fc83892e95757666cfed9d4bc2911dcff4e395c9 bd-mala-92r6.10: Fix skeleton methods to raise NotImplementedError when configured\n- ed18c8972e29065915ad1db46cba1664e687aa72 bd-mala-92r6.2: Add code_review parsing stub + integration tests\n- ae49c08e3b3c8711caa097bef72b262dbae9b50d bd-mala-92r6.16: Fix _parse_rename_path brace handling + unquote paths\n- bd4e3fc002a951b3661490fd1528aa724640e0f5 bd-mala-92r6.10: Add run_start_commit capture + run_end trigger skeleton\n\n[... 5 more commits omitted]\n\n## Child Issues\n- mala-92r6.1\n- mala-92r6.10\n- mala-92r6.11\n- mala-92r6.12\n- mala-92r6.13\n- mala-92r6.14\n- mala-92r6.15\n- mala-92r6.16\n- mala-92r6.17\n- mala-92r6.18\n- mala-92r6.19\n- mala-92r6.2\n- mala-92r6.20\n- mala-92r6.21\n- mala-92r6.22\n- mala-92r6.23\n- mala-92r6.24\n- mala-92r6.25\n- mala-92r6.26\n- mala-92r6.27\n- mala-92r6.28\n- mala-92r6.29\n- mala-92r6.3\n- mala-92r6.30\n- mala-92r6.4\n- mala-92r6.5\n- mala-92r6.6\n- mala-92r6.7\n- mala-92r6.8\n- mala-92r6.9\n\n## Unmet Criterion\nPeriodic triggers should parse code_review configuration consistently with other trigger types\n\n## Evidence\nIn /home/cyou/mala/src/domain/validation/config_loader.py:814, `_PERIODIC_FIELDS` doesn't include 'code_review' and the `_parse_periodic_trigger()` function doesn't parse it, despite `PeriodicTriggerConfig` inheriting the field from `BaseTriggerConfig`. All other trigger types (epic_completion, session_end, run_end) properly parse code_review configuration.\n\n## Priority\nP2 (informational)\n\n## Resolution\nThis is advisory (P2/P3) and does not block epic closure. When complete, close this issue.\n","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-10T01:44:50.36670259Z","created_by":"cyou","updated_at":"2026-01-10T01:49:30.462165252Z","closed_at":"2026-01-10T01:49:30.462165252Z","close_reason":"Closed","labels":["auto_generated","epic_remediation:mala-92r6:4df2b9cadde55d8be171fe8f6c1278d5ede3bc3e4b591308073df402129b8472"]}
{"id":"mala-et9i","title":"[Review] 4 non-blocking findings from mala-03qr","description":"## Review Findings\n\nThis issue consolidates 4 non-blocking findings from code review.\n\n**Source issue:** mala-03qr\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P3] Align docs with actually-enforced import-linter contracts\n\n**Priority:** P3\n**Reviewer:** codex\n**Location:** docs/architecture.md:530-536\n\n`docs/architecture.md` lists “Infra subpackage isolation” as an **expected-to-pass** constraint including `src.infra.hooks`, but the corresponding hooks-isolation contract is currently commented out in `pyproject.toml` (and the enforced contracts are directional rather than “no subpackage depends on any other”). This makes the doc imply enforcement that doesn’t exist today; either adjust the bullet to reflect the active contracts (e.g., omit `hooks` / clarify directionality) or move it under the aspirational section.\n\n---\n\n### Finding 2: [P2] Inconsistent layer documentation for src.pipeline in architecture.md\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** docs/architecture.md:57\n\nThe Layer Dependencies table in `docs/architecture.md` states that `src.pipeline` can import `domain` and `core`, but adds `(not infra.clients)` in parentheses. This is confusing because it omits `infra` from the primary allowed layers list while simultaneously implying it is partially allowed. Given that `AgentSessionRunner` depends on `src.infra.agent_runtime`, the documentation should explicitly list `infra` (or `infra` except `clients`) as an allowed dependency for the pipeline layer to match the actual architectural boundary and the import-linter contracts.\n\n---\n\n### Finding 3: [P2] Incomplete guidance in AgentRuntimeBuilder error message\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/infra/agent_runtime.py:218-222\n\nThe `ValueError` message in `AgentRuntimeBuilder._build_mcp_servers` suggests explicitly providing servers via `with_mcp(servers={...})` as an alternative to the factory. However, the commit introduced a new requirement for the lock enforcement hook. If a user provides custom servers that lack the locking tools, the runtime will be broken (file writes blocked) unless they also disable the hook. The error message should be updated to include `...or disable lock enforcement via with_hooks(include_lock_enforcement_hook=False)` to provide clear guidance as requested in the original finding 4.\n\n---\n\n### Finding 4: [P2] Fragile boolean defaults in AgentRuntimeBuilder.with_hooks\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/infra/agent_runtime.py:133-154\n\nThe `with_hooks` method uses `True` as defaults for its boolean parameters. In a builder pattern, multiple calls to `with_hooks` will cause any previously set `False` values to be reset to `True` if they are omitted in the subsequent call. This makes the configuration fragile and deviates from standard builder behavior where omitted values preserve current state. Use a sentinel like `None` or `_UNSET` to only update fields that are explicitly provided.\n\n---\n\n\u003c!-- fp:82a7b7c320fb42eb --\u003e\n\u003c!-- fp:5892367d2b899f6f --\u003e\n\u003c!-- fp:c398dd5dfce31f37 --\u003e\n\u003c!-- fp:b3d614810a983c63 --\u003e\n\u003c!-- review_finding:4a1f42ebc925 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-05T04:54:42.007628873Z","created_by":"cyou","updated_at":"2026-01-05T05:01:39.547993992Z","closed_at":"2026-01-05T05:01:39.547993992Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-03qr"]}
{"id":"mala-eyxq","title":"Decouple pipeline from infra clients in LifecycleEffectHandler","description":"Pipeline now imports infra client helpers (format_review_issues) + ReviewResult type via LifecycleEffectHandler. This couples pipeline to infra client implementations and blocks stricter layer rules.\\n\\nGoal: move formatter + review result model into pipeline/domain (or expose via protocol) and inject from orchestration; remove direct infra.clients imports from pipeline.\\n\\nFiles: src/pipeline/lifecycle_effect_handler.py, src/infra/clients/cerberus_review.py, src/infra/clients/review_output_parser.py.\\n\\nAcceptance: pipeline has no imports from src.infra.clients; tests updated accordingly.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-05T01:26:27.54142757Z","created_by":"cyou","updated_at":"2026-01-05T01:40:58.22564131Z","closed_at":"2026-01-05T01:40:58.22564131Z","close_reason":"Closed"}
{"id":"mala-ez0x","title":"[Review] 1 non-blocking finding from mala-b79o.3","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-b79o.3\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Missing issue_id in on_review_warning\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/infra/io/event_sink.py:335-341\n\nThe new `on_review_warning` method is missing the `issue_id` parameter, which is inconsistent with all other review-related events (`on_review_started`, `on_review_passed`, `on_review_retry`). In `ConsoleEventSink`, omitting `issue_id` when calling `log` prevents the warning from being correctly attributed to the specific issue in multi-agent or multi-issue execution environments.\n\n---\n","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T06:44:28.084091615Z","created_by":"cyou","updated_at":"2026-01-03T06:47:40.118411865Z","closed_at":"2026-01-03T06:47:40.118411865Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_findings:f78a7aeaa188","source:mala-b79o.3"]}
{"id":"mala-f30x","title":"Move max_epic_verification_retries from env var to mala.yaml","description":"Currently configured via MALA_MAX_EPIC_VERIFICATION_RETRIES env var in MalaConfig. Should be moved to mala.yaml config for consistency. Add field to ValidationConfig and wire through factory to orchestrator.","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-10T21:22:03.498007685Z","created_by":"cyou","updated_at":"2026-01-10T22:15:47.973862615Z","closed_at":"2026-01-10T22:15:47.973862615Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-f30x","depends_on_id":"mala-fhm5","type":"blocks","created_at":"2026-01-10T21:26:08.35747794Z","created_by":"cyou"}]}
{"id":"mala-f69","title":"Default to unlimited concurrent agents","description":"Goal: Change the default for --max-agents/-n to unlimited (no cap on concurrent agents).\n\nCurrent behavior: max_agents defaults to 3, limiting concurrent agent execution.\n\nDesired behavior: By default, spawn as many agents as there are ready issues. Users can still use --max-agents to set a limit if needed.\n\nScope/files: src/cli.py, src/orchestrator.py.\n\nWork:\n- Change max_agents default from 3 to None (unlimited).\n- Update orchestrator logic to handle None as 'spawn all ready issues'.\n- Update help text to reflect new default.\n\nAcceptance criteria:\n- 'mala run' with no flags spawns agents for all ready issues.\n- 'mala run --max-agents 3' limits to 3 concurrent agents.\n- Logging/status output reflects unlimited when no cap set.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-26T00:18:53.28766291Z","updated_at":"2025-12-26T00:50:20.634125479Z","closed_at":"2025-12-26T00:50:20.634125479Z","close_reason":"Closed"}
{"id":"mala-fag4","title":"[Review] 1 non-blocking finding from mala-rmhn","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-rmhn\n**Highest priority:** P3\n\n---\n\n### Finding 1: [P3] Update docs still referencing removed CLI flags\n\n**Priority:** P3\n**Reviewer:** codex\n**Location:** docs/quality-hardening-plan.md:241-244\n\n`docs/quality-hardening-plan.md` still references `--disable-validations`, which is removed by this commit. After this change, following those examples will fail; please update them to `--disable` (repeatable and/or CSV) and adjust the example values accordingly (e.g., `--disable slow-tests`, `--disable followup-on-run-validate-fail`, etc.).\n\n---\n\n\u003c!-- fp:c0a1023ad442076d --\u003e\n\u003c!-- review_finding:f3fbb1f24b28 --\u003e","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-06T03:37:29.424722744Z","created_by":"cyou","updated_at":"2026-01-06T03:44:20.19162305Z","closed_at":"2026-01-06T03:44:20.19162305Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-rmhn"]}
{"id":"mala-fdp1","title":"Epic: Language-Agnostic Configuration via mala.yaml","description":"# Epic: Language-Agnostic Configuration via mala.yaml\n\n## Context\n- **Spec**: `docs/2026-01-01-language-agnostic-config-spec.md`\n- **Plan**: `docs/2026-01-01-language-agnostic-config-plan.md`\n- Replace hard-coded Python commands (pytest, ruff, ty, uv) with user-configurable `mala.yaml`\n- Enable mala to work with any language/toolchain without code changes\n\n## Scope\n- Configuration schema and dataclasses for `mala.yaml`\n- Strict config loading with fail-fast validation (eager at startup)\n- Preset registry with 4 built-in presets (python-uv, node-npm, go, rust) using `importlib.resources`\n- Config merging (preset as base, user overrides on top)\n- Tool name extraction for quality gate messaging\n- Code pattern matching for file-triggered validation\n- Per-command timeout support (optional, default 120s)\n- Cobertura XML coverage parsing with configurable threshold\n- Direct migration from hard-coded Python commands (breaking change)\n\n## Non-Goals\n- Backwards compatibility fallback to Python defaults\n- Windows shell compatibility (document as limitation)\n- Hot reload of config during session\n- CLI `--config` flag for alternate config paths\n- Non-Cobertura coverage formats (deferred)\n- Migration tooling or `mala init` command\n\n## Breaking Changes\n- Existing Python repos without `mala.yaml` will fail\n- `ValidationCommand.command` changes from `list[str]` to `str`\n- `RepoType` enum removed\n\n## Acceptance Criteria\n1. Go project with `mala.yaml` using go preset executes all commands\n2. Node.js project with node-npm preset executes all commands\n3. Python project with preset + custom override works correctly\n4. Minimal config (only setup + test) skips undefined commands\n5. Missing `mala.yaml` fails with clear error message\n6. Invalid YAML syntax fails with specific error details\n7. Coverage config with xml format, custom file, threshold works\n8. No coverage section skips coverage evaluation\n9. Custom `code_patterns` filter files correctly\n10. Tool name extraction shows friendly names in quality gate output","status":"closed","priority":1,"issue_type":"epic","created_at":"2026-01-02T00:32:04.61706417Z","created_by":"cyou","updated_at":"2026-01-02T16:38:43.583964709Z","closed_at":"2026-01-02T16:38:43.583964709Z","close_reason":"Closed"}
{"id":"mala-fdp1.1","title":"Create Configuration Schema and Dataclasses","description":"# Task 1: Create Configuration Schema and Dataclasses\n\n## Context\n- Foundation for the entire language-agnostic configuration feature\n- Defines core data structures that all other tasks depend on\n- Must be completed before config loader, preset registry, or merger can be implemented\n- Follows frozen dataclass pattern from `src/infra/io/config.py`\n\n## Scope\n**In**: Create new `src/domain/validation/config.py` with:\n- `CommandConfig`: frozen dataclass with `command: str`, `timeout: int | None = None`\n- `YamlCoverageConfig`: frozen dataclass (named to avoid collision with existing `CoverageConfig`)\n- `CommandsConfig`: frozen dataclass with optional command fields\n- `ValidationConfig`: frozen dataclass with preset, commands, coverage, patterns\n- `ConfigError`, `PresetNotFoundError`: custom exceptions\n\n**Out**: No YAML parsing, no loading logic, no preset handling\n\n## Acceptance Criteria\n- [ ] All dataclasses are frozen (immutable)\n- [ ] `CommandConfig` supports both string shorthand and object form\n- [ ] `YamlCoverageConfig` has: `command`, `format`, `file`, `threshold`, `timeout`\n- [ ] `CommandsConfig` has optional fields: `setup`, `test`, `lint`, `format`, `typecheck`, `e2e`\n- [ ] `ValidationConfig` has: `preset`, `commands`, `coverage`, `code_patterns`, `config_files`, `setup_files`\n- [ ] `ConfigError` and `PresetNotFoundError` exceptions are defined\n- [ ] Factory methods follow existing pattern from `src/infra/io/config.py`\n\n## Test Plan\n- [ ] Create `tests/test_validation_config.py`\n- [ ] Test dataclass instantiation with all field combinations\n- [ ] Test immutability (attempt mutation raises)\n- [ ] Test default values\n- [ ] Run: `uv run pytest tests/test_validation_config.py -v`\n\n## Notes for Agent\n- Look at `src/infra/io/config.py` for frozen dataclass patterns\n- The YAML schema uses nested `commands:` wrapper; preserve this structure\n- Use `field(default_factory=list)` for list fields\n- Keep exceptions simple with clear messages","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-02T00:32:22.340074188Z","created_by":"cyou","updated_at":"2026-01-02T03:19:13.826118525Z","closed_at":"2026-01-02T03:19:13.826118525Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-fdp1.1","depends_on_id":"mala-fdp1","type":"parent-child","created_at":"2026-01-02T00:32:22.346926243Z","created_by":"daemon"}]}
{"id":"mala-fdp1.10","title":"Update Lint Cache for Dynamic Tool Detection","description":"# Task 10: Update Lint Cache for Dynamic Tool Detection\n\n## Context\n- Uses tool name extractor for dynamic lint tool detection\n- Supports AC 1, 2 (lint tools from any language)\n- Depends on Task 5 (tool name extractor) and Task 7 (spec builder)\n\n## Scope\n**In**: Modify `src/infra/hooks/lint_cache.py`:\n- Remove hard-coded `LINT_COMMAND_PATTERNS` (ruff, ty only)\n- Update `LintCache` to accept lint/typecheck tool names from `ValidationSpec`\n- Use `extract_tool_name()` to determine cache key from command\n- Cache invalidation based on extracted tool name\n\n**Out**: No changes to quality gate messaging (Task 11)\n\n## Acceptance Criteria\n- [ ] Hard-coded `LINT_COMMAND_PATTERNS` removed\n- [ ] `LintCache` uses tool names from `ValidationSpec`\n- [ ] Cache key extracted via `extract_tool_name()`\n- [ ] Works with eslint, golangci-lint, cargo clippy, etc.\n\n## Test Plan\n- [ ] Update `tests/test_lint_cache.py`:\n  - Test cache with eslint command\n  - Test cache with golangci-lint command\n  - Test cache key extraction\n- [ ] Run: `uv run pytest tests/test_lint_cache.py -v`\n\n## Notes for Agent\n- Import `extract_tool_name` from `tool_name_extractor.py`\n- Tool name becomes the cache key\n- May need to update how `LintCache` receives `ValidationSpec`","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-02T00:35:00.934172219Z","created_by":"cyou","updated_at":"2026-01-02T15:43:51.227092802Z","closed_at":"2026-01-02T15:43:51.227092802Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-fdp1.10","depends_on_id":"mala-fdp1","type":"parent-child","created_at":"2026-01-02T00:35:00.94181129Z","created_by":"daemon"},{"issue_id":"mala-fdp1.10","depends_on_id":"mala-fdp1.7","type":"blocks","created_at":"2026-01-02T00:36:37.312696155Z","created_by":"daemon"}]}
{"id":"mala-fdp1.11","title":"Update Quality Gate Messaging","description":"# Task 11: Update Quality Gate Messaging\n\n## Context\n- Uses extracted tool names in quality gate output\n- AC 10: tool name display\n- Depends on Task 5 (tool name extractor) and Task 7 (spec builder)\n\n## Scope\n**In**: Modify `src/domain/quality_gate.py`:\n- Remove hard-coded `KIND_TO_NAME` mapping\n- Use `extract_tool_name(command)` to get display name\n- Update `ValidationEvidence` to store tool name\n- Update quality gate messages to use extracted names\n\n**Out**: No changes to lint cache (Task 10)\n\n## Acceptance Criteria\n- [ ] Hard-coded `KIND_TO_NAME` mapping removed\n- [ ] Quality gate shows `\"eslint\"` not `\"npx eslint .\"`\n- [ ] Quality gate shows `\"pytest\"` not `\"uv run pytest\"`\n- [ ] `ValidationEvidence` stores extracted tool name\n\n## Test Plan\n- [ ] Update `tests/test_quality_gate.py`:\n  - Test quality gate output shows `\"eslint\"` not `\"npx eslint .\"`\n  - Test quality gate output shows `\"pytest\"` not `\"uv run pytest\"`\n- [ ] Run: `uv run pytest tests/test_quality_gate.py -v`\n\n## Notes for Agent\n- Import `extract_tool_name` from `tool_name_extractor.py`\n- May need to modify `ValidationEvidence` dataclass\n- Ensure friendly names in all user-facing output","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-02T00:35:11.605453839Z","created_by":"cyou","updated_at":"2026-01-02T15:51:09.454613703Z","closed_at":"2026-01-02T15:51:09.454613703Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-fdp1.11","depends_on_id":"mala-fdp1","type":"parent-child","created_at":"2026-01-02T00:35:11.616706642Z","created_by":"daemon"},{"issue_id":"mala-fdp1.11","depends_on_id":"mala-fdp1.7","type":"blocks","created_at":"2026-01-02T00:36:37.374139224Z","created_by":"daemon"}]}
{"id":"mala-fdp1.12","title":"Update Command Execution for Shell Mode","description":"# Task 12: Update Command Execution for Shell Mode\n\n## Context\n- Execute shell string commands with `shell=True`\n- AC 1-4: command execution for all languages\n- Depends on Task 7 (spec builder with shell string commands)\n\n## Scope\n**In**: \n- Modify `src/infra/tools/command_runner.py`:\n  - Update `CommandRunner.run()` and `run_async()` to accept shell string commands\n  - Add `shell: bool = False` parameter (default False for backwards compatibility)\n  - When `shell=True`, pass command as string to `subprocess.Popen(cmd, shell=True, ...)`\n  - Ensure stdout/stderr capture works with shell mode\n  - Timeout handling already implemented via process group termination\n- Modify `src/domain/validation/spec_executor.py`:\n  - Pass `shell=True` when executing `ValidationCommand` with shell string\n  - Pass `timeout=cmd.timeout` to CommandRunner\n- Update `src/domain/validation/coverage.py` (`BaselineCoverageService` uses CommandRunner)\n\n**Out**: No changes to spec builder (Task 7)\n\n## Acceptance Criteria\n- [ ] `CommandRunner.run()` accepts `shell=True` parameter\n- [ ] Shell string commands execute via `subprocess.Popen(..., shell=True)`\n- [ ] stdout/stderr capture works in shell mode\n- [ ] Timeout enforcement works with shell commands\n- [ ] Exit code captured correctly from shell commands\n- [ ] `spec_executor.py` passes `shell=True` for `ValidationCommand`\n\n## Test Plan\n- [ ] Update `tests/test_command_runner.py`:\n  - Test shell string execution with `shell=True`\n  - Test timeout enforcement with shell commands\n  - Test exit code capture from shell commands\n- [ ] Run: `uv run pytest tests/test_command_runner.py tests/test_spec.py -v`\n\n## Notes for Agent\n- Keep `shell=False` as default for backwards compatibility\n- Process group termination for timeout should work the same\n- Test with commands that use shell features (pipes, redirects)","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-02T00:35:24.97301344Z","created_by":"cyou","updated_at":"2026-01-02T16:18:57.494903143Z","closed_at":"2026-01-02T16:18:57.494903143Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-fdp1.12","depends_on_id":"mala-fdp1","type":"parent-child","created_at":"2026-01-02T00:35:24.984690681Z","created_by":"daemon"},{"issue_id":"mala-fdp1.12","depends_on_id":"mala-fdp1.7","type":"blocks","created_at":"2026-01-02T00:36:37.440616048Z","created_by":"daemon"},{"issue_id":"mala-fdp1.12","depends_on_id":"mala-fdp1.9","type":"blocks","created_at":"2026-01-02T00:41:43.962651236Z","created_by":"daemon"}]}
{"id":"mala-fdp1.13","title":"Wire Code Patterns into Validation Gating","description":"# Task 13: Wire Code Patterns into Validation Gating\n\n## Context\n- Integrates code pattern matcher into change detection\n- AC 9: custom code_patterns filter files correctly\n- Depends on Task 6 (code pattern matcher) and Task 7 (spec builder)\n\n## Scope\n**In**: \n- Identify change detection location (likely in orchestrator or spec_runner.py)\n- Modify `src/domain/validation/spec_runner.py` or `src/orchestration/`:\n  - Import `filter_matching_files` from `code_pattern_matcher.py`\n  - When evaluating changed files, use `spec.code_patterns` to filter\n  - If no files match any pattern, skip validation (unless empty, then all trigger)\n  - Changes to `mala.yaml` itself always trigger validation\n- Add cache invalidation logic:\n  - Changes to `config_files` trigger lint/format/typecheck re-run\n  - Changes to `setup_files` trigger setup command re-run\n\n**Out**: No changes to pattern matcher itself (Task 6)\n\n## Acceptance Criteria\n- [ ] Only matching files trigger validation when `code_patterns` specified\n- [ ] Non-matching files (e.g., `.md` when patterns is `[\"*.py\"]`) don't trigger\n- [ ] Empty `code_patterns` matches all files\n- [ ] `mala.yaml` change always triggers validation\n- [ ] `config_files` change triggers lint cache invalidation\n- [ ] `setup_files` change triggers setup re-run\n\n## Test Plan\n- [ ] Create `tests/test_validation_gating.py`:\n  - Test: Only `.py` files trigger when `code_patterns: [\"*.py\"]`\n  - Test: Non-matching files don't trigger\n  - Test: Empty `code_patterns` matches all\n  - Test: `mala.yaml` change always triggers\n  - Test: `config_files` change triggers lint cache invalidation\n  - Test: `setup_files` change triggers setup re-run\n- [ ] Run: `uv run pytest tests/test_validation_gating.py -v`\n\n## Notes for Agent\n- Explore `src/orchestration/` to find change detection logic\n- May need to modify how file changes are evaluated\n- Ensure `mala.yaml` is special-cased","notes":"Quality gate failed: External review failed: Empty output from review-gate\nLog: /home/cyou/.claude/projects/-home-cyou-mala/393b4aef-e157-42f1-94f5-b837269f4190.jsonl","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-02T00:35:42.787485069Z","created_by":"cyou","updated_at":"2026-01-02T16:23:42.502310871Z","closed_at":"2026-01-02T16:23:42.502310871Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-fdp1.13","depends_on_id":"mala-fdp1","type":"parent-child","created_at":"2026-01-02T00:35:42.798750102Z","created_by":"daemon"},{"issue_id":"mala-fdp1.13","depends_on_id":"mala-fdp1.7","type":"blocks","created_at":"2026-01-02T00:36:37.50944903Z","created_by":"daemon"}]}
{"id":"mala-fdp1.14","title":"Integration Testing for Language-Agnostic Config","description":"# Task 14: Integration Testing for Language-Agnostic Config\n\n## Context\n- End-to-end validation of config-driven workflow\n- AC 1-10: all acceptance criteria\n- Depends on Tasks 1-13 (all previous tasks)\n\n## Scope\n**In**: Create `tests/test_validation_config_integration.py`:\n- Test: Go project with `mala.yaml` using go preset (AC 1)\n- Test: Node.js project with node-npm preset (AC 2)\n- Test: Python project with python-uv preset + custom test override (AC 3)\n- Test: Minimal config (only setup + test) skips lint/format/typecheck (AC 4)\n- Test: Missing `mala.yaml` fails with clear error (AC 5)\n- Test: Invalid YAML syntax fails with specific error (AC 6)\n- Test: Coverage config with xml format, custom file, threshold (AC 7)\n- Test: No coverage section skips coverage (AC 8)\n- Test: Custom `code_patterns` filter files correctly (AC 9)\n- Test: Tool name extraction in quality gate output (AC 10)\n- Create test fixtures with actual project structures\n\n**Out**: No new functionality, only testing\n\n## Acceptance Criteria\n- [ ] All 10 AC covered by integration tests\n- [ ] Test fixtures with actual project structures created\n- [ ] Tests marked with `@pytest.mark.integration`\n- [ ] Full test suite passes: `uv run pytest -m \"unit or integration\"`\n- [ ] 85% coverage maintained: `uv run pytest --cov --cov-fail-under=85`\n\n## Test Plan\n- [ ] Create `tests/test_validation_config_integration.py`\n- [ ] Create project fixture directories with realistic structure\n- [ ] Run: `uv run pytest tests/test_validation_config_integration.py -v -m integration`\n- [ ] Run full suite: `uv run pytest -m \"unit or integration\"`\n- [ ] Verify coverage: `uv run pytest --cov --cov-fail-under=85`\n\n## Notes for Agent\n- Use `tmp_path` fixture for temporary project directories\n- May need to mock external tools (go, npm, cargo) for CI\n- Consider using `monkeypatch` to avoid actual tool execution\n- Integration tests should be realistic but fast","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-02T00:35:59.387771971Z","created_by":"cyou","updated_at":"2026-01-02T16:35:13.519283868Z","closed_at":"2026-01-02T16:35:13.519283868Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-fdp1.14","depends_on_id":"mala-fdp1","type":"parent-child","created_at":"2026-01-02T00:35:59.399033614Z","created_by":"daemon"},{"issue_id":"mala-fdp1.14","depends_on_id":"mala-fdp1.9","type":"blocks","created_at":"2026-01-02T00:36:44.881597891Z","created_by":"daemon"},{"issue_id":"mala-fdp1.14","depends_on_id":"mala-fdp1.10","type":"blocks","created_at":"2026-01-02T00:36:44.943565498Z","created_by":"daemon"},{"issue_id":"mala-fdp1.14","depends_on_id":"mala-fdp1.11","type":"blocks","created_at":"2026-01-02T00:36:45.002136962Z","created_by":"daemon"},{"issue_id":"mala-fdp1.14","depends_on_id":"mala-fdp1.12","type":"blocks","created_at":"2026-01-02T00:36:45.058520897Z","created_by":"daemon"},{"issue_id":"mala-fdp1.14","depends_on_id":"mala-fdp1.13","type":"blocks","created_at":"2026-01-02T00:36:45.125433138Z","created_by":"daemon"}]}
{"id":"mala-fdp1.2","title":"Implement YAML Config Loader","description":"# Task 2: Implement YAML Config Loader\n\n## Context\n- Loads and validates `mala.yaml` with strict schema checking\n- Uses dataclasses from Task 1 (mala-fdp1.1)\n- Provides fail-fast validation with clear error messages\n- AC 5: Missing config error; AC 6: Invalid YAML error\n\n## Scope\n**In**: Create new `src/domain/validation/config_loader.py` with:\n- `load_config(repo_path: Path) -\u003e ValidationConfig`: Load `mala.yaml` from repo root\n- `_parse_yaml(content: str) -\u003e dict`: Parse YAML with error handling\n- `_validate_schema(data: dict) -\u003e None`: Validate against expected schema\n- `_build_config(data: dict) -\u003e ValidationConfig`: Convert dict to dataclass\n- `_validate_config(config: ValidationConfig) -\u003e None`: Post-build validation\n\n**Out**: No preset loading, no config merging\n\n## Acceptance Criteria\n- [ ] Missing file raises `ConfigError`: \"mala.yaml not found in {repo_path}. Mala requires a configuration file to run.\"\n- [ ] Invalid YAML syntax raises `ConfigError`: \"Invalid YAML syntax in mala.yaml: {details}\"\n- [ ] Unknown field raises `ConfigError`: \"Unknown field '{field}' in mala.yaml\"\n- [ ] Invalid type raises `ConfigError`: \"Field '{field}' must be {expected_type}, got {actual_type}\"\n- [ ] No commands defined raises `ConfigError`: \"At least one command must be defined. Specify a preset or define commands directly.\"\n- [ ] Empty command string raises `ConfigError`: \"Command cannot be empty string. Use null to disable.\"\n- [ ] Unsupported coverage format raises `ConfigError`: \"Unsupported coverage format '{format}'. Supported formats: xml\"\n\n## Test Plan\n- [ ] Create `tests/test_config_loader.py`\n- [ ] Create fixtures: `tests/fixtures/mala-configs/valid-minimal.yaml`, `valid-full.yaml`, `invalid-syntax.yaml`, `invalid-unknown-field.yaml`\n- [ ] Test successful load with valid config\n- [ ] Test missing file error (exact message match)\n- [ ] Test invalid YAML syntax error\n- [ ] Test unknown field rejection\n- [ ] Test type validation\n- [ ] Test \"no commands defined\" error\n- [ ] Test empty command string error\n- [ ] Test unsupported coverage format error\n- [ ] Run: `uv run pytest tests/test_config_loader.py -v`\n\n## Notes for Agent\n- Import from `config.py` (Task 1)\n- `_build_config()` will be reused by preset registry (Task 3)\n- PyYAML is available as dependency\n- Validate `coverage.format == \"xml\"` for MVP (other formats deferred)","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-02T00:32:41.631928701Z","created_by":"cyou","updated_at":"2026-01-02T03:28:47.853878406Z","closed_at":"2026-01-02T03:28:47.853878406Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-fdp1.2","depends_on_id":"mala-fdp1","type":"parent-child","created_at":"2026-01-02T00:32:41.639744461Z","created_by":"daemon"},{"issue_id":"mala-fdp1.2","depends_on_id":"mala-fdp1.1","type":"blocks","created_at":"2026-01-02T00:36:28.43076917Z","created_by":"daemon"}]}
{"id":"mala-fdp1.3","title":"Create Preset Registry with Built-in Presets","description":"# Task 3: Create Preset Registry with Built-in Presets\n\n## Context\n- Provides built-in presets discoverable via `importlib.resources`\n- AC 1: Go preset; AC 2: Node preset; AC 3: preset + override\n- Depends on Task 1 (config.py) and Task 2 (config_loader.py for `_build_config`)\n\n## Scope\n**In**: \n- Create `src/domain/validation/preset_registry.py`:\n  - `PresetRegistry` class with `get(name)`, `list_presets()`, `_load_preset_yaml()`\n  - Use `importlib.resources` to read preset files\n  - Reuse `_build_config()` from config_loader.py\n- Create `src/domain/validation/presets/__init__.py` (empty package)\n- Create 4 preset files: `python-uv.yaml`, `node-npm.yaml`, `go.yaml`, `rust.yaml`\n- Modify `pyproject.toml`: Add hatch build config for preset inclusion, add PyYAML dependency\n\n**Out**: No config merging (Task 4), no integration with spec builder\n\n## Acceptance Criteria\n- [ ] `PresetRegistry.get(\"python-uv\")` returns valid `ValidationConfig`\n- [ ] `PresetRegistry.get(\"node-npm\")` returns valid `ValidationConfig`\n- [ ] `PresetRegistry.get(\"go\")` returns valid `ValidationConfig`\n- [ ] `PresetRegistry.get(\"rust\")` returns valid `ValidationConfig`\n- [ ] `PresetRegistry.list_presets()` returns `[\"go\", \"node-npm\", \"python-uv\", \"rust\"]`\n- [ ] Unknown preset raises `PresetNotFoundError`: \"Unknown preset '{name}'. Available presets: python-uv, node-npm, go, rust\"\n- [ ] Preset files include documentation comments\n- [ ] `pyproject.toml` includes preset files in wheel and has PyYAML dependency\n\n## Test Plan\n- [ ] Create `tests/test_preset_registry.py`\n- [ ] Unit tests (mock `importlib.resources`):\n  - Test `get()` returns valid `ValidationConfig`\n  - Test `list_presets()` returns all 4 presets\n  - Test `PresetNotFoundError` for unknown preset\n- [ ] Integration tests (real package):\n  - Test actual preset file loading\n  - Test preset YAML syntax validity\n- [ ] Run: `uv run pytest tests/test_preset_registry.py -v`\n\n## Notes for Agent\n- Preset content is defined in the plan (lines 158-249)\n- Use `importlib.resources.files()` for Python 3.9+ compatibility\n- Presets should have comments explaining their purpose\n- pyproject.toml uses hatchling as build backend","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-02T00:32:58.196595344Z","created_by":"cyou","updated_at":"2026-01-02T03:47:23.762216637Z","closed_at":"2026-01-02T03:47:23.762216637Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-fdp1.3","depends_on_id":"mala-fdp1","type":"parent-child","created_at":"2026-01-02T00:32:58.205625628Z","created_by":"daemon"},{"issue_id":"mala-fdp1.3","depends_on_id":"mala-fdp1.2","type":"blocks","created_at":"2026-01-02T00:36:28.499262843Z","created_by":"daemon"}]}
{"id":"mala-fdp1.4","title":"Implement Config Merger","description":"# Task 4: Implement Config Merger\n\n## Context\n- Merges preset config with user overrides (user wins)\n- AC 3: preset + custom override; AC 4: partial config\n- Depends on Task 1 (config.py) and Task 3 (preset_registry.py)\n\n## Scope\n**In**: Create `src/domain/validation/config_merger.py` with:\n- `merge_configs(preset: ValidationConfig | None, user: ValidationConfig) -\u003e ValidationConfig`\n- Merge rules:\n  - If no preset, return user config as-is\n  - Command fields: user replaces preset if present; `null` disables; omitted inherits\n  - Coverage: user replaces preset entirely if present\n  - List fields (code_patterns, config_files, setup_files): user **replaces** preset entirely (no merge)\n\n**Out**: No loading, no preset lookup, no spec building\n\n## Acceptance Criteria\n- [ ] No preset → user config returned unchanged\n- [ ] Preset with no user overrides → preset config returned\n- [ ] User override replaces preset command\n- [ ] User `null` disables preset command (even if preset defines it)\n- [ ] User `None`/omitted inherits preset value\n- [ ] List fields replace entirely (not extend/append)\n- [ ] Coverage override replaces entirely\n\n## Test Plan\n- [ ] Create `tests/test_config_merger.py`\n- [ ] Test no preset (user config returned unchanged)\n- [ ] Test preset with no user overrides\n- [ ] Test user override replaces preset command\n- [ ] Test user `null` disables preset command\n- [ ] Test omitted field inherits preset value\n- [ ] Test list fields replace (not extend)\n- [ ] Test coverage override replaces entirely\n- [ ] Run: `uv run pytest tests/test_config_merger.py -v`\n\n## Notes for Agent\n- Import `ValidationConfig` from `config.py`\n- Must handle `None` vs explicit `null` in YAML (Python `None` vs sentinel)\n- Consider using a sentinel value or explicit flag to distinguish \"not set\" from \"set to null\"","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-02T00:33:23.189435009Z","created_by":"cyou","updated_at":"2026-01-02T04:10:25.89361994Z","closed_at":"2026-01-02T04:10:25.89361994Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-fdp1.4","depends_on_id":"mala-fdp1","type":"parent-child","created_at":"2026-01-02T00:33:23.198815531Z","created_by":"daemon"},{"issue_id":"mala-fdp1.4","depends_on_id":"mala-fdp1.3","type":"blocks","created_at":"2026-01-02T00:36:28.578457763Z","created_by":"daemon"}]}
{"id":"mala-fdp1.5","title":"Implement Tool Name Extractor","description":"# Task 5: Implement Tool Name Extractor\n\n## Context\n- Extracts human-readable tool name from shell command for quality gate messaging\n- AC 10: \"npx eslint\" → \"eslint\"\n- Standalone utility with no dependencies on other config tasks\n\n## Scope\n**In**: Create `src/domain/validation/tool_name_extractor.py` with:\n- `extract_tool_name(command: str) -\u003e str`\n- Algorithm:\n  1. Handle shell operators (`\u0026\u0026`, `||`, `|`, `;`) by trying each segment\n  2. Parse via `shlex.split`; fallback to whitespace-delimited if parsing fails\n  3. Skip env var assignments (tokens with `=` before command)\n  4. Skip shell built-ins (`export`, `set`, `cd`)\n  5. Strip path prefixes (`/usr/bin/eslint` → `eslint`)\n  6. Apply wrapper rules:\n     - Single-token: `npx`, `bunx`, `uvx`, `pipx` → skip wrapper, use next positional\n     - Multi-token: `python -m`, `uv run`, `poetry run` → skip sequence\n     - Compound: `go test`, `cargo clippy`, `npm test` → include subcommand\n     - Script: `npm run lint` → `npm run:lint`\n  7. Fallback: return first token, log warning\n\n**Out**: No integration with quality gate or lint cache (separate tasks)\n\n## Acceptance Criteria\n- [ ] `\"npx eslint .\"` → `\"eslint\"`\n- [ ] `\"uvx ruff check .\"` → `\"ruff\"`\n- [ ] `\"uv run pytest\"` → `\"pytest\"`\n- [ ] `\"go test ./...\"` → `\"go test\"`\n- [ ] `\"cargo clippy\"` → `\"cargo clippy\"`\n- [ ] `\"npm run lint\"` → `\"npm run:lint\"`\n- [ ] Path stripping works: `/usr/bin/eslint` → `eslint`\n- [ ] Unknown wrapper falls back gracefully with warning logged\n- [ ] Empty/malformed commands return best-effort result with warning\n\n## Test Plan\n- [ ] Create `tests/test_tool_name_extractor.py`\n- [ ] Test all spec examples (full table from plan lines 317-325)\n- [ ] Test path stripping\n- [ ] Test unknown wrapper (fallback behavior)\n- [ ] Test empty/malformed commands\n- [ ] Run: `uv run pytest tests/test_tool_name_extractor.py -v`\n\n## Notes for Agent\n- Use `shlex.split` for proper shell parsing\n- Log warnings for fallback cases using Python logging\n- Keep shell built-ins list minimal but complete","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-02T00:33:41.973528579Z","created_by":"cyou","updated_at":"2026-01-02T03:17:47.804832475Z","closed_at":"2026-01-02T03:17:47.804832475Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-fdp1.5","depends_on_id":"mala-fdp1","type":"parent-child","created_at":"2026-01-02T00:33:41.981806157Z","created_by":"daemon"}]}
{"id":"mala-fdp1.6","title":"Implement Code Pattern Matcher","description":"# Task 6: Implement Code Pattern Matcher\n\n## Context\n- Matches file paths against glob patterns for validation gating\n- AC 9: custom code_patterns filter files correctly\n- Standalone utility with no dependencies on other config tasks\n\n## Scope\n**In**: Create `src/domain/validation/code_pattern_matcher.py` with:\n- `glob_to_regex(pattern: str) -\u003e re.Pattern`: Convert glob to regex\n  - `*` matches non-slash chars\n  - `**` matches anything including `/`\n  - Invalid pattern → treat as literal string, log warning (per user decision 15)\n- `matches_pattern(path: str, pattern: str) -\u003e bool`:\n  - Filename-only patterns (no `/`): match against `os.path.basename(path)`\n  - Path patterns (contain `/`): match against full relative path\n- `filter_matching_files(files: list[str], patterns: list[str]) -\u003e list[str]`:\n  - Return files matching any pattern\n  - Empty patterns list → matches everything\n\n**Out**: No integration with validation runner (Task 13)\n\n## Acceptance Criteria\n- [ ] `*.py` matches `foo.py`, not `foo.js`\n- [ ] `src/*.py` matches `src/foo.py`, not `src/sub/foo.py`\n- [ ] `src/**/*.py` matches `src/foo.py` and `src/sub/deep/file.py`\n- [ ] `**/test_*.py` matches `test_main.py` and `tests/test_utils.py`\n- [ ] Empty patterns list matches everything\n- [ ] Invalid pattern treated as literal string (doesn't raise)\n- [ ] `filter_matching_files` returns correct subset\n\n## Test Plan\n- [ ] Create `tests/test_code_pattern_matcher.py`\n- [ ] Test `*.py` pattern\n- [ ] Test `src/*.py` pattern (single-level)\n- [ ] Test `src/**/*.py` pattern (recursive)\n- [ ] Test `**/test_*.py` pattern\n- [ ] Test empty patterns (matches all)\n- [ ] Test invalid pattern (treated as literal)\n- [ ] Test `filter_matching_files` function\n- [ ] Run: `uv run pytest tests/test_code_pattern_matcher.py -v`\n\n## Notes for Agent\n- Use `fnmatch` or regex translation; consider `pathlib.PurePath.match` limitations\n- `**` must handle zero-or-more directory levels\n- Log warnings for invalid patterns using Python logging","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-02T00:33:57.705106224Z","created_by":"cyou","updated_at":"2026-01-02T03:15:34.48977877Z","closed_at":"2026-01-02T03:15:34.48977877Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-fdp1.6","depends_on_id":"mala-fdp1","type":"parent-child","created_at":"2026-01-02T00:33:57.714649996Z","created_by":"daemon"}]}
{"id":"mala-fdp1.7","title":"Create Config-Driven Spec Builder","description":"# Task 7: Create Config-Driven Spec Builder\n\n## Context\n- Core integration: replaces hard-coded Python commands with config-driven `ValidationSpec`\n- AC 1-4: all language command execution\n- Depends on Tasks 1-6 (config, loader, presets, merger, utilities)\n- **Critical**: This is the central integration point that ties everything together\n\n## Scope\n**In**: Modify `src/domain/validation/spec.py`:\n- Update `build_validation_spec(repo_path: Path)`:\n  - Call `load_config(repo_path)` eagerly at startup\n  - If `preset` specified, load via `PresetRegistry.get()` and merge\n  - Build `ValidationCommand` instances from merged config\n  - Use 120s default timeout, override with per-command timeout if specified\n  - Set `code_patterns`, `config_files`, `setup_files` on `ValidationSpec`\n- Extend `ValidationSpec` dataclass:\n  - Add `code_patterns: list[str]`, `config_files: list[str]`, `setup_files: list[str]`\n- Modify `ValidationCommand`:\n  - Change `command: list[str]` to `command: str` (shell string)\n  - Add `shell: bool = True`, `timeout: int = 120`\n- Remove hard-coded Python command logic and `RepoType` enum\n- Modify `src/domain/validation/__init__.py`: Remove `RepoType` from re-exports\n\n**Out**: Coverage parsing (Task 8), command execution (Task 12), validation gating (Task 13)\n\n## Acceptance Criteria\n- [ ] `build_validation_spec()` loads `mala.yaml` from repo root\n- [ ] Preset config is loaded and merged when specified\n- [ ] `ValidationSpec` has `code_patterns`, `config_files`, `setup_files` fields\n- [ ] `ValidationCommand.command` is `str` (shell string), not `list[str]`\n- [ ] `ValidationCommand.shell` defaults to `True`\n- [ ] `ValidationCommand.timeout` defaults to 120, can be overridden\n- [ ] `RepoType` enum is removed\n- [ ] All tests constructing `ValidationCommand` are updated\n\n## Test Plan\n- [ ] Update `tests/test_spec.py`:\n  - Remove tests for `RepoType` detection\n  - Add tests for config-driven spec building\n  - Test `ValidationSpec` with pattern fields\n  - Test `ValidationCommand` with `shell=True` and timeout\n- [ ] Create fixtures: `tests/fixtures/mala-configs/go-project.yaml`, `node-project.yaml`, `preset-override.yaml`, `partial-config.yaml`, `command-with-timeout.yaml`\n- [ ] Update all tests constructing `ValidationCommand`:\n  - `tests/test_validation.py`\n  - `tests/test_orchestrator.py`\n  - `tests/test_quality_gate.py`\n  - `tests/test_spec_workspace.py`\n  - `tests/test_gate_runner.py`\n- [ ] Run: `uv run pytest tests/test_spec.py -v`\n\n## Notes for Agent\n- This is a significant refactor with many test updates\n- Search for all `ValidationCommand` usages in tests\n- `RepoType` removal is breaking — ensure no lingering references\n- Import from new config modules","notes":"Quality gate failed: External review failed: Empty output from review-gate\nLog: /home/cyou/.claude/projects/-home-cyou-mala/915ca1e4-f242-484c-977f-d99940ab45f1.jsonl","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-02T00:34:20.299104855Z","created_by":"cyou","updated_at":"2026-01-02T15:25:31.788036109Z","closed_at":"2026-01-02T15:25:31.788036109Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-fdp1.7","depends_on_id":"mala-fdp1","type":"parent-child","created_at":"2026-01-02T00:34:20.299636532Z","created_by":"daemon"},{"issue_id":"mala-fdp1.7","depends_on_id":"mala-fdp1.4","type":"blocks","created_at":"2026-01-02T00:36:28.643300535Z","created_by":"daemon"},{"issue_id":"mala-fdp1.7","depends_on_id":"mala-fdp1.5","type":"blocks","created_at":"2026-01-02T00:36:28.714430435Z","created_by":"daemon"},{"issue_id":"mala-fdp1.7","depends_on_id":"mala-fdp1.6","type":"blocks","created_at":"2026-01-02T00:36:28.777394696Z","created_by":"daemon"}]}
{"id":"mala-fdp1.8","title":"Update Coverage Parsing for Config","description":"# Task 8: Update Coverage Parsing for Config\n\n## Context\n- Uses coverage config for file path, format, and threshold\n- AC 7: coverage parsing; AC 8: no coverage section\n- Depends on Task 7 (spec builder provides `YamlCoverageConfig`)\n\n## Scope\n**In**: Modify `src/domain/validation/coverage.py`:\n- Update `parse_coverage_xml` signature to accept `YamlCoverageConfig`\n- Use `coverage_config.file` for XML path (instead of hardcoded `coverage.xml`)\n- Use `coverage_config.threshold` for pass/fail determination\n- Keep existing Cobertura XML parsing logic\n- Update call sites:\n  - `parse_and_check_coverage()` wrapper (~line 228)\n  - `get_baseline_coverage()` (~line 249)\n  - `src/domain/validation/spec_result_builder.py` - `_check_coverage()` method\n- Handle missing coverage config: skip evaluation entirely (return `None`)\n- Missing coverage file after test run: validation fails with clear error\n\n**Out**: Baseline refresh with custom command (Task 9)\n\n## Acceptance Criteria\n- [ ] `parse_coverage_xml` accepts `YamlCoverageConfig` parameter\n- [ ] Uses `coverage_config.file` for coverage file path\n- [ ] Uses `coverage_config.threshold` for pass/fail\n- [ ] Pass at threshold, fail below threshold\n- [ ] No coverage config → skip coverage (return `None`)\n- [ ] Missing coverage file → clear error message\n\n## Test Plan\n- [ ] Update `tests/test_coverage.py`:\n  - Test with `YamlCoverageConfig` specifying file and threshold\n  - Test threshold enforcement (pass at threshold, fail below)\n  - Test behavior when no coverage config (skip)\n  - Test missing coverage file error\n  - Update all existing tests to use new signature\n- [ ] Run: `uv run pytest tests/test_coverage.py -v`\n\n## Notes for Agent\n- Existing `CoverageConfig` in spec.py has different fields (`enabled`, `min_percent`, `branch`, `report_path`)\n- Use new `YamlCoverageConfig` from `config.py` (Task 1)\n- Keep Cobertura XML parsing unchanged (uses `\u003ccoverage line-rate\u003e` attribute)","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-02T00:34:36.228703392Z","created_by":"cyou","updated_at":"2026-01-02T15:33:42.537977377Z","closed_at":"2026-01-02T15:33:42.537977377Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-fdp1.8","depends_on_id":"mala-fdp1","type":"parent-child","created_at":"2026-01-02T00:34:36.22918131Z","created_by":"daemon"},{"issue_id":"mala-fdp1.8","depends_on_id":"mala-fdp1.7","type":"blocks","created_at":"2026-01-02T00:36:37.183860856Z","created_by":"daemon"}]}
{"id":"mala-fdp1.9","title":"Generalize BaselineCoverageService for Config","description":"# Task 9: Generalize BaselineCoverageService for Config\n\n## Context\n- Supports config-driven coverage command for baseline refresh\n- AC 7: coverage command execution\n- Depends on Task 8 (coverage parsing with `YamlCoverageConfig`)\n\n## Scope\n**In**: Modify `src/domain/validation/coverage.py`:\n- Update `BaselineCoverageService` to accept `YamlCoverageConfig`\n- Use `coverage_config.command` for running coverage (required for baseline refresh)\n- Use `coverage_config.timeout` (or default 120s)\n- If no coverage config, baseline refresh is not available\n- If `coverage.command` execution fails, overall validation fails\n\n**Out**: Integration with other components (handled by spec builder)\n\n## Acceptance Criteria\n- [ ] `BaselineCoverageService` accepts `YamlCoverageConfig`\n- [ ] Uses `coverage_config.command` to generate coverage\n- [ ] Uses `coverage_config.timeout` for command execution\n- [ ] No coverage config → baseline refresh unavailable\n- [ ] `coverage.command` failure → overall validation fails\n\n## Test Plan\n- [ ] Update `tests/test_coverage.py`:\n  - Test baseline refresh with custom coverage command\n  - Test failure when `coverage.command` fails\n  - Test behavior without coverage config (baseline refresh unavailable)\n- [ ] Run: `uv run pytest tests/test_coverage.py -v`\n\n## Notes for Agent\n- `coverage.command` is required for baseline refresh (per user decision 14)\n- Reuse existing command execution infrastructure\n- Coordinate with Task 12 for shell command execution","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-02T00:34:49.60237416Z","created_by":"cyou","updated_at":"2026-01-02T16:10:51.292327835Z","closed_at":"2026-01-02T16:10:51.292327835Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-fdp1.9","depends_on_id":"mala-fdp1","type":"parent-child","created_at":"2026-01-02T00:34:49.609535984Z","created_by":"daemon"},{"issue_id":"mala-fdp1.9","depends_on_id":"mala-fdp1.8","type":"blocks","created_at":"2026-01-02T00:36:37.25032021Z","created_by":"daemon"}]}
{"id":"mala-fhm5","title":"Wire max_gate_retries from mala.yaml to orchestrator","description":"Add top-level or session_end config field for max_gate_retries (per-session quality gate retries). Currently hardcoded to 3. Follow the pattern used for max_review_retries - extract from validation config in _derive_config() and use in orchestrator init.","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-10T21:21:57.487366052Z","created_by":"cyou","updated_at":"2026-01-10T22:02:19.412785348Z","closed_at":"2026-01-10T22:02:19.412785348Z","close_reason":"Closed"}
{"id":"mala-fhyl","title":"[Review] src/domain/prompts.py:55: [P2] Default format command uses `ruff format` instead of `ruff format --check`","description":"## Review Finding\n\nThis issue was auto-created from a P2 review finding.\n\n**Source issue:** mala-vnzl\n**Reviewer:** claude\n**Location:** src/domain/prompts.py:55\n\n## Details\n\nThe fallback default in `prompts.py:55` and `agent_session_runner.py:997` uses `uvx ruff format .` which modifies files in-place, while the python-uv preset correctly uses `ruff format --check .` which only checks. This inconsistency means when no config file exists, the format command will silently reformat files instead of reporting violations. This could cause confusing behavior where format checks \"pass\" by reformatting rather than failing on unformatted code.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T00:31:02.154069373Z","created_by":"cyou","updated_at":"2026-01-03T00:42:21.532384582Z","closed_at":"2026-01-03T00:42:21.532384582Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_finding:3d807ef83981","source:mala-vnzl"]}
{"id":"mala-fih","title":"Claude orchestrator with manual context management","description":"Add back a Claude-based orchestrator that manually manages context windows (non-MCP), for long-running tasks that exceed default context limits.","status":"closed","priority":4,"issue_type":"epic","created_at":"2025-12-26T00:55:42.188094716Z","updated_at":"2026-01-01T21:40:54.414344386Z","closed_at":"2026-01-01T21:40:54.414344386Z","close_reason":"Closed"}
{"id":"mala-fnst","title":"Epic: AgentSessionRunner Refactor - Full Decomposition","description":"# AgentSessionRunner Refactor: Full Decomposition\n\n**Plan**: `plans/2026-01-04-agent-session-runner-refactor-plan.md`\n**Source**: Architecture review finding - \"AgentSessionRunner is a complex class mixing multiple concerns (~1535 LOC)\"\n\n## Goals\n- Extract `IdleTimeoutRetryPolicy` (retry logic, backoff handling)\n- Extract `LifecycleEffectHandler` (gate/review effect processing)\n- Make `AgentSessionRunner` orchestration-only, delegating to extracted components\n\n## Phases\n1. **Foundation**: Create dataclasses for new components\n2. **Phase 1**: Extract IdleTimeoutRetryPolicy + tests\n3. **Phase 2**: Extract LifecycleEffectHandler + tests\n4. **Validation**: Verify integration tests pass\n\n## Behavioral Acceptance Criteria\n- [ ] `AgentSessionRunner` no longer contains exponential backoff calculation logic\n- [ ] `AgentSessionRunner` no longer directly calls `asyncio.sleep()` for retry delays\n- [ ] All gate/review side-effect routing is handled by `LifecycleEffectHandler`\n- [ ] `IdleTimeoutRetryPolicy` can be unit tested with mock SDK client\n- [ ] `LifecycleEffectHandler` can be unit tested with mock gate/review runners\n\n## File Impact\n- `src/pipeline/idle_retry_policy.py` — **New**\n- `src/pipeline/lifecycle_effect_handler.py` — **New**\n- `src/pipeline/agent_session_runner.py` — Modify (~500+ LOC removed)\n- `tests/unit/pipeline/test_idle_retry_policy.py` — **New**\n- `tests/unit/pipeline/test_lifecycle_effect_handler.py` — **New**","status":"closed","priority":1,"issue_type":"epic","created_at":"2026-01-04T21:14:14.961431936Z","created_by":"cyou","updated_at":"2026-01-05T01:25:07.996720637Z","closed_at":"2026-01-05T01:25:07.996720637Z","close_reason":"Closed"}
{"id":"mala-fnst.1","title":"[T001] Create IterationResult dataclass and RetryConfig","description":"## Goal\nCreate the new module with `IterationResult` dataclass and `RetryConfig` for idle timeout retry handling.\n\n## Context\nFoundation task for Phase 1 extraction. These types are needed by `IdleTimeoutRetryPolicy`.\n\n## Scope\n- **In**: Create `src/pipeline/idle_retry_policy.py` with dataclasses\n- **Out**: The `IdleTimeoutRetryPolicy` class itself (T002)\n\n## Changes\n- `src/pipeline/idle_retry_policy.py` — **New** — Create module with:\n  ```python\n  @dataclass\n  class RetryConfig:\n      max_idle_retries: int = 2\n      idle_retry_backoff: tuple[float, ...] = (0.0, 5.0, 15.0)\n\n  @dataclass\n  class IterationResult:\n      success: bool\n      session_id: str | None = None\n      should_continue: bool = True\n      error_message: str | None = None\n  ```\n\n## Acceptance Criteria\n- [ ] `IterationResult` dataclass exists with `success`, `session_id`, `should_continue`, `error_message` fields\n- [ ] `RetryConfig` dataclass exists with `max_idle_retries`, `idle_retry_backoff` fields\n- [ ] Module imports cleanly without circular dependencies\n\n## Verification\n```bash\nuvx ruff check src/pipeline/idle_retry_policy.py\nuvx ty check\npython -c \"from src.pipeline.idle_retry_policy import IterationResult, RetryConfig\"\n```\n\n## Notes for Agent\n- Use `from __future__ import annotations` for forward references\n- Use TYPE_CHECKING imports if needed for types from other modules","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-04T21:14:28.003722171Z","created_by":"cyou","updated_at":"2026-01-04T23:39:28.989770618Z","closed_at":"2026-01-04T23:39:28.989770618Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-fnst.1","depends_on_id":"mala-fnst","type":"parent-child","created_at":"2026-01-04T21:14:28.02383352Z","created_by":"daemon"}]}
{"id":"mala-fnst.10","title":"[Review] 2 non-blocking findings from mala-fnst.4","description":"## Review Findings\n\nThis issue consolidates 2 non-blocking findings from code review.\n\n**Source issue:** mala-fnst.4\n**Highest priority:** P3\n\n---\n\n### Finding 1: [P3] Debug log missing blocking issue count after extraction\n\n**Priority:** P3\n**Reviewer:** claude\n**Location:** src/pipeline/agent_session_runner.py:607-615\n\nThe original `_handle_review_effect` logged `blocking=%d` in the 'review completed' debug message (showing blocking issue count). After extraction, the inlined code in `_run_lifecycle_loop` omits this field from the log message. While debug logging isn't critical, this is a minor regression in observability for operators debugging review behavior.\n\n---\n\n### Finding 2: [P3] FakeEventSink missing on_gate_failed method in unit tests\n\n**Priority:** P3\n**Reviewer:** claude\n**Location:** tests/unit/pipeline/test_lifecycle_effect_handler.py:48-158\n\nThe `FakeEventSink` class in `test_lifecycle_effect_handler.py` is missing the `on_gate_failed` method that exists in the integration test's `FakeEventSink`. This will cause `AttributeError` if any unit test exercises the gate failure path with an event sink. Currently the unit tests don't trigger this path, but it's a test gap that could cause failures if tests are extended.\n\n---\n\n\u003c!-- fp:f11ac057c546cfbe --\u003e\n\u003c!-- fp:0e93abcdd7a623ca --\u003e\n\u003c!-- review_finding:bf847397d14a --\u003e","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-05T00:51:05.430510748Z","created_by":"cyou","updated_at":"2026-01-05T00:55:19.352227281Z","closed_at":"2026-01-05T00:55:19.352227281Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-fnst.4"],"dependencies":[{"issue_id":"mala-fnst.10","depends_on_id":"mala-fnst","type":"parent-child","created_at":"2026-01-05T00:51:05.430921716Z","created_by":"daemon"}]}
{"id":"mala-fnst.2","title":"[T002] Extract IdleTimeoutRetryPolicy class","description":"## Goal\nMove `_run_message_iteration`, `_prepare_idle_retry`, `_apply_retry_backoff`, `_process_message_stream`, and `_disconnect_client_safely` logic into `IdleTimeoutRetryPolicy` class.\n\n## Context\nThis is the primary Phase 1 extraction. The retry policy encapsulates idle timeout handling with backoff semantics.\n\n## Scope\n- **In**: Extract retry logic into new class, wire into AgentSessionRunner\n- **Out**: Unit tests (T003), lifecycle effect handling (T004)\n\n## Changes\n- `src/pipeline/idle_retry_policy.py` — Exists — Add `IdleTimeoutRetryPolicy` class with:\n  - `__init__(sdk_client_factory, message_stream_processor, callbacks, config)`\n  - `execute_iteration(query, options, state, lifecycle_ctx, lint_cache, idle_timeout_seconds, tracer) -\u003e IterationResult`\n  - `_apply_retry_backoff(retry_count) -\u003e None`\n  - `_prepare_idle_retry(state, lifecycle_ctx, issue_id) -\u003e str`\n  - `_process_message_stream(client, state, lifecycle_ctx, lint_cache, tracer) -\u003e None`\n  - `_disconnect_client_safely(client, issue_id) -\u003e None`\n- `src/pipeline/agent_session_runner.py` — Exists — \n  - Remove `_run_message_iteration`, `_prepare_idle_retry`, `_apply_retry_backoff`, `_process_message_stream`, `_disconnect_client_safely` methods\n  - Keep `_get_stream_processor()` - it creates the MessageStreamProcessor passed to the policy\n  - Add `_retry_policy: IdleTimeoutRetryPolicy` field\n  - Instantiate in `__post_init__`\n  - Update `_run_lifecycle_loop` to call `self._retry_policy.execute_iteration()`\n\n## Wiring Map\n- `RetryConfig` → `AgentSessionRunner.__post_init__` → `IdleTimeoutRetryPolicy.__init__`\n- `SDKClientFactoryProtocol` → `IdleTimeoutRetryPolicy` → `execute_iteration`\n- `MessageStreamProcessor` (from `_get_stream_processor()`) → `IdleTimeoutRetryPolicy` → `_process_message_stream`\n\n## Acceptance Criteria\n- [ ] `AgentSessionRunner` no longer contains `_apply_retry_backoff` method\n- [ ] `AgentSessionRunner` no longer contains `_prepare_idle_retry` method\n- [ ] `AgentSessionRunner` no longer contains `_process_message_stream` method\n- [ ] `AgentSessionRunner` no longer directly calls `asyncio.sleep()` for retry delays\n- [ ] `IdleTimeoutRetryPolicy.execute_iteration()` returns `IterationResult`\n\n## Verification\n```bash\nuvx ruff check src/pipeline/\nuvx ty check\ngrep -c \"_apply_retry_backoff\\|_prepare_idle_retry\\|_process_message_stream\" src/pipeline/agent_session_runner.py  # Should be 0\n```\n\n## Notes for Agent\n- Preserve exact async behavior including `asyncio.shield` patterns\n- Keep `IdleTimeoutError` exception handling intact\n- The `MessageStreamProcessor` is created by `_get_stream_processor()` in the runner - pass it to the policy\n- `IdleTimeoutStream` wrapper should be used within the policy\n- Preserve logging with same logger name pattern\n- `_disconnect_client_safely` handles graceful SDK client disconnection with timeout","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-04T21:14:48.508618075Z","created_by":"cyou","updated_at":"2026-01-05T00:14:24.596766589Z","closed_at":"2026-01-05T00:14:24.596766589Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-fnst.2","depends_on_id":"mala-fnst","type":"parent-child","created_at":"2026-01-04T21:14:48.528323459Z","created_by":"daemon"},{"issue_id":"mala-fnst.2","depends_on_id":"mala-fnst.1","type":"blocks","created_at":"2026-01-04T21:14:52.44128177Z","created_by":"daemon"}]}
{"id":"mala-fnst.3","title":"[T003] Add unit tests for IdleTimeoutRetryPolicy","description":"## Goal\nCreate comprehensive unit tests for the extracted retry policy.\n\n## Context\nTests verify the retry policy in isolation with mock SDK client. This task includes the integration-path-test requirement.\n\n## Scope\n- **In**: Unit tests for retry policy with mock dependencies\n- **Out**: Integration tests (T006)\n\n## Changes\n- `tests/unit/pipeline/test_idle_retry_policy.py` — **New** — Unit tests including:\n  - `test_execute_iteration_success`: Normal execution returns success\n  - `test_execute_iteration_timeout_triggers_retry`: Timeout triggers backoff\n  - `test_execute_iteration_max_retries_exceeded`: Max retries raises `IdleTimeoutError`\n  - `test_backoff_timing_uses_config`: Backoff uses configured delays\n  - `test_retry_config_override`: Config override test for `max_idle_retries=0`\n\n## Acceptance Criteria\n- [ ] Test: Normal execution (no timeout) returns success\n- [ ] Test: Timeout triggers backoff and retry\n- [ ] Test: Max retries exceeded raises `IdleTimeoutError`\n- [ ] Test: Backoff timing uses configured delays\n- [ ] 85% coverage on `idle_retry_policy.py`\n\n## Verification\n```bash\nuv run pytest tests/unit/pipeline/test_idle_retry_policy.py -v\nuv run pytest tests/unit/pipeline/test_idle_retry_policy.py --cov=src/pipeline/idle_retry_policy --cov-fail-under=85\n```\n\n## Config Override Test (Required)\nTest that `RetryConfig(max_idle_retries=0)` causes immediate failure on first timeout:\n```python\ndef test_retry_config_zero_max_retries_fails_immediately():\n    config = RetryConfig(max_idle_retries=0)\n    policy = IdleTimeoutRetryPolicy(..., config=config)\n    # First timeout should raise IdleTimeoutError immediately\n```\n\n## Notes for Agent\n- Use `pytest-asyncio` for async tests\n- Mock `SDKClientFactoryProtocol` and `MessageStreamProcessor`\n- Use `unittest.mock.AsyncMock` for async methods\n- Follow existing test patterns in `tests/unit/pipeline/test_message_stream_processor.py`\n\n## Tags\n[integration-path-test]","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-04T21:15:08.796851423Z","created_by":"cyou","updated_at":"2026-01-05T00:38:41.285841829Z","closed_at":"2026-01-05T00:38:41.285841829Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-fnst.3","depends_on_id":"mala-fnst","type":"parent-child","created_at":"2026-01-04T21:15:08.8062265Z","created_by":"daemon"},{"issue_id":"mala-fnst.3","depends_on_id":"mala-fnst.2","type":"blocks","created_at":"2026-01-04T21:15:12.109817517Z","created_by":"daemon"}]}
{"id":"mala-fnst.4","title":"[T004] Extract LifecycleEffectHandler class","description":"## Goal\nMove `_handle_gate_check`, `_handle_review_check`, `_handle_gate_effect`, `_handle_review_effect` into `LifecycleEffectHandler` class.\n\n## Context\nThis is the primary Phase 2 extraction. The effect handler encapsulates all gate/review side-effect processing.\n\n## Scope\n- **In**: Extract effect handling into new class, wire into AgentSessionRunner\n- **Out**: Unit tests (T005)\n\n## Changes\n- `src/pipeline/lifecycle_effect_handler.py` — **New** — Create class with:\n  - `__init__(config, callbacks, event_sink)`\n  - `process_gate_check(input, state, lifecycle, lifecycle_ctx) -\u003e tuple[str | None, TransitionResult]`\n  - `process_review_check(input, state, lifecycle, lifecycle_ctx) -\u003e tuple[str | None, TransitionResult | None]`\n  - `process_gate_effect(input, gate_result, lifecycle, lifecycle_ctx, new_offset) -\u003e tuple[str | None, bool, TransitionResult]`\n  - `process_review_effect(input, log_path, session_id, lifecycle, lifecycle_ctx) -\u003e ReviewEffectResult`\n  - Move helper functions: `_emit_review_result_events`, `_emit_gate_passed_events`, `_count_blocking_issues`, `_make_review_effect_result`, `_build_review_retry_prompt`\n- `src/pipeline/agent_session_runner.py` — Exists —\n  - Remove `_handle_gate_check`, `_handle_review_check`, `_handle_gate_effect`, `_handle_review_effect` methods\n  - Remove helper functions that moved\n  - Add `_effect_handler: LifecycleEffectHandler` field\n  - Instantiate in `__post_init__`\n  - Update `_run_lifecycle_loop` to call `self._effect_handler.process_*()` methods\n\n## Wiring Map\n- `SessionCallbacks` → `LifecycleEffectHandler.__init__`\n- `AgentSessionConfig` → `LifecycleEffectHandler` (for prompts, max retries)\n- `LifecycleEffectHandler` → `AgentSessionRunner._run_lifecycle_loop`\n\n## Acceptance Criteria\n- [ ] `AgentSessionRunner` no longer contains `_handle_gate_check` method\n- [ ] `AgentSessionRunner` no longer contains `_handle_review_check` method\n- [ ] `AgentSessionRunner` no longer contains `_handle_gate_effect` method\n- [ ] `AgentSessionRunner` no longer contains `_handle_review_effect` method\n- [ ] All gate/review side-effect routing handled by `LifecycleEffectHandler`\n\n## Verification\n```bash\nuvx ruff check src/pipeline/\nuvx ty check\ngrep -c \"_handle_gate_check\\|_handle_review_check\\|_handle_gate_effect\\|_handle_review_effect\" src/pipeline/agent_session_runner.py  # Should be 0\n```\n\n## Notes for Agent\n- The `ReviewEffectResult` dataclass already exists in agent_session_runner.py - keep it there or move to lifecycle_effect_handler.py\n- Preserve event emission logic exactly (on_gate_passed, on_review_retry, etc.)\n- Follow existing callback injection patterns from SessionCallbacks\n- Use TYPE_CHECKING imports for types to avoid circular imports","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-04T21:15:33.849474402Z","created_by":"cyou","updated_at":"2026-01-05T00:51:05.355271291Z","closed_at":"2026-01-05T00:51:05.355271291Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-fnst.4","depends_on_id":"mala-fnst","type":"parent-child","created_at":"2026-01-04T21:15:33.860407485Z","created_by":"daemon"},{"issue_id":"mala-fnst.4","depends_on_id":"mala-fnst.2","type":"blocks","created_at":"2026-01-04T21:15:37.985556634Z","created_by":"daemon"}]}
{"id":"mala-fnst.5","title":"[T005] Add unit tests for LifecycleEffectHandler","description":"## Goal\nCreate comprehensive unit tests for the extracted effect handler.\n\n## Context\nTests verify the effect handler in isolation with mock gate/review runners.\n\n## Scope\n- **In**: Unit tests for effect handler with mock dependencies\n- **Out**: Integration validation (T006)\n\n## Changes\n- `tests/unit/pipeline/test_lifecycle_effect_handler.py` — **New** — Unit tests including:\n  - `test_process_gate_check_calls_callback`: Gate check routing\n  - `test_process_review_check_calls_callback`: Review check routing\n  - `test_process_gate_effect_success`: Gate effect success path\n  - `test_process_gate_effect_retry`: Gate effect retry path\n  - `test_process_review_effect_success`: Review effect success path\n  - `test_process_review_effect_retry`: Review effect retry path\n  - `test_event_emissions`: Event sink methods called correctly\n\n## Acceptance Criteria\n- [ ] Test: Gate check routing calls callback correctly\n- [ ] Test: Review check routing calls callback correctly\n- [ ] Test: Gate effect produces correct transition results\n- [ ] Test: Review effect produces correct transition results\n- [ ] Test: Event emissions are triggered correctly\n- [ ] 85% coverage on `lifecycle_effect_handler.py`\n\n## Verification\n```bash\nuv run pytest tests/unit/pipeline/test_lifecycle_effect_handler.py -v\nuv run pytest tests/unit/pipeline/test_lifecycle_effect_handler.py --cov=src/pipeline/lifecycle_effect_handler --cov-fail-under=85\n```\n\n## Notes for Agent\n- Use `pytest-asyncio` for async tests\n- Mock `SessionCallbacks` and `MalaEventSink`\n- Use `unittest.mock.MagicMock` for sync methods, `AsyncMock` for async\n- Follow existing test patterns in `tests/unit/pipeline/test_context_pressure_handler.py`","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-04T21:15:51.011631255Z","created_by":"cyou","updated_at":"2026-01-05T00:52:41.467219135Z","closed_at":"2026-01-05T00:52:41.467219135Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-fnst.5","depends_on_id":"mala-fnst","type":"parent-child","created_at":"2026-01-04T21:15:51.022969186Z","created_by":"daemon"},{"issue_id":"mala-fnst.5","depends_on_id":"mala-fnst.4","type":"blocks","created_at":"2026-01-04T21:15:54.676026956Z","created_by":"daemon"}]}
{"id":"mala-fnst.6","title":"[T006] Verify behavioral integration tests pass","description":"## Goal\nVerify that all behavioral integration tests for `run_session` pass. Migrate unit tests for private methods to new component test files.\n\n## Context\nFinal validation task. Ensures the refactor has no regressions and cleans up tests that directly called removed private methods.\n\n## Scope\n- **In**: Run integration tests, migrate/remove unit tests for private methods\n- **Out**: E2E tests (optional manual verification)\n\n## Changes\n- `tests/integration/pipeline/test_agent_session_runner.py` — Exists — \n  - Remove or migrate tests that directly call `_handle_gate_check()`, `_handle_review_check()`, etc.\n  - Keep all `run_session` behavioral tests unchanged\n\n## Acceptance Criteria\n- [ ] All `run_session` behavioral tests pass\n- [ ] No tests directly call removed private methods\n- [ ] Integration test suite passes with `-n auto`\n- [ ] E2E tests pass (if available)\n\n## Verification\n```bash\nuv run pytest tests/integration/pipeline/test_agent_session_runner.py -v\nuv run pytest -m \"unit or integration\" -n auto\nuv run pytest -m e2e  # Optional\n```\n\n## Notes for Agent\n- Tests like `TestHandleGateCheck`, `TestHandleReviewCheck`, `TestHandleReviewEffectIntegration` call private methods - these should be migrated to the new test files or removed if redundant\n- Behavioral tests that call `runner.run_session()` directly should pass unchanged\n- Check for any fixtures that need updating for new component structure","status":"closed","priority":2,"issue_type":"chore","created_at":"2026-01-04T21:16:08.028551376Z","created_by":"cyou","updated_at":"2026-01-05T01:23:35.132592761Z","closed_at":"2026-01-05T01:23:35.132592761Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-fnst.6","depends_on_id":"mala-fnst","type":"parent-child","created_at":"2026-01-04T21:16:08.040312574Z","created_by":"daemon"},{"issue_id":"mala-fnst.6","depends_on_id":"mala-fnst.4","type":"blocks","created_at":"2026-01-04T21:16:11.835353634Z","created_by":"daemon"},{"issue_id":"mala-fnst.6","depends_on_id":"mala-fnst.5","type":"blocks","created_at":"2026-01-04T21:16:11.854324189Z","created_by":"daemon"}]}
{"id":"mala-fnst.7","title":"[Review] 1 non-blocking finding from mala-fnst.1","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-fnst.1\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Validate RetryConfig backoff length vs max retries\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/pipeline/idle_retry_policy.py:16-17\n\n`RetryConfig` allows `max_idle_retries` to be set higher than the number of entries in `idle_retry_backoff` (or negative), which can later cause an `IndexError` or incorrect sleep behavior when the retry policy indexes into the backoff sequence. This is likely to surface when callers customize config (e.g., `max_idle_retries=5` with the default 3 backoff values). Consider adding a small `__post_init__` to enforce invariants (e.g., `max_idle_retries \u003e= 0` and `len(idle_retry_backoff) \u003e= max_idle_retries + 1`, or clearly document/encode the intended indexing semantics).\n\n---\n\n\u003c!-- fp:a904cf54ff9edc66 --\u003e\n\u003c!-- review_finding:6fcdfc6c0297 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-04T23:39:29.068715764Z","created_by":"cyou","updated_at":"2026-01-04T23:46:14.013078777Z","closed_at":"2026-01-04T23:46:14.013078777Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-fnst.1"],"dependencies":[{"issue_id":"mala-fnst.7","depends_on_id":"mala-fnst","type":"parent-child","created_at":"2026-01-04T23:39:29.069167081Z","created_by":"daemon"}]}
{"id":"mala-fnst.8","title":"[Review] 1 non-blocking finding from mala-fnst.7","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-fnst.7\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Fix RetryConfig backoff length invariant (off-by-one vs usage)\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/pipeline/idle_retry_policy.py:57-66\n\n`RetryConfig.__post_init__` requires `len(idle_retry_backoff) \u003e= max_idle_retries + 1`, but the policy indexes backoff with a 1-based `retry_count` and clamps to the last entry, so it never needs `+1` and won’t raise `IndexError` anyway.\n\n```\nbackoff_idx = min(retry_count - 1, len(self._config.idle_retry_backoff) - 1)\nbackoff = self._config.idle_retry_backoff[backoff_idx]\n```\n\nAs written, this will reject configs that the code path handles fine (e.g., `max_idle_retries=1` with a single backoff value, or `max_idle_retries=0` with an empty tuple). The docstring (“including retry 0”) and the unit tests should be updated to match the intended semantics once the invariant is corrected.\n\n---\n\n\u003c!-- fp:47d99b1d16413173 --\u003e\n\u003c!-- review_finding:ebfc81d71c0f --\u003e","notes":"Quality gate failed: Aborted due to unrecoverable error","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-04T23:46:14.086599287Z","created_by":"cyou","updated_at":"2026-01-05T01:21:17.876816456Z","closed_at":"2026-01-05T01:21:17.876816456Z","close_reason":"Closed","labels":["auto_generated","needs-followup","review_finding","source:mala-fnst.7"],"dependencies":[{"issue_id":"mala-fnst.8","depends_on_id":"mala-fnst","type":"parent-child","created_at":"2026-01-04T23:46:14.087013184Z","created_by":"daemon"},{"issue_id":"mala-fnst.8","depends_on_id":"mala-fnst.2","type":"blocks","created_at":"2026-01-04T23:49:18.038153613Z","created_by":"daemon"}]}
{"id":"mala-fnst.9","title":"[Review] 2 non-blocking findings from mala-fnst.2","description":"## Review Findings\n\nThis issue consolidates 2 non-blocking findings from code review.\n\n**Source issue:** mala-fnst.2\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Make RetryConfig defaults pass their own validation\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/pipeline/idle_retry_policy.py:59-70\n\n`RetryConfig` defaults currently raise `ValueError` because `max_idle_retries` defaults to `2` while `idle_resume_prompt` defaults to `\"\"`, and `__post_init__` enforces a non-empty prompt when retries are enabled. This makes `RetryConfig()` unusable and surprising given it provides defaults. Fix by aligning defaults with validation (e.g., default `max_idle_retries=0`, or provide a non-empty default `idle_resume_prompt`, or relax the validation to avoid making the default constructor invalid).\n\n---\n\n### Finding 2: [P3] Use state.pending_tool_ids in integration test IdleTimeoutStream\n\n**Priority:** P3\n**Reviewer:** codex\n**Location:** tests/integration/pipeline/test_agent_session_runner.py:1808-1814\n\nThe integration test wraps `mock_stream()` with `IdleTimeoutStream(..., set())` rather than using `state.pending_tool_ids`. In production, the policy passes `state.pending_tool_ids` so tool-id tracking is shared between the stream wrapper and state. Using a different set reduces test fidelity and could mask issues where pending tool IDs suppress idle timeouts.\n\n---\n\n\u003c!-- fp:4cafd6c2bc961fe2 --\u003e\n\u003c!-- fp:ac02d7d8c5a68eee --\u003e\n\u003c!-- review_finding:ec1efb2a72dc --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-05T00:14:24.671700907Z","created_by":"cyou","updated_at":"2026-01-05T00:20:17.910763422Z","closed_at":"2026-01-05T00:20:17.910763422Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-fnst.2"],"dependencies":[{"issue_id":"mala-fnst.9","depends_on_id":"mala-fnst","type":"parent-child","created_at":"2026-01-05T00:14:24.672109335Z","created_by":"daemon"}]}
{"id":"mala-ftx1","title":"[Review] 2 non-blocking findings from mala-aepf","description":"## Review Findings\n\nThis issue consolidates 2 non-blocking findings from code review.\n\n**Source issue:** mala-aepf\n**Highest priority:** P3\n\n---\n\n### Finding 1: [P3] Update docs to describe integration-tests, not slow tests\n\n**Priority:** P3\n**Reviewer:** codex\n**Location:** docs/quality-hardening-plan.md:324-326\n\n`--disable integration-tests` is described in the CLI as excluding `@pytest.mark.integration` tests, but the docs still say it maps to “non-slow pytest only.” This is now inaccurate and could cause users to disable the wrong thing or misinterpret EvidenceGate expectations; update the mapping text to refer to excluding integration tests (or whatever the actual gate expectation is for that flag).\n\n---\n\n### Finding 2: [P3] Rename “slow tests” default to match integration-tests toggle\n\n**Priority:** P3\n**Reviewer:** codex\n**Location:** docs/quality-hardening-plan.md:233-237\n\nThe “Defaults” list still says “slow tests: ON”, but the documented/implemented opt-out is now `--disable integration-tests` and there’s no `slow-tests` concept in the CLI. This mismatch was introduced by the rename and should be updated (e.g., “integration tests: ON”) to keep the defaults consistent with the actual flag users can disable.\n\n---\n\n\u003c!-- fp:50d0ec370c8d672a --\u003e\n\u003c!-- fp:91af7a915a215b8e --\u003e\n\u003c!-- review_finding:8542cdec5d64 --\u003e","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-06T03:50:16.054144401Z","created_by":"cyou","updated_at":"2026-01-06T03:59:45.554272967Z","closed_at":"2026-01-06T03:59:45.554272967Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-aepf"]}
{"id":"mala-fx2","title":"Improve orchestrator terminal logging visibility","description":"## Problem\n\nAfter codex review passes, there are no logs until tests pass, making it unclear if the orchestrator is hung or actively working. The logging posture has significant gaps throughout the orchestration lifecycle.\n\n## Current Logging Gaps\n\n### Phase 1: Log File Waiting (AWAITING_LOG state)\n- No log when entering AWAITING_LOG state\n- No log when log file is found (silent success)\n- Only timeout warnings are logged\n\n### Phase 2: Quality Gate Execution (CRITICAL GAP)\nThis is the **most important gap**:\n- No log when starting quality gate check (orchestrator.py:860)\n- No log when gate passes (orchestrator.py:874)\n- No log showing which validation steps are running (pytest, ruff, ty)\n- Gate retry messages exist (line 885) but initial attempts are silent\n- Individual validation commands run completely silently\n\n### Phase 3: State Transitions\n- Lifecycle effect transitions don't log entry/exit\n- Users can't see which state the orchestrator is in\n\n## Locations to Fix\n\n1. **orchestrator.py:860** - Add log before quality gate starts\n2. **orchestrator.py:874** - Add log when gate passes  \n3. **quality_gate.py** - Add logs for each validation step (pytest/ruff/ty)\n4. **orchestrator.py:828-857** - Add logs for log file polling lifecycle\n5. **lifecycle.py** - Consider logging state transitions\n\n## Acceptance Criteria\n\n- [ ] Log message when entering quality gate phase\n- [ ] Log message for each validation step (pytest/ruff check/ruff format/ty)\n- [ ] Log message when quality gate passes/fails with summary\n- [ ] Log message when waiting for log file and when found\n- [ ] State transitions visible at verbose level (-v flag)\n- [ ] No silent gaps \u003e 5 seconds during normal operation\n- [ ] Logs should be concise but informative (icon + brief message pattern)\n\n## Non-Goals\n\n- Don't add excessive logging that clutters output\n- Don't log internal implementation details\n- Keep existing log format/style consistent","status":"closed","priority":2,"issue_type":"feature","created_at":"2025-12-28T19:55:30.940708588Z","updated_at":"2025-12-28T20:24:38.311409891Z","closed_at":"2025-12-28T20:24:38.311409891Z","close_reason":"Closed","labels":["enhancement"]}
{"id":"mala-g1sg","title":"[Review] src/infra/clients/cerberus_review.py:248-250: [P2] Robustly check both stderr and stdout for 'already active' error","description":"## Review Finding\n\nThis issue was auto-created from a P2 review finding.\n\n**Source issue:** mala-awsa\n**Reviewer:** gemini\n**Location:** src/infra/clients/cerberus_review.py:248-250\n\n## Details\n\nThe `detail` string is constructed using `stderr or stdout`, which means if the tool emits any output to `stderr` (such as a warning or progress message) while the actual 'already active' error is in `stdout`, the check will only evaluate `stderr` and miss the fatal error condition. Consider combining both streams or checking them independently to ensure the fatal error is always detected.","notes":"Quality gate failed: External review failed: spawn failed: Artifact written: /home/cyou/.claude/projects/-home-cyou-mala/cerberus/0912b4db-bf82-4e51-9436-a4b9b8cdbe58/latest.md\nReview gate already active (status: pending, age: 37s)\nUse /home/cyou/.claude/plugins/cache/cerberus/cerberus/1.10.6/bin/review-gate resolve to complete the existing gate first.\nLog: /home/cyou/.claude/projects/-home-cyou-mala/0912b4db-bf82-4e51-9436-a4b9b8cdbe58.jsonl","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-01T23:55:18.801114849Z","created_by":"cyou","updated_at":"2026-01-02T00:48:36.303676191Z","closed_at":"2026-01-02T00:48:36.303676191Z","close_reason":"Closed","labels":["auto_generated","needs-followup","review_finding","review_finding:13f6d14e6989","source:mala-awsa"]}
{"id":"mala-g3h","title":"Epic: Focus mode prioritization (epic-grouped ordering)","description":"Context:\n- Currently tasks are ordered by priority only, causing interleaved commits across unrelated epics\n- This makes PR reviews difficult as changes span multiple unrelated features\n- Need epic-grouped ordering as the default: finish one epic before starting the next\n- Orphan tasks (no parent epic) compete by their own priority\n\nScope:\n- In: New `--focus/--no-focus` CLI flag (default True), epic-grouped ordering algorithm, parent epic lookup\n- Out: Changes to how epics themselves are prioritized or closed\n\nAcceptance Criteria:\n- Default behavior groups tasks by epic, finishing one before starting another\n- `--no-focus` reverts to priority-only interleaved ordering\n- Orphan tasks compete with epic groups by min priority\n- Tiebreaker for equal-priority groups: most recently updated first\n\nPrimary files: src/beads_client.py, src/cli.py, src/orchestrator.py","status":"closed","priority":1,"issue_type":"epic","created_at":"2025-12-27T18:14:07.040578765Z","updated_at":"2025-12-28T02:15:11.779722936Z","closed_at":"2025-12-28T02:15:11.779722936Z","close_reason":"All children completed"}
{"id":"mala-g3h.1","title":"BeadsClient: add parent epic lookup methods","description":"Context:\n- Need to determine parent epic for each task to enable grouping\n- `bd dep tree \u003cid\u003e --direction=down` returns parent chain (epic at depth=1)\n- Should cache results to avoid repeated subprocess calls for tasks in same epic\n\nScope:\n- In: Add `get_parent_epic_async(issue_id) -\u003e str | None` method to BeadsClient\n- In: Add batch method `get_parent_epics_async(issue_ids) -\u003e dict[str, str | None]` for efficiency\n- Out: Changes to existing `get_ready_async` signature (handled in later issue)\n\nAcceptance Criteria:\n- `get_parent_epic_async` returns parent epic ID or None for orphans\n- Batch method minimizes subprocess calls (one call per unique epic, not per task)\n- Method handles errors gracefully (returns None on failure)\n\nTest Plan:\n- Add unit tests with mocked subprocess calls\n- Test orphan detection (no parent epic)\n- Test caching behavior\n\nNotes for Agent:\n- Use existing `_run_subprocess_async` pattern\n- Epic is identified by `issue_type == \"epic\"` at depth \u003e 0 in tree output\n\nPrimary files: src/beads_client.py","notes":"Quality gate failed: Codex review failed: N/A:0: Missing scope item: “Add batch method `get_parent_epics_async(issue_ids) -\u003e dict[str, str | None]` for efficiency” — current implementation makes one subprocess call per issue and provides no caching or epic-level reuse, so it does not minimize calls per unique epic as required.; src/beads_client.py:0: Acceptance criterion unmet: “Batch method minimizes subprocess calls (one call per unique epic, not per task)” — `get_parent_epics_async` always calls `get_parent_epic_async` for every issue (no cache or shared lookup), so repeated tasks under the same epic still incur repeated subprocess calls.\nLog: /home/cyou/.claude/projects/-home-cyou-mala/f8ea7fdd-e1ed-420b-82f7-41ae08fccb3f.jsonl","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-27T18:19:30.185666573Z","updated_at":"2025-12-28T01:05:50.928479045Z","closed_at":"2025-12-28T01:05:50.928479045Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-g3h.1","depends_on_id":"mala-g3h","type":"parent-child","created_at":"2025-12-27T18:19:30.192508435Z","created_by":"daemon"}]}
{"id":"mala-g3h.2","title":"BeadsClient: implement epic-grouped sorting in get_ready_async","description":"Context:\n- `get_ready_async` currently sorts by `(status, priority)` with optional WIP prioritization\n- Need to add `focus` parameter that groups by parent epic before sorting by priority\n- Tiebreaker for equal-priority groups: max `updated_at` descending (recent first)\n\nScope:\n- In: Add `focus: bool = True` parameter to `get_ready_async`\n- In: Implement grouping algorithm: group by epic → sort groups by (min_priority, max_updated DESC) → sort within groups by (priority, updated DESC)\n- In: Orphan tasks form virtual group with same sorting rules\n- Out: CLI changes (separate issue)\n\nAcceptance Criteria:\n- When `focus=True`: tasks grouped by epic, one epic completed before next\n- When `focus=False`: current behavior (priority-only, interleaved)\n- WIP prioritization (`prioritize_wip`) still moves in_progress to front globally\n- Equal-priority groups ordered by most recently updated first\n\nTest Plan:\n- TDD: Add failing test for epic-grouped ordering\n- Test orphan tasks competing with epic groups\n- Test tiebreaker behavior (updated_at)\n- Test composition with `prioritize_wip`\n- Run `uv run pytest tests/test_wip_prioritization.py` (expand this file or create new)\n\nNotes for Agent:\n- Use `get_parent_epics_async` from previous issue for batch lookup\n- Issues from `bd ready --json` include `updated_at` field\n\nPrimary files: src/beads_client.py","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-27T18:19:46.264724796Z","updated_at":"2025-12-28T01:17:06.18218596Z","closed_at":"2025-12-28T01:17:06.18218596Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-g3h.2","depends_on_id":"mala-g3h","type":"parent-child","created_at":"2025-12-27T18:19:46.271892236Z","created_by":"daemon"},{"issue_id":"mala-g3h.2","depends_on_id":"mala-g3h.1","type":"blocks","created_at":"2025-12-27T18:20:16.400724757Z","created_by":"daemon"}]}
{"id":"mala-g3h.3","title":"CLI: add --focus/--no-focus flag for epic-grouped ordering","description":"Context:\n- Need CLI interface for the focus mode feature\n- Should be default True (epic-grouped), with `--no-focus` to revert to old behavior\n- Typer supports `--flag/--no-flag` syntax natively\n\nScope:\n- In: Add `--focus/--no-focus` flag to `run` command (default True)\n- In: Pass `focus` parameter through to orchestrator\n- In: Update help text to explain the behavior\n- Out: Changes to beads_client (already done in previous issue)\n\nAcceptance Criteria:\n- `mala run` uses epic-grouped ordering by default\n- `mala run --no-focus` uses priority-only ordering\n- Help text clearly explains the difference\n- Flag composes correctly with `--wip`\n\nTest Plan:\n- Test CLI parsing for `--focus` and `--no-focus`\n- Integration test showing different orderings\n- Run `uv run pytest tests/test_cli.py`\n\nNotes for Agent:\n- Use Typer's `--flag/--no-flag` syntax: `typer.Option(\"--focus/--no-focus\")`\n- Default value is `True`\n\nPrimary files: src/cli.py","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-27T18:19:57.722163143Z","updated_at":"2025-12-28T01:48:27.726665128Z","closed_at":"2025-12-28T01:48:27.726665128Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-g3h.3","depends_on_id":"mala-g3h","type":"parent-child","created_at":"2025-12-27T18:19:57.728621219Z","created_by":"daemon"},{"issue_id":"mala-g3h.3","depends_on_id":"mala-g3h.4","type":"blocks","created_at":"2025-12-27T18:22:11.479341622Z","created_by":"daemon"}]}
{"id":"mala-g3h.4","title":"Orchestrator: wire focus parameter through to BeadsClient","description":"Context:\n- Orchestrator receives config from CLI and passes to BeadsClient\n- Need to thread `focus` parameter through `MalaOrchestrator.__init__` and `run` method\n\nScope:\n- In: Add `focus` parameter to `MalaOrchestrator.__init__`\n- In: Pass `focus` to `beads.get_ready_async` calls in orchestrator\n- Out: CLI changes (done in previous issue), beads_client changes (done earlier)\n\nAcceptance Criteria:\n- `MalaOrchestrator` accepts `focus` parameter\n- Parameter passed correctly to all `get_ready_async` calls\n- Default behavior is focus mode enabled\n\nTest Plan:\n- Unit test orchestrator initialization with focus parameter\n- Integration test end-to-end with `--focus` and `--no-focus`\n- Run `uv run pytest tests/test_orchestrator.py`\n\nNotes for Agent:\n- Check all call sites of `get_ready_async` in orchestrator\n- Ensure consistency with existing parameter threading pattern (like `prioritize_wip`)\n\nPrimary files: src/orchestrator.py","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-27T18:20:08.198862782Z","updated_at":"2025-12-28T01:41:56.900739631Z","closed_at":"2025-12-28T01:41:56.900739631Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-g3h.4","depends_on_id":"mala-g3h","type":"parent-child","created_at":"2025-12-27T18:20:08.206662307Z","created_by":"daemon"},{"issue_id":"mala-g3h.4","depends_on_id":"mala-g3h.5","type":"blocks","created_at":"2025-12-27T18:23:43.739408416Z","created_by":"daemon"}]}
{"id":"mala-g3h.5","title":"BeadsClient: exclude tasks whose parent epic is blocked","description":"Context:\n- Currently `get_ready_async` relies on `bd ready` for blocker detection\n- But `bd ready` doesn't consider parent epic status\n- If Epic A is blocked by Epic B, tasks in Epic A still appear ready\n- This causes work to start on tasks that shouldn't be worked on yet\n\nScope:\n- In: When filtering ready tasks, check if parent epic is blocked\n- In: Exclude tasks whose parent epic has status \"blocked\" or has unmet dependencies\n- In: Use parent epic lookup from g3h.1 to get parent epic ID, then check its status\n- Out: Changes to how epic blocking works in beads itself\n\nAcceptance Criteria:\n- Tasks with a blocked parent epic do not appear in ready list\n- Tasks with an unblocked parent epic (or no parent) appear normally\n- Works correctly with focus mode enabled or disabled\n\nTest Plan:\n- TDD: Create test with mocked blocked epic, verify children excluded\n- Test orphan tasks still appear (no parent epic to block them)\n- Test task appears after parent epic unblocked\n- Run `uv run pytest tests/test_beads_client.py`\n\nNotes for Agent:\n- Can check epic status via `bd show \u003cepic_id\u003e --json`\n- Or batch check via `bd list --json` and filter\n- Consider caching epic status lookups for efficiency\n\nPrimary files: src/beads_client.py","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-27T18:23:21.585735056Z","updated_at":"2025-12-28T01:25:08.698681216Z","closed_at":"2025-12-28T01:25:08.698681216Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-g3h.5","depends_on_id":"mala-g3h","type":"parent-child","created_at":"2025-12-27T18:23:21.593215576Z","created_by":"daemon"},{"issue_id":"mala-g3h.5","depends_on_id":"mala-g3h.2","type":"blocks","created_at":"2025-12-27T18:23:43.705974665Z","created_by":"daemon"}]}
{"id":"mala-g3h.6","title":"CLI: add --dry-run flag to preview task order","description":"Context:\n- Users want to see what order tasks will be processed before actually running\n- Helps verify focus mode is working as expected\n- Useful for debugging prioritization issues\n\nScope:\n- In: Add `--dry-run` flag to `mala run` command\n- In: When set, call `get_ready_async` with current settings and display ordered task list\n- In: Show task ID, title, parent epic, priority for each task\n- In: Exit without claiming or processing any tasks\n- Out: Changes to actual task processing logic\n\nAcceptance Criteria:\n- `mala run --dry-run` outputs ordered list of tasks that would be processed\n- Output shows grouping by epic when focus mode enabled\n- Output includes task metadata (ID, title, epic, priority)\n- No tasks are claimed or modified\n- Works with all other flags (--epic, --only, --wip, --no-focus)\n\nTest Plan:\n- Test dry-run output matches actual run order\n- Test with various flag combinations\n- Test empty task list case\n- Run `uv run pytest tests/test_cli.py`\n\nNotes for Agent:\n- Consider table or tree format for output\n- Show epic headers when in focus mode to visualize grouping\n- Include count of tasks per epic\n\nPrimary files: src/cli.py","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-27T18:25:24.595986994Z","updated_at":"2025-12-28T02:15:11.760785313Z","closed_at":"2025-12-28T02:15:11.760785313Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-g3h.6","depends_on_id":"mala-g3h","type":"parent-child","created_at":"2025-12-27T18:25:24.59642406Z","created_by":"daemon"},{"issue_id":"mala-g3h.6","depends_on_id":"mala-g3h.3","type":"blocks","created_at":"2025-12-27T18:25:29.144583329Z","created_by":"daemon"}]}
{"id":"mala-g90u","title":"[Review] tests/test_code_pattern_matcher.py:70: [P2] mock_compile drops arguments passed to re.compile","description":"## Review Finding\n\nThis issue was auto-created from a P2 review finding.\n\n**Source issue:** mala-67vp\n**Reviewer:** gemini\n**Location:** tests/test_code_pattern_matcher.py:70\n\n## Details\n\nThe `mock_compile` helper function accepts `*args` and `**kwargs` but does not pass them to `original_compile` in the fallback path. If `glob_to_regex` uses flags (like `re.IGNORECASE`) when compiling the regex, they will be lost in the second call, potentially resulting in an incorrect regex object and invalidating the test.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-02T03:21:30.477554196Z","created_by":"cyou","updated_at":"2026-01-02T03:25:29.110208343Z","closed_at":"2026-01-02T03:25:29.110208343Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_finding:247ec6f5af2a","source:mala-67vp"]}
{"id":"mala-gc5l","title":"[Review] 1 non-blocking finding from mala-d6x.8","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-d6x.8\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Strip indented closing code fences in fallback\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/domain/prompts.py:154\n\nThe new closing-fence regex requires the ``` fence to start immediately after an optional newline. Markdown allows fences indented by up to a few spaces, so inputs like \" ```\\ntext\\n ```\" will no longer have the closing fence stripped and the fallback returns extra ``` text. This is a regression from the prior `\\s*` handling. Consider allowing horizontal whitespace before the closing fence while still requiring it to be on its own line (e.g., `\\n[ \\t]*```[ \\t]*\\Z`).\n\n---\n\n\u003c!-- fp:61f2bc643b27f295 --\u003e\n\u003c!-- review_finding:3d7c0f01e2b6 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T20:27:52.655327203Z","created_by":"cyou","updated_at":"2026-01-03T20:44:15.304877338Z","closed_at":"2026-01-03T20:44:15.304877338Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-d6x.8"]}
{"id":"mala-ggi","title":"Add continuous run mode flag to CLI","description":"Context:\nAdd a run mode flag in the CLI where if there are no ready tasks, it just keeps waiting infinitely. Same behavior at the end - just keeps running until an issue is ready.\n\nLocation:\n- src/cli/cli.py (CLI entry point, flag definition)\n- src/pipeline/issue_execution_coordinator.py:139-246 (run_loop)\n- src/orchestration/orchestrator.py:897-940 (_run_main_loop)\n\nScope:\n- In: CLI flag (--continuous or --wait) for infinite wait mode\n- In: Poll for new ready issues when queue empty\n- In: Clean shutdown on Ctrl+C\n- Out: Auto-creating issues; external triggers\n\n## Acceptance Criteria\n- `mala run --continuous` waits indefinitely when no ready tasks\n- Continues running after completing tasks instead of exiting\n- Polls periodically for new ready issues (configurable interval)\n- Ctrl+C triggers clean shutdown (see mala-b0m)\n\nTest Plan:\n- Unit: Test wait loop logic with mock issue provider\n- Integration: Start with no issues, add one, verify it's picked up\n- Manual: Run in continuous mode, add issues mid-run\n\nNotes for Agent:\n- Consider --poll-interval flag for custom poll frequency\n- Default poll interval: 30 seconds\n- May want --timeout to auto-exit after N minutes of no work","status":"closed","priority":2,"issue_type":"feature","created_at":"2025-12-27T05:54:29.745014725Z","updated_at":"2026-01-05T02:38:15.963377228Z","closed_at":"2026-01-05T02:38:15.963377228Z","close_reason":"Closed"}
{"id":"mala-ggx","title":"Spawn continuation agent with prior work summary","status":"tombstone","priority":2,"issue_type":"epic","created_at":"2025-12-26T00:55:42.639177056Z","updated_at":"2025-12-26T00:56:32.195175308Z","deleted_at":"2025-12-26T00:56:32.195175308Z","deleted_by":"daemon","delete_reason":"delete","original_type":"epic"}
{"id":"mala-giyp","title":"Epic: Watch Mode (--watch)","description":"# Watch Mode Implementation\n\nImplement persistent `--watch` mode for the mala orchestrator.\n\n## Summary\n- Add `--watch` flag to keep orchestrator running after all ready tasks complete\n- Poll for new issues every 60 seconds instead of exiting\n- Add `--validate-every N` for periodic run-level validation\n- Graceful SIGINT handling with proper exit codes\n\n## Spec\n`docs/2026-01-05-persistent-run-mode-spec.md`\n\n## Plan\n`docs/2026-01-05-watch-mode-plan.md`\n\n## Key Files\n- `src/pipeline/issue_execution_coordinator.py` - Main watch loop\n- `src/orchestration/orchestrator.py` - SIGINT handling, validation callback\n- `src/cli/cli.py` - CLI flags\n- `src/orchestration/types.py` - WatchConfig, WatchState, RunResult","status":"closed","priority":1,"issue_type":"epic","created_at":"2026-01-05T02:19:13.455763534Z","created_by":"cyou","updated_at":"2026-01-05T15:10:33.786101768Z","closed_at":"2026-01-05T15:10:33.786101768Z","close_reason":"Closed"}
{"id":"mala-giyp.1","title":"[T001] Add WatchConfig, WatchState, RunResult dataclasses","description":"## Goal\nAdd the core dataclasses needed for watch mode to `src/orchestration/types.py`.\n\n## Context\nThese types are the foundation for watch mode. All other tasks depend on them.\n\n## Changes\n- `src/orchestration/types.py` — Exists — Add three dataclasses:\n\n```python\n@dataclass\nclass WatchConfig:\n    \"\"\"Configuration for watch mode behavior.\"\"\"\n    enabled: bool = False\n    validate_every: int = 10\n    poll_interval_seconds: float = 60.0  # Internal, not CLI-exposed\n\n@dataclass\nclass WatchState:\n    \"\"\"Runtime state for watch mode (internal to Coordinator).\"\"\"\n    completed_count: int = 0\n    last_validation_at: int = 0\n    next_validation_threshold: int = 10\n    consecutive_poll_failures: int = 0\n    last_idle_log_time: float = 0.0\n    was_idle: bool = False\n\n@dataclass\nclass RunResult:\n    \"\"\"Result of a coordinator run loop.\"\"\"\n    issues_spawned: int\n    exit_code: int  # 0, 1, 2, 3, or 130\n    exit_reason: str  # \"success\", \"validation_failed\", \"poll_failed\", \"interrupted\"\n```\n\n## Acceptance Criteria\n- All three dataclasses exist with correct fields and defaults\n- Types are importable from `src/orchestration/types.py`\n\n## Verification\n- `python -c \"from src.orchestration.types import WatchConfig, WatchState, RunResult; print('OK')\"`\n- `uv run pytest tests/unit/orchestration/ -v -k types` (if existing type tests)\n\n## Notes for Agent\n- Follow existing dataclass patterns in the file\n- Add docstrings matching the plan","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-05T02:19:37.991935661Z","created_by":"cyou","updated_at":"2026-01-05T02:30:16.13838108Z","closed_at":"2026-01-05T02:30:16.13838108Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-giyp.1","depends_on_id":"mala-giyp","type":"parent-child","created_at":"2026-01-05T02:19:38.000668576Z","created_by":"cyou"}]}
{"id":"mala-giyp.10","title":"[T010] BeadsClient: Implement get_blocked_count_async","description":"## Goal\nImplement `get_blocked_count_async()` in BeadsClient to distinguish \"no issues\" from \"issues blocked\".\n\n## Context\nThe spec requires logging \"Idle: N issues exist but none ready\" when issues are blocked. The BeadsClient needs a method to query this count.\n\n## Changes\n- `src/infra/clients/beads_client.py` — Exists — Add method:\n  ```python\n  async def get_blocked_count_async(self) -\u003e int | None:\n      \"\"\"Get count of issues that exist but aren't ready.\n      \n      Returns None if the count cannot be determined.\n      \"\"\"\n      try:\n          # Query blocked issues using bd CLI\n          result = await self._run_bd_command([\"list\", \"--status\", \"blocked\", \"--json\"])\n          if result.returncode != 0:\n              return None\n          data = json.loads(result.stdout)\n          return len(data) if isinstance(data, list) else None\n      except Exception:\n          return None  # Graceful fallback\n  ```\n\n## Acceptance Criteria\n- Method returns int count of blocked issues\n- Returns None on error (graceful degradation)\n- Uses existing bd CLI patterns from BeadsClient\n\n## Verification\n- Unit test with mocked bd CLI\n- `uv run pytest tests/unit/infra/clients/ -v -k beads` → pass\n\n## Notes for Agent\n- Follow existing BeadsClient patterns for bd CLI calls\n- Return None on any error - caller handles gracefully\n- This is P3 priority - can be skipped if blocked issues distinction isn't critical for MVP","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-05T02:24:52.984231304Z","created_by":"cyou","updated_at":"2026-01-05T02:33:28.995443647Z","closed_at":"2026-01-05T02:33:28.995443647Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-giyp.10","depends_on_id":"mala-giyp","type":"parent-child","created_at":"2026-01-05T02:24:52.99439296Z","created_by":"cyou"},{"issue_id":"mala-giyp.10","depends_on_id":"mala-giyp.2","type":"blocks","created_at":"2026-01-05T02:24:57.418352978Z","created_by":"cyou"}]}
{"id":"mala-giyp.11","title":"[T011] Integration tests: SIGINT handling and full watch flow","description":"## Goal\nCreate integration tests that verify the full watch mode flow including signal handling.\n\n## Context\nThis is the final verification task. All unit tests should pass; this validates the full integration path.\n\n## Changes\n- `tests/integration/test_watch_mode.py` — New — Create integration tests:\n  ```python\n  @pytest.mark.integration\n  async def test_sigint_during_idle_exits_with_code_130():\n      \"\"\"SIGINT during idle sleep exits cleanly with code 130.\"\"\"\n      # Set up orchestrator with short poll interval\n      # Start watch loop in background task\n      # Wait for idle state\n      # Send SIGINT\n      # Assert exit code 130\n      \n  @pytest.mark.integration\n  async def test_new_issues_picked_up_after_poll():\n      \"\"\"Issues added during watch are processed on next poll.\"\"\"\n      # Start with no issues\n      # Watch mode enters idle\n      # Add issue via FakeIssueProvider\n      # Assert issue processed after poll interval\n      \n  @pytest.mark.integration\n  async def test_max_issues_stops_watch_mode():\n      \"\"\"--max-issues limit stops watch even with more work available.\"\"\"\n      # Set max_issues=2\n      # Process 2 issues\n      # Assert exits despite more issues ready\n      \n  @pytest.mark.integration\n  async def test_validation_runs_at_threshold():\n      \"\"\"--validate-every triggers validation at correct intervals.\"\"\"\n      # Set validate_every=2\n      # Process 2 issues\n      # Assert validation_callback called\n      # Process 2 more\n      # Assert called again\n  ```\n\n## Acceptance Criteria\n- Integration tests cover SIGINT handling\n- Tests verify issues picked up after poll\n- Tests verify --max-issues interaction with watch\n- Tests use asyncio.timeout for bounded execution\n- All tests pass consistently (no flakiness)\n\n## Verification\n- `uv run pytest tests/integration/test_watch_mode.py -v` → all pass\n- `uv run pytest -m integration -n auto --reruns 2` → stable with retries\n\n## Notes for Agent\n- Use short poll intervals (0.1s) for fast tests\n- Use asyncio.timeout to bound test duration\n- Send SIGINT via os.kill(os.getpid(), signal.SIGINT)\n- May need to run signal tests in subprocess for isolation","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-05T02:25:16.443283155Z","created_by":"cyou","updated_at":"2026-01-05T15:08:36.76767345Z","closed_at":"2026-01-05T15:08:36.76767345Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-giyp.11","depends_on_id":"mala-giyp","type":"parent-child","created_at":"2026-01-05T02:25:16.455488728Z","created_by":"cyou"},{"issue_id":"mala-giyp.11","depends_on_id":"mala-giyp.4","type":"blocks","created_at":"2026-01-05T02:25:24.162127994Z","created_by":"cyou"},{"issue_id":"mala-giyp.11","depends_on_id":"mala-giyp.5","type":"blocks","created_at":"2026-01-05T02:25:24.184233074Z","created_by":"cyou"},{"issue_id":"mala-giyp.11","depends_on_id":"mala-giyp.6","type":"blocks","created_at":"2026-01-05T02:25:24.197160273Z","created_by":"cyou"},{"issue_id":"mala-giyp.11","depends_on_id":"mala-giyp.7","type":"blocks","created_at":"2026-01-05T02:25:24.209651204Z","created_by":"cyou"},{"issue_id":"mala-giyp.11","depends_on_id":"mala-giyp.8","type":"blocks","created_at":"2026-01-05T02:25:24.22143194Z","created_by":"cyou"}]}
{"id":"mala-giyp.12","title":"[Review] 2 non-blocking findings from mala-giyp.1","description":"## Review Findings\n\nThis issue consolidates 2 non-blocking findings from code review.\n\n**Source issue:** mala-giyp.1\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Fix RunResult exit_code mapping in docstring\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/orchestration/types.py:267-269\n\n`RunResult.exit_code`’s docstring encodes a specific mapping (`2=poll_failed, 3=error`) that conflicts with the watch-mode plan (exit code `2` reserved for invalid args; poll failures exit `3`). This can easily lead to returning the wrong code if an implementer follows the docstring.\n\nExample in code:\n`exit_code: Exit code (0=success, 1=validation_failed, 2=poll_failed, 3=error, 130=interrupted).`\n\n---\n\n### Finding 2: [P2] Avoid hardcoding WatchState.next_validation_threshold=10\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/orchestration/types.py:254-257\n\n`WatchState.next_validation_threshold` defaults to `10`, which can silently desync from `WatchConfig.validate_every` when users set `validate_every` to something else. If the coordinator constructs `WatchState()` without explicitly setting this field, validation cadence will be wrong (e.g., `validate_every=5` but first validation at 10 completions).\n\nCurrent default:\n`next_validation_threshold: int = 10`\n\n---\n\n\u003c!-- fp:7f77a91c7aa8e1ce --\u003e\n\u003c!-- fp:215d242fd7f0dba9 --\u003e\n\u003c!-- review_finding:264da8375f0b --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-05T02:30:16.179656102Z","created_by":"cyou","updated_at":"2026-01-05T02:38:27.222263297Z","closed_at":"2026-01-05T02:38:27.222263297Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-giyp.1"],"dependencies":[{"issue_id":"mala-giyp.12","depends_on_id":"mala-giyp","type":"parent-child","created_at":"2026-01-05T02:30:16.180147589Z","created_by":"cyou"}]}
{"id":"mala-giyp.13","title":"[Review] 2 non-blocking findings from mala-giyp.9","description":"## Review Findings\n\nThis issue consolidates 2 non-blocking findings from code review.\n\n**Source issue:** mala-giyp.9\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Avoid round-half-even when formatting wait_seconds\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/infra/io/console_sink.py:521-530\n\n`{wait_seconds:.0f}` uses round-half-even, so exact half-seconds can round *down* unexpectedly (e.g. `44.5` displays as `44`), making the UI claim the next poll is sooner than it will be. This is user-visible accuracy drift; consider using an explicit policy like `math.ceil(wait_seconds)` (never underestimates) or another clearly-chosen rounding strategy.\n\n```py\npoll_s = math.ceil(wait_seconds)\n```\n\n\n---\n\n### Finding 2: [P3] Tests rely on strict mock call counts\n\n**Priority:** P3\n**Reviewer:** codex\n**Location:** tests/unit/infra/test_event_sink.py:871-903\n\nThe new tests add more `mock_log.assert_called_once()` assertions. Given the repo’s testing guidance (“avoid call_count / ‘was called’ assertions”), this increases brittleness (extra logging, refactors, or batching could break tests without changing user-visible behavior). You can usually assert on the last call’s message content without pinning the exact call count.\n\n\n---\n\n\u003c!-- fp:a16ce87e33f45e0b --\u003e\n\u003c!-- fp:b1391128c516f2ed --\u003e\n\u003c!-- review_finding:23ede57668cd --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-05T02:31:40.912480718Z","created_by":"cyou","updated_at":"2026-01-05T02:34:13.630522683Z","closed_at":"2026-01-05T02:34:13.630522683Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-giyp.9"],"dependencies":[{"issue_id":"mala-giyp.13","depends_on_id":"mala-giyp","type":"parent-child","created_at":"2026-01-05T02:31:40.912877095Z","created_by":"cyou"}]}
{"id":"mala-giyp.14","title":"[Review] 1 non-blocking finding from mala-giyp.13","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-giyp.13\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Guard `math.ceil` against non-finite `wait_seconds`\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/infra/io/console_sink.py:519-525\n\n`math.ceil(wait_seconds)` raises on `float('inf')` (OverflowError) and `float('nan')` (ValueError). Previously, formatting with `{wait_seconds:.0f}` would render `inf`/`nan` without crashing, so this commit introduces a potential runtime exception if upstream ever passes a non-finite backoff/poll delay.\n\nA small guard avoids the crash while preserving the \"never underestimate\" policy for normal values:\n\n```py\npoll_s = math.ceil(wait_seconds) if math.isfinite(wait_seconds) else wait_seconds\n```\n\n\n---\n\n\u003c!-- fp:7a8526118654e53a --\u003e\n\u003c!-- review_finding:1c16e9a44ac4 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-05T02:34:13.675154784Z","created_by":"cyou","updated_at":"2026-01-05T02:40:42.72961076Z","closed_at":"2026-01-05T02:40:42.72961076Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-giyp.13"],"dependencies":[{"issue_id":"mala-giyp.14","depends_on_id":"mala-giyp","type":"parent-child","created_at":"2026-01-05T02:34:13.675735601Z","created_by":"cyou"}]}
{"id":"mala-giyp.15","title":"[Review] 1 non-blocking finding from mala-giyp.12","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-giyp.12\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Align RunResult.exit_code docstring with actual exit codes\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/orchestration/types.py:269-271\n\n`RunResult.exit_code` now documents `3=poll_failed`, but the coordinator currently returns `exit_code=3` for abort/fatal error paths (e.g., `IssueExecutionCoordinator.run_loop` returns `exit_code=3` with `exit_reason=\"abort\"`). This makes the mapping misleading; either update the docstring to reflect current meanings (e.g., `3=abort/error`) or change the code to use distinct exit codes for poll failures vs abort/error.\n\n```py\nexit_code=3,\nexit_reason=\"abort\",\n```\n\n---\n\n\u003c!-- fp:13cced0d09d5b50e --\u003e\n\u003c!-- review_finding:9d92130fa3e9 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-05T02:38:27.26290619Z","created_by":"cyou","updated_at":"2026-01-05T02:49:26.022022631Z","closed_at":"2026-01-05T02:49:26.022022631Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-giyp.12"],"dependencies":[{"issue_id":"mala-giyp.15","depends_on_id":"mala-giyp","type":"parent-child","created_at":"2026-01-05T02:38:27.2633791Z","created_by":"cyou"}]}
{"id":"mala-giyp.16","title":"[Review] 2 non-blocking findings from mala-giyp.11","description":"## Review Findings\n\nThis issue consolidates 2 non-blocking findings from code review.\n\n**Source issue:** mala-giyp.11\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Enforce idle precondition before setting interrupt\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** tests/integration/pipeline/test_watch_mode.py:60-68\n\n`set_interrupt_after_idle()` always calls `interrupt_event.set()` after 0.5s even if `watch_idle` never occurred. On a slower run (or if the loop doesn’t emit `watch_idle` yet), this can interrupt before the coordinator is idle, making the test flaky (and sometimes failing the later `watch_idle` assertion). Consider waiting until `watch_idle` is observed (bounded by the existing outer timeout) and `pytest.fail(...)` if it never appears.\n\n---\n\n### Finding 2: [P2] Require idle before adding issue to validate polling\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** tests/integration/pipeline/test_watch_mode.py:190-200\n\n`add_issue_after_idle()` will add `new-issue` even if `watch_idle` was never observed within the loop. That can let the test pass even if “picked up after poll interval” is broken (because the issue might be present before the watch loop actually idles/polls). Make the helper fail if `watch_idle` isn’t seen before mutation (or wait longer under the existing timeout).\n\n---\n\n\u003c!-- fp:7fb3966112c88efe --\u003e\n\u003c!-- fp:bf83e3d5a60f776a --\u003e\n\u003c!-- review_finding:2d9ef520b91a --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-05T15:10:33.814579332Z","created_by":"cyou","updated_at":"2026-01-05T15:14:41.774840353Z","closed_at":"2026-01-05T15:14:41.774840353Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-giyp.11"],"dependencies":[{"issue_id":"mala-giyp.16","depends_on_id":"mala-giyp","type":"parent-child","created_at":"2026-01-05T15:10:33.815229059Z","created_by":"cyou"}]}
{"id":"mala-giyp.17","title":"[Review] 2 non-blocking findings from mala-giyp.16","description":"## Review Findings\n\nThis issue consolidates 2 non-blocking findings from code review.\n\n**Source issue:** mala-giyp.16\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Ensure helper failure aborts run_loop to avoid masking pytest.fail\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** tests/integration/pipeline/test_watch_mode.py:71-80\n\nIf `watch_idle` is never emitted, `set_interrupt_after_idle()` raises via `pytest.fail(...)`, but the test won’t observe that exception until after `await coord.run_loop(...)`. Since the helper no longer sets `interrupt_event` on failure, `run_loop` may keep running until the outer `asyncio.timeout(2.0)` triggers, masking the intended failure message (and making debugging harder). Consider cancelling/aborting `run_loop` when the helper fails (e.g., run both with `asyncio.gather(...)` so an exception cancels the other task), or set `interrupt_event` just before failing:\n\n`interrupt_event.set()`\n`pytest.fail(\"watch_idle event never observed before timeout\")`\n\n---\n\n### Finding 2: [P2] Abort watch loop when add_issue_after_idle fails\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** tests/integration/pipeline/test_watch_mode.py:204-213\n\nSame pattern in the polling test: if `watch_idle` is never observed, `add_issue_after_idle()` fails, but the exception is only surfaced after `await coord.run_loop(...)`. Because no issue is added and `interrupt_event` is never set, `run_loop` can run until the outer `asyncio.timeout(2.0)` fires, hiding the more actionable `pytest.fail(...)` message. Fix by awaiting both tasks together (so helper failure cancels `run_loop`) or by unblocking `run_loop` before failing.\n\n---\n\n\u003c!-- fp:a3dfe81542508dc8 --\u003e\n\u003c!-- fp:e3c730a2e597752c --\u003e\n\u003c!-- review_finding:40a7ff79fc48 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-05T15:14:41.803459859Z","created_by":"cyou","updated_at":"2026-01-05T15:17:37.181498073Z","closed_at":"2026-01-05T15:17:37.181498073Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-giyp.16"],"dependencies":[{"issue_id":"mala-giyp.17","depends_on_id":"mala-giyp","type":"parent-child","created_at":"2026-01-05T15:14:41.803882757Z","created_by":"cyou"}]}
{"id":"mala-giyp.18","title":"[Review] 1 non-blocking finding from mala-giyp.17","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-giyp.17\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] gather() won’t cancel run_loop on helper exception\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** tests/integration/pipeline/test_watch_mode.py:71-80\n\n`asyncio.gather(run_loop_coro, set_interrupt_after_idle())` will raise when the helper hits `pytest.fail(...)`, but it **does not cancel** the other awaitable on exception. That means `coord.run_loop(...)` may keep running after the test has already failed, which can leak work into later tests / produce “pending task” warnings. The nearby comment (“helper failure cancels run_loop immediately”) is also misleading.\n\nSince you’re already on Python 3.11+ (uses `asyncio.timeout`), prefer `asyncio.TaskGroup()` so an exception in the helper cancels `run_loop`:\n`async with asyncio.TaskGroup() as tg:\n    run_task = tg.create_task(coord.run_loop(...))`\n(and keep a handle to read `run_task.result()` afterward). Same issue appears again in the polling test block.\n\n---\n\n\u003c!-- fp:3aff73430ab3894f --\u003e\n\u003c!-- review_finding:19e135cc9f28 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-05T15:17:37.211869246Z","created_by":"cyou","updated_at":"2026-01-05T15:23:25.685409478Z","closed_at":"2026-01-05T15:23:25.685409478Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-giyp.17"],"dependencies":[{"issue_id":"mala-giyp.18","depends_on_id":"mala-giyp","type":"parent-child","created_at":"2026-01-05T15:17:37.212332634Z","created_by":"cyou"}]}
{"id":"mala-giyp.2","title":"[T002] Add on_watch_idle event and get_blocked_count_async to protocols","description":"## Goal\nExtend event sink and issue provider protocols with watch mode methods.\n\n## Context\nThe watch mode needs to emit idle events and optionally query blocked issue count.\n\n## Changes\n- `src/core/protocols.py` — Exists — Add to MalaEventSink protocol:\n  ```python\n  def on_watch_idle(self, wait_seconds: float, issues_blocked: int | None) -\u003e None:\n      \"\"\"Called when watch mode enters idle sleep.\"\"\"\n      ...\n  ```\n  \n- `src/core/protocols.py` — Exists — Add to IssueProvider protocol:\n  ```python\n  async def get_blocked_count_async(self) -\u003e int | None:\n      \"\"\"Get count of issues that exist but aren't ready. Returns None if unknown.\"\"\"\n      ...\n  ```\n\n- `src/infra/io/base_sink.py` — Exists — Add no-op default:\n  ```python\n  def on_watch_idle(self, wait_seconds: float, issues_blocked: int | None) -\u003e None:\n      pass\n  ```\n\n## Acceptance Criteria\n- `on_watch_idle` method exists in MalaEventSink protocol\n- `get_blocked_count_async` method exists in IssueProvider protocol  \n- BaseEventSink has no-op implementation of on_watch_idle\n\n## Verification\n- `python -c \"from src.core.protocols import MalaEventSink, IssueProvider; print('OK')\"`\n- Type checker passes: `uvx ty check src/core/protocols.py src/infra/io/base_sink.py`\n\n## Notes for Agent\n- Follow existing protocol patterns (use `...` in protocol, implement in base)\n- The `issues_blocked` parameter is `int | None` to handle providers that can't answer","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-05T02:19:52.856892295Z","created_by":"cyou","updated_at":"2026-01-05T02:27:25.65640041Z","closed_at":"2026-01-05T02:27:25.65640041Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-giyp.2","depends_on_id":"mala-giyp","type":"parent-child","created_at":"2026-01-05T02:19:52.865962627Z","created_by":"cyou"}]}
{"id":"mala-giyp.3","title":"[T003] [integration-path-test] Skeleton + Integration: Watch loop in coordinator","description":"## Goal\nAdd watch mode skeleton to `run_loop()` with failing integration tests that exercise the full path.\n\n## Context\nThis is the skeleton+integration task. The loop signature changes but stubs return immediately. Integration tests fail for the RIGHT reason (behavior not implemented), not due to missing imports/wiring.\n\n## Changes\n- `src/pipeline/issue_execution_coordinator.py` — Exists — Update `run_loop()` signature:\n  ```python\n  async def run_loop(\n      self,\n      spawn_callback: SpawnCallback,\n      finalize_callback: FinalizeCallback,\n      abort_callback: AbortCallback,\n      watch_config: WatchConfig | None = None,\n      interrupt_event: asyncio.Event | None = None,\n      validation_callback: Callable[[], Awaitable[bool]] | None = None,\n      sleep_fn: Callable[[float], Awaitable[None]] = asyncio.sleep,\n  ) -\u003e RunResult\n  ```\n  - Return `RunResult(issues_spawned=count, exit_code=0, exit_reason=\"success\")` at current exit points\n  - Add stub branches for watch mode (pass through for now)\n  - Initialize WatchState at loop start (threshold from config)\n\n- `tests/unit/pipeline/test_watch_mode.py` — New — Create test file with:\n  - Test: `test_watch_mode_sleeps_when_no_ready_issues` - should fail (not implemented)\n  - Test: `test_watch_mode_exits_on_interrupt` - should fail (not implemented)\n  - Test: `test_validation_triggers_at_threshold` - should fail (not implemented)\n  - Use FakeIssueProvider and injected sleep_fn\n\n- `tests/unit/pipeline/test_issue_execution_coordinator.py` — Exists — UPDATE existing tests:\n  - Tests that assert on `int` return value must be updated to check `result.issues_spawned`\n  - Example: `assert result == 5` → `assert result.issues_spawned == 5`\n  - All existing tests must still pass after signature change\n\n## Wiring Map\n- `WatchConfig` → `run_loop()` parameter → used in loop conditions\n- `RunResult` ← `run_loop()` return → consumed by `_run_main_loop()`\n\n## Acceptance Criteria\n- `run_loop()` signature accepts new parameters with defaults\n- Returns `RunResult` instead of `int`\n- Project compiles/imports successfully\n- New integration tests exist and FAIL for intended reason (stub behavior)\n- Existing coordinator tests PASS (updated for RunResult)\n\n## Verification\n- `python -c \"from src.pipeline.issue_execution_coordinator import IssueExecutionCoordinator; print('OK')\"`\n- `uv run pytest tests/unit/pipeline/test_issue_execution_coordinator.py -v` → existing tests pass\n- `uv run pytest tests/unit/pipeline/test_watch_mode.py -v` → new tests exist but fail (expected)\n\n## Notes for Agent\n- This is skeleton only - implementation comes in T004-T006\n- Ensure backward compatibility: without watch_config, behavior is unchanged\n- Tests should use `FakeIssueProvider` pattern from existing coordinator tests\n- CRITICAL: Update existing tests for RunResult return type before adding new tests","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-05T02:20:39.90449341Z","created_by":"cyou","updated_at":"2026-01-05T02:43:26.026854893Z","closed_at":"2026-01-05T02:43:26.026854893Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-giyp.3","depends_on_id":"mala-giyp","type":"parent-child","created_at":"2026-01-05T02:20:39.913813821Z","created_by":"cyou"},{"issue_id":"mala-giyp.3","depends_on_id":"mala-giyp.1","type":"blocks","created_at":"2026-01-05T02:20:44.178704308Z","created_by":"cyou"},{"issue_id":"mala-giyp.3","depends_on_id":"mala-giyp.2","type":"blocks","created_at":"2026-01-05T02:20:44.197712297Z","created_by":"cyou"}]}
{"id":"mala-giyp.4","title":"[T004] Implement watch loop behavior (sleep, re-poll, idle detection)","description":"## Goal\nImplement the core watch loop: when no ready issues and no active agents, sleep and re-poll.\n\n## Context\nThis is the main watch behavior. When `watch_config.enabled` is True and idle (no ready + no active), emit `on_watch_idle`, sleep using interruptible pattern, then re-poll.\n\n## Changes\n- `src/pipeline/issue_execution_coordinator.py` — Exists — Implement watch loop:\n  ```python\n  # Initialize WatchState with correct threshold from config\n  watch_state = WatchState(\n      next_validation_threshold=watch_config.validate_every if watch_config else 10\n  )\n  \n  # In run_loop, when none_ready and not active_tasks:\n  if watch_config and watch_config.enabled:\n      # Check idle state (no ready AND no active)\n      if not ready and not self.active_tasks:\n          # Detect transition to idle\n          if not watch_state.was_idle:\n              watch_state.was_idle = True\n              watch_state.last_idle_log_time = time.time()\n              # Emit event\n              blocked_count = await self.beads.get_blocked_count_async()\n              self.event_sink.on_watch_idle(watch_config.poll_interval_seconds, blocked_count)\n          elif time.time() - watch_state.last_idle_log_time \u003e= 300:  # 5 minutes\n              watch_state.last_idle_log_time = time.time()\n              blocked_count = await self.beads.get_blocked_count_async()\n              self.event_sink.on_watch_idle(watch_config.poll_interval_seconds, blocked_count)\n          \n          # Interruptible sleep - MUST respond to interrupt immediately\n          if interrupt_event:\n              try:\n                  await asyncio.wait_for(interrupt_event.wait(), timeout=watch_config.poll_interval_seconds)\n                  # Event was set - SIGINT received during idle\n                  # Run final validation if any issues completed\n                  if watch_state.completed_count \u003e watch_state.last_validation_at:\n                      if validation_callback:\n                          await validation_callback()\n                  return RunResult(issues_spawned, exit_code=130, exit_reason=\"interrupted\")\n              except asyncio.TimeoutError:\n                  pass  # Normal timeout, continue loop\n          else:\n              await sleep_fn(watch_config.poll_interval_seconds)\n          continue  # Re-poll\n      else:\n          watch_state.was_idle = False  # Transitioned out of idle\n  else:\n      break  # Original behavior: exit when no work\n  ```\n\n- `tests/unit/pipeline/test_watch_mode.py` — Exists — Add/update tests:\n  - `test_watch_mode_sleeps_when_no_ready_issues` - now passes\n  - `test_watch_mode_continues_when_issues_appear_after_sleep`\n  - `test_idle_logging_rate_limited_to_5_minutes`\n  - `test_idle_state_requires_no_active_agents`\n  - `test_sigint_during_idle_runs_final_validation_if_issues_completed`\n  - `test_watch_state_threshold_initialized_from_config`\n\n## Acceptance Criteria\n- Loop sleeps and re-polls when watch enabled and idle\n- Loop exits immediately when watch disabled (current behavior)\n- `on_watch_idle` emitted on transition to idle and every 5 minutes\n- Idle = no ready issues AND no active agents\n- SIGINT during idle breaks sleep immediately AND runs final validation if issues completed\n- WatchState.next_validation_threshold initialized from watch_config.validate_every\n\n## Verification\n- `uv run pytest tests/unit/pipeline/test_watch_mode.py -v -k \"sleep or idle or sigint\"` → pass\n\n## Notes for Agent\n- Use `asyncio.wait_for(event.wait(), timeout=N)` for interruptible sleep\n- Rate-limit idle logging: once on transition, then every 5 min\n- `was_idle` field detects transition\n- CRITICAL: SIGINT during idle MUST run final validation if completed_count \u003e last_validation_at\n- Initialize next_validation_threshold from config, not hardcoded 10","notes":"Quality gate failed: Aborted due to unrecoverable error\nLog: /home/cyou/.claude/projects/-home-cyou-mala/9f25c26c-88b0-47c6-97e2-4ff9e0257ee0.jsonl","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-05T02:21:07.154654256Z","created_by":"cyou","updated_at":"2026-01-05T06:22:14.401737095Z","closed_at":"2026-01-05T06:22:14.401737095Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-giyp.4","depends_on_id":"mala-giyp","type":"parent-child","created_at":"2026-01-05T02:21:07.164285315Z","created_by":"cyou"},{"issue_id":"mala-giyp.4","depends_on_id":"mala-giyp.3","type":"blocks","created_at":"2026-01-05T02:21:12.606810452Z","created_by":"cyou"},{"issue_id":"mala-giyp.4","depends_on_id":"mala-giyp.6","type":"blocks","created_at":"2026-01-05T03:25:37.719865802Z","created_by":"cyou"}]}
{"id":"mala-giyp.5","title":"[T005] Implement periodic validation triggers and exit codes","description":"## Goal\nImplement the `--validate-every N` logic: track completions, trigger validation at thresholds, handle final validation on exit, return proper exit codes. Also update --max-issues to use completion counter.\n\n## Context\nWhen `completed_count \u003e= next_validation_threshold`, pause spawning, wait for active agents, call `validation_callback()`, and either continue or abort based on result.\n\n**IMPORTANT**: Per spec, `--max-issues` and `--validate-every` both use the same `completed_count` (terminal states). This changes --max-issues from counting spawned issues to counting completed issues.\n\n## Changes\n- `src/pipeline/issue_execution_coordinator.py` — Exists — Add validation logic:\n  ```python\n  # After issue finalization:\n  watch_state.completed_count += 1\n  \n  # Check if max_issues reached (using completed_count, not spawned count)\n  if max_issues is not None and watch_state.completed_count \u003e= max_issues:\n      # Run final validation if needed\n      if watch_state.completed_count \u003e watch_state.last_validation_at:\n          if validation_callback:\n              validation_passed = await validation_callback()\n              if not validation_passed:\n                  return RunResult(issues_spawned, exit_code=1, exit_reason=\"validation_failed\")\n      return RunResult(issues_spawned, exit_code=0, exit_reason=\"max_issues_reached\")\n  \n  # Check if validation threshold crossed\n  if (watch_config and watch_config.enabled and \n      watch_state.completed_count \u003e= watch_state.next_validation_threshold):\n      # Wait for active agents to finish (blocking validation)\n      if self.active_tasks:\n          await asyncio.gather(*self.active_tasks.values(), return_exceptions=True)\n      \n      # Run validation\n      if validation_callback:\n          validation_passed = await validation_callback()\n          if not validation_passed:\n              return RunResult(issues_spawned, exit_code=1, exit_reason=\"validation_failed\")\n      \n      # Advance threshold\n      watch_state.last_validation_at = watch_state.completed_count\n      watch_state.next_validation_threshold += watch_config.validate_every\n  ```\n\n  # Handle interrupt during active processing (not idle)\n  ```python\n  # After processing completions, check interrupt_event:\n  if interrupt_event and interrupt_event.is_set():\n      # SIGINT during active processing\n      # Stop spawning, wait for remaining active agents\n      if self.active_tasks:\n          await asyncio.gather(*self.active_tasks.values(), return_exceptions=True)\n      # Run final validation if needed\n      if watch_state.completed_count \u003e watch_state.last_validation_at:\n          if validation_callback:\n              await validation_callback()\n      return RunResult(issues_spawned, exit_code=130, exit_reason=\"interrupted\")\n  ```\n\n  # Exit code logic at loop exit:\n  - Normal exit (no more issues, watch disabled):\n    - If completed_count \u003e last_validation_at: run final validation\n    - Return exit_code=0 if validation passes\n    - Return exit_code=1 if fails\n  - Max issues reached: Run final validation, return exit_code=0\n  - Validation failure: Return exit_code=1 immediately (no final validation)\n  - Interrupt: Return exit_code=130 after final validation\n\n- `tests/unit/pipeline/test_watch_mode.py` — Exists — Add tests:\n  - `test_validation_triggers_at_threshold` - validation_callback called at 10\n  - `test_validation_threshold_advances_after_trigger` - 10→20→30\n  - `test_parallel_completions_trigger_validation_once` - 9→12 triggers once\n  - `test_validation_failure_returns_exit_code_1`\n  - `test_successful_completion_returns_exit_code_0`\n  - `test_sigint_during_active_runs_final_validation`\n  - `test_final_validation_skipped_if_just_ran_at_threshold`\n  - `test_final_validation_runs_on_normal_exit_if_issues_completed`\n  - `test_max_issues_uses_completed_count_not_spawned`\n  - `test_max_issues_exits_watch_mode`\n\n## Acceptance Criteria\n- Validation triggers when `completed_count \u003e= next_threshold`\n- Threshold advances by N after validation\n- Parallel completions (9→12) trigger validation once\n- --max-issues uses completed_count (same counter as --validate-every)\n- Exit code 1 on validation failure\n- Exit code 0 on success (after final validation if needed)\n- Exit code 130 on interrupt (after final validation if needed)\n- Final validation runs if completed_count \u003e last_validation_at\n- Final validation skipped if validation just ran at threshold (avoids redundant)\n\n## Verification\n- `uv run pytest tests/unit/pipeline/test_watch_mode.py -v -k \"validation or exit_code or max_issues\"` → pass\n\n## Notes for Agent\n- Counter increments on finalize, not on spawn\n- Only count terminal states (success OR failure), not retries\n- Validation is blocking: wait for all active agents first\n- CRITICAL: --max-issues now uses completed_count, not issues_spawned counter\n- CRITICAL: Check interrupt_event.is_set() after processing completions\n- CRITICAL: Final validation runs on ANY exit if completed_count \u003e last_validation_at\n- Skip final validation only if: (1) validation just ran at threshold, or (2) aborting due to validation failure","notes":"Quality gate failed: External review failed: src/pipeline/issue_execution_coordinator.py:193: [P1] SIGINT/interrupt can hang waiting for active tasks; src/pipeline/issue_execution_coordinator.py:513: [P1] Call abort_callback in all interrupt paths for lifecycle consistency\nLog: /home/cyou/.claude/projects/-home-cyou-mala/4b1807d1-846b-448c-b7c0-e1b3a6fea205.jsonl","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-05T02:21:30.654433099Z","created_by":"cyou","updated_at":"2026-01-05T05:46:13.976536249Z","closed_at":"2026-01-05T05:46:13.976536249Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-giyp.5","depends_on_id":"mala-giyp","type":"parent-child","created_at":"2026-01-05T02:21:30.663509101Z","created_by":"cyou"},{"issue_id":"mala-giyp.5","depends_on_id":"mala-giyp.3","type":"blocks","created_at":"2026-01-05T02:21:34.97935981Z","created_by":"cyou"}]}
{"id":"mala-giyp.6","title":"[T006] Implement poll failure handling (3 consecutive failures abort)","description":"## Goal\nHandle `get_ready_async()` failures: log, retry, abort after 3 consecutive failures.\n\n## Context\nIn long-running watch sessions, poll failures will happen. The spec requires: log error, wait 60s, retry. After 3 consecutive failures, run final validation if any issues completed, then abort with exit code 3.\n\n## Changes\n- `src/pipeline/issue_execution_coordinator.py` — Exists — Wrap poll in try/except:\n  ```python\n  try:\n      ready = await self.beads.get_ready_async(...)\n      watch_state.consecutive_poll_failures = 0  # Reset on success\n  except Exception as e:\n      logger.error(\"Poll failed: %s\", e)\n      watch_state.consecutive_poll_failures += 1\n      \n      if watch_state.consecutive_poll_failures \u003e= 3:\n          # Run final validation if any issues completed\n          if watch_state.completed_count \u003e watch_state.last_validation_at:\n              if validation_callback:\n                  await validation_callback()\n          return RunResult(issues_spawned, exit_code=3, exit_reason=\"poll_failed\")\n      \n      # Wait and retry\n      await sleep_fn(watch_config.poll_interval_seconds if watch_config else 60.0)\n      continue\n  ```\n\n- `tests/unit/pipeline/test_watch_mode.py` — Exists — Add tests:\n  - `test_poll_failure_increments_counter`\n  - `test_poll_success_resets_failure_counter`\n  - `test_three_consecutive_poll_failures_aborts`\n  - `test_poll_failure_abort_returns_exit_code_3`\n  - `test_poll_failure_abort_runs_final_validation_if_issues_completed`\n\n## Acceptance Criteria\n- Poll failure logged and counter incremented\n- Success resets counter to 0\n- 3 consecutive failures → abort with exit code 3\n- Final validation runs if any issues completed before abort\n\n## Verification\n- `uv run pytest tests/unit/pipeline/test_watch_mode.py -v -k \"poll_fail\"` → pass\n\n## Notes for Agent\n- Counter resets on ANY successful poll, not just when issues are found\n- Final validation on abort only if `completed_count \u003e last_validation_at`","notes":"Quality gate failed: External review failed: src/pipeline/issue_execution_coordinator.py:253: [P1] Finalize completed tasks before poll-failed abort decision; src/pipeline/issue_execution_coordinator.py:194: [P1] Missing task finalization and validation result handling in top-of-loop interrupt; src/pipeline/issue_execution_coordinator.py:258: [P1] Missing task finalization and incomplete validation in poll failure abort\nLog: /home/cyou/.claude/projects/-home-cyou-mala/0ec65f49-0a50-416b-b7c4-8f0a9100f098.jsonl","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-05T02:21:49.483267143Z","created_by":"cyou","updated_at":"2026-01-05T05:47:59.787552Z","closed_at":"2026-01-05T05:47:59.787552Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-giyp.6","depends_on_id":"mala-giyp","type":"parent-child","created_at":"2026-01-05T02:21:49.500071977Z","created_by":"cyou"},{"issue_id":"mala-giyp.6","depends_on_id":"mala-giyp.3","type":"blocks","created_at":"2026-01-05T02:21:54.233962247Z","created_by":"cyou"}]}
{"id":"mala-giyp.7","title":"[T007] Orchestrator: SIGINT handler + validation callback + exit code propagation","description":"## Goal\nSet up SIGINT handler in orchestrator, create validation callback, and propagate exit codes from coordinator to CLI.\n\n## Context\nThe orchestrator owns signal handling and validation execution. It passes an `asyncio.Event` and a validation callback to the coordinator, then propagates the exit code upward.\n\n## Changes\n- `src/orchestration/orchestrator.py` — Exists — In `run()` method:\n  ```python\n  # Create interrupt event\n  interrupt_event = asyncio.Event()\n  \n  # Install SIGINT handler\n  def handle_sigint(sig, frame):\n      interrupt_event.set()\n  \n  original_handler = signal.signal(signal.SIGINT, handle_sigint)\n  try:\n      # Create validation callback wrapping RunCoordinator\n      async def validation_callback() -\u003e bool:\n          return await self.run_coordinator.run_validation(...)\n      \n      # Pass to coordinator\n      result = await self._run_main_loop(\n          run_metadata,\n          watch_config=watch_config,\n          interrupt_event=interrupt_event,\n          validation_callback=validation_callback,\n      )\n      \n      return result.exit_code  # Propagate to CLI\n  finally:\n      signal.signal(signal.SIGINT, original_handler)\n  ```\n\n- `src/orchestration/orchestrator.py` — Exists — Update `_run_main_loop()`:\n  - Accept `watch_config`, `interrupt_event`, `validation_callback` parameters\n  - Pass them to `coordinator.run_loop()`\n  - Return `RunResult` instead of `int`\n\n## Wiring Map\n- `WatchConfig` from CLI → `OrchestratorConfig` → `orchestrator.run()` → `_run_main_loop()` → `coordinator.run_loop()`\n- `RunResult.exit_code` from coordinator → `_run_main_loop()` return → `run()` return → CLI `sys.exit()`\n\n## Acceptance Criteria\n- SIGINT handler sets interrupt_event\n- validation_callback wraps RunCoordinator.run_validation()\n- exit_code propagates from coordinator to orchestrator return value\n- Original SIGINT handler restored in finally block\n\n## Verification\n- `uv run pytest tests/unit/orchestration/ -v -k orchestrator` → existing tests pass\n- Manual: Run with watch, Ctrl+C → clean exit with code 130\n\n## Notes for Agent\n- SIGINT handler must be restored in finally block\n- Validation callback captures `self.run_coordinator` from enclosing scope\n- Defer SIGINT during validation (don't call handler until validation completes) - this may require deferring event.set() or checking event after validation","notes":"Quality gate failed: External review failed: src/pipeline/issue_execution_coordinator.py:454: [P1] Always clean up interrupt_task on cancellation; src/pipeline/issue_execution_coordinator.py:155: [P1] Fix interrupt completion counting to not skip validation; src/pipeline/issue_execution_coordinator.py:483: [P1] Inefficient interrupt_task creation in loop\nLog: /home/cyou/.claude/projects/-home-cyou-mala/a3d15653-02e3-46c7-8284-fc131ead3ea2.jsonl","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-05T02:22:13.818986202Z","created_by":"cyou","updated_at":"2026-01-05T14:22:24.713670789Z","closed_at":"2026-01-05T14:22:24.713670789Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-giyp.7","depends_on_id":"mala-giyp","type":"parent-child","created_at":"2026-01-05T02:22:13.829183277Z","created_by":"cyou"},{"issue_id":"mala-giyp.7","depends_on_id":"mala-giyp.1","type":"blocks","created_at":"2026-01-05T02:22:18.025317787Z","created_by":"cyou"},{"issue_id":"mala-giyp.7","depends_on_id":"mala-giyp.3","type":"blocks","created_at":"2026-01-05T02:22:18.045165421Z","created_by":"cyou"}]}
{"id":"mala-giyp.8","title":"[T008] CLI: Add --watch and --validate-every flags with argument validation","description":"## Goal\nAdd CLI flags for watch mode and wire them through to the orchestrator.\n\n## Context\nThe CLI is the entry point. It validates arguments (exit code 2 for invalid) and constructs `WatchConfig` for the orchestrator.\n\n## Changes\n- `src/cli/cli.py` — Exists — In `run()` command, add parameters:\n  ```python\n  watch: Annotated[\n      bool,\n      typer.Option(\n          \"--watch\",\n          help=\"Keep running and poll for new issues instead of exiting when idle\"\n      ),\n  ] = False,\n  validate_every: Annotated[\n      int,\n      typer.Option(\n          \"--validate-every\",\n          help=\"Run validation after every N issues complete (default: 10, only in watch mode)\"\n      ),\n  ] = 10,\n  ```\n\n- `src/cli/cli.py` — Exists — Add argument validation:\n  ```python\n  if validate_every \u003c 1:\n      console.print(\"[red]Error: --validate-every must be at least 1[/red]\")\n      raise typer.Exit(2)\n  if max_issues is not None and max_issues \u003c 1:\n      console.print(\"[red]Error: --max-issues must be at least 1[/red]\")\n      raise typer.Exit(2)\n  ```\n\n- `src/cli/cli.py` — Exists — Create WatchConfig and pass to orchestrator:\n  ```python\n  watch_config = WatchConfig(\n      enabled=watch,\n      validate_every=validate_every,\n  )\n  # Pass to orchestrator via OrchestratorConfig or directly\n  ```\n\n- `src/cli/cli.py` — Exists — Use exit code from orchestrator:\n  ```python\n  exit_code = await orchestrator.run(...)\n  raise typer.Exit(exit_code)\n  ```\n\n## Acceptance Criteria\n- `--watch` flag exists and defaults to False\n- `--validate-every N` exists and defaults to 10\n- `--validate-every 0` exits with code 2 and error message\n- `--max-issues 0` exits with code 2 and error message\n- Exit code from orchestrator is used for process exit\n\n## Verification\n- `uv run mala run --help` → shows --watch and --validate-every\n- `uv run mala run --validate-every 0` → exit code 2\n- `uv run mala run --max-issues 0` → exit code 2\n\n## Notes for Agent\n- Follow existing CLI patterns for option definitions\n- Validation happens before any orchestrator setup\n- Import WatchConfig from types module","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-05T02:24:05.435374804Z","created_by":"cyou","updated_at":"2026-01-05T14:49:47.893041491Z","closed_at":"2026-01-05T14:49:47.893041491Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-giyp.8","depends_on_id":"mala-giyp","type":"parent-child","created_at":"2026-01-05T02:24:05.444353317Z","created_by":"cyou"},{"issue_id":"mala-giyp.8","depends_on_id":"mala-giyp.1","type":"blocks","created_at":"2026-01-05T02:24:10.887395659Z","created_by":"cyou"},{"issue_id":"mala-giyp.8","depends_on_id":"mala-giyp.7","type":"blocks","created_at":"2026-01-05T02:24:10.907488703Z","created_by":"cyou"}]}
{"id":"mala-giyp.9","title":"[T009] Console sink: Implement on_watch_idle with rate-limited logging","description":"## Goal\nImplement the `on_watch_idle` event in console sink with user-friendly log messages.\n\n## Context\nThe console sink displays messages to users. When watch mode enters idle, we log either \"Idle: no ready issues\" or \"Idle: N issues exist but none ready\" depending on the `issues_blocked` parameter.\n\n## Changes\n- `src/infra/io/console_sink.py` — Exists — Add method:\n  ```python\n  def on_watch_idle(self, wait_seconds: float, issues_blocked: int | None) -\u003e None:\n      if issues_blocked is None or issues_blocked == 0:\n          self.console.print(\n              f\"[dim]Idle: no ready issues. Polling in {wait_seconds:.0f}s...[/dim]\"\n          )\n      else:\n          self.console.print(\n              f\"[dim]Idle: {issues_blocked} issues exist but none ready. Polling in {wait_seconds:.0f}s...[/dim]\"\n          )\n  ```\n\n## Acceptance Criteria\n- `on_watch_idle` prints message to console\n- Message format matches spec: \"Idle: no ready issues\" or \"Idle: N issues exist but none ready\"\n- Message includes poll interval in seconds\n- Uses dim styling for non-critical info\n\n## Verification\n- Unit test: mock console, call on_watch_idle, verify printed message\n- `uv run pytest tests/unit/infra/io/ -v -k console_sink` → pass\n\n## Notes for Agent\n- Follow existing console_sink patterns for styling\n- The coordinator handles rate-limiting, not the sink\n- `issues_blocked=None` means provider couldn't answer → treat as \"no ready issues\"","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-05T02:24:24.628902788Z","created_by":"cyou","updated_at":"2026-01-05T02:31:40.87216816Z","closed_at":"2026-01-05T02:31:40.87216816Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-giyp.9","depends_on_id":"mala-giyp","type":"parent-child","created_at":"2026-01-05T02:24:24.638710566Z","created_by":"cyou"},{"issue_id":"mala-giyp.9","depends_on_id":"mala-giyp.2","type":"blocks","created_at":"2026-01-05T02:24:28.944147116Z","created_by":"cyou"}]}
{"id":"mala-go0b","title":"[Review] tests/test_config_merger.py:878-896: [P3] Test docstring claims to match merge_configs example but uses different API","description":"## Review Finding\n\nThis issue was auto-created from a P3 review finding.\n\n**Source issue:** mala-lq68\n**Reviewer:** claude\n**Location:** tests/test_config_merger.py:878-896\n\n## Details\n\nThe test docstring at lines 878-895 claims \"This matches the example from the merge_configs docstring\" but shows direct constructor usage. The actual `merge_configs` docstring (lines 91-109) uses `from_dict`. The test is still valid (tests programmatic configs work), but the comment is misleading and could confuse future maintainers.","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-02T04:23:34.319478324Z","created_by":"cyou","updated_at":"2026-01-02T04:27:45.881489993Z","closed_at":"2026-01-02T04:27:45.881489993Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_finding:1c7429f9bfe3","source:mala-lq68"]}
{"id":"mala-grm7","title":"[Review] tests/test_cerberus_review.py:724: [P3] Redundant imports in test methods","description":"## Review Finding\n\nThis issue was auto-created from a P3 review finding.\n\n**Source issue:** mala-apsz\n**Reviewer:** gemini\n**Location:** tests/test_cerberus_review.py:724\n\n## Details\n\nIn `tests/test_cerberus_review.py`, the new test methods redundantly import `Path` from `pathlib` locally, even though it is already imported at the top of the file (line 14). This clutters the test code without providing any benefit.","notes":"Quality gate failed: External review failed: spawn failed: Artifact written: /home/cyou/.claude/projects/-home-cyou-mala/cerberus/e0c0d0f3-1b24-45af-b2ea-e849c9b13e68/latest.md\nReview gate already active (status: pending, age: 55s)\nUse /home/cyou/.claude/plugins/cache/cerberus/cerberus/1.10.6/bin/review-gate resolve to complete the existing gate first.\nLog: /home/cyou/.claude/projects/-home-cyou-mala/e0c0d0f3-1b24-45af-b2ea-e849c9b13e68.jsonl","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-01T23:49:33.652249167Z","created_by":"cyou","updated_at":"2026-01-02T00:47:48.173223152Z","closed_at":"2026-01-02T00:47:48.173223152Z","close_reason":"Closed","labels":["auto_generated","needs-followup","review_finding","review_finding:1f37bbb63222","source:mala-apsz"]}
{"id":"mala-gvg4","title":"Fix import-linter architecture violations","description":"## Problem\n\n3 of 14 import-linter contracts are currently broken:\n\n1. **Layered Architecture** - `src.pipeline.issue_execution_coordinator` imports `src.orchestration.types`\n2. **SDK confined to infra** - Transitive leak: `pipeline` → `infra.agent_runtime` → `infra.mcp` → `infra.tools.locking_mcp` → `claude_agent_sdk`\n3. **Hooks isolated** - `infra.hooks.dangerous_commands` → `infra.mcp` → `core.models`\n\nAll three violations trace back to `src.infra.mcp` having too many responsibilities.\n\n## Proposed Fix\n\n1. Move `orchestration.types` to `core` or `domain` - types used by pipeline shouldn't live in orchestration\n\n2. Split `infra.mcp`:\n   - Extract protocol-level MCP abstractions from SDK-specific code\n   - Move SDK-dependent code to a location that doesn't get imported by hooks\n\n3. Break the `infra.mcp` → `core.models` dependency by injecting models or using protocols\n\n## Verification\n\n```bash\nuv run lint-imports  # All 14 contracts should pass\n```","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-05T03:33:56.42160836Z","created_by":"cyou","updated_at":"2026-01-05T04:23:50.741103559Z","closed_at":"2026-01-05T04:23:50.741103559Z","close_reason":"Closed","labels":["architecture","tech-debt"]}
{"id":"mala-he7","title":"Disallow git restore command","description":"The CLI should not allow the git restore command to be executed. This command can be destructive as it discards uncommitted changes without confirmation.","status":"closed","priority":0,"issue_type":"task","created_at":"2025-12-27T20:06:38.195974516Z","updated_at":"2025-12-27T20:57:22.04486967Z","closed_at":"2025-12-27T20:57:22.04486967Z","close_reason":"Closed"}
{"id":"mala-hgnx","title":"[P2] Test uses PWD env var instead of chdir","description":"## Review Finding\n\n**Priority:** P2\n**Trigger:** session_end\n**Baseline:** 14fb49c..3438772\n**Location:** tests/integration/cli/test_init.py:47-57\n**Source Issue:** mala-ptio.1\n\n---\n\n`test_dry_run_no_backup` sets `PWD` in `env` but does not change the process working directory (e.g., via `monkeypatch.chdir`). Python's `Path.cwd()` ignores `PWD`, so this test will run against the project root instead of `tmp_path`. It will fail to detect the `mala.yaml` created in `tmp_path`, rendering the test ineffective.","notes":"Quality gate failed: Quality gate failed: Validation command(s) failed: git\nLog: /home/cyou/.claude/projects/-home-cyou-mala/595b3179-712c-4875-8209-8f6d06c92caa.jsonl","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-10T21:26:10.240578867Z","created_by":"cyou","updated_at":"2026-01-11T02:36:52.410967589Z","closed_at":"2026-01-11T02:36:52.410967589Z","close_reason":"Closed","labels":["auto_generated","cumulative-review","fp:718ee92a251a1033","needs-followup","review_finding","trigger:session_end"]}
{"id":"mala-hmfb","title":"add issue retry, with review awareness","status":"tombstone","priority":2,"issue_type":"feature","created_at":"2026-01-02T15:32:40.591855646Z","created_by":"cyou","updated_at":"2026-01-06T22:52:10.761723406Z","deleted_at":"2026-01-06T22:52:10.761723406Z","deleted_by":"batch delete","delete_reason":"batch delete","original_type":"feature"}
{"id":"mala-hof5","title":"[Review] src/domain/validation/config_merger.py:52-66: [P3] Fix docstring example to reflect new _fields_set behavior","description":"## Review Finding\n\nThis issue was auto-created from a P3 review finding.\n\n**Source issue:** mala-fdp1.4\n**Reviewer:** codex\n**Location:** src/domain/validation/config_merger.py:52-66\n\n## Details\n\nThe example in `merge_configs` shows programmatic construction via `ValidationConfig(...)`/`CommandsConfig(...)`, but overrides won’t apply because `_fields_set` is empty unless `from_dict` is used. This example now demonstrates behavior that no longer works as shown. Update it to use `from_dict` (or mention setting `_fields_set`).","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-02T04:10:25.934776424Z","created_by":"cyou","updated_at":"2026-01-02T04:19:31.917974619Z","closed_at":"2026-01-02T04:19:31.917974619Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_finding:22e8389addf7","source:mala-fdp1.4"]}
{"id":"mala-hro","title":"Epic: Strict-by-default validation pipeline (v5 plan)","description":"Context:\n- Implements the v5 quality-hardening plan (docs/quality-hardening-plan.md).\n- Coordinates validation modules plus orchestrator/gate integration.\n\nScope:\n- In: all child issues required to implement strict-by-default validation pipeline.\n- Out: product feature changes unrelated to validation.\n\nAcceptance Criteria:\n- All child issues closed and dependencies satisfied.\n- End-to-end validation pipeline implemented per plan.\n\nTest Plan:\n- Review child issue test plans; run full suite when integration is complete.\n\nNotes for Agent:\n- This is an umbrella epic for dependency management only.","status":"closed","priority":1,"issue_type":"epic","created_at":"2025-12-26T22:58:40.016961193Z","updated_at":"2025-12-27T05:57:43.092716959Z","closed_at":"2025-12-27T05:57:43.092716959Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-hro","depends_on_id":"mala-2i7","type":"blocks","created_at":"2025-12-26T23:00:26.313705147Z","created_by":"daemon"},{"issue_id":"mala-hro","depends_on_id":"mala-x00","type":"blocks","created_at":"2025-12-26T23:00:26.33116988Z","created_by":"daemon"}]}
{"id":"mala-hx3m","title":"[Review] 1 non-blocking finding from mala-y34j","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-y34j\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Make re-entrant acquire unit test actually cover re-entrance\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** tests/unit/infra/tools/test_locking_mcp.py:312-341\n\n`test_no_waiting_for_reentrant_acquire` patches `try_lock` to always return `True`, so it doesn’t validate the new behavior (existing lock file held by the same agent now returns `True` instead of `False`). This test would have passed before the change as well, since `lock_acquire` emits no WAITING events whenever `try_lock` returns `True`. To make it meaningful, set up a real lock in a temp lock dir (or don’t patch `try_lock`) and call `lock_acquire` twice for the same canonical path, asserting the second call is acquired and emits no WAITING.\n\n---\n\n\u003c!-- fp:58ec97ed22d1edd7 --\u003e\n\u003c!-- review_finding:4d45ef9a1acf --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-04T23:29:37.358138615Z","created_by":"cyou","updated_at":"2026-01-04T23:36:39.275529175Z","closed_at":"2026-01-04T23:36:39.275529175Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-y34j"]}
{"id":"mala-i757","title":"[Review] 3 non-blocking findings from mala-1h9t","description":"## Review Findings\n\nThis issue consolidates 3 non-blocking findings from code review.\n\n**Source issue:** mala-1h9t\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Protocol docstring says 'open' but implementation uses 'ready'\n\n**Priority:** P2\n**Reviewer:** claude\n**Location:** src/core/protocols.py:498\n\nThe `reopen_issue_async` docstring in `protocols.py:498` still says \"setting status to open\" but the actual implementations in `beads_client.py` and `FakeIssueProvider` correctly use \"ready\". The second commit updated the implementations but missed updating the protocol docstring, creating misleading documentation.\n\n---\n\n### Finding 2: [P3] Inline comment references 'open' instead of 'ready'\n\n**Priority:** P3\n**Reviewer:** claude\n**Location:** src/orchestration/deadlock_handler.py:189\n\nThe comment on line 189 says \"Reset status to open\" but the implementation actually sets status to \"ready\". Should be updated for consistency with the actual behavior.\n\n---\n\n### Finding 3: [P2] Inconsistent docstrings and comments regarding issue status\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/core/protocols.py:497\n\nCommit 2 correctly changed the implementation to use `ready` status instead of `open`, but failed to update several docstrings and comments. `IssueProvider.reopen_issue_async` in `src/core/protocols.py`, `DeadlockHandlerCallbacks.reopen_issue` in `src/orchestration/deadlock_handler.py`, and the inline comment in `DeadlockHandler.handle_deadlock` still state the status is being set to `open`.\n\n---\n\n\u003c!-- fp:f4651fe7d5b2608f --\u003e\n\u003c!-- fp:e7f75704dd127ada --\u003e\n\u003c!-- fp:0683e2590bfcbdfa --\u003e\n\u003c!-- review_finding:ccdc4abe757d --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-05T02:22:11.855658499Z","created_by":"cyou","updated_at":"2026-01-05T02:24:17.911258813Z","closed_at":"2026-01-05T02:24:17.911258813Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-1h9t"]}
{"id":"mala-i8ke","title":"[Review] src/orchestration/orchestrator.py:747-761: [P2] Duplicated state between Orchestrator and Coordinator","description":"## Review Finding\n\nThis issue was auto-created from a P2 review finding.\n\n**Source issue:** mala-cavk.3\n**Reviewer:** gemini\n**Location:** src/orchestration/orchestrator.py:747-761\n\n## Details\n\nThe `IssueExecutionCoordinator` maintains its own copies of `active_tasks`, `failed_issues`, and `completed_ids`. `MalaOrchestrator` also maintains these, leading to a need for manual synchronization (as seen in `spawn_agent` and `finalize_callback`). Modifications to `MalaOrchestrator` attributes like `max_issues` after initialization will not be reflected in the coordinator unless synced manually, as demonstrated by the fix in `test_respects_max_issues_limit`.","notes":"Quality gate failed: External review failed: spawn failed: Error: --exclude expects git exclude pathspec syntax (e.g. :!path or :(exclude,glob)path): .beads/\nLog: /home/cyou/.claude/projects/-home-cyou-mala/22d76d35-50cf-4de4-afa0-b3807467e18e.jsonl","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-01T22:23:07.43843243Z","created_by":"cyou","updated_at":"2026-01-02T01:01:46.123732965Z","closed_at":"2026-01-02T01:01:46.123732965Z","close_reason":"Closed","labels":["auto_generated","needs-followup","review_finding","review_finding:aaab6cd0b9b4","source:mala-cavk.3"]}
{"id":"mala-iag1","title":"[Review] 2 non-blocking findings from mala-kyq1.3","description":"## Review Findings\n\nThis issue consolidates 2 non-blocking findings from code review.\n\n**Source issue:** mala-kyq1.3\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P3] Remove redundant asyncio import inside _handle_dry_run\n\n**Priority:** P3\n**Reviewer:** claude\n**Location:** src/cli/cli.py:336\n\nThe `import asyncio` on line 336 inside `_handle_dry_run()` is redundant since `asyncio` is already imported at module level (line 105). This is the only local import in the file and is inconsistent with the codebase style. Simply remove the local import and use the module-level one.\n\n---\n\n### Finding 2: [P2] Remove unused top-level asyncio import\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/cli/cli.py:105\n\nAfter moving the dry-run logic into `_handle_dry_run()`, the module-level `import asyncio` is unused. Ruff will flag this (`F401`) and CI will fail if linting runs. Either drop the top-level import or use it consistently by removing the inner `import asyncio` and referencing the module-level import instead. Example: `import asyncio`.\n\n---\n","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T07:36:44.060122861Z","created_by":"cyou","updated_at":"2026-01-03T07:41:21.63905059Z","closed_at":"2026-01-03T07:41:21.63905059Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_findings:ca11f015bfb9","source:mala-kyq1.3"]}
{"id":"mala-idx6","title":"Epic: Fix domain layer dependency on infra/IO","description":"## Summary\nDomain modules import infra command execution (`run_command`), logging (`console.log`), locking (`lock_path`, `wait_for_lock`), and config helpers. This blurs the \"functional core, imperative shell\" split and makes domain behavior hard to test without real IO.\n\n## Primary Files\n- `src/domain/quality_gate.py:21-22`\n- `src/domain/validation/spec_executor.py:21-23`\n- `src/domain/validation/coverage.py:25,461-546`\n- `src/domain/validation/e2e.py:24-25`\n- `src/domain/validation/helpers.py:12`\n\n## Category\nBoundaries\n\n## Source\ncodex\n\n## Fix Approach\n1. Introduce explicit ports (protocols) for command execution, logging, and lock management in `src/core/protocols.py` (or `domain/ports`)\n2. Move IO implementations into infra\n3. Inject implementations into domain services via constructor/factory\n\n## Non-Goals\n- Creating adapter layers for every utility function\n- Moving all domain code to pure functions (some orchestration is acceptable)\n\n## Acceptance Criteria\n- [ ] Domain modules no longer import `src.infra` directly; they depend on ports/interfaces\n- [ ] Orchestrator/pipeline wires concrete infra implementations into domain services\n- [ ] No behavioral changes\n\n## Test Plan\n- Verify no `from src.infra` imports in `src/domain/` after refactor\n- Domain unit tests use fake implementations of ports\n\n## Agent Notes\nThe re-export shim at `src/domain/validation/command_runner.py` should be removed as part of this work. Per CLAUDE.md, no backward-compatibility shims are allowed.","status":"closed","priority":1,"issue_type":"epic","created_at":"2026-01-03T05:48:06.844172458Z","created_by":"cyou","updated_at":"2026-01-03T22:16:03.733655445Z","closed_at":"2026-01-03T22:16:03.733655445Z","close_reason":"Closed"}
{"id":"mala-idx6.1","title":"Define CommandRunnerPort protocol in src/core/protocols.py","description":"## Problem\nDomain modules directly import `CommandRunner`, `run_command`, and `CommandResult` from `src.infra.tools.command_runner`, violating the layered architecture principle.\n\n## Solution\nDefine a `CommandRunnerPort` protocol in `src/core/protocols.py` that abstracts command execution.\n\n## Files to modify\n- `src/core/protocols.py` - Add `CommandRunnerPort`, `CommandResultProtocol` protocols\n\n## Protocol definition\n```python\n@runtime_checkable\nclass CommandResultProtocol(Protocol):\n    ok: bool\n    returncode: int\n    stdout: str\n    stderr: str\n    timed_out: bool\n\n@runtime_checkable\nclass CommandRunnerPort(Protocol):\n    def run(\n        self,\n        cmd: list[str],\n        *,\n        cwd: Path | None = None,\n        env: Mapping[str, str] | None = None,\n        timeout_seconds: float | None = None,\n        capture: bool = True,\n    ) -\u003e CommandResultProtocol: ...\n\n    async def run_async(\n        self,\n        cmd: list[str],\n        *,\n        cwd: Path | None = None,\n        env: Mapping[str, str] | None = None,\n        timeout_seconds: float | None = None,\n    ) -\u003e CommandResultProtocol: ...\n```\n\n## Acceptance criteria\n- Protocol is defined in src/core/protocols.py\n- Protocol matches the interface of src.infra.tools.command_runner.CommandRunner\n- Tests pass","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T05:53:09.50539668Z","created_by":"cyou","updated_at":"2026-01-03T07:06:58.625239988Z","closed_at":"2026-01-03T07:06:58.625239988Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-idx6.1","depends_on_id":"mala-idx6","type":"parent-child","created_at":"2026-01-03T17:58:44.748886422Z","created_by":"daemon"}]}
{"id":"mala-idx6.10","title":"Remove command_runner.py re-export shim","description":"## Problem\n`src/domain/validation/command_runner.py` is a backwards-compatibility re-export shim that violates the \"No re-exports\" rule from CLAUDE.md:\n\n```python\n\"\"\"Backwards-compatibility re-export for command_runner.\"\"\"\nfrom src.infra.tools.command_runner import (\n    DEFAULT_KILL_GRACE_SECONDS,\n    TIMEOUT_EXIT_CODE,\n    CommandResult,\n    CommandRunner,\n    run_command,\n    run_command_async,\n)\n```\n\n## Solution\nDelete the file and update any imports that use it.\n\n## Files to modify\n- `src/domain/validation/command_runner.py` - DELETE this file\n- Any files importing from this module (search and update)\n\n## Changes\n1. Search for all imports from `src.domain.validation.command_runner`\n2. Update those imports to use the injected CommandRunnerPort instead\n3. Delete `src/domain/validation/command_runner.py`\n4. Ensure no broken imports remain\n\n## Acceptance criteria\n- `src/domain/validation/command_runner.py` is deleted\n- No imports reference this module\n- All tests pass","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T05:54:36.509847147Z","created_by":"cyou","updated_at":"2026-01-03T15:07:12.347215721Z","closed_at":"2026-01-03T15:07:12.347215721Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-idx6.10","depends_on_id":"mala-idx6.2","type":"blocks","created_at":"2026-01-03T05:54:54.763494201Z","created_by":"daemon"},{"issue_id":"mala-idx6.10","depends_on_id":"mala-idx6.3","type":"blocks","created_at":"2026-01-03T05:54:54.819655203Z","created_by":"daemon"},{"issue_id":"mala-idx6.10","depends_on_id":"mala-idx6.4","type":"blocks","created_at":"2026-01-03T05:54:54.877962761Z","created_by":"daemon"},{"issue_id":"mala-idx6.10","depends_on_id":"mala-idx6.5","type":"blocks","created_at":"2026-01-03T05:54:54.948920979Z","created_by":"daemon"},{"issue_id":"mala-idx6.10","depends_on_id":"mala-idx6","type":"parent-child","created_at":"2026-01-03T17:58:44.829536435Z","created_by":"daemon"}]}
{"id":"mala-idx6.11","title":"[Remediation] Domain modules no longer import `src.infra` directly; they d...","description":"## Context\nThis issue was auto-created by epic verification for epic `mala-idx6`.\n\n## Epic Description / Acceptance Criteria\nTitle: Epic: Fix domain layer dependency on infra/IO\n\n## Summary\nDomain modules import infra command execution (`run_command`), logging (`console.log`), locking (`lock_path`, `wait_for_lock`), and config helpers. This blurs the \"functional core, imperative shell\" split and makes domain behavior hard to test without real IO.\n\n## Primary Files\n- `src/domain/quality_gate.py:21-22`\n- `src/domain/validation/spec_executor.py:21-23`\n- `src/domain/validation/coverage.py:25,461-546`\n- `src/domain/validation/e2e.py:24-25`\n- `src/domain/validation/helpers.py:12`\n\n## Category\nBoundaries\n\n## Source\ncodex\n\n## Fix Approach\n1. Introduce explicit ports (protocols) for command execution, logging, and lock management in `src/core/protocols.py` (or `domain/ports`)\n2. Move IO implementations into infra\n3. Inject implementations into domain services via constructor/factory\n\n## Non-Goals\n- Creating adapter layers for every utility function\n- Moving all domain code to pure functions (some orchestration is acceptable)\n\n## Acceptance Criteria\n- [ ] Domain modules no longer import `src.infra` directly; they depend on ports/interfaces\n- [ ] Orchestrator/pipeline wires concrete infra implementations into domain services\n- [ ] No behavioral changes\n\n## Test Plan\n- Verify no `from src.infra` imports in `src/domain/` after refactor\n- Domain unit tests use fake implementations of ports\n\n## Agent Notes\nThe re-export shim at `src/domain/validation/command_runner.py` should be removed as part of this work. Per CLAUDE.md, no backward-compatibility shims are allowed.\n\n## Commit Scope\n- Range hint: bc64f309fe2a85131dd2679c41aa1ceef1953907^..4a3eced032c5f70d05a9dda7760f960d1cdf3e44\n- Commits:\n- 4a3eced032c5f70d05a9dda7760f960d1cdf3e44 bd-mala-b79o.5: Wire event sink to SpecValidationRunner\n- d1ac84b28bb981c057d85f48c988fa89b6feed79 bd-mala-b79o.2: Remove no_morph references from cli and tests\n- 9554f2e186fad3a38e959cd9157a5340655b733e bd-mala-b79o.2: Use MalaEventSink protocol and replace logger with event sink\n- 78042f75a8605f73afea6aa8419a223d276a970f bd-mala-b79o.2: Route SpecCommandExecutor logging through event sink\n- c2a82f0b2dbc8b01acfa7347506d7c8c3ce4a92f bd-mala-idx6.6: Fix LoggerPort color contract and wire logger through SpecRunner\n- 2d4ae75c8e63e31d27223886fc34b010bdd9b56e bd-mala-idx6.6: Define LoggerPort protocol and inject into spec_executor\n- 52303d52023fab5cf3f8533282c111c4286351eb bd-mala-idx6.10: Remove command_runner.py re-export shim\n- 910c88d3ca14c116987d72ae1b75bc5bc1cadb09 bd-mala-idx6.2: Use kw_only=True for ExecutorConfig dataclass\n- d816b1c35b17af2793aebc0f3c57aa9dc5b80ace bd-mala-idx6.2: Inject CommandRunner into spec_executor.py via constructor\n- fad5a8d5e919450e5241c40ce6bd654930b047a0 bd-mala-brwf.1: Fix ty type errors for CommandRunner protocol compatibility\n- 27027abeab2f17f42b2ca21443315022b2e7e1f3 bd-mala-idx6.9: Refactor quality_gate.py to use injected LogProvider\n- 80880bd7bed1ef32ec151d50ddaffa8a905a8640 bd-mala-brwf.2: Extract _run_coverage_with_fallback from _run_refresh\n- a2cc0373a46ccbe90b4d72427145033ce0b003c6 bd-mala-brwf.1: Extract pure command-rewrite helpers from _run_refresh\n- 17e110e458217c2ce02d4d67f58e9f2f465b7ea9 bd-mala-idx6.3: Fix test failures for command_runner injection\n- 0aef642cb595afd4cca9138bc8cffd4584250232 bd-mala-idx6.9: Fix command_runner propagation and lint cache invalidation\n- d806fdc0569a02afdc2a8a3e575002dcea4c5706 bd-mala-idx6.7: Add stdout_tail/stderr_tail to CommandResultProtocol and fix release_lock ownership\n- c2ab6e4cf634cc7c7524fc098320c0ae48160dc6 bd-mala-idx6.7: Add release_lock to LockManagerPort and fix abstraction\n- 8907de14e173138a6ce5860700fc58ff06793d2e bd-mala-idx6.9: Update FileSystemLogProvider to use JsonlEntryProtocol and fix CommandRunner cwd\n- a0b1b2362a3a04937256bfebe54a2f69bdf59b45 bd-mala-idx6.7: Define LockManagerPort protocol and inject into coverage.py\n- 15052074f54efbe23dc2dd7e2f3ba225c998975d bd-mala-idx6.4: Update tests to patch CommandRunner.run instead of run_command\n- 6e04b7e7b67e56da4609e8558879e275c4b1d6b6 bd-mala-idx6.5: Fix cwd handling in injected CommandRunner and test assertions\n- 47cace988c6d4d7026fd08f4a76730412ace02f3 bd-mala-idx6.4: Inject CommandRunner into coverage.py and helpers.py\n- dd9a49348de9ae07c71989cd3d1045a67e8887e8 bd-mala-idx6.5: Inject CommandRunner into quality_gate.py and e2e.py\n- b1671b947f37f4dd73d329220bc2ed4e77f279f9 bd-mala-idx6.8: Define EnvConfigPort protocol and inject into domain modules\n- a312a3e25369bbe5e5ccb79b5566921bc3d2ca3d bd-mala-idx6.1: Fix CommandRunnerPort to match CommandRunner signature\n- bc64f309fe2a85131dd2679c41aa1ceef1953907 bd-mala-idx6.1: Define CommandRunnerPort protocol in src/core/protocols.py\n\n## Child Issues\n- mala-b79o.2\n- mala-b79o.5\n- mala-brwf.1\n- mala-brwf.2\n- mala-brwf.3\n- mala-brwf.4\n- mala-idx6.1\n- mala-idx6.10\n- mala-idx6.2\n- mala-idx6.3\n- mala-idx6.4\n- mala-idx6.5\n- mala-idx6.6\n- mala-idx6.7\n- mala-idx6.8\n- mala-idx6.9\n\n## Unmet Criterion\nDomain modules no longer import `src.infra` directly; they depend on ports/interfaces\n\n## Evidence\nFound 19 direct `from src.infra` imports across domain modules including: quality_gate.py:298, coverage.py:373,424,434,446,459,518,852, e2e.py:172,318, spec_executor.py:195,300,338, spec_runner.py:156,225,362,445, helpers.py:218,261,296, spec_result_builder.py:209, and epic/scope.py:6. These are fallback imports when dependencies are not injected, but still represent direct infra dependencies.\n\n## Priority\nP1 (blocking)\n\n## Resolution\nAddress this criterion to unblock epic closure. When complete, close this issue.\n","notes":"Quality gate failed: External review failed: src/domain/validation/coverage.py:1: [P1] Domain modules still contain direct `src.infra` imports\nLog: /home/cyou/.claude/projects/-home-cyou-mala/3b6a88b3-6e0d-468f-accb-6d5b3d18c3fd.jsonl","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-03T18:06:40.680176389Z","created_by":"cyou","updated_at":"2026-01-03T21:17:33.330848493Z","closed_at":"2026-01-03T21:17:33.330848493Z","close_reason":"Closed","labels":["auto_generated","epic_remediation:mala-idx6:3999f894092f7323bb17bde37a8598aabb9f272c272836fe1240eaf56eadce09","needs-followup"],"dependencies":[{"issue_id":"mala-idx6.11","depends_on_id":"mala-idx6","type":"parent-child","created_at":"2026-01-03T18:06:40.680736815Z","created_by":"daemon"}]}
{"id":"mala-idx6.12","title":"[Remediation] Orchestrator/pipeline wires concrete infra implementations i...","description":"## Context\nThis issue was auto-created by epic verification for epic `mala-idx6`.\n\n## Epic Description / Acceptance Criteria\nTitle: Epic: Fix domain layer dependency on infra/IO\n\n## Summary\nDomain modules import infra command execution (`run_command`), logging (`console.log`), locking (`lock_path`, `wait_for_lock`), and config helpers. This blurs the \"functional core, imperative shell\" split and makes domain behavior hard to test without real IO.\n\n## Primary Files\n- `src/domain/quality_gate.py:21-22`\n- `src/domain/validation/spec_executor.py:21-23`\n- `src/domain/validation/coverage.py:25,461-546`\n- `src/domain/validation/e2e.py:24-25`\n- `src/domain/validation/helpers.py:12`\n\n## Category\nBoundaries\n\n## Source\ncodex\n\n## Fix Approach\n1. Introduce explicit ports (protocols) for command execution, logging, and lock management in `src/core/protocols.py` (or `domain/ports`)\n2. Move IO implementations into infra\n3. Inject implementations into domain services via constructor/factory\n\n## Non-Goals\n- Creating adapter layers for every utility function\n- Moving all domain code to pure functions (some orchestration is acceptable)\n\n## Acceptance Criteria\n- [ ] Domain modules no longer import `src.infra` directly; they depend on ports/interfaces\n- [ ] Orchestrator/pipeline wires concrete infra implementations into domain services\n- [ ] No behavioral changes\n\n## Test Plan\n- Verify no `from src.infra` imports in `src/domain/` after refactor\n- Domain unit tests use fake implementations of ports\n\n## Agent Notes\nThe re-export shim at `src/domain/validation/command_runner.py` should be removed as part of this work. Per CLAUDE.md, no backward-compatibility shims are allowed.\n\n## Commit Scope\n- Range hint: bc64f309fe2a85131dd2679c41aa1ceef1953907^..4a3eced032c5f70d05a9dda7760f960d1cdf3e44\n- Commits:\n- 4a3eced032c5f70d05a9dda7760f960d1cdf3e44 bd-mala-b79o.5: Wire event sink to SpecValidationRunner\n- d1ac84b28bb981c057d85f48c988fa89b6feed79 bd-mala-b79o.2: Remove no_morph references from cli and tests\n- 9554f2e186fad3a38e959cd9157a5340655b733e bd-mala-b79o.2: Use MalaEventSink protocol and replace logger with event sink\n- 78042f75a8605f73afea6aa8419a223d276a970f bd-mala-b79o.2: Route SpecCommandExecutor logging through event sink\n- c2a82f0b2dbc8b01acfa7347506d7c8c3ce4a92f bd-mala-idx6.6: Fix LoggerPort color contract and wire logger through SpecRunner\n- 2d4ae75c8e63e31d27223886fc34b010bdd9b56e bd-mala-idx6.6: Define LoggerPort protocol and inject into spec_executor\n- 52303d52023fab5cf3f8533282c111c4286351eb bd-mala-idx6.10: Remove command_runner.py re-export shim\n- 910c88d3ca14c116987d72ae1b75bc5bc1cadb09 bd-mala-idx6.2: Use kw_only=True for ExecutorConfig dataclass\n- d816b1c35b17af2793aebc0f3c57aa9dc5b80ace bd-mala-idx6.2: Inject CommandRunner into spec_executor.py via constructor\n- fad5a8d5e919450e5241c40ce6bd654930b047a0 bd-mala-brwf.1: Fix ty type errors for CommandRunner protocol compatibility\n- 27027abeab2f17f42b2ca21443315022b2e7e1f3 bd-mala-idx6.9: Refactor quality_gate.py to use injected LogProvider\n- 80880bd7bed1ef32ec151d50ddaffa8a905a8640 bd-mala-brwf.2: Extract _run_coverage_with_fallback from _run_refresh\n- a2cc0373a46ccbe90b4d72427145033ce0b003c6 bd-mala-brwf.1: Extract pure command-rewrite helpers from _run_refresh\n- 17e110e458217c2ce02d4d67f58e9f2f465b7ea9 bd-mala-idx6.3: Fix test failures for command_runner injection\n- 0aef642cb595afd4cca9138bc8cffd4584250232 bd-mala-idx6.9: Fix command_runner propagation and lint cache invalidation\n- d806fdc0569a02afdc2a8a3e575002dcea4c5706 bd-mala-idx6.7: Add stdout_tail/stderr_tail to CommandResultProtocol and fix release_lock ownership\n- c2ab6e4cf634cc7c7524fc098320c0ae48160dc6 bd-mala-idx6.7: Add release_lock to LockManagerPort and fix abstraction\n- 8907de14e173138a6ce5860700fc58ff06793d2e bd-mala-idx6.9: Update FileSystemLogProvider to use JsonlEntryProtocol and fix CommandRunner cwd\n- a0b1b2362a3a04937256bfebe54a2f69bdf59b45 bd-mala-idx6.7: Define LockManagerPort protocol and inject into coverage.py\n- 15052074f54efbe23dc2dd7e2f3ba225c998975d bd-mala-idx6.4: Update tests to patch CommandRunner.run instead of run_command\n- 6e04b7e7b67e56da4609e8558879e275c4b1d6b6 bd-mala-idx6.5: Fix cwd handling in injected CommandRunner and test assertions\n- 47cace988c6d4d7026fd08f4a76730412ace02f3 bd-mala-idx6.4: Inject CommandRunner into coverage.py and helpers.py\n- dd9a49348de9ae07c71989cd3d1045a67e8887e8 bd-mala-idx6.5: Inject CommandRunner into quality_gate.py and e2e.py\n- b1671b947f37f4dd73d329220bc2ed4e77f279f9 bd-mala-idx6.8: Define EnvConfigPort protocol and inject into domain modules\n- a312a3e25369bbe5e5ccb79b5566921bc3d2ca3d bd-mala-idx6.1: Fix CommandRunnerPort to match CommandRunner signature\n- bc64f309fe2a85131dd2679c41aa1ceef1953907 bd-mala-idx6.1: Define CommandRunnerPort protocol in src/core/protocols.py\n\n## Child Issues\n- mala-b79o.2\n- mala-b79o.5\n- mala-brwf.1\n- mala-brwf.2\n- mala-brwf.3\n- mala-brwf.4\n- mala-idx6.1\n- mala-idx6.10\n- mala-idx6.2\n- mala-idx6.3\n- mala-idx6.4\n- mala-idx6.5\n- mala-idx6.6\n- mala-idx6.7\n- mala-idx6.8\n- mala-idx6.9\n\n## Unmet Criterion\nOrchestrator/pipeline wires concrete infra implementations into domain services\n\n## Evidence\nOrchestration layer only partially wires dependencies. QualityGate receives log_provider injection in factory.py:218, but SpecValidationRunner calls in run_coordinator.py:250 and other locations do not inject command_runner, env_config, or other dependencies, causing fallback to direct infra imports.\n\n## Priority\nP1 (blocking)\n\n## Resolution\nAddress this criterion to unblock epic closure. When complete, close this issue.\n","notes":"Quality gate failed: Timeout after 60 minutes\nLog: /home/cyou/.claude/projects/-home-cyou-mala/d326d7cb-785b-4a0e-9168-c287d633ee96.jsonl","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-03T18:06:40.698380597Z","created_by":"cyou","updated_at":"2026-01-03T22:13:07.81364379Z","closed_at":"2026-01-03T22:13:07.81364379Z","close_reason":"Closed","labels":["auto_generated","epic_remediation:mala-idx6:e6b358ffaaffeeb8273a97d6577ffd6cb8331fee2160ec6ddc242da99c01af8f","needs-followup"],"dependencies":[{"issue_id":"mala-idx6.12","depends_on_id":"mala-idx6","type":"parent-child","created_at":"2026-01-03T18:06:40.698757944Z","created_by":"daemon"},{"issue_id":"mala-idx6.12","depends_on_id":"mala-idx6.11","type":"blocks","created_at":"2026-01-03T19:26:25.675314881Z","created_by":"daemon"}]}
{"id":"mala-idx6.13","title":"[Review] 1 non-blocking finding from mala-idx6.12","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-idx6.12\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Docstring example missing required repo_path argument\n\n**Priority:** P2\n**Reviewer:** claude\n**Location:** src/domain/validation/runner.py:17\n\nThe docstring example for `build_validation_spec` in `runner.py` is missing the required `repo_path` first argument. The actual function signature is `build_validation_spec(repo_path: Path, scope: ValidationScope | None = None, ...)`. The example in `__init__.py` correctly includes it, but `runner.py` shows `build_validation_spec(scope=ValidationScope.PER_ISSUE, ...)` which would fail at runtime.\n\n---\n\n\u003c!-- fp:f0709e3013588fc3 --\u003e\n\u003c!-- review_finding:714afc84d728 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T22:16:03.757414698Z","created_by":"cyou","updated_at":"2026-01-03T22:18:09.598097621Z","closed_at":"2026-01-03T22:18:09.598097621Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-idx6.12"],"dependencies":[{"issue_id":"mala-idx6.13","depends_on_id":"mala-idx6","type":"parent-child","created_at":"2026-01-03T22:16:03.75782094Z","created_by":"daemon"}]}
{"id":"mala-idx6.2","title":"Inject CommandRunner into spec_executor.py via constructor","description":"## Problem\n`spec_executor.py` directly imports `CommandRunner` from `src.infra.tools.command_runner` (line 22).\n\n## Solution\nAccept `CommandRunnerPort` via constructor injection and update all usages.\n\n## Files to modify\n- `src/domain/validation/spec_executor.py` - Accept runner via constructor/config\n- Tests for spec_executor\n\n## Changes\n1. Add `command_runner: CommandRunnerPort` parameter to `SpecCommandExecutor.__init__` or `ExecutorConfig`\n2. Remove direct import of `CommandRunner` from infra\n3. Use the injected runner for all command execution\n4. Update tests to inject mock/fake runners\n\n## Acceptance criteria\n- No direct imports from `src.infra.tools.command_runner`\n- CommandRunner injected via constructor\n- Tests pass with injected fake runner","notes":"Quality gate failed: Quality gate failed: No commit with bd-mala-idx6.2 found after run baseline (stale commits from previous runs are rejected)\nLog: /home/cyou/.claude/projects/-home-cyou-mala/63c11f9a-62c2-4bc7-a94c-0a4350ac8717.jsonl","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T05:53:16.492209942Z","created_by":"cyou","updated_at":"2026-01-03T15:01:35.381869716Z","closed_at":"2026-01-03T15:01:35.381869716Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-idx6.2","depends_on_id":"mala-idx6.1","type":"blocks","created_at":"2026-01-03T05:54:49.353736611Z","created_by":"daemon"},{"issue_id":"mala-idx6.2","depends_on_id":"mala-idx6","type":"parent-child","created_at":"2026-01-03T17:58:44.765494903Z","created_by":"daemon"}]}
{"id":"mala-idx6.3","title":"Inject CommandRunner into worktree.py and lint_cache.py","description":"## Problem\nThese domain modules directly import `run_command` from `src.infra.tools.command_runner`:\n- `worktree.py` (line 15): uses `run_command` for git worktree operations\n- `lint_cache.py` (line 28): uses `run_command` for git state detection\n\n## Solution\nAccept `CommandRunnerPort` via constructor/function parameter and update usages.\n\n## Files to modify\n- `src/domain/validation/worktree.py` - Accept runner in WorktreeManager or functions\n- `src/domain/validation/lint_cache.py` - Accept runner in LintCache or `_run_git_command`\n- Tests for both modules\n\n## Changes\n1. Add `command_runner: CommandRunnerPort` parameter where needed\n2. Remove direct imports from infra\n3. Update callers to pass the runner\n4. Update tests\n\n## Acceptance criteria\n- No direct imports from `src.infra.tools.command_runner` in these files\n- CommandRunner injected via constructor or parameter\n- Tests pass","notes":"Quality gate failed: Quality gate failed: Missing validation evidence for: test, typecheck, lint, format\nLog: /home/cyou/.claude/projects/-home-cyou-mala/ed78591b-dbfb-430a-ab1e-0f3f1a044647.jsonl","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T05:53:24.443767588Z","created_by":"cyou","updated_at":"2026-01-03T14:30:03.007135084Z","closed_at":"2026-01-03T14:30:03.007135084Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-idx6.3","depends_on_id":"mala-idx6.1","type":"blocks","created_at":"2026-01-03T05:54:49.428972683Z","created_by":"daemon"},{"issue_id":"mala-idx6.3","depends_on_id":"mala-idx6","type":"parent-child","created_at":"2026-01-03T17:58:44.774057152Z","created_by":"daemon"}]}
{"id":"mala-idx6.4","title":"Inject CommandRunner into coverage.py and helpers.py","description":"## Problem\nThese domain modules directly import `run_command` from `src.infra.tools.command_runner`:\n- `coverage.py` (lines 25, 545): uses `run_command` for coverage operations\n- `helpers.py` (line 12): uses `run_command` for git/fixture operations\n\n## Solution\nAccept `CommandRunnerPort` via constructor/function parameter.\n\n## Files to modify\n- `src/domain/validation/coverage.py` - Accept runner in BaselineCoverageService and functions\n- `src/domain/validation/helpers.py` - Accept runner in helper functions\n- Tests for both modules\n\n## Changes\n1. Add `command_runner: CommandRunnerPort` parameter to relevant classes/functions\n2. Remove direct imports from infra\n3. Update callers to pass the runner\n4. Update tests\n\n## Acceptance criteria\n- No direct imports from `src.infra.tools.command_runner` in these files\n- CommandRunner injected appropriately\n- Tests pass","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T05:53:30.659869989Z","created_by":"cyou","updated_at":"2026-01-03T08:22:23.545755443Z","closed_at":"2026-01-03T08:22:23.545755443Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-idx6.4","depends_on_id":"mala-idx6.1","type":"blocks","created_at":"2026-01-03T05:54:49.486391387Z","created_by":"daemon"},{"issue_id":"mala-idx6.4","depends_on_id":"mala-idx6","type":"parent-child","created_at":"2026-01-03T17:58:44.782288653Z","created_by":"daemon"}]}
{"id":"mala-idx6.5","title":"Inject CommandRunner into quality_gate.py and e2e.py","description":"## Problem\nThese domain modules directly import from `src.infra.tools.command_runner`:\n- `quality_gate.py` (line 21): uses `run_command`\n- `e2e.py` (line 25): uses `CommandRunner`\n\n## Solution\nAccept `CommandRunnerPort` via constructor/function parameter.\n\n## Files to modify\n- `src/domain/quality_gate.py` - Accept runner for commit checks\n- `src/domain/validation/e2e.py` - Accept runner in E2ERunner\n- Tests for both modules\n\n## Changes\n1. Add `command_runner: CommandRunnerPort` parameter to classes/functions\n2. Remove direct imports from infra\n3. Update callers to pass the runner\n4. Update tests\n\n## Acceptance criteria\n- No direct imports from `src.infra.tools.command_runner` in these files\n- CommandRunner injected appropriately\n- Tests pass","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T05:53:36.67842526Z","created_by":"cyou","updated_at":"2026-01-03T08:06:20.583913466Z","closed_at":"2026-01-03T08:06:20.583913466Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-idx6.5","depends_on_id":"mala-idx6.1","type":"blocks","created_at":"2026-01-03T05:54:49.543688562Z","created_by":"daemon"},{"issue_id":"mala-idx6.5","depends_on_id":"mala-idx6","type":"parent-child","created_at":"2026-01-03T17:58:44.790704643Z","created_by":"daemon"}]}
{"id":"mala-idx6.6","title":"Define LoggerPort protocol and inject into spec_executor.py","description":"## Problem\n`spec_executor.py` directly imports logging utilities from `src.infra.io.log_output.console` (line 21):\n- `Colors` - ANSI color codes\n- `log` - Console logging function\n\nThis creates a direct dependency on infra's logging implementation.\n\n## Solution\nDefine a `LoggerPort` protocol and inject it into SpecCommandExecutor.\n\n## Files to modify\n- `src/core/protocols.py` - Add `LoggerPort` protocol\n- `src/domain/validation/spec_executor.py` - Accept logger via constructor\n- Tests for spec_executor\n\n## Protocol definition\n```python\n@runtime_checkable\nclass LoggerPort(Protocol):\n    def log(\n        self,\n        message: str,\n        *,\n        level: str = \"info\",\n        color: str | None = None,\n    ) -\u003e None: ...\n```\n\n## Changes\n1. Define LoggerPort protocol in src/core/protocols.py\n2. Add `logger: LoggerPort` parameter to SpecCommandExecutor or ExecutorConfig\n3. Remove direct import of Colors/log from infra\n4. Use injected logger for all logging\n5. Update tests with mock logger\n\n## Acceptance criteria\n- No direct imports from `src.infra.io.log_output.console`\n- Logger injected via constructor\n- Tests pass","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T05:53:51.771758272Z","created_by":"cyou","updated_at":"2026-01-03T15:26:10.472263088Z","closed_at":"2026-01-03T15:26:10.472263088Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-idx6.6","depends_on_id":"mala-idx6.2","type":"blocks","created_at":"2026-01-03T05:54:58.561152669Z","created_by":"daemon"},{"issue_id":"mala-idx6.6","depends_on_id":"mala-idx6","type":"parent-child","created_at":"2026-01-03T17:58:44.798347318Z","created_by":"daemon"}]}
{"id":"mala-idx6.7","title":"Define LockManagerPort protocol and inject into coverage.py","description":"## Problem\n`coverage.py` directly imports locking utilities from `src.infra.tools.locking` (line 461):\n- `lock_path` - Get lock file path\n- `try_lock` - Non-blocking lock acquisition\n- `wait_for_lock` - Blocking lock acquisition\n\nThis creates a direct dependency on infra's locking implementation.\n\n## Solution\nDefine a `LockManagerPort` protocol and inject it into BaselineCoverageService.\n\n## Files to modify\n- `src/core/protocols.py` - Add `LockManagerPort` protocol\n- `src/domain/validation/coverage.py` - Accept lock manager via constructor\n- Tests for coverage\n\n## Protocol definition\n```python\n@runtime_checkable\nclass LockManagerPort(Protocol):\n    def lock_path(self, filepath: str | Path) -\u003e Path: ...\n    \n    def try_lock(self, filepath: str | Path) -\u003e contextlib.AbstractContextManager[bool]: ...\n    \n    def wait_for_lock(\n        self,\n        filepath: str | Path,\n        *,\n        timeout: float = 60.0,\n        poll_interval: float = 0.5,\n    ) -\u003e contextlib.AbstractContextManager[None]: ...\n```\n\n## Changes\n1. Define LockManagerPort protocol in src/core/protocols.py\n2. Add `lock_manager: LockManagerPort` parameter to BaselineCoverageService\n3. Remove direct import of locking from infra\n4. Update tests with mock lock manager\n\n## Acceptance criteria\n- No direct imports from `src.infra.tools.locking`\n- Lock manager injected via constructor\n- Tests pass","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T05:54:00.834797658Z","created_by":"cyou","updated_at":"2026-01-03T08:59:03.007820362Z","closed_at":"2026-01-03T08:59:03.007820362Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-idx6.7","depends_on_id":"mala-idx6.4","type":"blocks","created_at":"2026-01-03T05:59:00.851836257Z","created_by":"daemon"},{"issue_id":"mala-idx6.7","depends_on_id":"mala-idx6","type":"parent-child","created_at":"2026-01-03T17:58:44.805658726Z","created_by":"daemon"}]}
{"id":"mala-idx6.8","title":"Define EnvConfigPort protocol and inject into domain modules","description":"## Problem\nDomain modules directly import environment configuration from infra:\n- `spec_executor.py` (line 23): `SCRIPTS_DIR`, `get_cache_dir`\n- `spec_runner.py` (line 14): `get_cache_dir`, `get_lock_dir`\n- `coverage.py` (line 546): `get_lock_dir`\n- `e2e.py` (line 24): `_find_cerberus_bin_path`\n\nThis creates direct dependencies on infra's environment/path resolution.\n\n## Solution\nDefine an `EnvConfigPort` protocol and inject it where needed.\n\n## Files to modify\n- `src/core/protocols.py` - Add `EnvConfigPort` protocol\n- `src/domain/validation/spec_executor.py` - Accept env config\n- `src/domain/validation/spec_runner.py` - Accept env config\n- `src/domain/validation/coverage.py` - Accept env config\n- `src/domain/validation/e2e.py` - Accept env config (for cerberus path)\n\n## Protocol definition\n```python\n@runtime_checkable\nclass EnvConfigPort(Protocol):\n    @property\n    def scripts_dir(self) -\u003e Path: ...\n    \n    @property\n    def cache_dir(self) -\u003e Path: ...\n    \n    @property\n    def lock_dir(self) -\u003e Path: ...\n    \n    def find_cerberus_bin_path(self) -\u003e Path | None: ...\n```\n\n## Changes\n1. Define EnvConfigPort protocol in src/core/protocols.py\n2. Add `env_config: EnvConfigPort` parameter to relevant classes\n3. Remove direct imports from infra.tools.env and infra.io.config\n4. Update tests with mock env config\n\n## Acceptance criteria\n- No direct imports from `src.infra.tools.env` or path-related infra.io.config\n- Env config injected via constructor\n- Tests pass","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T05:54:17.571499494Z","created_by":"cyou","updated_at":"2026-01-03T07:24:13.39150778Z","closed_at":"2026-01-03T07:24:13.39150778Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-idx6.8","depends_on_id":"mala-idx6.1","type":"blocks","created_at":"2026-01-03T05:59:00.877441128Z","created_by":"daemon"},{"issue_id":"mala-idx6.8","depends_on_id":"mala-idx6","type":"parent-child","created_at":"2026-01-03T17:58:44.813134392Z","created_by":"daemon"}]}
{"id":"mala-idx6.9","title":"Refactor quality_gate.py to use injected LogProvider","description":"## Problem\n`quality_gate.py` directly imports session log parsing from infra (lines 22-26):\n- `FileSystemLogProvider` - Concrete log provider implementation\n- `JsonlEntry` - Log entry data class\n- `SessionLogParser` - Parser implementation\n\nNote: A `LogProvider` protocol already exists in `src/core/protocols.py` (TYPE_CHECKING import on line 43), but quality_gate.py still imports the concrete implementation.\n\n## Solution\nUse the existing LogProvider protocol and inject it properly.\n\n## Files to modify\n- `src/domain/quality_gate.py` - Accept LogProvider via constructor\n- Tests for quality_gate\n\n## Changes\n1. Remove direct import of `FileSystemLogProvider`, `SessionLogParser` from infra\n2. Accept `LogProvider` via constructor (protocol already exists)\n3. Move `JsonlEntry` usage to protocol-based `JsonlEntryProtocol` (already defined)\n4. Update callers to inject the log provider\n5. Update tests with mock log provider\n\n## Acceptance criteria\n- No direct imports from `src.infra.io.session_log_parser`\n- LogProvider injected via constructor\n- Uses existing protocols from src/core/protocols.py\n- Tests pass","notes":"Quality gate failed: Quality gate failed: Missing validation evidence for: test, typecheck, lint, format\nLog: /home/cyou/.claude/projects/-home-cyou-mala/b484adbd-a224-4f41-8df5-b058d0ae0457.jsonl","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T05:54:26.548076209Z","created_by":"cyou","updated_at":"2026-01-03T14:49:00.361078988Z","closed_at":"2026-01-03T14:49:00.361078988Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-idx6.9","depends_on_id":"mala-idx6.5","type":"blocks","created_at":"2026-01-03T05:59:00.867330195Z","created_by":"daemon"},{"issue_id":"mala-idx6.9","depends_on_id":"mala-idx6","type":"parent-child","created_at":"2026-01-03T17:58:44.821502962Z","created_by":"daemon"}]}
{"id":"mala-ifkl","title":"Add contract tests for remaining fakes","description":"## Problem\\nOnly FakeIssueProvider and FakeEventSink have contract tests. Other fakes (FakeCommandRunner, FakeLockManager, FakeEpicVerificationModel, FakeGateChecker, FakeSDKClient) lack explicit fake-vs-real parity checks, so protocol drift can go unnoticed.\\n\\n## Scope\\n- Add contract tests for each fake to verify protocol completeness and behavior parity with real implementations (where practical).\\n- For fakes that wrap SDKs or external processes, add at least protocol-completeness tests and behavior tests against a minimal real stub or documented assumptions.\\n- Ensure markers align with unit vs integration (real providers in integration).\\n\\n## Acceptance Criteria\\n- New tests in tests/contracts/ cover FakeCommandRunner, FakeLockManager, FakeEpicVerificationModel, FakeGateChecker, FakeSDKClient.\\n- Each test either checks protocol method completeness or parity with the real implementation where feasible.\\n- Clear skip conditions for external dependencies (if any).\\n\\n## Files\\n- tests/contracts/ (new tests)\\n- tests/fakes/ (if missing helper APIs for assertions)\\n\\n## Verification\\n- uv run pytest tests/contracts/ -v\\n- uvx ty check","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-05T14:18:37.984705635Z","created_by":"cyou","updated_at":"2026-01-05T16:28:25.065781516Z","closed_at":"2026-01-05T16:28:25.065781516Z","close_reason":"Closed"}
{"id":"mala-ih0","title":"Use codex for code review","description":"Use Codex for code review: add an optional post-implementation review pass and surface findings before commit/close.","status":"closed","priority":4,"issue_type":"epic","created_at":"2025-12-26T00:55:42.345522647Z","updated_at":"2025-12-27T06:45:00.821373421Z","closed_at":"2025-12-27T06:45:00.821373421Z","close_reason":"Already implemented: Codex review exists in the validation pipeline"}
{"id":"mala-ih94","title":"Trigger queue uses failed_count instead of pass/fail outcome","description":"In src/pipeline/epic_verification_coordinator.py check_epic_closure(), the epic_completion trigger is only queued on failure when `verification_result.failed_count \u003e 0`.\n\nThis is incorrect - if verify_epic() returns a non-passing outcome but reports failed_count == 0 (e.g., no verdict entries, no counted failures), fire_on=failure and fire_on=both will not queue.\n\n## Fix\nTreat 'success' as passed_count \u003e 0 (as already done) and treat 'failure' as the else branch where verification completed but did not pass, without gating on failed_count.\n\n## Context\nBlocked mala-smaj.10 from passing review (lines L195-219).","status":"closed","priority":1,"issue_type":"bug","created_at":"2026-01-08T21:02:38.629698298Z","created_by":"cyou","updated_at":"2026-01-08T21:07:29.319176085Z","closed_at":"2026-01-08T21:07:29.319176085Z","close_reason":"Already fixed in commit 7bcb7b6 (post-review fix, just not re-reviewed)"}
{"id":"mala-ii08","title":"Wire code_review.cerberus config from mala.yaml to DefaultReviewer","description":"CerberusConfig exists in ValidationConfig (spawn_args, wait_args, env, timeout) but DefaultReviewer uses mala_config.cerberus_* from env vars. Should extract cerberus settings from code_review.cerberus and pass to DefaultReviewer, falling back to mala_config for backwards compat.","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-10T21:22:56.362817671Z","created_by":"cyou","updated_at":"2026-01-10T22:23:29.293255675Z","closed_at":"2026-01-10T22:23:29.293255675Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-ii08","depends_on_id":"mala-f30x","type":"blocks","created_at":"2026-01-10T21:26:13.967527296Z","created_by":"cyou"}]}
{"id":"mala-ilt","title":"Non-verbose mode: single line per tool call","description":"Add a non-verbose mode (--quiet or --verbose=false) that prints only one line per tool call for cleaner output:\n\n## Tool Output Formats\n\n| Tool | Output |\n|------|--------|\n| Read | file_path |\n| Edit | file_path |\n| Write | file_path |\n| Bash | description field |\n| Other | truncated args dict |\n\n## Implementation\n\n- Add --verbose / --quiet CLI flag (default: verbose)\n- Pass verbosity setting to logging helpers\n- Update log_tool to format output based on verbosity level\n- Keep existing verbose output as default for backwards compatibility\n\n## Acceptance Criteria\n\n- [ ] CLI accepts --verbose / --quiet flag\n- [ ] Non-verbose mode shows single line per tool call\n- [ ] File tools (Read/Edit/Write) show only file_path\n- [ ] Bash shows description field\n- [ ] Other tools show truncated args dict\n- [ ] Verbose mode (default) unchanged\n- [ ] Tests cover both modes","status":"closed","priority":3,"issue_type":"task","created_at":"2025-12-26T23:17:38.426214113Z","updated_at":"2025-12-27T09:02:44.015665617Z","closed_at":"2025-12-27T09:02:44.015665617Z","close_reason":"Closed"}
{"id":"mala-iml","title":"Widen quality gate commit window","description":"Context:\n- Quality gate only searches commits from the last 24 hours; long-running work gets flagged as failed even when commits exist.\n- Needs verification: confirm intended commit search window policy.\n\nScope:\n- In: widen or parameterize the commit search window and update related tests.\n- Out: changing validation command requirements or gate semantics beyond the commit window.\n\nAcceptance Criteria:\n- A commit older than 1 day with bd-\u003cissue_id\u003e satisfies the gate.\n- Failure message reflects the new window if no commit is found.\n\nTest Plan:\n- TDD: add a failing test for an older commit, implement the change, then run quality gate tests in tests/test_orchestrator.py.\n\nNotes for Agent:\n- Keep the log parsing behavior unchanged.","status":"closed","priority":2,"issue_type":"bug","created_at":"2025-12-26T17:35:48.828509883Z","updated_at":"2025-12-26T18:47:04.234602018Z","closed_at":"2025-12-26T18:47:04.234602018Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-iml","depends_on_id":"mala-jnq","type":"parent-child","created_at":"2025-12-26T17:36:15.712687221Z","created_by":"daemon"}]}
{"id":"mala-in2v","title":"[Review] src/orchestration/orchestrator.py:539-544: [P2] Skip finalization but mark completed when run_metadata is None","description":"## Review Finding\n\nThis issue was auto-created from a P2 review finding.\n\n**Source issue:** mala-nsjx\n**Reviewer:** claude\n**Location:** src/orchestration/orchestrator.py:539-544\n\n## Details\n\nWhen `run_metadata is None`, the code skips `_finalize_issue_result` (which closes the issue via `beads.close_async` and emits events) but still calls `mark_completed`. This leaves remediation issues in an inconsistent state: the coordinator considers them complete, but they're never actually closed. While the current call site always passes `run_metadata`, the function signature suggests `None` is valid, and a future caller could trigger this inconsistency. Consider either always finalizing (perhaps with a dummy metadata) or documenting that `run_metadata=None` means \"skip finalization entirely\".","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-02T02:06:16.018011421Z","created_by":"cyou","updated_at":"2026-01-02T02:14:40.812266823Z","closed_at":"2026-01-02T02:14:40.812266823Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_finding:4cf5a39e065a","source:mala-nsjx"]}
{"id":"mala-iniy","title":"[Review] 3 non-blocking findings from mala-c1ag","description":"## Review Findings\n\nThis issue consolidates 3 non-blocking findings from code review.\n\n**Source issue:** mala-c1ag\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Remove or document remaining MagicMock CommandRunner in quality gate tests\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** tests/unit/domain/test_quality_gate.py:42-46\n\n`tests/unit/domain/test_quality_gate.py` still builds a `CommandRunnerPort` via an un-specced `MagicMock`, which is now outside the documented mock exceptions and undermines the “fakes over mocks” rule.\n\nExample:\n`def make_mock_command_runner(result: CommandResult) -\u003e MagicMock:`\n`    mock_runner = MagicMock()`\n\nEither migrate `make_mock_command_runner(...)` call sites to `FakeCommandRunner(responses={...})` (preferred), or add back a targeted exception entry in `tests/README.md` that covers this specific pattern.\n\n---\n\n### Finding 2: [P2] Incomplete migration to FakeCommandRunner in test_quality_gate.py\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** tests/unit/domain/test_quality_gate.py:38-43\n\nThe migration from `MagicMock` to `FakeCommandRunner` in `tests/unit/domain/test_quality_gate.py` is incomplete. The helper function `make_mock_command_runner` still returns a `MagicMock`, and several tests (e.g., `test_no_change_skips_commit_check`, `test_missing_commit_message_is_clear`) continue to use it. This defeats the purpose of removing the `MagicMock` exception from `tests/README.md` and results in type hint inconsistencies where tests request a `FakeCommandRunner` fixture but use a local `MagicMock` instead.\n\n---\n\n### Finding 3: [P2] Redundant QualityGate initialization in test_missing_commit_message_is_clear\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** tests/unit/domain/test_quality_gate.py:1402-1445\n\nIn `test_missing_commit_message_is_clear`, the `gate` object is initialized twice: first using the `mock_command_runner` fixture, and then immediately overwritten by a new `QualityGate` using a `MagicMock` from `make_mock_command_runner`. The first initialization is redundant, and the second should use the fake runner to comply with the project's 'fakes over mocks' policy.\n\n---\n\n\u003c!-- fp:35fab7c844597176 --\u003e\n\u003c!-- fp:3e6d0b7d4e13ff07 --\u003e\n\u003c!-- fp:bdedad6fe728f1c0 --\u003e\n\u003c!-- review_finding:f5f4f2d001ee --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-05T17:47:35.643142453Z","created_by":"cyou","updated_at":"2026-01-05T17:58:28.728750763Z","closed_at":"2026-01-05T17:58:28.728750763Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-c1ag"]}
{"id":"mala-iy6l","title":"Epic: Import-linter package restructure (3-phase plan)","description":"Addresses long-term import-linter maintainability with package restructure.\n\n## Context\n\n- Current import-linter config requires manually updating 15+ contracts when adding modules\n- Plan approved (Revision 4) after review\n- Builds on mala-bp3h (immediate contract fixes) with future-proofing work\n- **Decision**: Generator script not needed; contracts updated manually per migration\n\n## Critical Constraint\n\n**NO SHIMS OR RE-EXPORTS**: All migrations must be pure refactors. When moving/renaming modules, update all imports directly. Never create re-export shims or backward-compatibility modules.\n\n## Phases\n\n- ~~**Phase 1**: Generator script~~ (Cancelled - manual updates sufficient)\n- **Phase 2**: Gradual package migration (src/core, src/infra, src/domain, src/orchestration, src/cli)\n- **Phase 3**: Simplify from 15 contracts to 10 cleaner contracts\n\n## Spec\n\nSee: `docs/import-linter-restructure-proposal.md` (Revision 4 - No Shims)\n\n## Scope\n\n**In:**\n- Phase 2a-e: Package migrations + manual contract updates\n- Phase 3: Simplified contracts\n\n**Out:**\n- Generator script (not needed)\n- Backward-compatibility shims (must update imports directly)\n- Behavior changes\n\n## Acceptance Criteria\n\n- [ ] All packages migrated with imports updated directly (no shims)\n- [ ] Contracts updated for each migration\n- [ ] Final state: 10 cleaner contracts passing\n- [ ] All tests pass throughout\n\n## P2 Review Notes (to address in Phase 3)\n\n- Hooks contract should include src.core.models in forbidden list\n- SDK contract should include client wrappers in forbidden list\n- Leaf modules: clarify stdlib-only vs no-src-imports constraint","status":"closed","priority":2,"issue_type":"epic","created_at":"2025-12-31T22:01:35.965628112Z","created_by":"cyou","updated_at":"2026-01-01T20:51:49.869784692Z","closed_at":"2026-01-01T20:51:49.869784692Z","close_reason":"Closed","labels":["architecture","import-linter refactor"]}
{"id":"mala-iy6l.1","title":"Create import-linter contract generator script","description":"Create scripts/generate_import_contracts.py to automate contract generation.\n\n## Context\n\n- Adding new modules currently requires updating ~5-8 contracts manually\n- Generator script will define module→layer mapping in one place\n- Must handle special constraints (hooks, telemetry, validation.result)\n- Plan reference: Task 1.1 in docs/import-linter-restructure-proposal.md\n\n## Scope\n\n**In:**\n- Create `scripts/generate_import_contracts.py` with LAYER_MAPPING, SPECIAL_CONSTRAINTS, LEAF_MODULES, INDEPENDENCE_CONTRACTS\n- Add script entry to pyproject.toml [project.scripts]\n- Create `scripts/` directory at repo root (NOT src/tools/)\n\n**Out:**\n- CI integration (separate task)\n- Package migrations\n\n## Implementation\n\n```python\nLAYER_MAPPING = {\n    \"cli\": [\"cli\", \"main\"],\n    \"orchestration\": [\"orchestrator\", \"orchestrator_factory\", \"orchestrator_types\", \"cli_support\"],\n    \"pipeline\": [\"pipeline\"],\n    \"domain\": [\"lifecycle\", \"quality_gate\", \"validation\", \"prompts\"],\n    \"infra\": [\n        \"anthropic_client\", \"beads_client\", \"braintrust_integration\", \"cerberus_review\",\n        \"config\", \"event_sink\", \"git_utils\", \"log_output\", \"mcp\",\n        \"session_log_parser\", \"telemetry\", \"tools\", \"epic_verifier\", \"issue_manager\",\n    ],\n    \"core\": [\"models\", \"protocols\", \"log_events\"],\n}\n\nSPECIAL_CONSTRAINTS = {\n    \"hooks\": {\"layer\": \"infra\", \"additional_forbidden\": [\"pipeline\", \"domain\", \"orchestration\"]},\n    \"telemetry\": {\"layer\": \"infra\", \"additional_forbidden\": [\"braintrust_integration\", \"braintrust\"]},\n    \"validation.result\": {\"layer\": \"domain\", \"additional_forbidden\": [\"tools\", \"config\", \"log_output\"]},\n}\n\nLEAF_MODULES = [\"models\", \"protocols\", \"log_events\"]\n\nINDEPENDENCE_CONTRACTS = {\n    \"Pipeline modules acyclic\": [\n        \"src.pipeline.agent_session_runner\",\n        \"src.pipeline.gate_runner\",\n        \"src.pipeline.review_runner\",\n        \"src.pipeline.run_coordinator\",\n    ],\n}\n```\n\n## Acceptance Criteria\n\n- [ ] `scripts/generate_import_contracts.py` generates TOML output matching current 15 contracts\n- [ ] Script can be run standalone: `uv run python scripts/generate_import_contracts.py`\n- [ ] Generated output, when applied, passes `uv run import-linter`\n- [ ] Script handles all current special cases\n\n## Test Plan\n\n1. Run generator, diff output against pyproject.toml contracts section (lines 128-584)\n2. Replace contracts section with generated output, run import-linter (should pass)\n3. Add a test module name to LAYER_MAPPING, verify contract output changes\n\n## Notes\n\n- P2 review feedback: plan's verification diff command is wrong (compares same output twice)\n- Correct approach: diff generated TOML against committed contracts section","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-31T22:01:53.324428964Z","created_by":"cyou","updated_at":"2025-12-31T22:06:14.043370866Z","closed_at":"2025-12-31T22:06:14.043375146Z","labels":["import-linter refactor"],"dependencies":[{"issue_id":"mala-iy6l.1","depends_on_id":"mala-iy6l","type":"parent-child","created_at":"2025-12-31T22:01:53.334145076Z","created_by":"daemon"}]}
{"id":"mala-iy6l.2","title":"Add CI validation for contract generator","description":"Add CI workflow to validate generator output matches committed contracts.\n\n## Context\n\n- Generator script exists but needs enforcement\n- Plan recommends CI validation over pre-commit for visibility\n- CI job should fail if generated contracts differ from committed version\n\n## Scope\n\n**In:**\n- Add CI workflow at `.github/workflows/validate-contracts.yml`\n- Workflow runs generator and diffs against pyproject.toml\n\n**Out:**\n- Pre-commit hook (alternative not chosen)\n- Generator script changes\n\n## Implementation\n\nWorkflow should:\n1. Run `uv run python scripts/generate_import_contracts.py \u003e /tmp/generated.toml`\n2. Extract contracts section from pyproject.toml\n3. Diff generated vs committed\n4. Fail with clear output if different\n\n## Acceptance Criteria\n\n- [ ] CI workflow exists at `.github/workflows/validate-contracts.yml`\n- [ ] Workflow runs on PR and push to main\n- [ ] Workflow fails if generated output differs from pyproject.toml contracts section\n- [ ] Workflow provides clear diff output on failure\n\n## Test Plan\n\n1. Create PR with intentionally wrong contract, verify CI fails\n2. Create PR with correct contracts, verify CI passes","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-31T22:02:07.948328124Z","created_by":"cyou","updated_at":"2025-12-31T22:06:14.060873309Z","closed_at":"2025-12-31T22:06:14.060877029Z","labels":["import-linter refactor"],"dependencies":[{"issue_id":"mala-iy6l.2","depends_on_id":"mala-iy6l","type":"parent-child","created_at":"2025-12-31T22:02:07.957899936Z","created_by":"daemon"},{"issue_id":"mala-iy6l.2","depends_on_id":"mala-iy6l.1","type":"blocks","created_at":"2025-12-31T22:03:40.171761845Z","created_by":"daemon"}]}
{"id":"mala-iy6l.3","title":"Create src/core/ package with models, protocols, log_events","description":"First package migration: move leaf modules to src/core/.\n\n## Context\n\n- First step in Phase 2 package migration\n- Move 3 leaf modules to src/core/ package\n- Create shims at old paths for backward compatibility\n- Update contracts in pyproject.toml for new paths\n\n## Scope\n\n**In:**\n- Create src/core/ directory with __init__.py\n- Move models.py, protocols.py, log_events.py to src/core/\n- Create shims at old paths with explicit re-exports\n- Update pyproject.toml contracts to include new paths\n\n**Out:**\n- src/infra, src/domain, src/orchestration, src/cli packages\n\n## Primary Files\n\n- src/core/__init__.py (new)\n- src/core/models.py (moved)\n- src/core/protocols.py (moved)\n- src/core/log_events.py (moved)\n- src/models.py (becomes shim)\n- src/protocols.py (becomes shim)\n- src/log_events.py (becomes shim)\n- pyproject.toml (update contracts)\n\n## Shim Pattern\n\n```python\n# src/models.py (shim)\nfrom src.core.models import (\n    Model1,\n    Model2,\n    ValidationResult,\n    # ... all public exports\n)\n\n__all__ = [\"Model1\", \"Model2\", \"ValidationResult\", ...]\n```\n\n## Contract Updates\n\nUpdate contracts 9-11 (leaf modules) to reference both old and new paths:\n- src.models AND src.core.models\n- src.protocols AND src.core.protocols  \n- src.log_events AND src.core.log_events\n\n## Acceptance Criteria\n\n- [ ] src/core/ package exists with all 3 modules\n- [ ] Old import paths work: `from src.models import X`\n- [ ] New import paths work: `from src.core.models import X`\n- [ ] `uv run import-linter` passes\n- [ ] `uv run pytest -m \"unit or integration\"` passes\n- [ ] `uvx ruff check .` passes\n- [ ] `uv run mala --help` works\n\n## Test Plan\n\n1. Verify old imports: `python -c \"from src.models import *\"`\n2. Verify new imports: `python -c \"from src.core.models import *\"`\n3. Run full test suite\n4. Run import-linter\n\n## Notes\n\n- Use explicit re-exports with __all__, not just star imports\n- Update pyproject.toml contracts to recognize both old and new paths","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-31T22:02:23.228856201Z","created_by":"cyou","updated_at":"2026-01-01T05:52:33.024814188Z","closed_at":"2026-01-01T05:52:33.024814188Z","close_reason":"Closed","labels":["import-linter refactor","package-migration"],"dependencies":[{"issue_id":"mala-iy6l.3","depends_on_id":"mala-iy6l","type":"parent-child","created_at":"2025-12-31T22:02:23.238157925Z","created_by":"daemon"}]}
{"id":"mala-iy6l.4","title":"Create src/infra/ package with clients, io, tools, hooks","description":"Largest migration: move infrastructure modules to src/infra/.\n\n## Context\n\n- Largest migration: 15+ modules moving to src/infra/\n- Includes moving existing packages: tools/, log_output/, hooks/\n- Package shims require __path__ manipulation for sub-module imports\n- Must preserve imports like `from src.tools.locking import FileLock`\n\n## Scope\n\n**In:**\n- Create src/infra/ with clients/, io/ subdirectories\n- Move tools/, log_output/, hooks/ packages with package shims\n- Move standalone infra modules with module shims\n- Update pyproject.toml contracts for new paths\n\n**Out:**\n- Other packages (domain, orchestration, cli)\n\n## Directory Structure\n\n\\`\\`\\`\nsrc/infra/\n├── __init__.py\n├── clients/\n│   ├── __init__.py\n│   ├── anthropic_client.py\n│   ├── beads_client.py\n│   ├── braintrust_integration.py\n│   └── cerberus_review.py\n├── io/\n│   ├── __init__.py\n│   ├── config.py\n│   ├── event_sink.py\n│   ├── log_output/  (moved package)\n│   └── session_log_parser.py\n├── tools/  (moved package)\n├── hooks/  (moved package)\n├── git_utils.py\n├── mcp.py\n├── telemetry.py\n├── epic_verifier.py\n└── issue_manager.py\n\\`\\`\\`\n\n## Package Shim Pattern\n\n\\`\\`\\`python\n# src/tools/__init__.py (shim)\nimport sys\nfrom src.infra.tools import *  # noqa: F401,F403\n\n# Enable sub-module imports: from src.tools.locking import ...\nfrom src.infra import tools as _new_tools\nsys.modules[__name__].__path__ = _new_tools.__path__  # type: ignore[attr-defined]\n\\`\\`\\`\n\n## Acceptance Criteria\n\n- [ ] Directory structure matches plan\n- [ ] All old import paths work via shims\n- [ ] Sub-module imports work: \\`from src.tools.locking import FileLock\\`\n- [ ] \\`uv run import-linter\\` passes (contracts updated)\n- [ ] All tests pass\n\n## Test Plan\n\n1. Test each shim: old path imports still work\n2. Test sub-module imports specifically for tools, log_output, hooks\n3. Run full test suite\n4. Run import-linter\n\n## Notes\n\n- This is the riskiest migration due to package moves\n- Package shims need sys.modules[__name__].__path__ manipulation\n- Update pyproject.toml contracts manually to include both old and new paths","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-31T22:02:43.204743805Z","created_by":"cyou","updated_at":"2026-01-01T06:35:03.738971856Z","closed_at":"2026-01-01T06:35:03.738971856Z","close_reason":"Closed","labels":["import-linter refactor","package-migration"],"dependencies":[{"issue_id":"mala-iy6l.4","depends_on_id":"mala-iy6l","type":"parent-child","created_at":"2025-12-31T22:02:43.216868411Z","created_by":"daemon"},{"issue_id":"mala-iy6l.4","depends_on_id":"mala-iy6l.3","type":"blocks","created_at":"2025-12-31T22:03:40.19424207Z","created_by":"daemon"}]}
{"id":"mala-iy6l.5","title":"Create src/domain/ package with validation, lifecycle, quality_gate, prompts","description":"Move domain modules to src/domain/.\n\n## Context\n\n- Move domain modules to src/domain/\n- validation/ is a package (needs package shim with __path__)\n- prompts.py is a single file (module shim)\n- Note: src/prompts/ directory has markdown templates, separate from prompts.py module\n\n## Scope\n\n**In:**\n- Create src/domain/ directory\n- Move validation/ package with package shim\n- Move lifecycle.py, quality_gate.py, prompts.py with module shims\n- Update pyproject.toml contracts for new paths\n\n**Out:**\n- Other packages (orchestration, cli)\n\n## Directory Structure\n\n\\`\\`\\`\nsrc/domain/\n├── __init__.py\n├── lifecycle.py\n├── quality_gate.py\n├── prompts.py\n└── validation/  (moved package)\n\\`\\`\\`\n\n## Acceptance Criteria\n\n- [ ] All domain modules accessible via both old and new paths\n- [ ] Validation sub-modules work: \\`from src.validation.result import ValidationResult\\`\n- [ ] All tests pass\n- [ ] Import-linter passes (contracts updated)\n\n## Test Plan\n\n1. Test validation sub-module imports\n2. Test all shims\n3. Run full test suite\n4. Run import-linter\n\n## Notes\n\n- prompts.py is a Python file; src/prompts/ is a separate markdown template directory\n- validation/ needs package shim with __path__ manipulation\n- Update pyproject.toml contracts manually","notes":"Quality gate failed: Timeout after 60 minutes\nLog: /home/cyou/.claude/projects/-home-cyou-mala/f161554a-08d4-4534-9f9b-f4ab741c541e.jsonl","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-31T22:02:54.601316286Z","created_by":"cyou","updated_at":"2026-01-01T15:27:37.696747124Z","closed_at":"2026-01-01T15:27:37.696747124Z","close_reason":"Closed","labels":["import-linter refactor","needs-followup","package-migration"],"dependencies":[{"issue_id":"mala-iy6l.5","depends_on_id":"mala-iy6l","type":"parent-child","created_at":"2025-12-31T22:02:54.608001106Z","created_by":"daemon"},{"issue_id":"mala-iy6l.5","depends_on_id":"mala-iy6l.4","type":"blocks","created_at":"2025-12-31T22:03:40.201196518Z","created_by":"daemon"}]}
{"id":"mala-iy6l.6","title":"Create src/orchestration/ package","description":"Move orchestration modules to src/orchestration/.\n\n## Critical Constraint\n\n**NO SHIMS OR RE-EXPORTS**: This is a pure refactor. When moving modules, update all imports directly throughout the codebase. Never create re-export shims or backward-compatibility modules.\n\n## Context\n\n- Move orchestration modules: orchestrator.py, orchestrator_factory.py, orchestrator_types.py, cli_support.py\n- All are single files, move and update all imports\n- Rename to cleaner names in new location\n\n## Scope\n\n**In:**\n- Create src/orchestration/ directory\n- Move 4 modules\n- Update ALL imports throughout codebase to use new paths\n- Update pyproject.toml contracts for new paths\n\n**Out:**\n- Backward-compatibility shims (forbidden)\n- Other packages (cli)\n\n## Directory Structure\n\n```\nsrc/orchestration/\n├── __init__.py\n├── orchestrator.py\n├── factory.py      # from orchestrator_factory.py\n├── types.py        # from orchestrator_types.py\n└── cli_support.py\n```\n\n## File Mapping\n\n- orchestrator.py → src/orchestration/orchestrator.py\n- orchestrator_factory.py → src/orchestration/factory.py\n- orchestrator_types.py → src/orchestration/types.py\n- cli_support.py → src/orchestration/cli_support.py\n\n## Acceptance Criteria\n\n- [ ] Modules moved to new locations\n- [ ] ALL imports updated to new paths (no old imports remain)\n- [ ] No re-export shims created\n- [ ] All tests pass\n- [ ] Import-linter passes (contracts updated)\n\n## Test Plan\n\n1. Move files\n2. Update all imports throughout codebase\n3. Run full test suite\n4. Run import-linter","notes":"Quality gate failed: Quality gate failed: Missing validation evidence for: ruff check, pytest, ruff format, ty check\nLog: /home/cyou/.claude/projects/-home-cyou-mala/bb03c7b1-5f44-479b-87da-8be719812077.jsonl","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-31T22:03:04.832165756Z","created_by":"cyou","updated_at":"2026-01-01T18:26:44.210895969Z","closed_at":"2026-01-01T18:16:50.484650969Z","close_reason":"Closed","labels":["import-linter refactor","needs-followup","package-migration"],"dependencies":[{"issue_id":"mala-iy6l.6","depends_on_id":"mala-iy6l","type":"parent-child","created_at":"2025-12-31T22:03:04.842002757Z","created_by":"daemon"},{"issue_id":"mala-iy6l.6","depends_on_id":"mala-iy6l.5","type":"blocks","created_at":"2025-12-31T22:03:40.208229365Z","created_by":"daemon"}]}
{"id":"mala-iy6l.7","title":"Create src/cli/ package","description":"Final package migration: move CLI modules to src/cli/.\n\n## Critical Constraint\n\n**NO SHIMS OR RE-EXPORTS**: This is a pure refactor. When moving modules, update all imports directly throughout the codebase. Never create re-export shims or backward-compatibility modules.\n\n## Context\n\n- Final package migration: cli.py, main.py\n- Must ensure pyproject.toml entry point still works\n\n## Scope\n\n**In:**\n- Create src/cli/ directory\n- Move cli.py, main.py\n- Update ALL imports throughout codebase to use new paths\n- Update pyproject.toml contracts for new paths\n\n**Out:**\n- Backward-compatibility shims (forbidden)\n\n## Directory Structure\n\n```\nsrc/cli/\n├── __init__.py\n├── main.py\n└── cli.py\n```\n\n## Acceptance Criteria\n\n- [ ] Modules moved to new locations\n- [ ] ALL imports updated to new paths (no old imports remain)\n- [ ] No re-export shims created\n- [ ] `uv run mala` still works (entry point)\n- [ ] All tests pass\n- [ ] Import-linter passes (contracts updated)\n\n## Test Plan\n\n1. Move files\n2. Update all imports throughout codebase\n3. Test CLI entry point: `uv run mala --help`\n4. Run full test suite\n5. Run import-linter\n\n## Notes\n\n- Verify pyproject.toml [project.scripts] entry point still works after migration\n- Update contracts manually in pyproject.toml","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-31T22:03:14.157049026Z","created_by":"cyou","updated_at":"2026-01-01T18:41:32.046128295Z","closed_at":"2026-01-01T18:41:32.046128295Z","close_reason":"Closed","labels":["import-linter refactor","package-migration"],"dependencies":[{"issue_id":"mala-iy6l.7","depends_on_id":"mala-iy6l","type":"parent-child","created_at":"2025-12-31T22:03:14.178547336Z","created_by":"daemon"},{"issue_id":"mala-iy6l.7","depends_on_id":"mala-iy6l.6","type":"blocks","created_at":"2025-12-31T22:03:40.215084774Z","created_by":"daemon"}]}
{"id":"mala-iy6l.8","title":"Implement simplified import-linter contracts (Phase 3)","description":"Replace 15 complex contracts with 10 cleaner ones.\n\n## Critical Constraint\n\n**NO SHIMS OR RE-EXPORTS**: This is a pure refactor. When reorganizing contracts or addressing any module references, update directly. Never create re-export shims or backward-compatibility modules.\n\n## Context\n\n- Final phase: replace 15 contracts with 10 cleaner ones\n- Uses layers contract type for main architecture\n- Preserves special constraints: hooks isolation, telemetry purity, validation.result, leaf modules\n\n## Scope\n\n**In:**\n- Replace current contracts in pyproject.toml with simplified version\n- Address P2 review feedback items\n\n**Out:**\n- Backward-compatibility shims (forbidden)\n- Shim removal (should be none to remove)\n\n## P2 Review Items to Address\n\n1. **Hooks contract**: Add src.core.models to forbidden list (current Contract 7 forbids it)\n2. **SDK contract**: Add src.infra.clients.* to forbidden list (current Contract 5 forbids client wrappers)\n3. **Leaf modules**: Document that constraint is no-src-imports, not stdlib-only\n\n## New Contracts (10 total)\n\n1. Layered Architecture (layers type)\n2. SDK confined to infra\n3. Only CLI imports typer\n4. Pipeline modules acyclic\n5. Hooks isolated\n6. Telemetry abstraction pure\n7. Validation result minimal\n8. Models is leaf\n9. Protocols is leaf\n10. Log events is leaf\n\nSee plan Task 3.1 in docs/import-linter-restructure-proposal.md for exact TOML definitions.\n\n## Acceptance Criteria\n\n- [ ] pyproject.toml has exactly 10 contracts as specified\n- [ ] No re-export shims exist in codebase\n- [ ] All contracts pass: `uv run import-linter`\n- [ ] All tests pass\n- [ ] Review feedback addressed in contract definitions\n\n## Test Plan\n\n1. Run import-linter with new contracts\n2. Run full test suite\n3. Verify all preserved constraints are still enforced","notes":"Quality gate failed: Quality gate failed: Missing validation evidence for: ruff check, pytest, ruff format, ty check\nLog: /home/cyou/.claude/projects/-home-cyou-mala/bf08c1ca-a65d-4a9d-8558-bcea79c6bd00.jsonl","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-31T22:03:31.718837326Z","created_by":"cyou","updated_at":"2026-01-01T18:50:06.624614071Z","closed_at":"2026-01-01T18:47:30.097302472Z","close_reason":"Closed","labels":["import-linter refactor","needs-followup"],"dependencies":[{"issue_id":"mala-iy6l.8","depends_on_id":"mala-iy6l","type":"parent-child","created_at":"2025-12-31T22:03:31.728513748Z","created_by":"daemon"},{"issue_id":"mala-iy6l.8","depends_on_id":"mala-iy6l.7","type":"blocks","created_at":"2025-12-31T22:03:40.221904062Z","created_by":"daemon"}]}
{"id":"mala-iy6l.9","title":"[Remediation] All packages migrated with imports updated directly (no shim...","description":"## Context\nThis issue was auto-created by epic verification for epic `mala-iy6l`.\n\n## Epic Description / Acceptance Criteria\nTitle: Epic: Import-linter package restructure (3-phase plan)\n\nAddresses long-term import-linter maintainability with package restructure.\n\n## Context\n\n- Current import-linter config requires manually updating 15+ contracts when adding modules\n- Plan approved (Revision 4) after review\n- Builds on mala-bp3h (immediate contract fixes) with future-proofing work\n- **Decision**: Generator script not needed; contracts updated manually per migration\n\n## Critical Constraint\n\n**NO SHIMS OR RE-EXPORTS**: All migrations must be pure refactors. When moving/renaming modules, update all imports directly. Never create re-export shims or backward-compatibility modules.\n\n## Phases\n\n- ~~**Phase 1**: Generator script~~ (Cancelled - manual updates sufficient)\n- **Phase 2**: Gradual package migration (src/core, src/infra, src/domain, src/orchestration, src/cli)\n- **Phase 3**: Simplify from 15 contracts to 10 cleaner contracts\n\n## Spec\n\nSee: `docs/import-linter-restructure-proposal.md` (Revision 4 - No Shims)\n\n## Scope\n\n**In:**\n- Phase 2a-e: Package migrations + manual contract updates\n- Phase 3: Simplified contracts\n\n**Out:**\n- Generator script (not needed)\n- Backward-compatibility shims (must update imports directly)\n- Behavior changes\n\n## Acceptance Criteria\n\n- [ ] All packages migrated with imports updated directly (no shims)\n- [ ] Contracts updated for each migration\n- [ ] Final state: 10 cleaner contracts passing\n- [ ] All tests pass throughout\n\n## P2 Review Notes (to address in Phase 3)\n\n- Hooks contract should include src.core.models in forbidden list\n- SDK contract should include client wrappers in forbidden list\n- Leaf modules: clarify stdlib-only vs no-src-imports constraint\n\n## Commit Scope\n- Range hint: 4e0c3d3958dcfc6fe5b1341d71a0b2bd6f948baf^..bb7a40c57b258176f7354ce933cfbb3a0c20317c\n- Commits:\n- 4e0c3d3958dcfc6fe5b1341d71a0b2bd6f948baf bd-mala-iy6l.3: Create src/core/ package with models, protocols, log_events\n- b4e8b3071b091e3bddf3646c4a6e2ed3cf5dd9ef bd-mala-iy6l.5: Create src/domain/ package __init__.py\n- 6b50079962521f7668dab18dffd275cb49971fa5 bd-mala-iy6l.5: Create shims and update contracts for src/domain/ migration\n- fef27796b613bf87848b121fd228b2aaf298610a bd-mala-iy6l.4: Address final review findings\n- f965433ad4ce8d9496cb4824520a77b73d7ffee8 bd-mala-iy6l.4: Fix review issues from external reviewers\n- 7eeb9cf101f3b17d93dd1ceacb06b724f33f6dc4 bd-mala-iy6l.4: Create src/infra/ package with clients/, io/, tools/, hooks/\n- 560ad04fed162117b1eb5dd6ba339e2672dfb7bd bd-mala-iy6l.7: Create src/cli/ package\n- bb7a40c57b258176f7354ce933cfbb3a0c20317c bd-mala-iy6l.8: Simplify import-linter to 10 contracts\n- e748c9afd3597aea091204f71606db2c1627f3f5 bd-mala-iy6l.6: Fix code formatting across codebase\n- aed236d440a7917b7b433342bf38bf299d4b6ea1 bd-mala-iy6l.6: Replace shim imports with direct paths in orchestration modules\n- 2e6b5ef1dafb2ed3f82dda977844e6964f1fbe78 bd-mala-iy6l.6: Migrate orchestration modules to src/orchestration/ package\n\n## Child Issues\n- mala-iy6l.1\n- mala-iy6l.2\n- mala-iy6l.3\n- mala-iy6l.4\n- mala-iy6l.5\n- mala-iy6l.6\n- mala-iy6l.7\n- mala-iy6l.8\n\n## Unmet Criterion\nAll packages migrated with imports updated directly (no shims)\n\n## Evidence\n15 backward-compatibility shim files remain in src/ root (models.py, protocols.py, log_events.py, config.py, etc.) that explicitly re-export from new package locations. These files contain comments like 'Backward-compatibility shim' and use 'from src.core.models import ...' patterns. The critical constraint explicitly states 'NO SHIMS OR RE-EXPORTS: All migrations must be pure refactors'.\n\n## Priority\nP0 (blocking)\n\n## Resolution\nAddress this criterion to unblock epic closure. When complete, close this issue.\n","status":"closed","priority":0,"issue_type":"task","created_at":"2026-01-01T19:22:13.821877296Z","created_by":"cyou","updated_at":"2026-01-01T20:26:06.147707491Z","closed_at":"2026-01-01T20:26:06.147707491Z","close_reason":"Closed","labels":["auto_generated","epic_remediation:mala-iy6l:4c7b64509fd64b98f0a453db9ca9d67a3a0f04c08553b06efd0d54a71fa4eb49"],"dependencies":[{"issue_id":"mala-iy6l.9","depends_on_id":"mala-iy6l","type":"parent-child","created_at":"2026-01-01T19:22:13.822398325Z","created_by":"daemon"}]}
{"id":"mala-izf4","title":"remove morph entirely, from everywhere, all references, flags, etc.","notes":"Quality gate failed: Timeout after 60 minutes\nLog: /home/cyou/.claude/projects/-home-cyou-mala/d6e7b85a-ea42-4e08-aba4-bdb03c4b1997.jsonl","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-03T15:19:42.704252128Z","created_by":"cyou","updated_at":"2026-01-03T17:06:15.19654411Z","closed_at":"2026-01-03T17:06:15.19654411Z","close_reason":"Closed","labels":["needs-followup"]}
{"id":"mala-izlr","title":"remove mcp tool guidance from implementer prompt if morph is not there -- more genearlly, it the implementer prompt should not talk about tools at all, that should be a modular prompt according to what mcp tools are enabled","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-01T17:01:09.593285174Z","created_by":"cyou","updated_at":"2026-01-01T22:27:24.044718986Z","closed_at":"2026-01-01T22:27:24.044718986Z","close_reason":"Closed"}
{"id":"mala-j5oe","title":"[Review] 1 non-blocking finding from mala-aio7","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-aio7\n**Highest priority:** P3\n\n---\n\n### Finding 1: [P3] Update docstring: CerberusGateCLI now parses session_dir from JSON\n\n**Priority:** P3\n**Reviewer:** claude\n**Location:** src/infra/clients/cerberus_gate_cli.py:83-84\n\nThe class docstring at line 83-84 states \"Does not parse JSON or map exit codes to domain results\" but this commit adds JSON parsing for `session_dir`. Update the docstring to reflect the new responsibility or clarify that minimal JSON parsing for specific fields is acceptable.\n\n---\n\n\u003c!-- fp:99609c609d49c36f --\u003e\n\u003c!-- review_finding:aa83376dc64f --\u003e","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-04T04:32:32.47390526Z","created_by":"cyou","updated_at":"2026-01-04T04:35:14.756144173Z","closed_at":"2026-01-04T04:35:14.756144173Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-aio7"]}
{"id":"mala-j6p","title":"Refactor validation module into package scaffolding","description":"Context:\n- Existing src/validation.py conflicts with the new validation package layout.\n- Plan introduces src/validation/ modules that must coexist cleanly.\n\nScope:\n- In: refactor src/validation.py into a package scaffold; move existing runner logic into src/validation/runner.py; add src/validation/__init__.py to preserve public imports.\n- Out: new spec/coverage/e2e logic (separate tasks).\n\nAcceptance Criteria:\n- src/validation becomes a package without import conflicts.\n- Existing ValidationRunner behavior preserved via the new module location.\n- Public imports continue to work (no breakage in current callers).\n\nTest Plan:\n- Add/adjust unit tests for validation module import path (if tests exist).\n- Run targeted tests that currently touch validation.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-26T23:00:01.641185848Z","updated_at":"2025-12-26T23:34:56.985031159Z","closed_at":"2025-12-26T23:34:56.985031159Z","close_reason":"Closed"}
{"id":"mala-j7kj","title":"[Advisory] Feature gated via --disable-validations=deadlock-detection","description":"## Context\nThis issue was auto-created by epic verification for epic `mala-ntpr`.\n\n## Epic Description / Acceptance Criteria\nTitle: add handle deadlocks\n\n## Epic: Deadlock Handling for Multi-Agent Coordination\n\nDetect and auto-resolve deadlocks between concurrent agents in real-time.\n\n### Goals\n- Detect deadlocks on lock contention (not timeout-based)\n- Auto-resolution: kill youngest agent, release its locks, add dependency\n- Surface deadlock details to orchestrator via event sink\n\n### Technical Approach\n- Centralized Wait-For Graph (WFG) in orchestrator\n- PostToolUse hook emits lock events after lock commands complete\n- DFS cycle detection on WAITING events (O(n) per event)\n- Feature gated via `--disable-validations=deadlock-detection`\n\n### Plan Reference\nSee: plans/2026-01-03-deadlock-handling-plan.md\n\n## Commit Scope\n- Range hint: 3d51b7ea56803f6d1e3e3de0e82273fbbb28b272^..2376bf14ae6a017bf2af45cbda4a9886492eced5\n- Commits:\n- 2376bf14ae6a017bf2af45cbda4a9886492eced5 bd-mala-ntpr.8: Add integration tests for deadlock detection\n- 89dd4691b37f5385e898bf3fdf984c430640bbe3 bd-mala-ntpr.7: Add --no-deadlock-detection flag to CLI\n- 6f1528d8e7bea5cbb7930eacdd6c1ec6450239ca bd-mala-ntpr.6: Add tests for on_deadlock_detected event sink method\n- c4a1a8cca72e61282af8825df08d9586a6f7a11a bd-mala-ntpr.5: Add deadlock detection event to MalaEventSink protocol\n- 0eeff6c96f11a0b2989cc3dcc61a1a0630aa0bee bd-mala-ntpr.15: Fix swallowed CancelledError and double cleanup in deadlock handling\n- 3a85c34d280766aad7067418d8f2809df33705a8 bd-mala-ntpr.4: Fix deadlock detection and self-cancel race conditions\n- 1a46171c3a4fe36c830856b4ec3e5c8b4ecf5579 bd-mala-ntpr.4: Fix deadlock monitor callback invocation and agent ID mismatch\n- 81fec6ed7c2fca94724a7e602b62bdc3d1908c6c bd-mala-ntpr.14: Fix digit before \u0026 incorrectly treated as redirection\n- 8d70a9a77142014cf652425002b351c681289c76 bd-mala-ntpr.13: Treat redirection \u0026 as safe in batch command detection\n- e93f133a76362e468c0b5d78952c0d79e57a42dd bd-mala-ntpr.4: Wire DeadlockMonitor into Orchestrator\n- 4f522de9e14cacd849a6ec813d6f56c0356dc182 bd-mala-ntpr.12: Fix 3 non-blocking review findings in deadlock hook\n- c862f3a1b141c1ed1a9f57524e986906e9a23ab8 bd-mala-ntpr.3: Fix regex to handle shell operators, quotes, and batched commands\n- f3b40f873b0a6f734d93f42da76709bf5b26c22a bd-mala-ntpr.3: Emit lock events from PostToolUse hook\n- a0ff3a8a2c5e7cbb34327758e8edb4df7595ae5c bd-mala-ntpr.10: Fix test signature for _build_sdk_options and ruff TC002 import\n- a339627d745f10c9785e0f2632561ed0ccc4fd4b bd-mala-ntpr.11: Make cycle detection linear-time with three-color DFS\n- eebb6074422b288d8c80315445837ebd75476cb6 bd-mala-ntpr.1: Fix victim blocked_on/blocker_id in multi-party deadlocks\n- 164f218b3e01a7205d6e9a84b18c5e541f8cebce bd-mala-ntpr.9: Propagate DeadlockMonitor into WiringDependencies\n- 3d51b7ea56803f6d1e3e3de0e82273fbbb28b272 bd-mala-ntpr.1: Add deadlock detection domain model\n\n## Child Issues\n- mala-ntpr.1\n- mala-ntpr.10\n- mala-ntpr.11\n- mala-ntpr.12\n- mala-ntpr.13\n- mala-ntpr.14\n- mala-ntpr.15\n- mala-ntpr.2\n- mala-ntpr.3\n- mala-ntpr.4\n- mala-ntpr.5\n- mala-ntpr.6\n- mala-ntpr.7\n- mala-ntpr.8\n- mala-ntpr.9\n\n## Unmet Criterion\nFeature gated via --disable-validations=deadlock-detection\n\n## Evidence\nImplementation uses --deadlock-detection/--no-deadlock-detection flag instead of --disable-validations=deadlock-detection. While functionally equivalent for disabling the feature, it deviates from the specific flag format mentioned in the acceptance criteria.\n\n## Priority\nP3 (informational)\n\n## Resolution\nThis is advisory (P2/P3) and does not block epic closure. When complete, close this issue.\n","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-04T01:12:48.105335233Z","created_by":"cyou","updated_at":"2026-01-04T01:56:02.417721537Z","closed_at":"2026-01-04T01:56:02.417721537Z","close_reason":"Closed","labels":["auto_generated","epic_remediation:mala-ntpr:389b98c7e17fb16572094f763194ecdc473d5fea29b314eb05bb190714f75c5c"]}
{"id":"mala-j8fj","title":"Allow users to configure setting_sources (project/user)","description":"Context:\nCurrently mala hardcodes setting_sources=['project', 'user'] in agent_session_runner.py, run_coordinator.py, and epic_verifier.py. Add CLI flag or config option to let users choose which sources to load (e.g., --no-user-settings to skip ~/.claude/CLAUDE.md).\n\nLocation:\n- src/pipeline/agent_session_runner.py:500-504 (setting_sources parameter)\n- src/pipeline/run_coordinator.py:420 (setting_sources)\n- src/infra/epic_verifier.py:229 (setting_sources)\n- src/cli/cli.py (CLI flags)\n\nScope:\n- In: CLI flag to configure setting_sources\n- In: Config file option for persistent preference\n- Out: Per-issue setting sources; dynamic switching\n\nAcceptance Criteria:\n- `mala run --no-user-settings` skips ~/.claude/CLAUDE.md\n- `mala run --settings project` loads only project settings\n- Default behavior unchanged (project + user)\n- Setting persisted in config file optional\n\nTest Plan:\n- Unit: Test setting_sources parameter propagation\n- Integration: Run with --no-user-settings, verify user CLAUDE.md not loaded\n- Manual: Test various flag combinations\n\nNotes for Agent:\n- Blocked: No clear use case yet. Low priority backlog item.\n- Related to mala-6hp (CLAUDE.md loading for Agent SDK apps)","notes":"Blocked: No clear use case yet. Low priority backlog item.","status":"tombstone","priority":3,"issue_type":"feature","created_at":"2026-01-01T15:42:05.756197309Z","created_by":"cyou","updated_at":"2026-01-06T22:52:10.761723406Z","deleted_at":"2026-01-06T22:52:10.761723406Z","deleted_by":"batch delete","delete_reason":"batch delete","original_type":"feature"}
{"id":"mala-jef","title":"Orchestrator: wire focus parameter through to BeadsClient","description":"Context:\n- Orchestrator receives config from CLI and passes to BeadsClient\n- Need to thread `focus` parameter through `MalaOrchestrator.__init__` and `run` method\n\nScope:\n- In: Add `focus` parameter to `MalaOrchestrator.__init__`\n- In: Pass `focus` to `beads.get_ready_async` calls in orchestrator\n- Out: CLI changes (done in previous issue), beads_client changes (done earlier)\n\nAcceptance Criteria:\n- `MalaOrchestrator` accepts `focus` parameter\n- Parameter passed correctly to all `get_ready_async` calls\n- Default behavior is focus mode enabled\n\nTest Plan:\n- Unit test orchestrator initialization with focus parameter\n- Integration test end-to-end with `--focus` and `--no-focus`\n- Run `uv run pytest tests/test_orchestrator.py`\n\nNotes for Agent:\n- Check all call sites of `get_ready_async` in orchestrator\n- Ensure consistency with existing parameter threading pattern (like `prioritize_wip`)\n\nPrimary files: src/orchestrator.py","status":"tombstone","priority":1,"issue_type":"task","created_at":"2025-12-27T18:14:59.07845602Z","updated_at":"2025-12-27T18:19:15.446361247Z","dependencies":[{"issue_id":"mala-jef","depends_on_id":"mala-g3h","type":"blocks","created_at":"2025-12-27T18:18:55.856283612Z","created_by":"daemon"}],"deleted_at":"2025-12-27T18:19:15.446361247Z","deleted_by":"daemon","delete_reason":"delete","original_type":"task"}
{"id":"mala-jen9","title":"Epic: Idle Timeout Retry/Recovery","description":"Agent sessions hang indefinitely when Claude CLI subprocess stops producing output but stays alive. Observed in run bd0a654e session 90f81fe7 - subprocess in ep_poll state.\n\nCurrent idle timeout detection works, but subprocess is not terminated and no retry happens.\n\n## Plan\nSee: docs/2025-12-31-idle-timeout-retry-plan.md\n\n## Goals\n- Add retry/recovery logic for idle timeout with disconnect, backoff, and resume\n- Terminate subprocess via disconnect() when idle timeout fires\n- Retry up to 2 times with backoff (0s, 5s, 15s)\n- First-turn hangs with no side effects start fresh; with side effects fail fast\n\n## Non-Goals\n- SDK changes\n- Lifecycle state machine changes\n- CLI config exposure\n\n## Acceptance Criteria\n- All child issues completed\n- Idle timeout triggers subprocess termination via disconnect()\n- Sessions retry up to 2 times with backoff\n- Side effect detection prevents unsafe retries","status":"closed","priority":1,"issue_type":"epic","created_at":"2026-01-01T02:00:13.507596138Z","created_by":"cyou","updated_at":"2026-01-01T06:53:04.632701289Z","closed_at":"2026-01-01T06:53:04.632701289Z","close_reason":"Closed","labels":["subprocess"]}
{"id":"mala-jen9.1","title":"Add disconnect() to SDKClientProtocol","description":"## Context\n- SDKClientProtocol (lines 80-117) defines the interface for SDK clients\n- Need to add disconnect() method for subprocess termination\n- SDK's ClaudeSDKClient.disconnect() already exists (verified at client.py:362)\n- Signature: async def disconnect(self) -\u003e None\n\n## Scope\n**In:** Add disconnect() method to SDKClientProtocol\n**Out:** Implementation logic, test fakes\n\n## Primary Files\n- src/pipeline/agent_session_runner.py\n\n## Acceptance Criteria\n- Protocol has async def disconnect(self) -\u003e None method with docstring\n- Type check passes (uvx ty check)\n\n## Test Plan\n- Run type checker to verify protocol is valid\n\n## Notes for Agent\n- This is a Protocol class, just add the method signature with ...\n- Keep docstring minimal","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-01T02:00:30.025992737Z","created_by":"cyou","updated_at":"2026-01-01T02:02:14.281955993Z","closed_at":"2026-01-01T02:02:14.281955993Z","close_reason":"wontfix","dependencies":[{"issue_id":"mala-jen9.1","depends_on_id":"mala-jen9","type":"parent-child","created_at":"2026-01-01T02:00:30.033816795Z","created_by":"daemon"}]}
{"id":"mala-jen9.2","title":"Add idle retry config options","description":"## Context\n- AgentSessionConfig dataclass (lines 159-188) holds session configuration\n- Need to add max_idle_retries and idle_retry_backoff fields\n- Config is internal-only, not exposed to CLI\n\n## Scope\n**In:** Add two config fields to AgentSessionConfig\n**Out:** CLI exposure, env var support\n\n## Primary Files\n- src/pipeline/agent_session_runner.py\n\n## Dependencies\n- Blocked by: mala-jen9.1 (protocol update, same file)\n\n## Acceptance Criteria\n- max_idle_retries: int = 2 field added\n- idle_retry_backoff: tuple[float, ...] = (0.0, 5.0, 15.0) field added\n- Type check passes\n\n## Test Plan\n- Verify config can be instantiated with defaults\n- Verify custom values can be set\n\n## Notes for Agent\n- Add fields after existing config fields (after line 188)\n- Internal-only: no CLI/env var plumbing needed\n- Backoff semantics: idle_retry_count tracks retries that occurred\n  - idle_retry_count = 0: First attempt (not a retry), no backoff\n  - idle_retry_count = 1: First retry, backoff index 0 → 0s\n  - idle_retry_count = 2: Second retry, backoff index 1 → 5s","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-01T02:00:41.132532778Z","created_by":"cyou","updated_at":"2026-01-01T02:02:14.274646716Z","closed_at":"2026-01-01T02:02:14.274646716Z","close_reason":"wontfix","dependencies":[{"issue_id":"mala-jen9.2","depends_on_id":"mala-jen9","type":"parent-child","created_at":"2026-01-01T02:00:41.140548555Z","created_by":"daemon"},{"issue_id":"mala-jen9.2","depends_on_id":"mala-jen9.1","type":"blocks","created_at":"2026-01-01T02:00:45.694181458Z","created_by":"daemon"}]}
{"id":"mala-jen9.3","title":"Add idle resume prompt file","description":"## Context\n- Other prompts use markdown files in src/prompts/ loaded via @functools.cache\n- Need minimal resume prompt for session continuation after idle timeout\n- Existing pattern: _PROMPT_DIR at line 65, _get_gate_followup_prompt() at line 69\n\n## Scope\n**In:** Create src/prompts/idle_resume.md with minimal content\n**Out:** Complex prompt content\n\n## Primary Files\n- src/prompts/idle_resume.md (new file)\n\n## Acceptance Criteria\n- File exists at src/prompts/idle_resume.md\n- Content is exactly: Continue on issue {issue_id}.\n- File is tracked by git (not ignored)\n\n## Test Plan\n- Verify file exists and is loadable\n- Verify {issue_id} placeholder works with .format()\n\n## Notes for Agent\n- Keep prompt minimal - SDK loads full context via session_id\n- Single line is sufficient\n- No trailing newline needed","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-01T02:00:56.272999259Z","created_by":"cyou","updated_at":"2026-01-01T02:02:14.26655582Z","closed_at":"2026-01-01T02:02:14.26655582Z","close_reason":"wontfix","dependencies":[{"issue_id":"mala-jen9.3","depends_on_id":"mala-jen9","type":"parent-child","created_at":"2026-01-01T02:00:56.279893944Z","created_by":"daemon"}]}
{"id":"mala-jen9.4","title":"Add disconnect() to FakeSDKClient and SequencedFactory","description":"## Context\n- FakeSDKClient, HangingSDKClient, SlowSDKClient used in tests\n- Need disconnect() method to satisfy updated protocol\n- Need SequencedSDKClientFactory for retry tests (returns different clients per call)\n\n## Scope\n**In:** Add disconnect() to fake clients; add SequencedSDKClientFactory\n**Out:** Retry logic tests (separate issue)\n\n## Primary Files\n- tests/test_agent_session_runner.py\n\n## Dependencies\n- Blocked by: mala-jen9.1 (protocol must be defined first)\n\n## Acceptance Criteria\n- FakeSDKClient has disconnect() method with disconnect_called flag and disconnect_delay for testing\n- HangingSDKClient and SlowSDKClient inherit disconnect() from parent\n- SequencedSDKClientFactory returns different clients per create() call\n- Protocol conformance check passes\n\n## Test Plan\n- Verify fake clients satisfy SDKClientProtocol\n- Verify SequencedSDKClientFactory returns clients in sequence\n\n## Notes for Agent\n- Add to FakeSDKClient.__init__: self.disconnect_called = False, self.disconnect_delay: float = 0\n- disconnect() implementation:\n  ```python\n  async def disconnect(self) -\u003e None:\n      if self.disconnect_delay \u003e 0:\n          await asyncio.sleep(self.disconnect_delay)\n      self.disconnect_called = True\n  ```\n- SequencedSDKClientFactory takes list of clients, returns them in order via _index counter","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-01T02:01:07.927359787Z","created_by":"cyou","updated_at":"2026-01-01T02:02:14.259368021Z","closed_at":"2026-01-01T02:02:14.259368021Z","close_reason":"wontfix","dependencies":[{"issue_id":"mala-jen9.4","depends_on_id":"mala-jen9","type":"parent-child","created_at":"2026-01-01T02:01:07.934337422Z","created_by":"daemon"},{"issue_id":"mala-jen9.4","depends_on_id":"mala-jen9.1","type":"blocks","created_at":"2026-01-01T02:01:12.815424162Z","created_by":"daemon"}]}
{"id":"mala-jen9.5","title":"Implement idle timeout retry logic","description":"## Context\n- Current code (lines 460-829) creates one client for entire session\n- Need to restructure: client creation inside loop with retry on idle timeout\n- Key: only query when pending_query is set; clear after success\n- Track tool_calls_this_turn for side effect detection\n- Plan: docs/2025-12-31-idle-timeout-retry-plan.md Task 3\n\n## Scope\n**In:** Restructure run_session() with retry wrapper around query+message iteration\n**Out:** Test implementation (separate issue)\n\n## Primary Files\n- src/pipeline/agent_session_runner.py\n\n## Dependencies\n- Blocked by: mala-jen9.2 (config must be added first, same file)\n- Blocked by: mala-jen9.3 (prompt file must exist)\n\n## Acceptance Criteria\n- pending_query: str | None tracks when query is needed (None = skip query block)\n- pending_query cleared after successful iteration\n- client.disconnect() called BEFORE retry limit check on idle timeout\n- If no session_id AND tool calls occurred, fail fast with clear error\n- If no session_id AND no tool calls, retry with original prompt (fresh session)\n- If session_id exists, retry with resume prompt\n- Backoff uses idle_retry_backoff[min(attempt - 1, len(...) - 1)]\n- Uses DISCONNECT_TIMEOUT = 10.0 seconds\n\n## Test Plan\n- Covered by separate test issue (mala-jen9.6)\n- Manual: type check passes, no regressions in existing tests\n\n## Notes for Agent\n- Add IDLE_RESUME_PROMPT_FILE = _PROMPT_DIR / \"idle_resume.md\" constant near line 66\n- Add _get_idle_resume_prompt() with @functools.cache (functools already imported at line 21)\n- Add DISCONNECT_TIMEOUT = 10.0 constant\n- IdleTimeoutError already exists at line 75-76, don't redefine\n- Uses Effect.SEND_GATE_RETRY style (line 641)\n- Query block gated by: if pending_query is not None:\n- Track tool_calls_this_turn: int = 0, increment on ToolUseBlock\n- Resume prompt format: _get_idle_resume_prompt().format(issue_id=input.issue_id)","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-01T02:01:33.341429994Z","created_by":"cyou","updated_at":"2026-01-01T02:02:14.244987263Z","closed_at":"2026-01-01T02:02:14.244987263Z","close_reason":"wontfix","dependencies":[{"issue_id":"mala-jen9.5","depends_on_id":"mala-jen9","type":"parent-child","created_at":"2026-01-01T02:01:33.348817916Z","created_by":"daemon"},{"issue_id":"mala-jen9.5","depends_on_id":"mala-jen9.2","type":"blocks","created_at":"2026-01-01T02:01:38.084537427Z","created_by":"daemon"},{"issue_id":"mala-jen9.5","depends_on_id":"mala-jen9.3","type":"blocks","created_at":"2026-01-01T02:01:38.0987557Z","created_by":"daemon"}]}
{"id":"mala-jen9.6","title":"Add unit tests for idle timeout retry","description":"## Context\n- Need 7 test cases covering retry success, failure, side effects, backoff\n- Use short idle_timeout_seconds (0.01s) to trigger timeout quickly\n- Mock asyncio.sleep for backoff, NOT asyncio.wait_for\n- Use SequencedSDKClientFactory for retry scenarios\n\n## Scope\n**In:** Add 7 unit tests for idle timeout retry behavior\n**Out:** Integration tests, e2e tests\n\n## Primary Files\n- tests/test_agent_session_runner.py\n\n## Dependencies\n- Blocked by: mala-jen9.5 (retry logic must be implemented)\n- Blocked by: mala-jen9.4 (test fakes must have disconnect())\n\n## Acceptance Criteria\nAll tests marked with @pytest.mark.unit:\n1. test_idle_timeout_retries_and_recovers: hang once then succeed\n2. test_idle_timeout_gives_up_after_max_retries: disconnect called on all clients\n3. test_idle_timeout_always_disconnects_even_on_final_failure\n4. test_idle_timeout_first_turn_no_side_effects_starts_fresh: uses original prompt\n5. test_idle_timeout_first_turn_with_tool_calls_fails_fast: error message mentions tool calls\n6. test_idle_timeout_non_query_phases_skip_query_block: no client created when pending_query is None\n7. test_idle_timeout_backoff_delays: verify 0s, 5s delays\n\n## Test Plan\n- Run: uv run pytest tests/test_agent_session_runner.py -k idle_timeout -v -m unit\n- All tests pass\n\n## Notes for Agent\n- Use mocker.patch(\"asyncio.sleep\", return_value=None) for backoff\n- HangingSDKClient should yield session_id in ResultMessage before hanging for some tests\n- Config: max_idle_retries=2, idle_timeout_seconds=0.01\n- For test 4/5: Create HangingClient that yields ToolUseBlock before hanging\n- For test 6: Set pending_query=None and verify no client.query() call","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-01T02:01:53.939415875Z","created_by":"cyou","updated_at":"2026-01-01T02:02:08.146121928Z","closed_at":"2026-01-01T02:02:08.146121928Z","close_reason":"wontfix","dependencies":[{"issue_id":"mala-jen9.6","depends_on_id":"mala-jen9","type":"parent-child","created_at":"2026-01-01T02:01:53.946296876Z","created_by":"daemon"},{"issue_id":"mala-jen9.6","depends_on_id":"mala-jen9.5","type":"blocks","created_at":"2026-01-01T02:01:59.287460861Z","created_by":"daemon"},{"issue_id":"mala-jen9.6","depends_on_id":"mala-jen9.4","type":"blocks","created_at":"2026-01-01T02:01:59.30081801Z","created_by":"daemon"}]}
{"id":"mala-jen9.7","title":"Idle timeout retry: protocol, config, prompt, and core logic","description":"## Context\nAgent sessions hang indefinitely when Claude CLI subprocess stops producing output. Current idle timeout detection works, but subprocess is not terminated and no retry happens.\n\nPlan: docs/2025-12-31-idle-timeout-retry-plan.md (Tasks 1-4 combined)\n\n## Scope\n**In:** \n- Add disconnect() to SDKClientProtocol (lines 80-117)\n- Add max_idle_retries and idle_retry_backoff to AgentSessionConfig (after line 188)\n- Create src/prompts/idle_resume.md with minimal content: \"Continue on issue {issue_id}.\"\n- Implement retry logic in run_session() (restructure lines 460-829)\n\n**Out:** Test fakes, unit tests (separate issue)\n\n## Primary Files\n- src/pipeline/agent_session_runner.py\n- src/prompts/idle_resume.md (new)\n\n## Acceptance Criteria\n1. Protocol: async def disconnect(self) -\u003e None added to SDKClientProtocol\n2. Config: max_idle_retries: int = 2 and idle_retry_backoff: tuple[float, ...] = (0.0, 5.0, 15.0)\n3. Prompt file: src/prompts/idle_resume.md exists with \"Continue on issue {issue_id}.\"\n4. Logic:\n   - pending_query: str | None tracks when query is needed\n   - pending_query cleared after successful iteration  \n   - client.disconnect() called BEFORE retry limit check\n   - If no session_id AND tool calls occurred: fail fast\n   - If no session_id AND no tool calls: retry with original prompt\n   - If session_id exists: retry with resume prompt\n   - Backoff: idle_retry_backoff[min(attempt - 1, len(...) - 1)]\n   - DISCONNECT_TIMEOUT = 10.0 seconds\n\n## Test Plan\n- Type check passes: uvx ty check\n- Existing tests still pass\n- Manual verification of code structure\n\n## Notes for Agent\n- IdleTimeoutError already exists at line 75-76\n- functools already imported at line 21\n- _PROMPT_DIR already exists at line 65\n- Uses Effect.SEND_GATE_RETRY style (line 641)\n- Track tool_calls_this_turn: int for side effect detection\n- Add _get_idle_resume_prompt() with @functools.cache","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-01T02:02:35.599741456Z","created_by":"cyou","updated_at":"2026-01-01T06:43:11.222712354Z","closed_at":"2026-01-01T06:43:11.222712354Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-jen9.7","depends_on_id":"mala-jen9","type":"parent-child","created_at":"2026-01-01T02:02:35.607276252Z","created_by":"daemon"}]}
{"id":"mala-jen9.8","title":"Idle timeout retry: test fakes and unit tests","description":"## Context\nNeed test infrastructure and unit tests for idle timeout retry feature.\n\nPlan: docs/2025-12-31-idle-timeout-retry-plan.md (Tasks 5-6 combined)\n\n## Scope\n**In:**\n- Add disconnect() to FakeSDKClient with disconnect_called flag and disconnect_delay\n- Add SequencedSDKClientFactory for retry tests\n- Add 7 unit tests for idle timeout retry behavior\n\n**Out:** Integration tests, e2e tests\n\n## Primary Files\n- tests/test_agent_session_runner.py\n\n## Dependencies\n- Blocked by: mala-jen9.7 (core logic must be implemented first)\n\n## Acceptance Criteria\n\n### Test Fakes\n- FakeSDKClient has disconnect() with disconnect_called=False and disconnect_delay=0\n- HangingSDKClient and SlowSDKClient inherit disconnect()\n- SequencedSDKClientFactory returns different clients per create() call\n\n### Unit Tests (all @pytest.mark.unit)\n1. test_idle_timeout_retries_and_recovers: hang once then succeed\n2. test_idle_timeout_gives_up_after_max_retries: disconnect on all clients\n3. test_idle_timeout_always_disconnects_even_on_final_failure\n4. test_idle_timeout_first_turn_no_side_effects_starts_fresh\n5. test_idle_timeout_first_turn_with_tool_calls_fails_fast\n6. test_idle_timeout_non_query_phases_skip_query_block\n7. test_idle_timeout_backoff_delays: verify 0s, 5s delays\n\n## Test Plan\n- Run: uv run pytest tests/test_agent_session_runner.py -k idle_timeout -v -m unit\n- All tests pass\n\n## Notes for Agent\n- Mock asyncio.sleep for backoff, NOT asyncio.wait_for\n- Use idle_timeout_seconds=0.01 to trigger quickly\n- Config: max_idle_retries=2\n- SequencedSDKClientFactory: takes list, returns in order via _index\n- For side effect tests: HangingClient yields ToolUseBlock before hanging","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-01T02:02:52.496655819Z","created_by":"cyou","updated_at":"2026-01-01T06:51:22.288298992Z","closed_at":"2026-01-01T06:51:22.288298992Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-jen9.8","depends_on_id":"mala-jen9","type":"parent-child","created_at":"2026-01-01T02:02:52.503345548Z","created_by":"daemon"},{"issue_id":"mala-jen9.8","depends_on_id":"mala-jen9.7","type":"blocks","created_at":"2026-01-01T02:02:56.667929156Z","created_by":"daemon"}]}
{"id":"mala-jgcy","title":"Epic verifier: Replace fragile JSON extraction regex with robust parser","description":"## Context\n\nThe epic verification feature uses a regex to extract JSON from model responses:\n```python\nr\"```(?:json)?\\s*(\\{.*\\})\\s*```\"\n```\n\nThis greedy regex with `re.DOTALL` can fail if the response contains multiple code blocks, as it will match across blocks and include markdown fences in the capture group, causing `json.loads()` to fail.\n\n## Problem\n\n- Fragile extraction that breaks on multi-block responses\n- Causes verification failures to fall back to human review unnecessarily\n\n## Proposed Solution\n\nReplace the regex-based extraction with a more robust approach:\n1. Use a dedicated markdown code block parser\n2. Or extract the first/last code block specifically\n3. Or use structured output from the model (JSON mode)\n\n## Acceptance Criteria\n\n- [ ] JSON extraction handles responses with multiple code blocks\n- [ ] Parsing failures are reduced/eliminated for well-formed model responses\n- [ ] Tests cover edge cases (multiple blocks, no blocks, malformed JSON)\n\n## References\n\n- File: `src/epic_verifier.py` line ~230 (`_parse_verdict` method)\n- From code review iteration 3","notes":"Quality gate failed: Timeout after 60 minutes\nLog: /home/cyou/.claude/projects/-home-cyou-mala/1c7d0fc0-0854-45fa-b71c-2b9e6b2e4497.jsonl","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-30T15:50:08.013953662Z","created_by":"cyou","updated_at":"2025-12-30T20:25:25.508064696Z","closed_at":"2025-12-30T20:25:25.508064696Z","close_reason":"Closed","labels":["needs-followup"]}
{"id":"mala-jnah","title":"add mode for non-epic issues only","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-01T20:26:48.588294678Z","created_by":"cyou","updated_at":"2026-01-01T23:18:02.882214387Z","closed_at":"2026-01-01T23:18:02.882214387Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-jnah","depends_on_id":"mala-dhry","type":"blocks","created_at":"2026-01-01T21:29:39.214812809Z","created_by":"daemon"}]}
{"id":"mala-jnq","title":"Epic: Code health fixes from review","description":"Goal: address high/medium issues from the code health review with minimal overlap and clear dependencies.\n\\nScope:\n- Focus on lock enforcement correctness, build packaging, Braintrust config order, quality gate robustness, async bd calls, agent loop consolidation, and repo hygiene.\n- Out: feature work or architecture redesign beyond the listed fixes.","status":"closed","priority":1,"issue_type":"epic","created_at":"2025-12-26T17:35:14.749720136Z","updated_at":"2025-12-26T19:32:39.518659356Z","closed_at":"2025-12-26T19:32:39.518659356Z","close_reason":"All children completed"}
{"id":"mala-jrnn","title":"[P2] Test assertion relaxed to ignore missing validation","description":"## Review Finding\n\n**Priority:** P2\n**Trigger:** run_end\n**Baseline:** ddc86bb..3deec8ab\n**Location:** tests/e2e/test_e2e.py:417-420\n\n---\n\nThe diff changes the E2E test to conditionally check coverage only `if run_validation is not None`. Since the fixture explicitly configures `run_end` validation, missing `run_validation` metadata implies validation failed to run or report. This relaxation masks regressions where validation is skipped entirely.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-11T02:30:42.314824731Z","created_by":"cyou","updated_at":"2026-01-11T02:40:41.392413002Z","closed_at":"2026-01-11T02:40:41.392413002Z","close_reason":"Closed","labels":["auto_generated","cumulative-review","fp:451a13ff0bd37110","review_finding","trigger:run_end"]}
{"id":"mala-jszz","title":"Epic: Decompose ConsoleEventSink.on_run_started (CCN 38 → \u003c15)","description":"## Summary\nThe 125-line `on_run_started` method (CCN 38) handles all startup logging with deeply nested conditionals for every config option.\n\n## Primary Files\n- `src/infra/io/event_sink.py:978-1103`\n\n## Category\nComplexity\n\n## Source\nclaude\n\n## Fix Approach\nExtract helper methods:\n- `_log_limits()`\n- `_log_review_config()`\n- `_log_morph_config()`\n- `_log_cli_args()`\n\nEach handles one concern with simple conditionals.\n\n## Non-Goals\n- Splitting ConsoleEventSink into multiple classes\n- Changing startup log format\n\n## Acceptance Criteria\n- [ ] `on_run_started` CCN reduced below 15\n- [ ] Startup log format unchanged\n- [ ] Helpers remain private to the class\n\n## Test Plan\n- Snapshot test for startup log output format\n- Unit tests for individual helper methods","status":"closed","priority":2,"issue_type":"epic","created_at":"2026-01-03T05:49:42.219550185Z","created_by":"cyou","updated_at":"2026-01-03T18:19:41.718715654Z","closed_at":"2026-01-03T18:19:41.718715654Z","close_reason":"Closed"}
{"id":"mala-jszz.1","title":"Decompose ConsoleEventSink.on_run_started into helper methods","description":"## Problem\n`ConsoleEventSink.on_run_started` (lines 978-1103 in `src/infra/io/event_sink.py`) is a 125-line method with CCN 38. It handles all startup logging with deeply nested conditionals for every config option.\n\n## Solution\nExtract focused helper methods to reduce complexity:\n\n### Helpers to extract:\n1. **`_log_limits(config)`** - Lines 984-997: max-issues, max-agents, timeout, gate-retries\n2. **`_log_review_config(config)`** - Lines 999-1011: Cerberus review enabled/disabled  \n3. **`_log_filters(config)`** - Lines 1013-1029: epic_id, only_ids, prioritize_wip, orphans_only\n4. **`_log_braintrust_config(config)`** - Lines 1031-1039: braintrust enabled/disabled\n5. **`_log_morph_config(config)`** - Lines 1041-1055: morph enabled/disabled with blocked tools\n6. **`_log_cli_args(config)`** - Lines 1057-1101: CLI argument reconstruction (highest complexity)\n\n### After refactor:\n```python\ndef on_run_started(self, config: EventRunConfig) -\u003e None:\n    print()\n    log(\"●\", \"mala orchestrator\", Colors.MAGENTA)\n    log(\"◐\", f\"repo: {config.repo_path}\", Colors.MUTED)\n    \n    self._log_limits(config)\n    self._log_review_config(config)\n    self._log_filters(config)\n    self._log_braintrust_config(config)\n    self._log_morph_config(config)\n    self._log_cli_args(config)\n    print()\n```\n\n## Acceptance Criteria\n- [ ] Extract all 6 helper methods as private methods on ConsoleEventSink\n- [ ] Each helper receives `config: EventRunConfig` parameter\n- [ ] Main method becomes a simple sequence of helper calls\n- [ ] No behavior changes - output must be identical\n- [ ] All existing tests pass","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T05:53:32.486873778Z","created_by":"cyou","updated_at":"2026-01-03T06:55:04.61715966Z","closed_at":"2026-01-03T06:55:04.61715966Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-jszz.1","depends_on_id":"mala-b79o.3","type":"blocks","created_at":"2026-01-03T06:02:12.009385927Z","created_by":"daemon"},{"issue_id":"mala-jszz.1","depends_on_id":"mala-jszz","type":"parent-child","created_at":"2026-01-03T17:57:18.265378296Z","created_by":"daemon"}]}
{"id":"mala-jszz.2","title":"[Remediation] Startup log format unchanged","description":"## Context\nThis issue was auto-created by epic verification for epic `mala-jszz`.\n\n## Epic Description / Acceptance Criteria\nTitle: Epic: Decompose ConsoleEventSink.on_run_started (CCN 38 → \u003c15)\n\n## Summary\nThe 125-line `on_run_started` method (CCN 38) handles all startup logging with deeply nested conditionals for every config option.\n\n## Primary Files\n- `src/infra/io/event_sink.py:978-1103`\n\n## Category\nComplexity\n\n## Source\nclaude\n\n## Fix Approach\nExtract helper methods:\n- `_log_limits()`\n- `_log_review_config()`\n- `_log_morph_config()`\n- `_log_cli_args()`\n\nEach handles one concern with simple conditionals.\n\n## Non-Goals\n- Splitting ConsoleEventSink into multiple classes\n- Changing startup log format\n\n## Acceptance Criteria\n- [ ] `on_run_started` CCN reduced below 15\n- [ ] Startup log format unchanged\n- [ ] Helpers remain private to the class\n\n## Test Plan\n- Snapshot test for startup log output format\n- Unit tests for individual helper methods\n\n## Commit Scope\n- Range hint: c497bd734795648548a038dde966fb99d450f0f0^..30b871e9a305d27bcc2381426ff3695f9a6d88c1\n- Commits:\n- 30b871e9a305d27bcc2381426ff3695f9a6d88c1 bd-mala-e17r.3: Extract console_sink.py with ConsoleEventSink\n- 91aacc150a68b69cf7bc26e43ce13be199edbeab bd-mala-e17r.2: Fix ConsoleEventSink log function signatures\n- 49c0f25eb0360824c85d67cd3dd9d1b60b18f835 bd-mala-e17r.2: Extract base_sink.py with BaseEventSink and NullEventSink\n- a078a6bdbe44ad5498f770052b9338bae3582cd4 bd-mala-e17r.1: Extract event_protocol.py with MalaEventSink protocol\n- c497bd734795648548a038dde966fb99d450f0f0 bd-mala-jszz.1: Decompose ConsoleEventSink.on_run_started into helper methods\n\n## Child Issues\n- mala-e17r.1\n- mala-e17r.2\n- mala-e17r.3\n- mala-e17r.4\n- mala-jszz.1\n\n## Unmet Criterion\nStartup log format unchanged\n\n## Evidence\nThe startup log format has been completely changed from the original format using log(\"●\", \"mala orchestrator\", Colors.MAGENTA) and log(\"◐\", ..., Colors.MUTED) to a new format using log(\"→\", \"[START] Run started\", agent_id=\"run\") and log(\"◦\", ..., agent_id=\"run\"). This changes the user-visible output format.\n\n## Priority\nP1 (blocking)\n\n## Resolution\nAddress this criterion to unblock epic closure. When complete, close this issue.\n","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-03T18:18:38.077352821Z","created_by":"cyou","updated_at":"2026-01-03T18:19:39.876716429Z","closed_at":"2026-01-03T18:19:39.876716429Z","close_reason":"Closed","labels":["auto_generated","epic_remediation:mala-jszz:89af733ebf591296fbc9ac0cb65c6e557a8d63ba2e373df1cf901a50004c1bce"],"dependencies":[{"issue_id":"mala-jszz.2","depends_on_id":"mala-jszz","type":"parent-child","created_at":"2026-01-03T18:18:38.077792251Z","created_by":"daemon"}]}
{"id":"mala-jxc","title":"Enforce lock ownership in PreToolUse","description":"Context:\n- Track A2 requires a PreToolUse hook that blocks file writes unless the agent holds the lock (docs/coordination-plan-codex.md:30-32).\n- hooks currently only block dangerous commands and Morph tool usage (src/hooks.py:37-86).\n- Orchestrator registers PreToolUse hooks in run_implementer (src/orchestrator.py:145-155).\n\nScope:\n- In: add a new PreToolUse hook to detect file-write tool calls and verify lock ownership via locking helpers.\n- In: register the new hook in the orchestrator PreToolUse hook list.\n- Out: no changes to lock key generation or prompt content.\n\nAcceptance Criteria:\n- File-write tool uses are blocked when the agent does not hold the lock; allowed when it does.\n- Block reason includes the file path and current lock holder when available.\n- Orchestrator includes the new hook in PreToolUse.\n\nTest Plan:\n- TDD: add failing unit tests for hook behavior with and without locks.\n- Implement hook, update tests to pass.\n- Run uv run pytest for the new/updated hook tests.\n\nNotes for Agent:\n- Confirm tool input shapes for file paths and which tools count as writes (needs verification).","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-26T16:43:36.955166144Z","updated_at":"2025-12-26T17:01:31.441554529Z","closed_at":"2025-12-26T17:01:31.441554529Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-jxc","depends_on_id":"mala-4w7","type":"parent-child","created_at":"2025-12-26T16:47:52.974558844Z","created_by":"daemon"}]}
{"id":"mala-kfno","title":"add an exit path for issues that don't have the right dependencies fulfilled","status":"tombstone","priority":2,"issue_type":"feature","created_at":"2026-01-03T23:26:44.81385559Z","created_by":"cyou","updated_at":"2026-01-06T22:52:10.761723406Z","deleted_at":"2026-01-06T22:52:10.761723406Z","deleted_by":"batch delete","delete_reason":"batch delete","original_type":"feature"}
{"id":"mala-kh8e","title":"[Review] 1 non-blocking finding from mala-nzr9.2","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-nzr9.2\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Avoid 400-line code duplication by moving extractor to shared location\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/infra/hooks/tool_name_extractor.py:1-12\n\nThe commit introduces a 400-line duplicate of `tool_name_extractor.py` in `src/infra/hooks/`. While this resolves the contract violation, it creates a significant maintenance burden. According to the architecture and `pyproject.toml`, `src.infra` is below `src.domain`, so the extractor could be moved to a shared location like `src/infra/tool_name_extractor.py` (or a new `src/infra/core/` package as suggested in the task context). This would allow both `src.infra.hooks` and `src.domain.validation` to import the same implementation while maintaining architectural boundaries.\n\n---\n\n\u003c!-- fp:482a5f08271f484a --\u003e\n\u003c!-- review_finding:3044520faf41 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T18:58:55.874515411Z","created_by":"cyou","updated_at":"2026-01-03T22:53:59.765387075Z","closed_at":"2026-01-03T22:53:59.765387075Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-nzr9.2"],"dependencies":[{"issue_id":"mala-kh8e","depends_on_id":"mala-nzr9.4","type":"blocks","created_at":"2026-01-03T19:53:58.836895824Z","created_by":"daemon"}]}
{"id":"mala-kpf","title":"Clean up file_lock shim / align lock owner format","description":"Problem: src/tools/locking.py:file_lock writes PID into lock files and src/filelock.py re-exports it, but the rest of the system uses AGENT_ID in lock files. file_lock is unused and inconsistent.\\n\\nScope/files: src/tools/locking.py, src/filelock.py (and possibly tests).\\n\\nOptions:\\n- Remove file_lock and filelock.py entirely, or\\n- Rework file_lock to use the same AGENT_ID scheme and lock keying as the scripts.\\n\\nAcceptance criteria:\\n- No dead/unused locking API left behind.\\n- If kept, file_lock uses the same lock format + keying as scripts.\\n- Tests updated if necessary.\\n\\nDependency: Must wait for lock-key hashing change (mala-4ls) because both edit src/tools/locking.py.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-25T21:38:08.362262314Z","updated_at":"2025-12-26T00:04:34.226695444Z","closed_at":"2025-12-26T00:04:34.226695444Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-kpf","depends_on_id":"mala-4ls","type":"blocks","created_at":"2025-12-25T21:38:46.924557872Z","created_by":"daemon"}]}
{"id":"mala-kru2","title":"[Review] docs/configuration.md:23-26: [P3] Document MALA_DISABLE_DEBUG_LOG environment variable","description":"## Review Finding\n\nThis issue was auto-created from a P3 review finding.\n\n**Source issue:** mala-dhry\n**Reviewer:** gemini\n**Location:** docs/configuration.md:23-26\n\n## Details\n\nThe commit introduces `MALA_DISABLE_DEBUG_LOG=1` as a way to opt-out of debug logging, but this is not documented in `docs/configuration.md`. Since the `--debug-log` flag was removed, users should be informed of this alternative if they need to disable logging (e.g., for performance or disk space reasons).","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-01T22:54:29.975650017Z","created_by":"cyou","updated_at":"2026-01-02T01:57:41.032265175Z","closed_at":"2026-01-02T01:57:41.032265175Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_finding:2398d9698d71","source:mala-dhry"],"dependencies":[{"issue_id":"mala-kru2","depends_on_id":"mala-cw6h","type":"blocks","created_at":"2026-01-01T23:33:53.667297762Z","created_by":"daemon"}]}
{"id":"mala-ksai","title":"[Review] src/orchestration/orchestrator.py:453-454: [P3] Fragile `__init__` delegation using `__dict__.update`","description":"## Review Finding\n\nThis issue was auto-created from a P3 review finding.\n\n**Source issue:** mala-cavk.3\n**Reviewer:** gemini\n**Location:** src/orchestration/orchestrator.py:453-454\n\n## Details\n\nThe legacy `__init__` path uses `self.__dict__.update(temp.__dict__)` to copy attributes from an instance created by the factory. This pattern is fragile as it bypasses any potential property setters or complex initialization logic that might be expected in future refactors.","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-01T22:23:07.464627725Z","created_by":"cyou","updated_at":"2026-01-02T00:47:57.596972307Z","closed_at":"2026-01-02T00:47:57.596972307Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_finding:24f32735fdea","source:mala-cavk.3"],"dependencies":[{"issue_id":"mala-ksai","depends_on_id":"mala-sp2s","type":"blocks","created_at":"2026-01-01T23:32:33.407293424Z","created_by":"daemon"}]}
{"id":"mala-ksyz","title":"refine subagent use","status":"closed","priority":2,"issue_type":"feature","created_at":"2026-01-03T21:33:55.186636603Z","created_by":"cyou","updated_at":"2026-01-04T02:42:49.344602873Z","closed_at":"2026-01-04T02:42:49.344602873Z","close_reason":"Closed"}
{"id":"mala-ku28","title":"[P1] Fix pytest hook signature so flaky reruns apply","description":"## Review Finding\n\n**Priority:** P1\n**Trigger:** run_end\n**Baseline:** bf7d0ad2..102e65c8\n**Location:** tests/e2e/conftest.py:25-31\n\n---\n\n`pytest_collection_modifyitems` is a pytest hook that (in this repo already) is defined as `pytest_collection_modifyitems(config, items)` in `tests/conftest.py`. The new hook in `tests/e2e/conftest.py` is defined as `pytest_collection_modifyitems(items)`.\n\nConcrete failure: pytest will call the hook with `(config, items)`; with the current signature, this either raises a `TypeError` at collection time or causes the hook not to be registered, so `pytest.mark.flaky(reruns=2, ...)` never gets added and `flaky_sdk` tests won’t rerun.\n\nSuggested fix: change to `def pytest_collection_modifyitems(config: pytest.Config, items: list[pytest.Item]) -\u003e None:` (or accept `*args` defensively) and update the `Item` typing accordingly.","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-11T06:40:16.133797564Z","created_by":"cyou","updated_at":"2026-01-11T07:04:57.480020632Z","closed_at":"2026-01-11T07:04:57.480020632Z","close_reason":"Closed","labels":["auto_generated","cumulative-review","fp:71a94224b8b99a90","review_finding","trigger:run_end"]}
{"id":"mala-kwx","title":"Update CLI coverage-threshold option","description":"Context:\n- CLI currently has `--coverage-threshold` defaulting to 85.0\n- Need to change default to None\n- Validation logic must allow None (currently rejects non-0-100 values)\n\nScope:\n- In: Change `--coverage-threshold` default from `85.0` to `None`\n- In: Update help text to explain \"no decrease\" mode\n- In: Update validation to allow None (skip range check if None)\n- Out: Actual coverage logic (that's in coverage.py/spec_runner.py)\n\nAcceptance Criteria:\n- `--coverage-threshold` defaults to None\n- Help text mentions \"no decrease\" behavior\n- Running without `--coverage-threshold` doesn't error\n- Running with `--coverage-threshold 80` still works\n\nTest Plan:\n- Test CLI parses None default correctly\n- Test explicit threshold value works\n- Test help text is updated\n\nNotes for Agent:\n- Type should be `float | None` with default `None`\n- Skip the `0 \u003c= coverage_threshold \u003c= 100` check when None\n- Primary file: src/cli.py","status":"tombstone","priority":1,"issue_type":"task","created_at":"2025-12-27T18:14:25.299018117Z","updated_at":"2025-12-27T18:20:40.634646298Z","labels":["cli"],"dependencies":[{"issue_id":"mala-kwx","depends_on_id":"mala-da4","type":"blocks","created_at":"2025-12-27T18:15:02.03951473Z","created_by":"daemon"}],"deleted_at":"2025-12-27T18:20:40.634646298Z","deleted_by":"daemon","delete_reason":"delete","original_type":"task"}
{"id":"mala-kyq1","title":"Epic: Decompose CLI run command (366 lines → \u003c100)","description":"## Summary\nThe `run()` function in `src/cli/cli.py` handles CLI argument parsing, validation, dry-run logic, configuration building, config overrides, OrchestratorConfig construction, and orchestrator invocation all in one 366-line function.\n\n## Primary Files\n- `src/cli/cli.py:241-606`\n\n## Category\nCohesion\n\n## Source\nMultiple generators (gemini, claude)\n\n## Fix Approach\n1. Extract validation into `_validate_run_args()`\n2. Extract config overrides into `_apply_config_overrides(config, cli_overrides)`\n3. Extract dry-run handling into `_handle_dry_run()`\n4. Make `run()` a thin orchestrator of these phases\n5. Consider extracting a `ConfigurationFactory` in `src/orchestration/bootstrap.py` for reuse\n\n## Non-Goals\n- Changing CLI argument names or behavior\n- Splitting into multiple subcommands\n\n## Acceptance Criteria\n- [ ] `run()` function under 100 lines\n- [ ] Config override logic unit-testable without typer\n- [ ] Validation errors have clear test coverage\n\n## Test Plan\n- Unit tests for `_validate_run_args` with various invalid combinations\n- Unit tests for `_apply_config_overrides` with partial overrides\n\n## Agent Notes\nCLI pulls in underscore helpers (`_parse_cerberus_args/_parse_cerberus_env`) from infra; consider exposing public config resolution API instead.","status":"closed","priority":2,"issue_type":"epic","created_at":"2026-01-03T05:48:53.331159655Z","created_by":"cyou","updated_at":"2026-01-03T18:27:22.428670996Z","closed_at":"2026-01-03T18:27:22.428670996Z","close_reason":"Closed"}
{"id":"mala-kyq1.1","title":"Extract _validate_run_args() from run() command","description":"## Summary\n\nExtract CLI argument validation logic from the `run()` function into a dedicated `_validate_run_args()` helper function.\n\n## Scope\n\n**Primary files:**\n- `src/cli/cli.py` - Extract validation logic, add new helper function\n\n**Lines to extract (from run()):**\n- Lines 407-415: Parse and validate `--only` flag\n- Lines 417-439: Parse and validate `--disable-validations` flag\n- Lines 441-448: Validate `--coverage-threshold` range\n- Lines 450-458: Validate `--epic` and `--orphans-only` mutual exclusivity\n- Lines 460-472: Parse and validate `--epic-override` flag\n- Lines 477-479: Validate repo_path exists\n\n## Implementation\n\nCreate a dataclass or named tuple to hold validated results:\n```python\n@dataclass\nclass ValidatedRunArgs:\n    only_ids: set[str] | None\n    disable_set: set[str] | None\n    epic_override_ids: set[str] | None\n```\n\nCreate helper function:\n```python\ndef _validate_run_args(\n    only: str | None,\n    disable_validations: str | None,\n    coverage_threshold: float | None,\n    epic: str | None,\n    orphans_only: bool,\n    epic_override: str | None,\n    repo_path: Path,\n) -\u003e ValidatedRunArgs:\n    \"\"\"Validate and parse CLI arguments, raising typer.Exit(1) on errors.\"\"\"\n```\n\n## Testing\n\n- Add unit tests for `_validate_run_args()` covering:\n  - Valid inputs return expected parsed values\n  - Invalid `--only` raises Exit(1)\n  - Invalid `--disable-validations` raises Exit(1)\n  - Unknown validation values raise Exit(1)\n  - Coverage threshold out of range raises Exit(1)\n  - Mutual exclusivity of `--epic` and `--orphans-only`\n  - Invalid `--epic-override` raises Exit(1)\n  - Non-existent repo_path raises Exit(1)","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T05:53:32.817337256Z","created_by":"cyou","updated_at":"2026-01-03T07:08:12.914743072Z","closed_at":"2026-01-03T07:08:12.914743072Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-kyq1.1","depends_on_id":"mala-kyq1","type":"parent-child","created_at":"2026-01-03T17:58:44.936935686Z","created_by":"daemon"}]}
{"id":"mala-kyq1.2","title":"Extract _apply_config_overrides() from run() command","description":"## Summary\n\nExtract CLI config override logic from the `run()` function into a dedicated `_apply_config_overrides()` helper function.\n\n## Scope\n\n**Primary files:**\n- `src/cli/cli.py` - Extract config override logic, add new helper function\n\n**Lines to extract (from run()):**\n- Lines 519-565: Apply CLI overrides to MalaConfig via `dataclasses.replace()`\n  - `review_timeout` override\n  - `cerberus_spawn_args` parsing and override\n  - `cerberus_wait_args` parsing and override\n  - `cerberus_env` parsing and override\n  - `max_epic_verification_retries` override\n\n## Implementation\n\nCreate helper function:\n```python\ndef _apply_config_overrides(\n    config: \"MalaConfig\",\n    review_timeout: int | None,\n    cerberus_spawn_args: str | None,\n    cerberus_wait_args: str | None,\n    cerberus_env: str | None,\n    max_epic_verification_retries: int | None,\n) -\u003e \"MalaConfig\":\n    \"\"\"Apply CLI overrides to MalaConfig, raising typer.Exit(1) on parse errors.\"\"\"\n```\n\nThe function should:\n1. Use `dataclasses.replace()` to apply overrides\n2. Parse cerberus args using existing `_parse_cerberus_args()` \n3. Parse cerberus env using existing `_parse_cerberus_env()` and `_normalize_cerberus_env()`\n4. Raise `typer.Exit(1)` with error logging on parse failures\n\n## Testing\n\n- Add unit tests for `_apply_config_overrides()` covering:\n  - No overrides returns config unchanged\n  - Each override type applies correctly\n  - Invalid cerberus_spawn_args raises Exit(1)\n  - Invalid cerberus_wait_args raises Exit(1)\n  - Invalid cerberus_env raises Exit(1)\n\n## Dependencies\n\nThis task depends on mala-kyq1.1 (_validate_run_args) being completed first, as both modify the same file.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T05:53:50.713545663Z","created_by":"cyou","updated_at":"2026-01-03T07:29:55.116110181Z","closed_at":"2026-01-03T07:29:55.116110181Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-kyq1.2","depends_on_id":"mala-kyq1.1","type":"blocks","created_at":"2026-01-03T05:54:46.914327738Z","created_by":"daemon"},{"issue_id":"mala-kyq1.2","depends_on_id":"mala-kyq1","type":"parent-child","created_at":"2026-01-03T17:58:44.945030988Z","created_by":"daemon"}]}
{"id":"mala-kyq1.3","title":"Extract _handle_dry_run() from run() command","description":"## Summary\n\nExtract dry-run handling logic from the `run()` function into a dedicated `_handle_dry_run()` helper function.\n\n## Scope\n\n**Primary files:**\n- `src/cli/cli.py` - Extract dry-run logic, add new helper function\n\n**Lines to extract (from run()):**\n- Lines 481-496: Dry-run mode handling\n  - Async wrapper `_dry_run()` \n  - BeadsClient instantiation\n  - `get_ready_issues_async()` call with filtering params\n  - `display_dry_run_tasks()` call\n  - Early exit with `typer.Exit(0)`\n\n## Implementation\n\nCreate helper function:\n```python\ndef _handle_dry_run(\n    repo_path: Path,\n    epic: str | None,\n    only_ids: set[str] | None,\n    wip: bool,\n    focus: bool,\n    orphans_only: bool,\n) -\u003e NoReturn:\n    \"\"\"Execute dry-run mode: display task order and exit.\"\"\"\n```\n\nThe function should:\n1. Create async inner function to fetch issues\n2. Use `_lazy(\"BeadsClient\")` pattern for lazy import\n3. Call `display_dry_run_tasks()` with fetched issues\n4. Raise `typer.Exit(0)` after displaying\n\n## Testing\n\n- Add unit tests for `_handle_dry_run()` covering:\n  - Calls BeadsClient with correct repo_path\n  - Passes correct filtering parameters to get_ready_issues_async\n  - Calls display_dry_run_tasks with fetched issues\n  - Raises typer.Exit(0)\n\n## Dependencies\n\nThis task can run in parallel with mala-kyq1.1 and mala-kyq1.2 since it extracts a distinct code block.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T05:54:08.371219901Z","created_by":"cyou","updated_at":"2026-01-03T07:36:44.037682251Z","closed_at":"2026-01-03T07:36:44.037682251Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-kyq1.3","depends_on_id":"mala-kyq1.2","type":"blocks","created_at":"2026-01-03T05:54:46.980217219Z","created_by":"daemon"},{"issue_id":"mala-kyq1.3","depends_on_id":"mala-kyq1","type":"parent-child","created_at":"2026-01-03T17:58:44.953077251Z","created_by":"daemon"}]}
{"id":"mala-kyq1.4","title":"Refactor run() as thin orchestrator of extracted helpers","description":"## Summary\n\nRefactor the `run()` function to be a thin orchestrator that delegates to the extracted helper functions, making the main flow clear and testable.\n\n## Scope\n\n**Primary files:**\n- `src/cli/cli.py` - Refactor run() to use extracted helpers\n\n## Implementation\n\nAfter extraction tasks complete, `run()` should become:\n\n```python\ndef run(\n    # ... all typer.Option/Argument parameters unchanged ...\n) -\u003e Never:\n    \"\"\"Run parallel issue processing.\"\"\"\n    set_verbose(verbose)\n    repo_path = repo_path.resolve()\n\n    # Phase 1: Validate CLI arguments\n    validated = _validate_run_args(\n        only=only,\n        disable_validations=disable_validations,\n        coverage_threshold=coverage_threshold,\n        epic=epic,\n        orphans_only=orphans_only,\n        epic_override=epic_override,\n        repo_path=repo_path,\n    )\n\n    # Ensure user config directory exists\n    USER_CONFIG_DIR.mkdir(parents=True, exist_ok=True)\n\n    # Phase 2: Handle dry-run early exit\n    if dry_run:\n        _handle_dry_run(\n            repo_path=repo_path,\n            epic=epic,\n            only_ids=validated.only_ids,\n            wip=wip,\n            focus=focus,\n            orphans_only=orphans_only,\n        )\n\n    # Phase 3: Build and configure MalaConfig\n    config = _lazy(\"MalaConfig\").from_env(validate=False)\n    config = _apply_config_overrides(\n        config=config,\n        review_timeout=review_timeout,\n        cerberus_spawn_args=cerberus_spawn_args,\n        cerberus_wait_args=cerberus_wait_args,\n        cerberus_env=cerberus_env,\n        max_epic_verification_retries=max_epic_verification_retries,\n    )\n\n    # Phase 4: Build cli_args for logging/metadata\n    cli_args = _build_cli_args_metadata(...)  # Optional: could extract this too\n\n    # Phase 5: Construct OrchestratorConfig and run\n    morph_enabled = False if no_morph else None\n    orch_config = _lazy(\"OrchestratorConfig\")(...)\n    orchestrator = _lazy(\"create_orchestrator\")(orch_config, mala_config=config)\n\n    success_count, total = asyncio.run(orchestrator.run())\n    raise typer.Exit(0 if success_count \u003e 0 or total == 0 else 1)\n```\n\n## Goals\n\n- `run()` should be ~50-60 lines (down from 366)\n- Each phase clearly labeled with comments\n- All business logic delegated to testable helpers\n- Main function focuses on orchestration flow\n\n## Testing\n\n- Verify existing CLI tests still pass\n- Add integration test that exercises full run() flow with mocked orchestrator\n\n## Dependencies\n\nThis task depends on all three extraction tasks being completed first:\n- mala-kyq1.1 (_validate_run_args)\n- mala-kyq1.2 (_apply_config_overrides)  \n- mala-kyq1.3 (_handle_dry_run)","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T05:54:33.590127967Z","created_by":"cyou","updated_at":"2026-01-03T07:42:00.672684256Z","closed_at":"2026-01-03T07:42:00.672684256Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-kyq1.4","depends_on_id":"mala-kyq1.3","type":"blocks","created_at":"2026-01-03T05:54:47.027953375Z","created_by":"daemon"},{"issue_id":"mala-kyq1.4","depends_on_id":"mala-kyq1","type":"parent-child","created_at":"2026-01-03T17:58:44.961120373Z","created_by":"daemon"}]}
{"id":"mala-kyq1.5","title":"[Remediation] Config override logic unit-testable without typer","description":"## Context\nThis issue was auto-created by epic verification for epic `mala-kyq1`.\n\n## Epic Description / Acceptance Criteria\nTitle: Epic: Decompose CLI run command (366 lines → \u003c100)\n\n## Summary\nThe `run()` function in `src/cli/cli.py` handles CLI argument parsing, validation, dry-run logic, configuration building, config overrides, OrchestratorConfig construction, and orchestrator invocation all in one 366-line function.\n\n## Primary Files\n- `src/cli/cli.py:241-606`\n\n## Category\nCohesion\n\n## Source\nMultiple generators (gemini, claude)\n\n## Fix Approach\n1. Extract validation into `_validate_run_args()`\n2. Extract config overrides into `_apply_config_overrides(config, cli_overrides)`\n3. Extract dry-run handling into `_handle_dry_run()`\n4. Make `run()` a thin orchestrator of these phases\n5. Consider extracting a `ConfigurationFactory` in `src/orchestration/bootstrap.py` for reuse\n\n## Non-Goals\n- Changing CLI argument names or behavior\n- Splitting into multiple subcommands\n\n## Acceptance Criteria\n- [ ] `run()` function under 100 lines\n- [ ] Config override logic unit-testable without typer\n- [ ] Validation errors have clear test coverage\n\n## Test Plan\n- Unit tests for `_validate_run_args` with various invalid combinations\n- Unit tests for `_apply_config_overrides` with partial overrides\n\n## Agent Notes\nCLI pulls in underscore helpers (`_parse_cerberus_args/_parse_cerberus_env`) from infra; consider exposing public config resolution API instead.\n\n## Commit Scope\n- Range hint: 0252d205b74c57e38f620f1927c78f1a12f3982f^..4f952a08c8ee9845c1667608171549bd16420f2e\n- Commits:\n- 4f952a08c8ee9845c1667608171549bd16420f2e bd-mala-kyq1.4: Refactor run() as thin orchestrator of extracted helpers\n- 5950f146410ec9d0b2ad4bb8c0d479690f7f5d31 bd-mala-kyq1.3: Extract _handle_dry_run() from run() command\n- a270c539b044fb147223b2cc66e4dafc88ac837f bd-mala-kyq1.2: Move replace import to module level\n- b9c6025c56e1bbce43f58e4c0f8b9af7be62d700 bd-mala-kyq1.2: Extract _apply_config_overrides() from run() command\n- 0252d205b74c57e38f620f1927c78f1a12f3982f bd-mala-kyq1.1: Extract _validate_run_args() from run() command\n\n## Child Issues\n- mala-kyq1.1\n- mala-kyq1.2\n- mala-kyq1.3\n- mala-kyq1.4\n\n## Unmet Criterion\nConfig override logic unit-testable without typer\n\n## Evidence\n_apply_config_overrides() function at line 343 still raises typer.Exit(1) on parse errors, creating a direct dependency on typer that prevents pure unit testing without the CLI framework\n\n## Priority\nP1 (blocking)\n\n## Resolution\nAddress this criterion to unblock epic closure. When complete, close this issue.\n","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-03T18:09:16.298520773Z","created_by":"cyou","updated_at":"2026-01-03T18:25:34.989218112Z","closed_at":"2026-01-03T18:25:34.989218112Z","close_reason":"Closed","labels":["auto_generated","epic_remediation:mala-kyq1:6e9b29e42b862ac04a8185a2cfd6fc8bae2eae56d7e4c1770290ce8ffcfdd607"],"dependencies":[{"issue_id":"mala-kyq1.5","depends_on_id":"mala-kyq1","type":"parent-child","created_at":"2026-01-03T18:09:16.299035009Z","created_by":"daemon"}]}
{"id":"mala-kz4","title":"Detect context exhaustion","status":"tombstone","priority":2,"issue_type":"epic","created_at":"2025-12-26T00:55:42.494971406Z","updated_at":"2025-12-26T00:56:32.189479702Z","deleted_at":"2025-12-26T00:56:32.189479702Z","deleted_by":"daemon","delete_reason":"delete","original_type":"epic"}
{"id":"mala-l072","title":"[Review] src/pipeline/issue_execution_coordinator.py:23-31: [P3] Implicit coupling between `spawn_callback` and `register_task`","description":"## Review Finding\n\nThis issue was auto-created from a P3 review finding.\n\n**Source issue:** mala-cavk.3\n**Reviewer:** gemini\n**Location:** src/pipeline/issue_execution_coordinator.py:23-31\n\n## Details\n\nThe `SpawnCallback` protocol returns a `bool`, but the implementation is implicitly required to call `coordinator.register_task` to ensure the coordinator tracks the task. Returning the `asyncio.Task` from the callback (or `None` on failure) would be a more explicit and safer contract.","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-01T22:23:07.477445733Z","created_by":"cyou","updated_at":"2026-01-02T01:58:59.94256398Z","closed_at":"2026-01-02T01:58:59.94256398Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_finding:aa669b2e1d45","source:mala-cavk.3"],"dependencies":[{"issue_id":"mala-l072","depends_on_id":"mala-i8ke","type":"blocks","created_at":"2026-01-01T23:32:50.769263326Z","created_by":"daemon"}]}
{"id":"mala-l10","title":"Codex reviewer reviews stale commit instead of current HEAD","description":"## Bug\n\nIn `src/orchestrator.py:893-898`, the Codex review is called with:\n```python\nreview_result = await run_codex_review(\n    self.repo_path,\n    lifecycle_ctx.last_gate_result.commit_hash,  # \u003c-- BUG: uses the SPECIFIC commit\n    ...\n    baseline_commit=baseline_commit_hash,\n)\n```\n\n**The problem:** When Codex reviews, it looks at `git diff baseline..commit_sha`. But if another agent made a subsequent commit that fixed/reverted the issues, Codex won't see those fixes because it only reviews up to the specific commit, not to the current HEAD.\n\n## Observed in mala-yg9.4\n\n1. Agent `mala-yg9.4` made commit `5b3fc31` with problematic changes to `legacy_runner.py`\n2. Agent `mala-g3h.3` made commit `5f79256` which reverted those changes\n3. Codex review ran for `mala-yg9.4` and reviewed `baseline..5b3fc31`\n4. Commit `5f79256` comes AFTER `5b3fc31`, so it's not included in the diff\n5. Codex kept flagging stale code that was already fixed on disk\n6. Agent verified files were correct but Codex kept failing (5 attempts wasted)\n\n## Fix\n\nChange line 895 to review from baseline to **current HEAD** instead of the specific commit:\n\n```python\n# Get current HEAD for review (includes any subsequent fixes)\ncurrent_head = await get_git_commit_async(self.repo_path)\n\nreview_result = await run_codex_review(\n    self.repo_path,\n    current_head,  # Review up to current HEAD, not the specific commit\n    ...\n    baseline_commit=baseline_commit_hash,\n)\n```\n\n## Acceptance Criteria\n\n- Codex reviews cumulative diff from baseline to current HEAD (not the agent's specific commit)\n- If another agent fixes issues after the first agent's commit, Codex sees the clean cumulative diff\n- Add test case for multi-commit scenario where later commit fixes earlier issues","status":"closed","priority":0,"issue_type":"task","created_at":"2025-12-28T02:17:00.103052285Z","updated_at":"2025-12-28T02:38:26.048150531Z","closed_at":"2025-12-28T02:38:26.048150531Z","close_reason":"Closed"}
{"id":"mala-l1u3","title":"[Review] 2 non-blocking findings from mala-fhm5","description":"## Review Findings\n\nThis issue consolidates 2 non-blocking findings from code review.\n\n**Source issue:** mala-fhm5\n**Highest priority:** P3\n\n---\n\n### Finding 1: [P3] ADDITIVE mode is documented but unreachable\n\n**Priority:** P3\n**Reviewer:** gemini\n**Location:** src/domain/validation/config.py:87\n\nThe `CustomOverrideMode.ADDITIVE` enum member remains with documentation implying support for `+` prefixed keys, but `CommandsConfig.from_dict` now explicitly raises `ConfigError` if a `+` prefix is found. This makes the mode unreachable via configuration.\n\nConsider removing the enum member or updating its docstring to reflect that it is no longer supported.\n\n---\n\n### Finding 2: [P3] Degraded error message for deprecated 'global_validation_commands'\n\n**Priority:** P3\n**Reviewer:** gemini\n**Location:** src/domain/validation/config.py:842-850\n\nThe removal of explicit checks for `global_validation_commands` (and `custom_commands`) means users will now receive a generic \"Unknown field\" error from schema validation instead of the specific deprecation message with migration guidance. This may confuse users who have not yet migrated.\n\n---\n\n\u003c!-- fp:b4f0617831270468 --\u003e\n\u003c!-- fp:5ac974354f867240 --\u003e\n\u003c!-- review_finding:69fa0ce8fcfe --\u003e","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-10T22:02:19.819465353Z","created_by":"cyou","updated_at":"2026-01-10T22:16:11.65237769Z","closed_at":"2026-01-10T22:16:11.65237769Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-fhm5"]}
{"id":"mala-l2i4","title":"Epic: Agent SDK Code Reviewer Integration","description":"Implement AgentSDKReviewer using true Claude Agent SDK with tool access, replacing Cerberus as the default code reviewer. Agent has access to git and file reading tools for interactive exploration.\n\nKey components:\n- Agent-friendly review prompt with tool usage guidance\n- SDK client creation via SDKClientFactory\n- Agent session lifecycle management\n- JSON output parsing\n- Factory wiring for reviewer selection\n\nPlan: /home/cyou/mala/plans/2026-01-07-agent-sdk-reviewer-v2-plan.md\nSpec: /home/cyou/mala/plans/2026-01-07-agent-sdk-reviewer-spec.md","status":"closed","priority":0,"issue_type":"epic","created_at":"2026-01-07T03:36:33.772917844Z","created_by":"cyou","updated_at":"2026-01-07T16:40:03.946479072Z","closed_at":"2026-01-07T16:40:03.946479072Z","close_reason":"Closed"}
{"id":"mala-l2i4.1","title":"[T001] Create agent-friendly review prompt","description":"Create src/prompts/review_agent.md with agent-friendly review instructions and tool usage guidance.\n\n**Goal**: Provide the agent with clear instructions on how to perform code reviews using available tools (git, file reading).\n\n**Context**: Agent SDK agents can interactively explore the codebase using tools. The prompt must guide the agent to:\n- Use git diff to examine changes\n- Read files for context\n- Output JSON review results\n\n**Files to Create**:\n- src/prompts/review_agent.md (New)\n\n**Acceptance Criteria**:\n- Prompt includes tool usage guidance (how to use git diff, file reading)\n- Prompt specifies JSON output schema (verdict + findings with P0-P3 priorities)\n- Prompt adapted from Cerberus review-code.md with agent-specific additions\n\n**Verification**:\n- File exists at src/prompts/review_agent.md\n- File contains JSON output schema specification\n- File includes at least one example of tool usage\n\n**Notes**:\n- Base on Cerberus review prompt but add tool usage examples\n- Emphasize that agent should explore beyond provided diff if needed\n- Include error handling guidance (what to do if git commands fail)","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-07T03:44:36.415022474Z","created_by":"cyou","updated_at":"2026-01-07T04:33:14.616074935Z","closed_at":"2026-01-07T04:33:14.616074935Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-l2i4.1","depends_on_id":"mala-l2i4","type":"parent-child","created_at":"2026-01-07T03:44:36.415669319Z","created_by":"cyou"}]}
{"id":"mala-l2i4.10","title":"[Review] 3 non-blocking findings from mala-l2i4.5","description":"## Review Findings\n\nThis issue consolidates 3 non-blocking findings from code review.\n\n**Source issue:** mala-l2i4.5\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Load configured reviewer_type via mala.yaml (cerberus case)\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** tests/integration/test_agent_sdk_integration.py:95-99\n\nThis test constructs `_ReviewerConfig` directly, so it can pass even if `mala.yaml` parsing/field wiring is broken (the stated goal is “when configured”). Write a minimal `mala.yaml` and call `_get_reviewer_config(tmp_path)` so the test traverses the full config→factory path.\n\n```python\n(tmp_path / \"mala.yaml\").write_text(\"reviewer_type: cerberus\\n\")\nreviewer_config = _get_reviewer_config(tmp_path)\n```\n\n---\n\n### Finding 2: [P2] Load configured reviewer_type via mala.yaml (agent_sdk case)\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** tests/integration/test_agent_sdk_integration.py:134-141\n\nSame issue as the cerberus test: building `_ReviewerConfig` directly doesn’t validate the real “configured via mala.yaml” wiring for `reviewer_type`, `agent_sdk_review_timeout`, and `agent_sdk_reviewer_model`. Create `mala.yaml` with these fields and use `_get_reviewer_config(tmp_path)`.\n\n```python\n(tmp_path / \"mala.yaml\").write_text(\"reviewer_type: agent_sdk\\nagent_sdk_review_timeout: 900\\nagent_sdk_reviewer_model: opus\\n\")\nreviewer_config = _get_reviewer_config(tmp_path)\n```\n\n---\n\n### Finding 3: [P2] Use directory path semantics for cerberus_bin_path in test\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** tests/integration/test_agent_sdk_integration.py:100-105\n\n`cerberus_bin_path` is treated as a directory containing `review-gate` (code appends `bin_path / \"review-gate\"`). Using `Path(\"/usr/bin/review-gate\")` is misleading and would produce `/usr/bin/review-gate/review-gate` at runtime; it can mask wiring mistakes. Use a directory-like value (and update the assertion) to match real behavior.\n\n```python\nmala_config.cerberus_bin_path = Path(\"/usr/bin\")\nassert reviewer.bin_path == Path(\"/usr/bin\")\n```\n\n---\n\n\u003c!-- fp:cc5fa1e27bdded60 --\u003e\n\u003c!-- fp:fb26700513db5dd1 --\u003e\n\u003c!-- fp:d58a69628f098dae --\u003e\n\u003c!-- review_finding:66b768571cf5 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-07T15:27:15.060791033Z","created_by":"cyou","updated_at":"2026-01-07T15:32:02.768132992Z","closed_at":"2026-01-07T15:32:02.768132992Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-l2i4.5"],"dependencies":[{"issue_id":"mala-l2i4.10","depends_on_id":"mala-l2i4","type":"parent-child","created_at":"2026-01-07T15:27:15.06131457Z","created_by":"cyou"}]}
{"id":"mala-l2i4.11","title":"[Review] 1 non-blocking finding from mala-l2i4.6","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-l2i4.6\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Skip when ANTHROPIC_API_KEY is missing\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** tests/e2e/test_agent_sdk_e2e.py:34-42\n\nThe task explicitly requires skipping if `ANTHROPIC_API_KEY` is not set (and the E2E is intended to validate the real SDK flow with an API key). `_skip_if_no_auth()` currently allows OAuth-only runs (`claude` CLI + credentials), so the test can run (and incur API cost) even when `ANTHROPIC_API_KEY` is unset and may not actually validate the API-key auth path.\n\nConsider changing the guard to skip unless `ANTHROPIC_API_KEY` is present, or splitting into distinct tests for API-key vs OAuth auth modes.\n\n---\n\n\u003c!-- fp:d8daecac44454b2d --\u003e\n\u003c!-- review_finding:4d44acad7175 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-07T15:46:09.375243892Z","created_by":"cyou","updated_at":"2026-01-07T15:48:36.107004527Z","closed_at":"2026-01-07T15:48:36.107004527Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-l2i4.6"],"dependencies":[{"issue_id":"mala-l2i4.11","depends_on_id":"mala-l2i4","type":"parent-child","created_at":"2026-01-07T15:46:09.375781577Z","created_by":"cyou"}]}
{"id":"mala-l2i4.12","title":"[T012] Re-enable agent_sdk review availability + coverage","description":"Goal: Allow AgentSDKReviewer to run by default and ensure review is not auto-disabled when reviewer_type=agent_sdk.\\n\\nScope\\n- Update review availability check so agent_sdk is treated as available (no forced disable).\\n- Add/adjust tests to validate review remains enabled for agent_sdk and disabled only for real errors.\\n\\nFiles to edit\\n- src/orchestration/factory.py (review availability logic)\\n- tests/unit/orchestration/... or tests/integration/... (add coverage)\\n\\nImplementation notes\\n- Remove the hardcoded 'agent_sdk reviewer not yet implemented' branch.\\n- Keep the existing cerberus availability checks unchanged.\\n- If unknown reviewer_type, still disable with a warning.\\n\\nAcceptance criteria\\n- create_orchestrator leaves review enabled when reviewer_type=agent_sdk and review is not explicitly disabled.\\n- A unit/integration test asserts review is enabled for agent_sdk (and still disabled for missing cerberus binary when reviewer_type=cerberus).\\n- No change to cerberus availability behavior.\\n- Existing tests still pass.\\n","notes":"Quality gate failed: Command failed with exit code -2 (exit code: -2)\nError output: Check stderr output for details","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-07T16:01:00.91507409Z","created_by":"cyou","updated_at":"2026-01-07T16:35:23.164480956Z","closed_at":"2026-01-07T16:35:23.164480956Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-l2i4.12","depends_on_id":"mala-l2i4","type":"parent-child","created_at":"2026-01-07T16:01:00.915611205Z","created_by":"cyou"}]}
{"id":"mala-l2i4.13","title":"[T013] Align Agent SDK review schema end-to-end","description":"Goal: Make prompt, query instructions, and parser agree on a single JSON schema and priority rules so reviews parse reliably.\\n\\nScope\\n- Choose a single schema (recommended: consensus_verdict + aggregated_findings to match ReviewOutputParser conventions) OR support both schemas explicitly.\\n- Ensure priorities allow P0-P3 with integer mapping only (no strings unless you intentionally support both).\\n- Update query template and parser validation accordingly.\\n- Update tests to cover the agreed schema (valid + invalid cases).\\n\\nFiles to edit\\n- src/prompts/review_agent.md (instructions and example schema)\\n- src/infra/clients/agent_sdk_review.py (query template + parser validation)\\n- tests/unit/infra/test_agent_sdk_review.py (schema tests)\\n\\nImplementation notes\\n- If you keep consensus_verdict/aggregated_findings: update review_agent.md to match and remove conflicting verdict/findings fields.\\n- If you support both schemas: parser should accept verdict/findings and map to ReviewResult; tests must cover both.\\n- Ensure output instructions are consistent about JSON fences vs raw JSON.\\n\\nAcceptance criteria\\n- Prompt, query, and parser are consistent about field names and verdict values.\\n- P0 findings are supported (priority=0 or converted from P0 if allowed).\\n- Parser returns parse_error for missing required fields per spec, not silently coerced values.\\n- Unit tests cover: valid PASS, valid FAIL with P0, malformed JSON, missing fields, wrong field names.\\n","notes":"Quality gate failed: Interrupted: Interrupted by user\nLog: /home/cyou/.claude/projects/-home-cyou-mala/e40ddd08-e2a8-4af4-b819-8ae34aa0dd7d.jsonl","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-07T16:01:15.732665973Z","created_by":"cyou","updated_at":"2026-01-07T16:36:58.158160131Z","closed_at":"2026-01-07T16:36:58.158160131Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-l2i4.13","depends_on_id":"mala-l2i4","type":"parent-child","created_at":"2026-01-07T16:01:15.733185929Z","created_by":"cyou"}]}
{"id":"mala-l2i4.14","title":"[T014] Document reviewer config in project-config","description":"Goal: Update docs so users can configure Agent SDK vs Cerberus reviewer in mala.yaml.\\n\\nScope\\n- Add a \"Code review configuration\" section in docs/project-config.md.\\n- Document reviewer_type (agent_sdk default, cerberus optional), agent_sdk_review_timeout, agent_sdk_reviewer_model.\\n- Provide YAML examples and clarify when to use each reviewer.\\n\\nFiles to edit\\n- docs/project-config.md\\n\\nAcceptance criteria\\n- reviewer_type, agent_sdk_review_timeout, agent_sdk_reviewer_model are documented with defaults and valid values.\\n- Example YAML snippet is valid and matches actual config parsing.\\n- Notes mention Agent SDK is default and Cerberus requires plugin installation.\\n","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-07T16:01:29.372107503Z","created_by":"cyou","updated_at":"2026-01-07T16:31:00.272946204Z","closed_at":"2026-01-07T16:31:00.272946204Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-l2i4.14","depends_on_id":"mala-l2i4","type":"parent-child","created_at":"2026-01-07T16:01:29.372631879Z","created_by":"cyou"}]}
{"id":"mala-l2i4.15","title":"[T015] Deprecate or reconcile old agent-sdk reviewer plan","description":"Goal: Remove or clearly mark the outdated plan doc that conflicts with the current spec/implementation.\\n\\nScope\\n- Identify outdated plan content in plans/2026-01-07-agent-sdk-reviewer-plan.md.\\n- Either update it to match the v2 plan/spec or add a clear deprecation note pointing to the v2 plan/spec.\\n\\nFiles to edit\\n- plans/2026-01-07-agent-sdk-reviewer-plan.md\\n\\nAcceptance criteria\\n- The older plan is either updated to match current design or explicitly deprecated with a pointer to the authoritative plan/spec.\\n- No conflicting instructions remain that suggest a different implementation (Messages API, review.md prompt, etc.).\\n","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-07T16:01:42.640371233Z","created_by":"cyou","updated_at":"2026-01-07T16:28:01.442769909Z","closed_at":"2026-01-07T16:28:01.442769909Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-l2i4.15","depends_on_id":"mala-l2i4","type":"parent-child","created_at":"2026-01-07T16:01:42.640901579Z","created_by":"cyou"}]}
{"id":"mala-l2i4.2","title":"[T002] Add config fields for reviewer selection","description":"Add configuration fields to ValidationConfig and PromptProvider for Agent SDK reviewer.\n\n**Goal**: Enable configuration of reviewer type and Agent SDK-specific settings.\n\n**Context**: Users need to choose between Agent SDK (default) and Cerberus reviewers, and configure timeout/model for Agent SDK.\n\n**Files to Modify**:\n- src/domain/validation/config.py (Add reviewer_type, agent_sdk_review_timeout, agent_sdk_reviewer_model)\n- src/domain/prompts.py (Add review_agent_prompt field to PromptProvider)\n\n**Wiring Map**:\n- mala.yaml → ValidationConfig.reviewer_type → Factory reviewer selection\n- mala.yaml → ValidationConfig.agent_sdk_review_timeout → AgentSDKReviewer.default_timeout\n- mala.yaml → ValidationConfig.agent_sdk_reviewer_model → AgentSDKReviewer.model\n- src/prompts/review_agent.md → PromptProvider.review_agent_prompt → AgentSDKReviewer constructor\n\n**Acceptance Criteria**:\n- ValidationConfig has reviewer_type field (default: 'agent_sdk', options: 'agent_sdk' | 'cerberus')\n- ValidationConfig has agent_sdk_review_timeout field (default: 600)\n- ValidationConfig has agent_sdk_reviewer_model field (default: 'sonnet' - SDK short name format)\n- PromptProvider has review_agent_prompt field with empty string default (backwards compat)\n- load_prompts() loads review_agent.md into review_agent_prompt\n\n**Verification**:\n- Run: pytest tests/unit/domain/test_config.py -k validation (if exists)\n- Verify ValidationConfig can be instantiated with new fields\n- Verify PromptProvider.review_agent_prompt loaded from file\n- **Config override test**: At least one unit test proving non-default reviewer_type reaches runtime\n\n**Notes**:\n- Use Literal['agent_sdk', 'cerberus'] for type hints\n- Model uses SDK short name format: 'sonnet', 'opus', 'haiku' (NOT full model IDs)\n- Add backwards compatibility: review_agent_prompt defaults to empty string\n- Update load_prompts() in src/domain/prompts.py to load review_agent.md","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-07T03:44:56.787467092Z","created_by":"cyou","updated_at":"2026-01-07T04:32:06.407114195Z","closed_at":"2026-01-07T04:32:06.407114195Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-l2i4.2","depends_on_id":"mala-l2i4","type":"parent-child","created_at":"2026-01-07T03:44:56.788088537Z","created_by":"cyou"}]}
{"id":"mala-l2i4.3","title":"[T003] AgentSDKReviewer skeleton + factory wiring","description":"Create AgentSDKReviewer skeleton class AND wire it into factory so integration tests can work.\n\n**Goal**: Establish compile-ready skeleton and factory wiring in one task, so downstream tasks can write tests against real factory.\n\n**Context**: TDD approach requires factory to know about AgentSDKReviewer before integration tests can run. This task combines skeleton creation with factory wiring.\n\n**Files to Create**:\n- src/infra/clients/agent_sdk_review.py (Skeleton AgentSDKReviewer class with stubs)\n\n**Files to Modify**:\n- src/orchestration/factory.py (Wire AgentSDKReviewer into reviewer selection logic)\n\n**Skeleton Requirements**:\n- AgentSDKReviewer @dataclass with all fields from plan\n- __call__ method stub returning NotImplementedError or empty ReviewResult\n- Helper method stubs (_check_diff_empty, _load_context, _create_review_query, _run_agent_session, _parse_response)\n- overrides_disabled_setting() method returning False\n\n**Factory Wiring Requirements**:\n- Import AgentSDKReviewer from src.infra.clients.agent_sdk_review\n- Read reviewer_type from config (default: 'agent_sdk')\n- Create AgentSDKReviewer for agent_sdk, DefaultReviewer for cerberus\n- Pass required dependencies: repo_path, review_agent_prompt, sdk_client_factory, event_sink, model, default_timeout\n\n**Acceptance Criteria**:\n- Project builds/compiles (no import errors)\n- Imports succeed: from src.infra.clients.agent_sdk_review import AgentSDKReviewer\n- Factory creates AgentSDKReviewer when reviewer_type='agent_sdk' or not set\n- Factory creates DefaultReviewer when reviewer_type='cerberus'\n- Skeleton __call__ raises NotImplementedError (implementation in T004)\n\n**Verification**:\n- Run: python -c 'from src.infra.clients.agent_sdk_review import AgentSDKReviewer; print(AgentSDKReviewer)'\n- Run: python -c 'from src.orchestration.factory import ...' (verify no import errors)\n- Type check passes: uvx ty check src/infra/clients/agent_sdk_review.py\n\n**Notes**:\n- DO NOT implement real logic yet - just stubs\n- Factory wiring must be complete so T005 integration tests can work\n- Use getattr() for backwards compatibility with config fields","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-07T03:45:19.548083615Z","created_by":"cyou","updated_at":"2026-01-07T05:07:49.455701586Z","closed_at":"2026-01-07T05:07:49.455701586Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-l2i4.3","depends_on_id":"mala-l2i4","type":"parent-child","created_at":"2026-01-07T03:45:19.54876881Z","created_by":"cyou"},{"issue_id":"mala-l2i4.3","depends_on_id":"mala-l2i4.1","type":"blocks","created_at":"2026-01-07T03:47:13.837181312Z","created_by":"cyou"},{"issue_id":"mala-l2i4.3","depends_on_id":"mala-l2i4.2","type":"blocks","created_at":"2026-01-07T03:47:18.945794912Z","created_by":"cyou"}]}
{"id":"mala-l2i4.4","title":"[T004] Implement AgentSDKReviewer core logic with unit tests","description":"Implement AgentSDKReviewer core logic following TDD: write failing unit tests, implement code.\n\n**Goal**: Turn skeleton into working implementation with comprehensive unit test coverage (12 tests).\n\n**Context**: This is the main implementation task. Follow TDD: unit test → code.\n\n**Files to Modify**:\n- src/infra/clients/agent_sdk_review.py (Implement all methods)\n\n**Files to Create**:\n- tests/unit/infra/test_agent_sdk_review.py (Comprehensive unit tests)\n\n**Implementation Requirements**:\n1. _check_diff_empty: Check git diff --stat output (for diff_range AND commit_shas)\n2. _load_context: Async file loading with asyncio.to_thread\n3. _create_review_query: Combine prompt + diff range + context\n4. _run_agent_session: SDK client lifecycle (create, query, stream, extract JSON)\n5. _parse_response: Robust JSON extraction with fallbacks\n6. __call__: Main flow with error handling and telemetry\n\n**Unit Test Requirements** (12 test cases):\n1. test_empty_diff_skips_agent_session\n2. test_empty_commit_sha_skips_agent_session (NEW)\n3. test_successful_review_pass\n4. test_successful_review_fail\n5. test_successful_review_needs_work\n6. test_malformed_json_response\n7. test_timeout_handling\n8. test_sdk_client_creation_failure\n9. test_context_file_loading\n10. test_priority_conversion (string 'P1' and int 1)\n11. test_telemetry_warning_on_error (on_review_warning for non-fatal errors)\n12. test_overrides_disabled_setting_returns_false (NEW)\n\n**Acceptance Criteria**:\n- All 12 unit tests pass\n- Empty diff short-circuits before agent session (both diff_range and commit_shas)\n- JSON parsing handles code blocks and fallback extraction\n- Telemetry emits on_review_warning for non-fatal errors (matches DefaultReviewer pattern)\n- ReviewResult includes review_log_path set to SDK session log\n- overrides_disabled_setting() returns False\n\n**Verification**:\n- Run: pytest tests/unit/infra/test_agent_sdk_review.py -v (all 12 pass)\n- Coverage: pytest --cov=src.infra.clients.agent_sdk_review tests/unit/infra/test_agent_sdk_review.py\n\n**Notes**:\n- Mock SDKClientFactory in unit tests\n- Use asyncio.to_thread for file I/O\n- Priority conversion: int(priority_raw[1:]) if starts with 'P' else int(priority_raw)\n- Model uses SDK short name format ('sonnet', not full model ID)\n- Set review_log_path to SDK client session log path","notes":"Quality gate failed: External review failed: src/infra/clients/agent_sdk_review.py:265: [P1] Ensure SDK client is disconnected after session\nLog: /home/cyou/.claude/projects/-home-cyou-mala/f18d5963-82db-4a88-9cb8-5296013e941a.jsonl","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-07T03:45:42.708432352Z","created_by":"cyou","updated_at":"2026-01-07T15:17:21.305554569Z","closed_at":"2026-01-07T15:17:21.305554569Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-l2i4.4","depends_on_id":"mala-l2i4","type":"parent-child","created_at":"2026-01-07T03:45:42.709027568Z","created_by":"cyou"},{"issue_id":"mala-l2i4.4","depends_on_id":"mala-l2i4.3","type":"blocks","created_at":"2026-01-07T03:47:24.06024983Z","created_by":"cyou"}]}
{"id":"mala-l2i4.5","title":"[T005] [integration-path-test] Factory and ReviewRunner integration tests","description":"Add integration tests for factory wiring and ReviewRunner.\n\n**Goal**: Verify end-to-end wiring works: factory creates correct reviewer, ReviewRunner can call it.\n\n**Context**: Factory wiring was done in T003 (skeleton). Now test that wiring works correctly with real factory and mocked SDK.\n\n**Files to Create**:\n- tests/integration/test_agent_sdk_integration.py\n\n**Integration Test Requirements** (4 tests, all MUST TRAVERSE FACTORY PATH):\n1. test_factory_creates_agent_sdk_reviewer_by_default: Verify factory creates AgentSDKReviewer when no config\n2. test_factory_creates_cerberus_reviewer_when_configured: Verify factory creates DefaultReviewer with reviewer_type=cerberus\n3. test_factory_creates_agent_sdk_reviewer_when_configured: Verify factory creates AgentSDKReviewer with reviewer_type=agent_sdk\n4. test_review_runner_integration: Verify ReviewRunner → AgentSDKReviewer → ReviewResult flow with mocked SDK\n\n**Acceptance Criteria**:\n- All 4 integration tests pass\n- Tests traverse full factory → reviewer creation path (not direct instantiation)\n- ReviewRunner integration test demonstrates full flow works\n- Tests use real factory but mock SDK client (no network calls)\n\n**Verification**:\n- Run: pytest tests/integration/test_agent_sdk_integration.py -v (all pass)\n- Run: pytest tests/unit/infra/test_agent_sdk_review.py tests/integration/test_agent_sdk_integration.py -v (all pass)\n- **Final check**: ALL tests (unit + integration) pass\n\n**Notes**:\n- Integration tests require T003 (factory wiring) and T004 (implementation) to be complete\n- Mock SDK client's query() and receive_response() methods\n- Verify correct reviewer type is instantiated, not just that something is created\n- Use isinstance() checks to verify reviewer type","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-07T03:46:13.558218068Z","created_by":"cyou","updated_at":"2026-01-07T15:27:14.774970039Z","closed_at":"2026-01-07T15:27:14.774970039Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-l2i4.5","depends_on_id":"mala-l2i4","type":"parent-child","created_at":"2026-01-07T03:46:13.558866524Z","created_by":"cyou"},{"issue_id":"mala-l2i4.5","depends_on_id":"mala-l2i4.4","type":"blocks","created_at":"2026-01-07T03:47:29.167753999Z","created_by":"cyou"}]}
{"id":"mala-l2i4.6","title":"[T006] Add E2E test with real Agent SDK","description":"Create E2E test that runs AgentSDKReviewer with real Agent SDK client (requires API key).\n\n**Goal**: Verify AgentSDKReviewer works end-to-end with real Agent SDK, not just mocks.\n\n**Context**: Unit and integration tests use mocks. E2E test validates actual agent behavior with tools.\n\n**Files to Create**:\n- tests/e2e/test_agent_sdk_e2e.py\n\n**Test Requirements**:\n- Mark with @pytest.mark.e2e\n- Skip if ANTHROPIC_API_KEY not set\n- Create small test diff (1-2 file changes)\n- Run AgentSDKReviewer with real SDK client\n- Verify ReviewResult structure (passed field, issues list, no parse_error)\n- Verify agent used tools (check session logs for git diff tool usage)\n- Clean up test files/repos after execution\n\n**Acceptance Criteria**:\n- test_real_agent_review_flow passes with ANTHROPIC_API_KEY set\n- Test skipped gracefully if API key missing\n- Test uses minimal API calls (keep costs low)\n- Test verifies ReviewResult structure is valid\n- Test confirms agent executed at least one tool call (git or file reading)\n\n**Verification**:\n- Run: ANTHROPIC_API_KEY=xxx pytest tests/e2e/test_agent_sdk_e2e.py -v -m e2e\n- Test passes with real API\n- Run: pytest tests/e2e/test_agent_sdk_e2e.py -v\n- Test skipped if no API key\n\n**Notes**:\n- Use small, cheap test cases to minimize API costs\n- Consider using pytest.skip if API key not available\n- Test may take 30-60 seconds due to agent exploration\n- Verify session log path in ReviewResult.review_log_path\n","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-07T03:46:33.859698639Z","created_by":"cyou","updated_at":"2026-01-07T15:46:09.08868231Z","closed_at":"2026-01-07T15:46:09.08868231Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-l2i4.6","depends_on_id":"mala-l2i4","type":"parent-child","created_at":"2026-01-07T03:46:33.860306325Z","created_by":"cyou"},{"issue_id":"mala-l2i4.6","depends_on_id":"mala-l2i4.5","type":"blocks","created_at":"2026-01-07T03:47:34.274204185Z","created_by":"cyou"}]}
{"id":"mala-l2i4.7","title":"[T007] Update project configuration documentation","description":"Update docs/project-config.md with new reviewer_type configuration option and Agent SDK settings.\n\n**Goal**: Document the new reviewer configuration for users.\n\n**Context**: Users need to know how to configure reviewer type and Agent SDK-specific settings.\n\n**Files to Modify**:\n- docs/project-config.md\n\n**Documentation Requirements**:\n- Add reviewer_type field documentation (default: agent_sdk, options: agent_sdk | cerberus)\n- Add agent_sdk_review_timeout field (default: 600 seconds)\n- Add agent_sdk_reviewer_model field (default: 'sonnet' - SDK short name format)\n- Explain when to use Agent SDK vs Cerberus\n- Provide example mala.yaml configuration\n- Note that Agent SDK is the default (no Cerberus plugin required)\n\n**Example Configuration**:\n```yaml\n# Use Agent SDK reviewer (default)\nreviewer_type: agent_sdk\nagent_sdk_review_timeout: 600\nagent_sdk_reviewer_model: sonnet  # SDK short names: sonnet, opus, haiku\n\n# Or use Cerberus reviewer\nreviewer_type: cerberus\n```\n\n**Acceptance Criteria**:\n- docs/project-config.md includes reviewer_type documentation\n- Documentation explains differences between agent_sdk and cerberus\n- Example configuration provided with correct model format\n- Default values documented (600s timeout, 'sonnet' model)\n\n**Verification**:\n- Read updated docs/project-config.md\n- Verify all new config fields documented\n- Verify example configuration is valid YAML\n\n**Notes**:\n- Emphasize that agent_sdk is default (no setup required)\n- Note that Agent SDK reviews may be slower but more thorough (agent explores with tools)\n- Document that Cerberus requires plugin installation\n- Model uses SDK short name format, NOT full model IDs","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-07T03:46:51.612713248Z","created_by":"cyou","updated_at":"2026-01-07T16:30:36.349343638Z","closed_at":"2026-01-07T16:30:36.349343638Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-l2i4.7","depends_on_id":"mala-l2i4","type":"parent-child","created_at":"2026-01-07T03:46:51.613378593Z","created_by":"cyou"},{"issue_id":"mala-l2i4.7","depends_on_id":"mala-l2i4.5","type":"blocks","created_at":"2026-01-07T03:47:39.380498931Z","created_by":"cyou"}]}
{"id":"mala-l2i4.8","title":"[Review] 1 non-blocking finding from mala-l2i4.2","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-l2i4.2\n**Highest priority:** P3\n\n---\n\n### Finding 1: [P3] Fix misleading reviewer merge comment\n\n**Priority:** P3\n**Reviewer:** codex\n**Location:** src/domain/validation/config_merger.py:196-197\n\nIn `merge_configs`, the comment says reviewer fields “user always takes precedence (presets cannot define them)” and “otherwise use defaults”, but the implementation explicitly *inherits from `preset.*` when the user didn’t set the field. This is easy to misread and could cause a future change to incorrectly drop preset inheritance. Update the comment to match the actual merge semantics (user overrides; otherwise inherit from preset).\n\n---\n\n\u003c!-- fp:e654892249cc6fa7 --\u003e\n\u003c!-- review_finding:368935bea53c --\u003e","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-07T04:32:06.691725021Z","created_by":"cyou","updated_at":"2026-01-07T04:40:39.929555583Z","closed_at":"2026-01-07T04:40:39.929555583Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-l2i4.2"],"dependencies":[{"issue_id":"mala-l2i4.8","depends_on_id":"mala-l2i4","type":"parent-child","created_at":"2026-01-07T04:32:06.692371757Z","created_by":"cyou"}]}
{"id":"mala-l2i4.9","title":"[Review] 3 non-blocking findings from mala-l2i4.3","description":"## Review Findings\n\nThis issue consolidates 3 non-blocking findings from code review.\n\n**Source issue:** mala-l2i4.3\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] ValidationConfig is loaded twice during reviewer creation\n\n**Priority:** P2\n**Reviewer:** claude\n**Location:** src/orchestration/factory.py:220-232\n\n`_get_reviewer_type()` and `_create_code_reviewer()` both call `load_config()` to read mala.yaml. Consider passing the reviewer_type to `_create_code_reviewer()` or caching the config to avoid redundant I/O and parsing.\n\n---\n\n### Finding 2: [P2] Avoid loading ValidationConfig twice\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/orchestration/factory.py:254-268\n\n`create_orchestrator()` loads `mala.yaml` via `_get_reviewer_type()`, and `_create_code_reviewer()` loads `mala.yaml` again to read `reviewer_type`/timeouts/models. This can double I/O and warnings, and can diverge if the config changes between reads; consider loading once and threading the resolved reviewer settings into both the availability check and reviewer construction.\n\n---\n\n### Finding 3: [P3] Fix stale availability-check comment\n\n**Priority:** P3\n**Reviewer:** codex\n**Location:** src/orchestration/factory.py:494-498\n\nThe comment says reviewer_type is passed so `agent_sdk` “doesn't require Cerberus”, but `_check_review_availability()` currently disables `agent_sdk` outright until T004. Update the comment to reflect the current behavior to avoid confusing future changes.\n\n---\n\n\u003c!-- fp:f2a99ef44480abaa --\u003e\n\u003c!-- fp:1383aa3c93141937 --\u003e\n\u003c!-- fp:7aebf31d960effc7 --\u003e\n\u003c!-- review_finding:fb7bb0fd3c68 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-07T05:07:49.743103046Z","created_by":"cyou","updated_at":"2026-01-07T05:13:09.707770184Z","closed_at":"2026-01-07T05:13:09.707770184Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-l2i4.3"],"dependencies":[{"issue_id":"mala-l2i4.9","depends_on_id":"mala-l2i4","type":"parent-child","created_at":"2026-01-07T05:07:49.743735223Z","created_by":"cyou"}]}
{"id":"mala-l2xt","title":"[Review] 2 non-blocking findings from mala-vjb9","description":"## Review Findings\n\nThis issue consolidates 2 non-blocking findings from code review.\n\n**Source issue:** mala-vjb9\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Orphan .meta files can remain if lock is stolen or deleted externally\n\n**Priority:** P2\n**Reviewer:** claude\n**Location:** src/infra/tools/locking.py:267-269\n\nThe `.meta` file is written after the atomic hardlink succeeds, which is correct. However, if an external process deletes just the `.lock` file (e.g., manual cleanup, or a different tool), the `.meta` file will remain orphaned. While `get_all_locks()` only iterates over `*.lock` files so this won't cause functional issues, the orphaned `.meta` files will accumulate in the lock directory. Consider adding a cleanup sweep in `get_all_locks()` or documenting this as expected behavior.\n\n---\n\n### Finding 2: [P2] Handle .meta write failures without leaving stale locks\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/infra/tools/locking.py:265-270\n\n`try_lock` now writes a companion .meta file after creating the lock. If `meta_path.write_text(...)` raises (disk full, permission, etc.), the `OSError` handler returns `False` but the lock file has already been created via `os.link`. That leaves an orphaned lock and the caller believes the lock was not acquired. Either ignore metadata-write failures (return `True` and proceed) or remove the lock file before returning `False` so state is consistent.\n\n---\n\n\u003c!-- fp:5328a77e57796d71 --\u003e\n\u003c!-- fp:cbc62ca0b41baa03 --\u003e\n\u003c!-- review_finding:922decc553ad --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T21:11:18.840818273Z","created_by":"cyou","updated_at":"2026-01-03T21:14:47.970974864Z","closed_at":"2026-01-03T21:14:47.970974864Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-vjb9"]}
{"id":"mala-l5s1","title":"improve logging -p 1","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-31T17:12:40.21272827Z","created_by":"cyou","updated_at":"2026-01-01T21:40:07.287229488Z","closed_at":"2026-01-01T21:40:07.287229488Z","close_reason":"Closed"}
{"id":"mala-l7r","title":"Support epic filter for getting ready tasks","description":"Goal: Allow specifying a beads epic ID to filter ready tasks, so only tasks that are children of that epic are returned.\n\nScope/files: src/beads_client.py, src/cli.py (or src/orchestrator.py if it exists).\n\nWork:\n- Add optional epic_id parameter to BeadsClient.get_ready() method.\n- Filter returned issues to only include those with a parent-child dependency on the specified epic.\n- Add --epic CLI option to 'mala run' command to pass epic filter.\n- Update orchestrator to pass epic filter to BeadsClient.\n\nAcceptance criteria:\n- 'mala run --epic mala-c01' only processes tasks that are children of epic mala-c01.\n- Without --epic flag, behavior unchanged (all ready tasks).\n- Epic itself is still excluded from ready tasks (existing behavior).\n- Tests cover epic filter logic.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-26T00:17:16.892104528Z","updated_at":"2025-12-26T00:48:18.823402862Z","closed_at":"2025-12-26T00:48:18.823402862Z","close_reason":"Closed"}
{"id":"mala-ldd1","title":"[Review] 1 non-blocking finding from mala-idx6.9","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-idx6.9\n**Highest priority:** P3\n\n---\n\n### Finding 1: [P3] Remove unused log_provider fixture from unrelated tests\n\n**Priority:** P3\n**Reviewer:** claude\n**Location:** tests/test_orchestrator.py:1840-1841\n\nMultiple test functions in `test_orchestrator.py` received the `log_provider` fixture parameter but never use it. Examples include `TestSubprocessTerminationOnTimeout`, `TestGetMcpServers`, and several `TestOrchestratorFactory` tests. These tests don't instantiate `QualityGate` and don't need the fixture. While harmless, unused fixtures add noise and may confuse future maintainers about which tests actually depend on log parsing.\n\n---\n","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-03T14:49:00.38311077Z","created_by":"cyou","updated_at":"2026-01-03T15:21:11.646462183Z","closed_at":"2026-01-03T15:21:11.646462183Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_findings:cea48251bfd4","source:mala-idx6.9"]}
{"id":"mala-ljbk","title":"[Review] 1 non-blocking finding from mala-47kd.6","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-47kd.6\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Empty string default for fixer_prompt may cause silent failures\n\n**Priority:** P2\n**Reviewer:** claude\n**Location:** src/pipeline/run_coordinator.py:77\n\nThe default value `fixer_prompt: str = \"\"` means if `RunCoordinatorConfig` is constructed without explicitly passing `fixer_prompt`, the fixer agent will receive an empty prompt. This could happen in tests or alternative construction paths. Consider either removing the default (making it required), or using a sentinel that raises an error if used without initialization.\n\n---\n","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T16:46:37.298369949Z","created_by":"cyou","updated_at":"2026-01-03T16:51:03.131222973Z","closed_at":"2026-01-03T16:51:03.131222973Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_findings:34d922e8c9dd","source:mala-47kd.6"]}
{"id":"mala-lm0","title":"Refactor: extract JSONLLogger","description":"## Context\n`JSONLLogger` currently lives in `src/main.py`. It should be moved into `src/logging/jsonl.py` to isolate logging and reduce `main.py` surface area.\n\n## Scope\nCreate the new module only. Do **not** modify `src/main.py` in this issue; integration will wire it up later.\n\n### Files\n- Add: `src/logging/jsonl.py`\n\n## Requirements\n- `JSONLLogger` behavior must remain unchanged (file path, JSONL structure, timestamps, flush behavior).\n- Use the same `JSONL_LOG_DIR` constant from `src/tools/env.py` (so this issue depends on the env module existing).\n\n## Acceptance Criteria\n- `src/logging/jsonl.py` exists and matches current JSONLLogger behavior.\n- No other files are modified in this issue.\n\n## Notes\n- Integration issue will update imports in `src/main.py`.\n","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-25T19:54:51.153849001Z","updated_at":"2025-12-25T20:11:41.797668608Z","closed_at":"2025-12-25T20:11:41.797668608Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-lm0","depends_on_id":"mala-9ki","type":"blocks","created_at":"2025-12-25T20:06:47.457341927Z","created_by":"daemon"}]}
{"id":"mala-lnsm","title":"add to config: file patterns that agents are not allowed to edit.","status":"tombstone","priority":2,"issue_type":"feature","created_at":"2026-01-03T20:33:10.768856879Z","created_by":"cyou","updated_at":"2026-01-06T22:52:10.761723406Z","deleted_at":"2026-01-06T22:52:10.761723406Z","deleted_by":"batch delete","delete_reason":"batch delete","original_type":"feature"}
{"id":"mala-lo9","title":"Add quality gate for commits + validation evidence","description":"Context:\n- Track A4 requires verifying commit message contains bd-\u003cissue_id\u003e and that validation checks ran (docs/coordination-plan-codex.md:38-41).\n- Orchestrator currently marks success solely by bd status closed (src/orchestrator.py:180-209).\n\nScope:\n- In: add a post-run quality gate that checks for a commit message containing bd-\u003cissue_id\u003e.\n- In: verify validation commands ran by parsing JSONL logs (or by rerunning in orchestrator if needed).\n- In: on gate failure, mark needs-followup and record failure context.\n- Out: no prompt updates and no handoff file generation (A6).\n\nAcceptance Criteria:\n- Issues only count as success when closed, a matching commit exists, and validation evidence is present.\n- Gate failures record the missing evidence and apply a needs-followup marker.\n- JSONL parsing recognizes uv sync, pytest, ruff check/format, and ty check commands.\n\nTest Plan:\n- TDD: add failing tests in tests/test_orchestrator.py for pass/fail gate scenarios (mock git log + JSONL data).\n- Implement gate logic and update tests to pass.\n- Run uv run pytest tests/test_orchestrator.py.\n\nNotes for Agent:\n- Decide whether needs-followup is a bd label or status and document the choice.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-26T16:44:33.280391503Z","updated_at":"2025-12-26T17:10:02.788723047Z","closed_at":"2025-12-26T17:10:02.788723047Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-lo9","depends_on_id":"mala-jxc","type":"blocks","created_at":"2025-12-26T16:44:48.861098735Z","created_by":"daemon"},{"issue_id":"mala-lo9","depends_on_id":"mala-4w7","type":"parent-child","created_at":"2025-12-26T16:47:53.007870582Z","created_by":"daemon"}]}
{"id":"mala-lq68","title":"[Review] src/domain/validation/config_merger.py:76-85: [P2] Preserve programmatic overrides when _fields_set empty","description":"## Review Finding\n\nThis issue was auto-created from a P2 review finding.\n\n**Source issue:** mala-e37n\n**Reviewer:** codex\n**Location:** src/domain/validation/config_merger.py:76-85\n\n## Details\n\nThe new merge logic only treats fields as overrides when they appear in `_fields_set`. That set is populated only by `from_dict`, so programmatic configs (e.g., `ValidationConfig(coverage=YamlCoverageConfig(...))` or `CommandsConfig(test=CommandConfig(...))`) now **silently inherit** preset values instead of overriding them. This is a behavioral regression from the previous merge rules and will surprise any non-YAML callers (the docstring example in this module is now wrong). Consider a fallback when `_fields_set` is empty (treat non-`None` values as explicit), or populate `_fields_set` in the constructors for programmatic use.\n\nExample line:\n```\nmerged_coverage = _merge_coverage(..., \"coverage\" in user._fields_set)\n```","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-02T04:14:26.267833658Z","created_by":"cyou","updated_at":"2026-01-02T04:23:34.298509634Z","closed_at":"2026-01-02T04:23:34.298509634Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_finding:b15e1a35933f","source:mala-e37n"]}
{"id":"mala-lshy","title":"Epic verifier: Implement RetryConfig for transient API failure handling","description":"## Context\n\nThe `EpicVerifier` constructor accepts a `RetryConfig` parameter but never uses it. The `ClaudeEpicVerificationModel.verify()` method makes API calls without any retry logic.\n\n## Problem\n\n- Transient API failures (rate limits, network issues, 500 errors) cause immediate verification failure\n- No exponential backoff or retry attempts\n- Makes epic verification more fragile than other LLM-dependent tasks in the codebase\n\n## Proposed Solution\n\n1. Wire the `RetryConfig` through to the model's `verify()` method\n2. Wrap the Anthropic API call with retry logic using the config\n3. Consider using tenacity or similar for retry implementation\n\n## Acceptance Criteria\n\n- [ ] RetryConfig is used to wrap model API calls\n- [ ] Transient failures trigger retries with exponential backoff\n- [ ] Max retries and timeout are configurable via RetryConfig\n- [ ] Tests verify retry behavior on transient failures\n\n## References\n\n- File: `src/epic_verifier.py` - `EpicVerifier.__init__` accepts `retry_config`\n- File: `src/epic_verifier.py` - `ClaudeEpicVerificationModel.verify()` makes unprotected API call\n- From code review iteration 3","notes":"Quality gate failed: Timeout after 60 minutes\nLog: /home/cyou/.claude/projects/-home-cyou-mala/64c280fd-2de0-47cd-8105-78a431c07740.jsonl","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-30T15:50:10.183319757Z","created_by":"cyou","updated_at":"2025-12-30T20:26:20.949105554Z","closed_at":"2025-12-30T20:26:20.949105554Z","close_reason":"Closed","labels":["needs-followup"]}
{"id":"mala-ltvw","title":"[Review] src/domain/validation/config_merger.py:108-129: [P2] Honor explicit commands null/empty to clear preset commands","description":"## Review Finding\n\nThis issue was auto-created from a P2 review finding.\n\n**Source issue:** mala-fdp1.4\n**Reviewer:** codex\n**Location:** src/domain/validation/config_merger.py:108-129\n\n## Details\n\n`ValidationConfig.from_dict` records when `commands` is explicitly present, even if set to null/empty, but `merge_configs` only checks per-command `_fields_set`. As a result, YAML like `commands: null` (or `commands: {}`) still inherits all preset commands, which contradicts the stated “explicit null/empty overrides” behavior and makes it impossible to clear all preset commands without listing each field. Consider short-circuiting when the top-level `commands` field was explicitly set, or treating an explicitly-set commands block with no fields as an override to empty.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-02T04:10:25.921033002Z","created_by":"cyou","updated_at":"2026-01-02T04:16:57.90184013Z","closed_at":"2026-01-02T04:16:57.90184013Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_finding:ba6c3a5ddce1","source:mala-fdp1.4"]}
{"id":"mala-lvw","title":"Single-threaded mode without locking","description":"Context:\n- Current architecture assumes multi-threaded execution with locking\n- Some users may prefer simpler single-threaded mode\n- Would simplify debugging and reduce complexity for small projects\n\nLocation:\n- src/orchestration/orchestrator.py (agent spawning, concurrency control)\n- src/pipeline/issue_execution_coordinator.py (run loop)\n- src/infra/tools/locking.py (lock implementation)\n\nScope:\n- In: CLI flag for single-threaded mode (--single-threaded or --agents 1)\n- In: Skip lock acquisition in single-threaded mode\n- In: Sequential issue execution (one at a time)\n- Out: Removing multi-threaded support; architectural changes\n\nAcceptance Criteria:\n- `mala run --single-threaded` runs one agent at a time\n- No lock files created in single-threaded mode\n- Issues processed sequentially in priority/dependency order\n- Same correctness guarantees as multi-threaded mode\n\nTest Plan:\n- Unit: Test single-threaded execution path\n- Integration: Run in single-threaded mode, verify sequential execution\n- Manual: Compare behavior between modes\n\nNotes for Agent:\n- May already work with --agents 1, just needs lock bypass\n- Consider detecting and warning about lock config in single-threaded mode\n- Epic because may touch multiple components to wire through flag","status":"closed","priority":4,"issue_type":"epic","created_at":"2025-12-26T00:57:29.777283988Z","updated_at":"2026-01-03T04:52:56.543377337Z","closed_at":"2026-01-03T04:52:56.543377337Z","close_reason":"Closed"}
{"id":"mala-lwef","title":"Epic: Resume with Final Review Feedback","description":"## Summary\nWhen an agent hits max review iterations and fails, resuming with `--resume` should provide the final reviewer feedback to the agent so it can address the issues that caused the failure.\n\n## Problem\nOn max retry exceeded, session ends with `COMPLETE_FAILURE` but no `pending_query` is sent to agent. The review issues exist in `ctx.last_review_result` but aren't injected into the resumed session.\n\n## Solution\nPersist P0/P1 review issues in run metadata (`IssueRun`). On resume, detect stored issues and use `review_followup` template as initial prompt instead of `implementer_prompt`.\n\n## Data Flow\n```\nlifecycle_ctx.last_review_result\n    → AgentSessionRunner (filter P0/P1)\n    → AgentSessionOutput.last_review_issues\n    → IssueResult.last_review_issues\n    → IssueRun.last_review_issues (persisted)\n    → SessionInfo.last_review_issues (on resume)\n    → _build_resume_prompt()\n    → review_followup template\n```\n\n## Plan\nSee: plans/2026-01-09-resume-review-feedback-plan.md\n\n## Acceptance Criteria\n- Resumed agent sees prior review issues via review_followup prompt\n- Only P0/P1 issues included\n- Backward compatible with old metadata (missing field defaults to None)\n- Non-review failures unaffected\n- Info-level log when resume uses review prompt","status":"closed","priority":1,"issue_type":"epic","created_at":"2026-01-09T00:51:33.08972036Z","created_by":"cyou","updated_at":"2026-01-09T19:16:25.08930222Z","closed_at":"2026-01-09T19:16:25.08930222Z","close_reason":"Closed"}
{"id":"mala-lwef.1","title":"[T001] [integration-path-test] Skeleton + Failing Integration Test","description":"## Goal\nAdd all dataclass fields and stubs needed for resume-with-review-feedback. Create a failing integration test that will pass once all implementation tasks are complete.\n\n## Context\nThis task establishes the type signatures and wiring surface. The integration test exercises the full data flow: AgentSessionRunner → IssueResult → IssueRun → SessionInfo → _build_resume_prompt.\n\n## Scope\n**In:**\n- Add `last_review_issues: list[dict[str, Any]] | None = None` field to: `IssueRun`, `SessionInfo`, `AgentSessionOutput`, `IssueResult`\n- Add `StoredReviewIssue` frozen dataclass with `from_dict()` classmethod\n- Add stub `_build_resume_prompt()` that returns `None`\n- Add imports: `build_custom_commands_section`, `format_review_issues`\n- Create failing integration test\n\n**Out:**\n- Actual filtering/formatting logic (T002-T005)\n- Unit tests for individual components (T002-T005)\n\n## Changes\n- `src/infra/io/log_output/run_metadata.py` — Exists — Add `last_review_issues` field to `IssueRun` and `SessionInfo` dataclasses\n- `src/orchestration/orchestrator.py` — Exists — Add `StoredReviewIssue` dataclass, stub `_build_resume_prompt()`, add imports\n- `src/pipeline/agent_session_runner.py` — Exists — Add `last_review_issues` field to `AgentSessionOutput`\n- `src/pipeline/issue_result.py` — Exists — Add `last_review_issues` field to `IssueResult`\n- `tests/integration/orchestration/test_resume_review_feedback.py` — **New** — Failing integration test for resume-with-review flow\n\n## Wiring Map\n```\nlifecycle_ctx.last_review_result\n    → AgentSessionOutput.last_review_issues (T002 fills)\n    → IssueResult.last_review_issues (T003 wires)\n    → IssueRun.last_review_issues (T003 persists)\n    → SessionInfo.last_review_issues (T004 extracts)\n    → _build_resume_prompt (T005 implements)\n```\n\n## StoredReviewIssue Definition\n```python\n@dataclass(frozen=True)\nclass StoredReviewIssue:\n    \"\"\"Typed adapter for review issues loaded from metadata.\"\"\"\n    file: str\n    line_start: int\n    line_end: int\n    priority: int | None\n    title: str\n    body: str\n    reviewer: str\n\n    @classmethod\n    def from_dict(cls, d: dict[str, Any]) -\u003e StoredReviewIssue:\n        def to_int(val: Any, default: int) -\u003e int:\n            if val is None:\n                return default\n            try:\n                return int(val)\n            except (ValueError, TypeError):\n                return default\n        line_start = to_int(d.get(\"line_start\"), 0)\n        return cls(\n            file=str(d.get(\"file\", \"unknown\")),\n            line_start=line_start,\n            line_end=to_int(d.get(\"line_end\"), line_start),\n            priority=d.get(\"priority\"),\n            title=str(d.get(\"title\", \"Unknown issue\")),\n            body=str(d.get(\"body\", \"\")),\n            reviewer=str(d.get(\"reviewer\", \"unknown\")),\n        )\n```\n\n## Acceptance Criteria\n- All 4 dataclasses have `last_review_issues` field with `None` default\n- `StoredReviewIssue.from_dict()` handles missing keys with safe defaults\n- Project compiles/type-checks without errors\n- Integration test exists and FAILS for the expected reason (stub returns None)\n\n## Verification\n```bash\nuvx ty check  # Type check passes\nuv run pytest tests/integration/orchestration/test_resume_review_feedback.py -v  # Test fails (expected)\n```\n\n## Notes for Agent\n- The integration test should verify that when resuming a session with stored review issues, the prompt used is `review_followup` not `implementer_prompt`\n- Stub `_build_resume_prompt` to return `None` so the test fails meaningfully\n- Field type is `list[dict[str, Any]] | None` for JSON serialization compatibility\n- Review issue dict structure: `file`, `line_start`, `line_end`, `priority`, `title`, `body`, `reviewer`","notes":"Quality gate failed: DefaultReviewer.__call__() got an unexpected keyword argument 'interrupt_event'\nLog: /home/cyou/.claude/projects/-home-cyou-mala/8ac56ef0-7137-468d-b4f9-30f71df3fe95.jsonl","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-09T00:51:55.368629013Z","created_by":"cyou","updated_at":"2026-01-09T07:49:27.613715928Z","closed_at":"2026-01-09T07:49:27.613715928Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-lwef.1","depends_on_id":"mala-lwef","type":"parent-child","created_at":"2026-01-09T00:51:55.369384282Z","created_by":"cyou"}]}
{"id":"mala-lwef.2","title":"[T002] Implement AgentSessionRunner P0/P1 filtering","description":"## Goal\nImplement the P0/P1 filtering logic in `AgentSessionRunner._build_session_output()` to populate `AgentSessionOutput.last_review_issues` when a session fails due to review.\n\n## Context\nThis is the single source of truth for filtering - all downstream code receives already-filtered issues. The filtering condition must distinguish review failures from other failure types (gate failures, timeouts).\n\n## Scope\n**In:**\n- Implement filtering in `_build_session_output()` \n- Filter to P0/P1 issues only (priority \u003c= 1)\n- Only populate when: not success AND last_review_result exists AND review_attempt \u003e 0\n- Convert ReviewIssue objects to dicts matching ReviewIssueProtocol\n- Unit tests for filtering logic\n\n**Out:**\n- Changes to IssueResult or downstream (T003)\n- Changes to run_metadata (T004)\n- Changes to orchestrator (T005)\n\n## Changes\n- `src/pipeline/agent_session_runner.py` — Exists — Add filtering logic to `_build_session_output()`\n- `tests/unit/pipeline/test_agent_session_runner.py` — **New** — Unit tests for P0/P1 filtering\n\n## Filtering Logic\n```python\n# In _build_session_output()\nlast_review_issues = None\nif (\n    not state.lifecycle_ctx.success\n    and state.lifecycle_ctx.last_review_result is not None\n    and state.lifecycle_ctx.retry_state.review_attempt \u003e 0  # Review actually ran\n):\n    blocking_issues = [\n        issue for issue in state.lifecycle_ctx.last_review_result.issues\n        if issue.priority is not None and issue.priority \u003c= 1\n    ]\n    if blocking_issues:\n        last_review_issues = [\n            {\n                \"file\": issue.file,\n                \"line_start\": issue.line_start,\n                \"line_end\": issue.line_end,\n                \"priority\": issue.priority,\n                \"title\": issue.title,\n                \"body\": issue.body,\n                \"reviewer\": issue.reviewer,\n            }\n            for issue in blocking_issues\n        ]\n\nreturn AgentSessionOutput(\n    # ... existing fields ...\n    last_review_issues=last_review_issues,\n)\n```\n\n## Acceptance Criteria\n- P2+ issues are excluded from `last_review_issues`\n- `last_review_issues` is None when success=True\n- `last_review_issues` is None when last_review_result is None\n- `last_review_issues` is None when review_attempt=0 (review never ran)\n- Dict structure matches ReviewIssueProtocol fields exactly\n\n## Verification\n```bash\nuv run pytest tests/unit/pipeline/test_agent_session_runner.py -v\nuvx ty check src/pipeline/agent_session_runner.py\n```\n\n## Notes for Agent\n- Use `issue.body` not `issue.description` (ReviewIssueProtocol field name)\n- Include `issue.reviewer` field\n- The `review_attempt \u003e 0` check prevents storing stale issues from previous lifecycle iterations\n- Empty list after filtering should result in `None`, not `[]`\n- Create new test file `tests/unit/pipeline/test_agent_session_runner.py` (doesn't exist yet)","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-09T00:52:50.170736258Z","created_by":"cyou","updated_at":"2026-01-09T07:53:14.246511965Z","closed_at":"2026-01-09T07:53:14.246511965Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-lwef.2","depends_on_id":"mala-lwef","type":"parent-child","created_at":"2026-01-09T00:52:50.171423236Z","created_by":"cyou"},{"issue_id":"mala-lwef.2","depends_on_id":"mala-lwef.1","type":"blocks","created_at":"2026-01-09T00:53:03.079078065Z","created_by":"cyou"}]}
{"id":"mala-lwef.3","title":"[T003] Implement IssueResult passthrough + IssueFinalizer persistence","description":"## Goal\nWire `last_review_issues` through `IssueResult` and persist to `IssueRun` via `IssueFinalizer`.\n\n## Context\nThe data flows: AgentSessionOutput.last_review_issues → IssueResult.last_review_issues → IssueRun.last_review_issues. This task implements the middle and final steps of persistence.\n\n## Scope\n**In:**\n- Update IssueResult construction in orchestrator to pass `last_review_issues`\n- Update IssueFinalizer to persist `last_review_issues` to IssueRun\n- Unit tests for passthrough and persistence\n\n**Out:**\n- AgentSessionRunner changes (T002)\n- SessionInfo extraction (T004)\n- _build_resume_prompt (T005)\n\n## Changes\n- `src/pipeline/issue_result.py` — Exists — Field already added in T001; ensure it's used\n- `src/pipeline/issue_finalizer.py` — Exists — Read IssueResult.last_review_issues, write to IssueRun.last_review_issues\n- `src/orchestration/orchestrator.py` — Exists — Pass `last_review_issues=output.last_review_issues` to IssueResult constructor (~line 807-821)\n- `tests/unit/pipeline/test_issue_finalizer.py` — **New** — Test persistence logic\n\n## IssueResult Construction Update\n```python\n# In orchestrator.py, ~line 807-821 (IssueResult construction)\nreturn IssueResult(\n    # ... existing fields ...\n    last_review_issues=output.last_review_issues,  # NEW\n)\n```\n\n## IssueFinalizer Persistence\n```python\n# In IssueFinalizer (when creating IssueRun)\nIssueRun(\n    # ... existing fields ...\n    last_review_issues=result.last_review_issues,  # NEW\n)\n```\n\n## Acceptance Criteria\n- IssueResult receives last_review_issues from AgentSessionOutput passthrough\n- IssueFinalizer persists last_review_issues to IssueRun\n- None values are preserved (not converted to empty list)\n- Serialized JSON includes last_review_issues when present\n\n## Verification\n```bash\nuv run pytest tests/unit/pipeline/test_issue_finalizer.py -v\nuv run pytest tests/unit/pipeline/test_run_metadata.py -v -k \"review_issues\"\nuvx ty check src/pipeline/issue_finalizer.py src/pipeline/issue_result.py\n```\n\n## Notes for Agent\n- Find IssueResult construction site in orchestrator.py (~line 807-821)\n- IssueFinalizer reads from IssueResult, writes to IssueRun\n- Preserve None (don't convert to [])","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-09T00:52:50.73413803Z","created_by":"cyou","updated_at":"2026-01-09T07:53:46.72850528Z","closed_at":"2026-01-09T07:53:46.72850528Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-lwef.3","depends_on_id":"mala-lwef","type":"parent-child","created_at":"2026-01-09T00:52:50.734855728Z","created_by":"cyou"},{"issue_id":"mala-lwef.3","depends_on_id":"mala-lwef.1","type":"blocks","created_at":"2026-01-09T00:53:03.192200421Z","created_by":"cyou"}]}
{"id":"mala-lwef.4","title":"[T004] Implement run_metadata SessionInfo extraction","description":"## Goal\nUpdate `run_metadata.py` to extract `last_review_issues` from stored `IssueRun` into `SessionInfo` during resume lookup.\n\n## Context\nWhen `lookup_prior_session_info()` finds a prior session, it returns `SessionInfo`. This task ensures `last_review_issues` is populated from the stored metadata.\n\n## Scope\n**In:**\n- Update `extract_session_from_run()` to include `last_review_issues` (~line 912-956)\n- Ensure `_from_dict()` handles the new field (~line 430-445)\n- Unit tests for serialization/deserialization\n- Cross-version compatibility tests\n\n**Out:**\n- AgentSessionRunner changes (T002)\n- IssueFinalizer changes (T003)\n- _build_resume_prompt (T005)\n\n## Changes\n- `src/infra/io/log_output/run_metadata.py` — Exists — Update `extract_session_from_run()` and serialization\n- `tests/unit/pipeline/test_run_metadata.py` — Exists — Add cross-version compatibility tests\n\n## extract_session_from_run Update\n```python\n# In extract_session_from_run() (~line 912-956)\nreturn SessionInfo(\n    # ... existing fields ...\n    last_review_issues=issue_run.last_review_issues,  # NEW\n)\n```\n\n## Compatibility Requirements\nThe codebase uses manual dict parsing with `.get()` calls (NOT dacite):\n- `_from_dict()` uses `dict.get(\"key\")` which returns None for missing keys\n- Unknown keys are silently ignored\n- New field must have `None` default for backward compat\n\n## Unit Tests Required\n1. IssueRun with last_review_issues serializes/deserializes correctly\n2. IssueRun without last_review_issues (old format) loads with None default\n3. JSON blob with extra unknown keys doesn't crash (forward compat)\n4. SessionInfo includes last_review_issues from IssueRun\n\n## Acceptance Criteria\n- `lookup_prior_session_info()` returns SessionInfo with `last_review_issues` populated\n- Old metadata without field loads with `last_review_issues=None`\n- New metadata with field loads correctly\n- Type checking passes\n\n## Verification\n```bash\nuv run pytest tests/unit/pipeline/test_run_metadata.py -v\nuvx ty check src/infra/io/log_output/run_metadata.py\n```\n\n## Notes for Agent\n- Manual dict parsing: use `dict.get(\"last_review_issues\")` pattern\n- Don't use dacite - codebase uses manual `.get()` calls\n- See existing patterns in `_from_dict()` (~line 430) and `extract_session_from_run()` (~line 912)\n- Field is `list[dict[str, Any]] | None`, not typed ReviewIssue","notes":"Quality gate failed: External review failed: tests/unit/pipeline/test_run_metadata.py:135: [P0] Missing implementation in run_metadata.py\nLog: /home/cyou/.claude/projects/-home-cyou-mala/8a038bca-af66-4c83-ac72-f745b04b1899.jsonl","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-09T00:52:51.306731227Z","created_by":"cyou","updated_at":"2026-01-09T16:17:25.834080359Z","closed_at":"2026-01-09T16:17:25.834080359Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-lwef.4","depends_on_id":"mala-lwef","type":"parent-child","created_at":"2026-01-09T00:52:51.307573245Z","created_by":"cyou"},{"issue_id":"mala-lwef.4","depends_on_id":"mala-lwef.1","type":"blocks","created_at":"2026-01-09T00:53:03.308936908Z","created_by":"cyou"}]}
{"id":"mala-lwef.5","title":"[T005] Implement _build_resume_prompt + orchestrator integration","description":"## Goal\nImplement `_build_resume_prompt()` to format stored review issues using `review_followup` template, and integrate into `run_implementer()`. This is the final task that makes the integration test pass.\n\n## Context\nWhen resuming a session that failed due to review, this function builds the prompt using the `review_followup` template. The prompt replaces `implementer_prompt` as the initial message sent to the resumed session.\n\n## Scope\n**In:**\n- Implement full `_build_resume_prompt()` logic\n- Integrate into `run_implementer()` to use resume prompt when issues exist\n- Add logging for observability\n- Unit tests for prompt building\n- Verify all integration tests pass\n\n**Out:**\n- Field additions (T001)\n- Filtering logic (T002)\n- Persistence logic (T003, T004)\n\n## Changes\n- `src/orchestration/orchestrator.py` — Exists — Implement `_build_resume_prompt()`, integrate into `run_implementer()`\n- `tests/unit/orchestration/test_orchestrator.py` — Exists — Add unit tests for resume prompt building\n\n## _build_resume_prompt Implementation\n```python\ndef _build_resume_prompt(\n    prior_session: SessionInfo,\n    prompts: PromptProvider,\n    validation_commands: PromptValidationCommands,\n    issue_id: str,\n    max_review_retries: int,\n    repo_path: Path,\n) -\u003e str | None:\n    \"\"\"Build resume prompt if prior session failed with review issues.\"\"\"\n    if not prior_session.last_review_issues:\n        return None\n\n    issues = [\n        StoredReviewIssue.from_dict(d)\n        for d in prior_session.last_review_issues\n    ]\n\n    logger.info(\n        \"Using review_followup for resume: %d issues from prior run %s\",\n        len(issues),\n        prior_session.run_id,\n    )\n\n    review_issues_text = format_review_issues(issues, base_path=repo_path)\n\n    return prompts.review_followup_prompt.format(\n        attempt=1,\n        max_attempts=max_review_retries,\n        review_issues=review_issues_text,\n        issue_id=issue_id,\n        lint_command=validation_commands.lint,\n        format_command=validation_commands.format,\n        typecheck_command=validation_commands.typecheck,\n        test_command=validation_commands.test,\n        custom_commands_section=build_custom_commands_section(\n            validation_commands.custom_commands\n        ),\n    )\n```\n\n## run_implementer Integration\n```python\n# After getting prior_session from lookup_prior_session_info()\nif prior_session:\n    resume_prompt = _build_resume_prompt(\n        prior_session, self._prompts, self._prompt_validation_commands,\n        issue_id, self.config.max_review_retries, self._repo_path\n    )\n    if resume_prompt:\n        prompt = resume_prompt\n    # else: use normal implementer_prompt\n```\n\n## Unit Tests Required\n1. `_build_resume_prompt` returns None when no issues\n2. `_build_resume_prompt` formats correctly with sample issues\n3. `StoredReviewIssue.from_dict()` handles missing/malformed keys\n4. Integration: resume with issues uses review_followup prompt\n5. Integration: resume without issues uses implementer_prompt\n\n## Acceptance Criteria\n- Resume with stored issues uses `review_followup` prompt\n- Resume without issues uses `implementer_prompt` (unchanged behavior)\n- Log message appears: \"Using review_followup for resume: N issues\"\n- All integration tests pass\n- attempt=1 (fresh retry counter on resume)\n\n## Verification\n```bash\nuv run pytest tests/unit/orchestration/test_orchestrator.py -v -k \"resume\"\nuv run pytest tests/integration/orchestration/test_resume_review_feedback.py -v  # Must pass now\nuvx ty check src/orchestration/orchestrator.py\n```\n\n## Notes for Agent\n- Import `build_custom_commands_section` from `src.domain.prompts`\n- Import `format_review_issues` from `src.pipeline.review_formatter`\n- attempt=1 because resume is a fresh session start (retry counters reset)\n- SDK resume appends prompt as new user message to restored history\n- This is the final task; all integration tests should pass after completion","notes":"Quality gate failed: External review failed: src/orchestration/orchestrator.py:809: [P1] Wire `run_implementer` to config/repo-path attributes per task context; tests/integration/orchestration/test_resume_review_feedback.py:231: [P1] Add missing import for `_build_resume_prompt` in integration test; src/orchestration/orchestrator.py:816: [P1] Incorrect attribute access on Orchestrator\nLog: /home/cyou/.claude/projects/-home-cyou-mala/ee2ed372-a9d4-463e-b014-842c54c4efe1.jsonl","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-09T00:52:51.886225045Z","created_by":"cyou","updated_at":"2026-01-09T19:13:04.728510866Z","closed_at":"2026-01-09T19:13:04.728510866Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-lwef.5","depends_on_id":"mala-lwef","type":"parent-child","created_at":"2026-01-09T00:52:51.886943483Z","created_by":"cyou"},{"issue_id":"mala-lwef.5","depends_on_id":"mala-lwef.2","type":"blocks","created_at":"2026-01-09T00:53:03.887993736Z","created_by":"cyou"},{"issue_id":"mala-lwef.5","depends_on_id":"mala-lwef.3","type":"blocks","created_at":"2026-01-09T00:53:03.998614389Z","created_by":"cyou"},{"issue_id":"mala-lwef.5","depends_on_id":"mala-lwef.4","type":"blocks","created_at":"2026-01-09T00:53:04.113923809Z","created_by":"cyou"},{"issue_id":"mala-lwef.5","depends_on_id":"mala-lwef.1","type":"blocks","created_at":"2026-01-09T00:55:36.696124353Z","created_by":"cyou"}]}
{"id":"mala-lwef.6","title":"[F001] Preserve last_review_issues in RunMetadata.load()","description":"Add last_review_issues deserialization in RunMetadata.load() so load/save round-trip preserves resume review issues; add unit test for round-trip.","notes":"Quality gate failed: Interrupted: Interrupted by user","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-09T23:24:45.954045044Z","created_by":"cyou","updated_at":"2026-01-10T00:23:55.32340056Z","closed_at":"2026-01-10T00:23:55.32340056Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-lwef.6","depends_on_id":"mala-lwef","type":"parent-child","created_at":"2026-01-09T23:24:45.955144306Z","created_by":"cyou"}]}
{"id":"mala-lwef.7","title":"[F002] Harden last_review_issues parsing","description":"Guard against invalid last_review_issues entries (non-dict list items). Filter in extract_session_from_run() or validate in _build_resume_prompt() so corrupted metadata doesn't raise.","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-09T23:24:54.783759453Z","created_by":"cyou","updated_at":"2026-01-10T00:27:23.295721856Z","closed_at":"2026-01-10T00:27:23.295721856Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-lwef.7","depends_on_id":"mala-lwef","type":"parent-child","created_at":"2026-01-09T23:24:54.784339678Z","created_by":"cyou"},{"issue_id":"mala-lwef.7","depends_on_id":"mala-lwef.6","type":"blocks","created_at":"2026-01-09T23:25:02.207433757Z","created_by":"cyou"}]}
{"id":"mala-lxll","title":"[Review] 6 non-blocking findings from mala-uco3","description":"## Review Findings\n\nThis issue consolidates 6 non-blocking findings from code review.\n\n**Source issue:** mala-uco3\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P3] Remove stale Braintrust references in test_lock_sdk.py\n\n**Priority:** P3\n**Reviewer:** claude\n**Location:** tests/e2e/test_lock_sdk.py:50-52\n\nThe docstring and comment still reference Braintrust (`\"Clean environment for tests - disable Braintrust, use CLI auth\"` and `\"# Disable Braintrust during tests...\"`), and there's a now-unnecessary `monkeypatch.delenv(\"BRAINTRUST_API_KEY\", ...)` call. Since the goal was to remove \"absolutely everything, no trace left\" of Braintrust, these should be cleaned up for consistency.\n\n---\n\n### Finding 2: [P2] Remove Braintrust instructions from CLI docs\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** docs/cli-reference.md:97-103\n\n`braintrust` has been removed from the codebase/deps, but the CLI docs still tell users to enable “Braintrust Tracing” via `BRAINTRUST_API_KEY`. This is now inaccurate and will likely waste time for anyone following the docs; the `.env` tree also still lists `BRAINTRUST_API_KEY` as a supported key.\n\n---\n\n### Finding 3: [P2] Update architecture docs to remove Braintrust constraints\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** docs/architecture.md:537-542\n\nArchitecture docs still describe `braintrust` as an allowed/confined dependency and reference Braintrust-backed telemetry, but the implementation was removed and `pyproject.toml` contracts were updated accordingly. This doc is now out of sync with the actual architecture/contracts.\n\n---\n\n### Finding 4: [P3] Remove Braintrust env cleanup/comments from E2E test\n\n**Priority:** P3\n**Reviewer:** codex\n**Location:** tests/e2e/test_lock_sdk.py:49-54\n\nThis test still mentions “disable Braintrust” and deletes `BRAINTRUST_API_KEY`, but Braintrust no longer exists in the codebase. Keeping this around is misleading (especially given the change goal “no trace left”).\n\n---\n\n### Finding 5: [P3] Remove Braintrust env cleanup/comments from E2E test\n\n**Priority:** P3\n**Reviewer:** codex\n**Location:** tests/e2e/test_session_resume.py:42-46\n\nSame issue as `tests/e2e/test_lock_sdk.py`: the fixture docstring and env cleanup still reference Braintrust even though it’s been removed.\n\n---\n\n### Finding 6: [P3] Stop using BRAINTRUST_API_KEY as dummy .env content in tests\n\n**Priority:** P3\n**Reviewer:** codex\n**Location:** tests/unit/cli/test_cli.py:460-463\n\nThese tests write `BRAINTRUST_API_KEY=test` into the temp global `.env`. With Braintrust removed, this is confusing signal for future readers; use a relevant key (e.g. `MALA_RUNS_DIR`, `MALA_LOCK_DIR`, etc.) or an empty file instead.\n\n---\n\n\u003c!-- fp:d144c8b0b4e20099 --\u003e\n\u003c!-- fp:cf3a8af9b1183c81 --\u003e\n\u003c!-- fp:f66c9499fabd0dd7 --\u003e\n\u003c!-- fp:c7c6093bfe5b92a3 --\u003e\n\u003c!-- fp:f87a615e89e57e98 --\u003e\n\u003c!-- fp:a81e1916042f896c --\u003e\n\u003c!-- review_finding:497721f80afa --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-06T00:18:08.57310588Z","created_by":"cyou","updated_at":"2026-01-06T00:26:30.402050582Z","closed_at":"2026-01-06T00:26:30.402050582Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-uco3"]}
{"id":"mala-m17h","title":"add agent exit path for changes that require a commit but does not need to run quality checks or code review. eg. documentation changes. for the file pattern matching, do check that the commit did not change any files that are in the project config and match as code","notes":"Quality gate failed: Interrupted: Interrupted by user\nLog: /home/cyou/.claude/projects/-home-cyou-mala/59dc5a05-d24d-421f-bf5f-5763848f95b9.jsonl","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-06T03:47:15.185302357Z","created_by":"cyou","updated_at":"2026-01-06T04:23:55.617770039Z","closed_at":"2026-01-06T04:23:55.617770039Z","close_reason":"Closed","labels":["needs-followup"]}
{"id":"mala-m1bh","title":"Epic: Architecture Refactor (High + Medium Priority)","description":"Address remaining architecture issues from multi-model review.\n\n**Work Packages:**\n1. Split `protocols.py` (~2400 LOC) into 8 domain-focused modules\n2. Replace `SessionCallbacks` dataclass with 3 typed Protocols\n3. Extract `LifecycleController` from Orchestrator\n4. Extract `TriggerEngine` and `FixerService` from RunCoordinator\n\n**Plan:** `plans/2026-01-13-architecture-refactor-plan.md`\n\n**Constraints:**\n- Single PR delivery\n- No backward-compatibility shims\n- All existing tests must pass\n- Import-linter contracts must pass","status":"closed","priority":1,"issue_type":"epic","created_at":"2026-01-13T04:42:12.470409695Z","created_by":"cyou","updated_at":"2026-01-13T19:16:57.995367801Z","closed_at":"2026-01-13T19:16:57.995367801Z","close_reason":"Closed"}
{"id":"mala-m1bh.1","title":"[T001] Create protocol directory + move all protocols","description":"**Goal**: Create `src/core/protocols/` directory with 8 domain-focused modules and move all protocols from `protocols.py`.\n\n**Type**: task | **Priority**: P2 | **Parallel**: No (foundation)\n**Primary Files**: src/core/protocols/*.py, src/core/protocols.py\n**Subsystems**: core\n\n**Context**:\n- Current `protocols.py` is 2400 LOC with 30+ protocols\n- Must split into \u003c300 LOC modules for maintainability\n- No re-exports per CLAUDE.md - empty `__init__.py`\n\n**Scope**:\n- In: Create directory, create 8 module files, move protocols\n- Out: Import updates (separate task), new protocols (separate task)\n\n**Changes**:\n- `src/core/protocols/` — New directory\n- `src/core/protocols/__init__.py` — New (empty, no re-exports)\n- `src/core/protocols/issue.py` — New (IssueProvider, IssueResolutionProtocol)\n- `src/core/protocols/review.py` — New (CodeReviewer, Review*Protocol)\n- `src/core/protocols/validation.py` — New (GateChecker, Validation*Protocol, Gate*Protocol)\n- `src/core/protocols/infra.py` — New (CommandRunnerPort, LockManagerPort, EnvConfigPort)\n- `src/core/protocols/sdk.py` — New (SDKClientProtocol, SDKClientFactoryProtocol)\n- `src/core/protocols/events.py` — New (MalaEventSink, TriggerSummary, EventRunConfig)\n- `src/core/protocols/lifecycle.py` — New (Deadlock*Protocol, LockEventProtocol)\n- `src/core/protocols/log.py` — New (LogProvider, JsonlEntryProtocol)\n\n**Acceptance Criteria**:\n- All 8 protocol modules created with correct protocols\n- Each module \u003c400 LOC (target \u003c300)\n- `__init__.py` is empty (no re-exports)\n- Protocols maintain exact signatures from original\n\n**Verification**:\n- `uvx ty check` passes on new modules\n- `uvx ruff check src/core/protocols/` passes\n- Original protocols.py still exists (not deleted yet)\n\n**Notes for Agent**:\n- Start with leaf protocols (infra, sdk, log) that have no internal dependencies\n- Use TYPE_CHECKING for forward references to avoid circular imports\n- Keep original protocols.py intact - deletion is separate task","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-13T04:42:31.191919373Z","created_by":"cyou","updated_at":"2026-01-13T05:10:44.530747091Z","closed_at":"2026-01-13T05:10:44.530747091Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-m1bh.1","depends_on_id":"mala-m1bh","type":"parent-child","created_at":"2026-01-13T04:42:31.1925744Z","created_by":"cyou"}]}
{"id":"mala-m1bh.10","title":"[Review] 1 non-blocking finding from mala-m1bh.1","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-m1bh.1\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Reduce src/core/protocols/events.py below 400 LOC\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/core/protocols/events.py:1-5\n\nTask acceptance criteria explicitly requires each protocol module to be \u003c400 LOC. `src/core/protocols/events.py` is currently 970 lines (new module added in this diff), so the change set does not meet the stated requirement.\n\nConcrete impact: it undermines the maintainability goal of splitting `protocols.py` and may block automated checks if the repo enforces the \u003c400 LOC threshold.\n\nEvidence in this diff: the file is introduced as a 970-line addition (and `wc -l` reports 970).\n\n---\n\n\u003c!-- fp:a8cd50b6e8311a39 --\u003e\n\u003c!-- review_finding:a3f212470b5b --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-13T05:10:44.883066921Z","created_by":"cyou","updated_at":"2026-01-13T05:31:28.299586118Z","closed_at":"2026-01-13T05:31:28.299586118Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-m1bh.1"],"dependencies":[{"issue_id":"mala-m1bh.10","depends_on_id":"mala-m1bh","type":"parent-child","created_at":"2026-01-13T05:10:44.883617618Z","created_by":"cyou"}]}
{"id":"mala-m1bh.11","title":"[Review] 2 non-blocking findings from mala-m1bh.3","description":"## Review Findings\n\nThis issue consolidates 2 non-blocking findings from code review.\n\n**Source issue:** mala-m1bh.3\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] on_tool_use args parameter should allow None to match SessionCallbacks\n\n**Priority:** P2\n**Reviewer:** claude\n**Location:** src/core/protocols/lifecycle.py:135\n\nThe `args` parameter is typed as `dict[str, Any]` but SessionCallbacks uses `ToolUseCallback = Callable[[str, str, dict[str, Any] | None], None]`. The acceptance criteria states \"Signatures match SessionCallbacks callback signatures exactly.\" The fix is to change `args: dict[str, Any]` to `args: dict[str, Any] | None`.\n\n---\n\n### Finding 2: [P2] Mismatched on_tool_use signature in ISessionLifecycle\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/core/protocols/lifecycle.py:130\n\nThe `on_tool_use` method in `ISessionLifecycle` defines `args` as `dict[str, Any]`, but `SessionCallbacks.on_tool_use` (via `ToolUseCallback`) defines it as `dict[str, Any] | None`. To match the existing signature exactly and safely handle potential `None` inputs from the SDK, the protocol should accept `None`.\n\n---\n\n\u003c!-- fp:75402cb02d134a85 --\u003e\n\u003c!-- fp:9d41e0710e78a0a4 --\u003e\n\u003c!-- review_finding:c107b8e8931e --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-13T05:19:48.820564759Z","created_by":"cyou","updated_at":"2026-01-13T05:24:30.658310684Z","closed_at":"2026-01-13T05:24:30.658310684Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-m1bh.3"],"dependencies":[{"issue_id":"mala-m1bh.11","depends_on_id":"mala-m1bh","type":"parent-child","created_at":"2026-01-13T05:19:48.821114005Z","created_by":"cyou"}]}
{"id":"mala-m1bh.12","title":"[Review] 5 non-blocking findings from mala-m1bh.4","description":"## Review Findings\n\nThis issue consolidates 5 non-blocking findings from code review.\n\n**Source issue:** mala-m1bh.4\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P3] Outdated docstring for _interrupt_event property\n\n**Priority:** P3\n**Reviewer:** claude\n**Location:** src/orchestration/orchestrator.py:687-691\n\nThe `_interrupt_event` property docstring states \"Returns None before run() is called (events created fresh per-run)\" but this is no longer accurate. After this refactoring, `LifecycleController` creates events in its `__init__` via `field(default_factory=asyncio.Event)`, so `_interrupt_event` returns an actual Event after `MalaOrchestrator.__init__`, not None. The test was correctly updated from `test_interrupt_event_none_before_run` to `test_interrupt_event_exists_before_run` to reflect this, but the property docstring wasn't updated to match.\n\n---\n\n### Finding 2: [P2] Propagate validation failure into LifecycleController\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/orchestration/lifecycle_controller.py:96-104\n\n`LifecycleController.handle_sigint()` snapshots Stage 2 exit code using its own `_validation_failed` flag:\n\n```py\nself._abort_exit_code = 1 if self._validation_failed else 130\n```\n\nHowever, the orchestrator no longer sets this flag anywhere (no references to `_lifecycle.validation_failed` in `src/orchestration/orchestrator.py`). As a result, Stage 2 Ctrl-C during/after a validation failure will still use `130` instead of `1`, breaking the intended “snapshot exit code at abort entry based on whether validation had already failed” behavior.\n\nConcrete failure path: run hits a validation failure (exit code should be `1`), user presses Ctrl-C twice → Stage 2 exit code remains `130` because `_validation_failed` never becomes `True` in the controller.\n\n\n---\n\n### Finding 3: [P3] Fix _interrupt_event semantics and docstring mismatch\n\n**Priority:** P3\n**Reviewer:** codex\n**Location:** src/orchestration/orchestrator.py:687-699\n\n`MalaOrchestrator._interrupt_event` now always returns a non-`None` event because `_lifecycle` is created in `__init__`, but the property docstring still claims it returns `None` before `run()`:\n\n```py\n\"\"\"Returns None before run() is called ...\"\"\"\nreturn self._lifecycle.interrupt_event\n```\n\nAdditionally, the setter ignores `None` assignments:\n\n```py\nif value is not None:\n    self._lifecycle.interrupt_event = value\n```\n\nSo callers/tests cannot clear `_interrupt_event` anymore (it will keep returning the existing `asyncio.Event`). This is a maintainability/regression risk for any code that relied on “unset” semantics.\n\n\n---\n\n### Finding 4: [P2] Missing wiring for validation_failed state\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/orchestration/orchestrator.py:1163-1173\n\nThe diff removes `self._validation_failed` from `MalaOrchestrator` but does not add logic to set `self._lifecycle.validation_failed`. Consequently, `LifecycleController` will always use exit code 130 for Stage 2 aborts, effectively disabling the feature that returns exit code 1 if validation has already failed. Logic to set this flag (likely when validation fails) needs to be restored.\n\n---\n\n### Finding 5: [P3] Inaccurate docstring for _interrupt_event\n\n**Priority:** P3\n**Reviewer:** gemini\n**Location:** src/orchestration/orchestrator.py:686-689\n\nThe docstring claims `_interrupt_event` returns `None` before `run()` is called, but `LifecycleController` now initializes the event in `__init__`, so it will always return an `asyncio.Event`. The docstring should be updated to reflect that the event exists immediately.\n\n---\n\n\u003c!-- fp:1f346ed6bde320d0 --\u003e\n\u003c!-- fp:f01bf9326f835e2e --\u003e\n\u003c!-- fp:b18a71604a6a8ffd --\u003e\n\u003c!-- fp:b612880fe44dff96 --\u003e\n\u003c!-- fp:d3405e164c3783aa --\u003e\n\u003c!-- review_finding:18fba6cffcd0 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-13T05:34:34.349098867Z","created_by":"cyou","updated_at":"2026-01-13T05:43:29.517451879Z","closed_at":"2026-01-13T05:43:29.517451879Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-m1bh.4"],"dependencies":[{"issue_id":"mala-m1bh.12","depends_on_id":"mala-m1bh","type":"parent-child","created_at":"2026-01-13T05:34:34.35016153Z","created_by":"cyou"}]}
{"id":"mala-m1bh.13","title":"[Remediation] Replace SessionCallbacks dataclass with 3 typed Protocols","description":"## Context\nThis issue was auto-created by epic verification for epic `mala-m1bh`.\n\n## Epic Description / Acceptance Criteria\nTitle: Epic: Architecture Refactor (High + Medium Priority)\n\nAddress remaining architecture issues from multi-model review.\n\n**Work Packages:**\n1. Split `protocols.py` (~2400 LOC) into 8 domain-focused modules\n2. Replace `SessionCallbacks` dataclass with 3 typed Protocols\n3. Extract `LifecycleController` from Orchestrator\n4. Extract `TriggerEngine` and `FixerService` from RunCoordinator\n\n**Plan:** `plans/2026-01-13-architecture-refactor-plan.md`\n\n**Constraints:**\n- Single PR delivery\n- No backward-compatibility shims\n- All existing tests must pass\n- Import-linter contracts must pass\n\n## Commit Scope\n- Range hint: 759a7e28888f588cf6f920130a694a9ca9fb22ef^..57e90db702f488cee725c72fbb9cf1a9b83e7245\n- Commits:\n- 57e90db702f488cee725c72fbb9cf1a9b83e7245 bd-mala-m1bh.9: Fix formatting in test_e2e.py\n- 443368941bd59ae46f5400685a18bec5d889ee77 bd-mala-m1bh.9: Delete old protocols.py and update import-linter contracts\n- 2f34811bc4dd7911f4a0a76c5694ce51040a1247 bd-mala-m1bh.8: Catch AttributeError in session_end check\n- afb810cec6ff941269c850253c2041a4accb4c2c bd-mala-m1bh.8: Handle NotImplementedError in session_end check\n- 99ce850afa5db355a9e483b5ff81543895450e36 bd-mala-m1bh.8: Add protocol interfaces to AgentSessionRunner\n- 0e008056171ee1cf1f805e99a4fb0e66ee71bc56 bd-mala-m1bh.7: Wire TriggerEngine and FixerService into RunCoordinator\n- d2eb23b1e957f08d55b90d9cf3aca8822abdf322 bd-mala-m1bh.6: Disable lock enforcement when no MCP factory\n- 3113d9f85fdc0c6ead73f95d496be94c9d5a7ac9 bd-mala-m1bh.12: Fix validation_failed wiring and docstring\n- 7d2bafba3851c36008d563150ccfa3ac5b223f56 bd-mala-m1bh.6: Handle None mcp_server_factory gracefully\n- 533add4a5d5f324d079c5efc38685bc530c93502 bd-mala-m1bh.2: Revert unrelated __version__ change\n- 21f3eb6238fd29a804f1026d38456745bb1b5ccb bd-mala-m1bh.10: Fix P1/P2 review findings\n- 094f31e5e9ef2c9ffa8fe71a73a8276e5fa8b093 bd-mala-m1bh.4: Create LifecycleController + wire into Orchestrator\n- 03e1c031e51d59f1609c946e2f22b32ab9a1c5ba bd-mala-m1bh.6: Fix P1/P2 review findings\n- ec3c6dfc285a5b7453bdd1e5b6819c3bb961431a bd-mala-m1bh.10: Remove old events.py (replaced by events/ package)\n- d9c0617e9142c6244c581972090454db43b89d0b bd-mala-m1bh.10: Split protocols/events.py into \u003c400 LOC modules\n- 997a4036cfadf856810a3c61b4b45db8f444b838 bd-mala-m1bh.5: Move ResolvedCommand to trigger_engine to avoid circular import\n- 48be6225f569ce9733700829bb645a1336d41b08 bd-mala-m1bh.11: Fix on_tool_use args type to allow None\n- 5e43d5b30d93dd598ae0984570764a3e0ece38c8 bd-mala-m1bh.2: Update all imports from src.core.protocols to submodules\n- f55e70104235fb883de972e4cbb7a25cf84d6e81 bd-mala-m1bh.6: Create FixerService with unit tests\n- 5dad46fa9497939b4ce1c08719b3506ab30facec bd-mala-m1bh.3: Add session lifecycle protocols (IGateRunner, IReviewRunner, ISessionLifecycle)\n- afc48b8d00b2e1ee5143ef29c04005e88e465900 bd-mala-m1bh.5: Create TriggerEngine with event-based interface\n- 4cbf46eca6edfa577729718c0ab925d630c47c41 bd-mala-m1bh.1: Fix review findings - empty __init__.py, add McpServerFactory\n- 759a7e28888f588cf6f920130a694a9ca9fb22ef bd-mala-m1bh.1: Create protocol directory with 8 domain-focused modules\n\n## Child Issues\n- mala-m1bh.1\n- mala-m1bh.10\n- mala-m1bh.11\n- mala-m1bh.12\n- mala-m1bh.2\n- mala-m1bh.3\n- mala-m1bh.4\n- mala-m1bh.5\n- mala-m1bh.6\n- mala-m1bh.7\n- mala-m1bh.8\n- mala-m1bh.9\n\n## Unmet Criterion\nReplace SessionCallbacks dataclass with 3 typed Protocols\n\n## Evidence\nProtocols IGateRunner, IReviewRunner, and ISessionLifecycle are defined in src/core/protocols/{validation,review,lifecycle}.py, but SessionCallbacks dataclass still exists as the primary interface in src/pipeline/agent_session_runner.py:340. AgentSessionRunner supports both approaches in parallel (line 434: callbacks: SessionCallbacks, lines 436-438: protocol interfaces), indicating incomplete migration rather than replacement. All test files and production code still primarily use SessionCallbacks constructor.\n\n## Priority\nP1 (blocking)\n\n## Resolution\nAddress this criterion to unblock epic closure. When complete, close this issue.\n","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-13T18:23:24.119941237Z","created_by":"cyou","updated_at":"2026-01-13T19:13:37.914117386Z","closed_at":"2026-01-13T19:13:37.914117386Z","close_reason":"Closed","labels":["auto_generated","epic_remediation:mala-m1bh:3e627263dda4d37b9fd9559842a812d08bc6239ace211b2781adaabda39ff382"],"dependencies":[{"issue_id":"mala-m1bh.13","depends_on_id":"mala-m1bh","type":"parent-child","created_at":"2026-01-13T18:23:24.120504715Z","created_by":"cyou"}]}
{"id":"mala-m1bh.2","title":"[T002] Update all imports across codebase","description":"**Goal**: Update all imports from `src.core.protocols` to `src.core.protocols.\u003cmodule\u003e` throughout the codebase.\n\n**Type**: task | **Priority**: P2 | **Parallel**: No (mechanical sweep)\n**Primary Files**: ~30 files across src/ and tests/\n**Subsystems**: core, pipeline, orchestration, domain, infra, cli\n\n**Context**:\n- Protocols moved to submodules in T001\n- Must update all import statements to use new paths\n- No re-exports allowed per CLAUDE.md\n\n**Scope**:\n- In: All `from src.core.protocols import X` statements in src/ AND tests/\n- Out: Protocol module content (done in T001)\n\n**Changes**:\nMechanical sweep - update imports like:\n- `from src.core.protocols import IssueProvider` → `from src.core.protocols.issue import IssueProvider`\n- `from src.core.protocols import CodeReviewer` → `from src.core.protocols.review import CodeReviewer`\n- `from src.core.protocols import GateChecker` → `from src.core.protocols.validation import GateChecker`\n- etc.\n\n**Acceptance Criteria**:\n- No remaining `from src.core.protocols import` statements in src/ or tests/\n- All imports use direct paths like `src.core.protocols.\u003cmodule\u003e`\n- Code compiles and type checks\n\n**Verification**:\n- `grep -r \"from src.core.protocols import\" src/ tests/ --include=\"*.py\" | grep -v \"protocols/\"` returns empty\n- `uvx ty check` passes\n- `uvx ruff check .` passes\n- `uv run pytest tests/ -x --collect-only` succeeds (imports work)\n\n**Notes for Agent**:\n- Use grep to find all files needing updates in BOTH src/ and tests/\n- Group imports by module in updated files\n- Preserve import ordering conventions","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-13T04:42:43.473300359Z","created_by":"cyou","updated_at":"2026-01-13T05:31:37.580439622Z","closed_at":"2026-01-13T05:31:37.580439622Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-m1bh.2","depends_on_id":"mala-m1bh","type":"parent-child","created_at":"2026-01-13T04:42:43.473906285Z","created_by":"cyou"},{"issue_id":"mala-m1bh.2","depends_on_id":"mala-m1bh.1","type":"blocks","created_at":"2026-01-13T04:43:02.263184648Z","created_by":"cyou"}]}
{"id":"mala-m1bh.3","title":"[T003] Add session lifecycle protocols (IGateRunner, IReviewRunner, ISessionLifecycle)","description":"**Goal**: Define the 3 new protocols that will replace SessionCallbacks dataclass.\n\n**Type**: task | **Priority**: P2 | **Parallel**: [P]\n**Primary Files**: src/core/protocols/validation.py, src/core/protocols/review.py, src/core/protocols/lifecycle.py\n**Subsystems**: core\n\n**Context**:\n- SessionCallbacks has 10 callback fields\n- Grouping into 3 coarse-grained protocols:\n  - IGateRunner: gate checking operations\n  - IReviewRunner: review operations\n  - ISessionLifecycle: lifecycle operations\n\n**Scope**:\n- In: Add new protocol definitions to existing modules\n- Out: Updating consumers (separate task)\n\n**Changes**:\n- `src/core/protocols/validation.py` — Exists — Add IGateRunner protocol\n- `src/core/protocols/review.py` — Exists — Add IReviewRunner protocol\n- `src/core/protocols/lifecycle.py` — Exists — Add ISessionLifecycle protocol\n\n**Protocol Definitions**:\n```python\n# validation.py\n@runtime_checkable\nclass IGateRunner(Protocol):\n    async def run_gate_check(self, issue_id: str, log_path: Path, retry_state: RetryState) -\u003e tuple[GateOutcome, int]: ...\n    async def run_session_end_check(self, issue_id: str, log_path: Path, retry_state: SessionEndRetryState) -\u003e SessionEndResult: ...\n\n# review.py\n@runtime_checkable\nclass IReviewRunner(Protocol):\n    async def run_review(self, issue_id: str, description: str | None, ...) -\u003e ReviewOutcome: ...\n    def check_no_progress(self, log_path: Path, log_offset: int, ...) -\u003e bool: ...\n\n# lifecycle.py\n@runtime_checkable\nclass ISessionLifecycle(Protocol):\n    def get_log_path(self, session_id: str) -\u003e Path: ...\n    def get_log_offset(self, log_path: Path, start_offset: int) -\u003e int: ...\n    def on_abort(self, reason: str) -\u003e None: ...\n    def get_abort_event(self) -\u003e asyncio.Event | None: ...\n    def on_tool_use(self, agent_id: str, tool_name: str, args: dict) -\u003e None: ...\n    def on_agent_text(self, agent_id: str, text: str) -\u003e None: ...\n```\n\n**Acceptance Criteria**:\n- 3 new protocols defined with @runtime_checkable\n- Signatures match SessionCallbacks callback signatures exactly\n- Type hints complete and correct\n\n**Verification**:\n- `uvx ty check src/core/protocols/` passes\n- `uvx ruff check src/core/protocols/` passes\n\n**Notes for Agent**:\n- Check SessionCallbacks in agent_session_runner.py for exact signatures\n- Use TYPE_CHECKING imports for types not needed at runtime","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-13T04:43:02.81867982Z","created_by":"cyou","updated_at":"2026-01-13T05:19:48.499615968Z","closed_at":"2026-01-13T05:19:48.499615968Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-m1bh.3","depends_on_id":"mala-m1bh","type":"parent-child","created_at":"2026-01-13T04:43:02.819651155Z","created_by":"cyou"},{"issue_id":"mala-m1bh.3","depends_on_id":"mala-m1bh.1","type":"blocks","created_at":"2026-01-13T04:46:55.03777508Z","created_by":"cyou"}]}
{"id":"mala-m1bh.4","title":"[T004] [integration-path-test] Create LifecycleController + wire into Orchestrator","description":"**Goal**: Extract signal/interrupt handling from MalaOrchestrator into dedicated LifecycleController class.\n\n**Type**: task | **Priority**: P2 | **Parallel**: [P]\n**Primary Files**: src/orchestration/lifecycle_controller.py, src/orchestration/orchestrator.py, tests/unit/orchestration/test_lifecycle_controller.py\n**Subsystems**: orchestration\n\n**Context**:\n- Orchestrator owns SIGINT handling, interrupt events, drain/abort mode\n- 3-stage escalation: interrupt → drain → abort\n- Signal registration stays in Orchestrator (main thread requirement)\n- Handler logic moves to LifecycleController\n\n**Scope**:\n- In: Create LifecycleController, extract methods/state, wire into Orchestrator\n- Out: Other orchestrator refactoring\n\n**Changes**:\n- `src/orchestration/lifecycle_controller.py` — New — LifecycleController class\n- `src/orchestration/orchestrator.py` — Exists — Remove lifecycle logic, use LifecycleController\n- `src/orchestration/orchestration_wiring.py` — Exists — Wire LifecycleController\n- `tests/unit/orchestration/test_lifecycle_controller.py` — New — Unit tests\n\n**LifecycleController Interface**:\n```python\n@dataclass\nclass LifecycleController:\n    interrupt_event: asyncio.Event\n    drain_event: asyncio.Event\n    _sigint_count: int = 0\n    _sigint_last_at: float = 0.0\n    _drain_mode_active: bool = False\n    _abort_mode_active: bool = False\n    \n    def handle_sigint(self, loop: AbstractEventLoop) -\u003e None: ...\n    def request_abort(self, reason: str) -\u003e None: ...\n    def is_interrupted(self) -\u003e bool: ...\n    def is_drain_mode(self) -\u003e bool: ...\n    def reset(self) -\u003e None: ...\n```\n\n**Acceptance Criteria**:\n- LifecycleController class created with all signal/interrupt state\n- Orchestrator uses LifecycleController instead of internal state\n- SIGINT 3-stage escalation behavior preserved exactly\n- Unit tests cover escalation states\n\n**Verification**:\n- `uv run pytest tests/unit/orchestration/test_lifecycle_controller.py -v` passes\n- `uv run pytest tests/` passes (no regressions)\n- Manual: `mala run` + Ctrl-C verifies escalation works\n- Integration path: Test exercises Orchestrator → LifecycleController wiring\n\n**Notes for Agent**:\n- Preserve exact timing behavior for escalation\n- Signal registration stays in Orchestrator (loop.add_signal_handler)\n- LifecycleController methods called by signal handlers","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-13T04:43:21.463651123Z","created_by":"cyou","updated_at":"2026-01-13T05:34:34.032149076Z","closed_at":"2026-01-13T05:34:34.032149076Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-m1bh.4","depends_on_id":"mala-m1bh","type":"parent-child","created_at":"2026-01-13T04:43:21.464361839Z","created_by":"cyou"},{"issue_id":"mala-m1bh.4","depends_on_id":"mala-m1bh.1","type":"blocks","created_at":"2026-01-13T04:46:55.152336067Z","created_by":"cyou"}]}
{"id":"mala-m1bh.5","title":"[T005] [integration-path-test] Create TriggerEngine + unit tests","description":"**Goal**: Extract trigger policy logic from RunCoordinator into dedicated TriggerEngine class.\n\n**Type**: task | **Priority**: P2 | **Parallel**: [P]\n**Primary Files**: src/pipeline/trigger_engine.py, tests/unit/pipeline/test_trigger_engine.py\n**Subsystems**: pipeline\n\n**Context**:\n- RunCoordinator has trigger evaluation logic spread throughout\n- TriggerEngine provides event-based interface for trigger decisions\n- Stateless service constructed per run\n\n**Scope**:\n- In: Create TriggerEngine class, move trigger evaluation logic\n- Out: Wiring into RunCoordinator (separate task)\n\n**Changes**:\n- `src/pipeline/trigger_engine.py` — New — TriggerEngine class\n- `tests/unit/pipeline/test_trigger_engine.py` — New — Unit tests\n\n**TriggerEngine Interface**:\n```python\n@dataclass\nclass TriggerActions:\n    should_run: bool\n    commands: list[ResolvedCommand]\n    failure_mode: FailureMode\n\nclass TriggerEngine:\n    def on_issue_completed(self, issue_id: str, result: IssueResult) -\u003e TriggerActions | None: ...\n    def on_epic_closed(self, epic_id: str) -\u003e TriggerActions | None: ...\n    def on_run_end(self, success_count: int, total_count: int) -\u003e TriggerActions | None: ...\n    def resolve_commands(self, trigger_config: BaseTriggerConfig) -\u003e list[ResolvedCommand]: ...\n```\n\n**Acceptance Criteria**:\n- TriggerEngine class with event-based interface\n- Trigger evaluation logic extracted from RunCoordinator\n- Unit tests cover all trigger types (issue_completed, epic_closed, run_end)\n\n**Verification**:\n- `uv run pytest tests/unit/pipeline/test_trigger_engine.py -v` passes\n- `uvx ty check src/pipeline/trigger_engine.py` passes\n\n**Notes for Agent**:\n- Look at RunCoordinator._check_and_queue_periodic_trigger and related methods\n- TriggerActions replaces the queue-based approach with explicit return values\n- Don't modify RunCoordinator yet - that's a separate task","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-13T04:43:37.450697338Z","created_by":"cyou","updated_at":"2026-01-13T05:26:24.165164019Z","closed_at":"2026-01-13T05:26:24.165164019Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-m1bh.5","depends_on_id":"mala-m1bh","type":"parent-child","created_at":"2026-01-13T04:43:37.451395094Z","created_by":"cyou"},{"issue_id":"mala-m1bh.5","depends_on_id":"mala-m1bh.1","type":"blocks","created_at":"2026-01-13T04:46:55.264370829Z","created_by":"cyou"}]}
{"id":"mala-m1bh.6","title":"[T006] [integration-path-test] Create FixerService + unit tests","description":"**Goal**: Extract fixer agent spawning logic from RunCoordinator into dedicated FixerService class.\n\n**Type**: task | **Priority**: P2 | **Parallel**: [P]\n**Primary Files**: src/pipeline/fixer_service.py, tests/unit/pipeline/test_fixer_service.py\n**Subsystems**: pipeline\n\n**Context**:\n- RunCoordinator._run_fixer_agent contains fixer spawning logic\n- FixerService encapsulates fixer agent lifecycle\n- Stateless service constructed per run\n\n**Scope**:\n- In: Create FixerService class, move fixer logic\n- Out: Wiring into RunCoordinator (separate task)\n\n**Changes**:\n- `src/pipeline/fixer_service.py` — New — FixerService class\n- `tests/unit/pipeline/test_fixer_service.py` — New — Unit tests\n\n**FixerService Interface**:\n```python\nclass FixerService:\n    async def run_fixer(\n        self,\n        failure_context: FailureContext,\n        interrupt_event: asyncio.Event | None = None,\n    ) -\u003e FixerResult: ...\n    \n    def cleanup_locks(self) -\u003e None: ...\n```\n\n**Acceptance Criteria**:\n- FixerService class with run_fixer and cleanup_locks methods\n- Fixer spawning logic extracted from RunCoordinator\n- Unit tests cover success, failure, and interrupt scenarios\n\n**Verification**:\n- `uv run pytest tests/unit/pipeline/test_fixer_service.py -v` passes\n- `uvx ty check src/pipeline/fixer_service.py` passes\n\n**Notes for Agent**:\n- Look at RunCoordinator._run_fixer_agent for existing logic\n- FailureContext should encapsulate all info needed to spawn fixer\n- Don't modify RunCoordinator yet - that's a separate task","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-13T04:43:49.354516981Z","created_by":"cyou","updated_at":"2026-01-13T05:46:54.383039669Z","closed_at":"2026-01-13T05:46:54.383039669Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-m1bh.6","depends_on_id":"mala-m1bh","type":"parent-child","created_at":"2026-01-13T04:43:49.355148898Z","created_by":"cyou"},{"issue_id":"mala-m1bh.6","depends_on_id":"mala-m1bh.1","type":"blocks","created_at":"2026-01-13T04:46:55.375217048Z","created_by":"cyou"}]}
{"id":"mala-m1bh.7","title":"[T007] Update RunCoordinator to use TriggerEngine + FixerService","description":"**Goal**: Refactor RunCoordinator to delegate to TriggerEngine and FixerService instead of containing logic inline.\n\n**Type**: task | **Priority**: P2 | **Parallel**: No\n**Primary Files**: src/pipeline/run_coordinator.py, src/orchestration/orchestration_wiring.py\n**Subsystems**: pipeline, orchestration\n\n**Context**:\n- TriggerEngine and FixerService created in T005/T006\n- RunCoordinator currently contains this logic inline\n- Delegation pattern: emit events to TriggerEngine, delegate execution to FixerService\n\n**Scope**:\n- In: Update RunCoordinator constructor, replace inline logic with service calls, update wiring\n- Out: TriggerEngine/FixerService implementation (done in previous tasks)\n\n**Changes**:\n- `src/pipeline/run_coordinator.py` — Exists — Accept TriggerEngine + FixerService as deps, delegate to them\n- `src/orchestration/orchestration_wiring.py` — Exists — Wire TriggerEngine + FixerService construction\n- `tests/unit/pipeline/test_run_coordinator.py` — Exists — Update mocks for new dependencies\n\n**Wiring Map**:\n- `TriggerEngine` → `orchestration_wiring.py` → `RunCoordinator.__init__`\n- `FixerService` → `orchestration_wiring.py` → `RunCoordinator.__init__`\n\n**Acceptance Criteria**:\n- RunCoordinator accepts TriggerEngine and FixerService via constructor\n- Trigger evaluation delegated to TriggerEngine.on_* methods\n- Fixer spawning delegated to FixerService.run_fixer\n- Original inline logic removed\n- All existing tests pass\n\n**Verification**:\n- `uv run pytest tests/unit/pipeline/test_run_coordinator.py -v` passes\n- `uv run pytest tests/` passes (no regressions)\n- Manual: `mala run --watch` with validation triggers\n\n**Notes for Agent**:\n- Follow existing DI pattern (see how other deps are injected)\n- RunCoordinator should focus on coordination, not policy decisions\n- Ensure all trigger queue/fire-on logic moves to TriggerEngine","notes":"Quality gate failed: External review failed: src/pipeline/fixer_interface.py:107: [P1] Remove backward-compat alias RunCoordinatorFixerAdapter; tests/unit/pipeline/test_trigger_execution.py:661: [P1] Fix tests mocking FixerService.run_fixer with old signature; src/pipeline/fixer_interface.py:107: [P1] Remove backward-compat alias RunCoordinatorFixerAdapter\nLog: /home/cyou/.claude/projects/-home-cyou-mala/409ea000-b74c-4de1-ba8f-c8b6323a5b64.jsonl","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-13T04:44:05.40863983Z","created_by":"cyou","updated_at":"2026-01-13T17:22:58.750936563Z","closed_at":"2026-01-13T17:22:58.750936563Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-m1bh.7","depends_on_id":"mala-m1bh","type":"parent-child","created_at":"2026-01-13T04:44:05.409391826Z","created_by":"cyou"},{"issue_id":"mala-m1bh.7","depends_on_id":"mala-m1bh.5","type":"blocks","created_at":"2026-01-13T04:44:20.520304256Z","created_by":"cyou"},{"issue_id":"mala-m1bh.7","depends_on_id":"mala-m1bh.6","type":"blocks","created_at":"2026-01-13T04:44:20.633209444Z","created_by":"cyou"},{"issue_id":"mala-m1bh.7","depends_on_id":"mala-m1bh.4","type":"blocks","created_at":"2026-01-13T04:47:05.733155974Z","created_by":"cyou"}]}
{"id":"mala-m1bh.8","title":"[T008] Refactor AgentSessionRunner to use Protocol interfaces","description":"**Goal**: Replace SessionCallbacks dataclass with IGateRunner, IReviewRunner, ISessionLifecycle protocol interfaces.\n\n**Type**: task | **Priority**: P2 | **Parallel**: [P]\n**Primary Files**: src/pipeline/agent_session_runner.py, src/pipeline/session_callback_factory.py, src/orchestration/orchestration_wiring.py\n**Subsystems**: pipeline, orchestration\n\n**Context**:\n- SessionCallbacks is a dataclass with 10 optional Callable fields\n- New protocols defined in T003: IGateRunner, IReviewRunner, ISessionLifecycle\n- AgentSessionRunner should accept protocol implementations instead of callbacks\n\n**Scope**:\n- In: Update AgentSessionRunner to accept protocols, update SessionCallbackFactory, update wiring\n- Out: Protocol definitions (done in T003)\n\n**Changes**:\n- `src/pipeline/agent_session_runner.py` — Exists — Replace SessionCallbacks with protocol params\n- `src/pipeline/session_callback_factory.py` — Exists — Create protocol implementations\n- `src/orchestration/orchestration_wiring.py` — Exists — Wire protocol implementations\n- `tests/pipeline/test_agent_session_runner.py` — Exists — Update for protocol interfaces\n\n**Wiring Map**:\n- `IGateRunner` impl → `SessionCallbackFactory` → `AgentSessionRunner`\n- `IReviewRunner` impl → `SessionCallbackFactory` → `AgentSessionRunner`\n- `ISessionLifecycle` impl → `SessionCallbackFactory` → `AgentSessionRunner`\n\n**Acceptance Criteria**:\n- AgentSessionRunner accepts protocol instances instead of SessionCallbacks\n- SessionCallbackFactory provides protocol implementations\n- SessionCallbacks dataclass removed or deprecated\n- All existing tests pass\n\n**Verification**:\n- `uv run pytest tests/pipeline/test_agent_session_runner.py -v` passes\n- `uv run pytest tests/` passes (no regressions)\n- `uvx ty check src/pipeline/` passes\n\n**Notes for Agent**:\n- May need to create concrete protocol implementations in session_callback_factory.py\n- Existing SessionRunContext can be adapted to provide protocol implementations\n- Preserve exact behavior - this is a structural refactor only","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-13T04:44:21.244336817Z","created_by":"cyou","updated_at":"2026-01-13T18:12:25.750724192Z","closed_at":"2026-01-13T18:12:25.750724192Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-m1bh.8","depends_on_id":"mala-m1bh","type":"parent-child","created_at":"2026-01-13T04:44:21.244954323Z","created_by":"cyou"},{"issue_id":"mala-m1bh.8","depends_on_id":"mala-m1bh.3","type":"blocks","created_at":"2026-01-13T04:44:37.087255543Z","created_by":"cyou"},{"issue_id":"mala-m1bh.8","depends_on_id":"mala-m1bh.7","type":"blocks","created_at":"2026-01-13T04:47:05.848598345Z","created_by":"cyou"}]}
{"id":"mala-m1bh.9","title":"[T009] Final verification + cleanup","description":"**Goal**: Delete old protocols.py, run full verification suite, ensure all acceptance criteria met.\n\n**Type**: task | **Priority**: P2 | **Parallel**: No (final gate)\n**Primary Files**: src/core/protocols.py (delete), pyproject.toml (update import-linter)\n**Subsystems**: core\n\n**Context**:\n- All protocol migration, service extraction, and callback refactoring complete\n- Original protocols.py can now be safely deleted\n- Full verification needed before PR\n\n**Scope**:\n- In: Delete protocols.py, update import-linter contracts, run full verification\n- Out: N/A (final task)\n\n**Changes**:\n- `src/core/protocols.py` — Delete\n- `pyproject.toml` — May need import-linter contract updates for new module boundaries\n\n**Acceptance Criteria**:\n- `src/core/protocols.py` deleted\n- All tests pass\n- Import-linter contracts pass\n- Type checking passes\n- Linting passes\n- Manual verification of SIGINT, triggers, fixer behavior\n\n**Verification**:\n- `uv run pytest tests/` passes\n- `uvx import-linter` passes\n- `uvx ty check` passes\n- `uvx ruff check .` passes\n- Manual: `mala run` + Ctrl-C at each stage (interrupt → drain → abort)\n- Manual: `mala run` with validation failures (fixer spawning)\n- Manual: `mala run --watch` (trigger firing)\n\n**Notes for Agent**:\n- This is the final gate before PR\n- If any verification fails, fix the issue before completing\n- Update import-linter contracts if new boundaries need enforcement","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-13T04:44:37.736836969Z","created_by":"cyou","updated_at":"2026-01-13T18:20:04.572390959Z","closed_at":"2026-01-13T18:20:04.572390959Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-m1bh.9","depends_on_id":"mala-m1bh","type":"parent-child","created_at":"2026-01-13T04:44:37.737538794Z","created_by":"cyou"},{"issue_id":"mala-m1bh.9","depends_on_id":"mala-m1bh.4","type":"blocks","created_at":"2026-01-13T04:44:42.833297335Z","created_by":"cyou"},{"issue_id":"mala-m1bh.9","depends_on_id":"mala-m1bh.7","type":"blocks","created_at":"2026-01-13T04:44:42.945341285Z","created_by":"cyou"},{"issue_id":"mala-m1bh.9","depends_on_id":"mala-m1bh.8","type":"blocks","created_at":"2026-01-13T04:44:43.051906356Z","created_by":"cyou"},{"issue_id":"mala-m1bh.9","depends_on_id":"mala-m1bh.2","type":"blocks","created_at":"2026-01-13T04:47:05.965732065Z","created_by":"cyou"}]}
{"id":"mala-m70v","title":"[Review] src/infra/clients/cerberus_review.py:222: [P2] Use directory path instead of glob for robust exclusion","description":"## Review Finding\n\nThis issue was auto-created from a P2 review finding.\n\n**Source issue:** mala-sw4q\n**Reviewer:** gemini\n**Location:** src/infra/clients/cerberus_review.py:222\n\n## Details\n\nThe pattern `.beads/*` typically fails to match hidden files (such as `.beads/.gitignore`) in standard glob implementations. Additionally, it may not recursively exclude nested subdirectories if they are added in the future. To ensure the entire issue tracker directory is consistently skipped as intended, use the directory path `.beads/` instead of a glob pattern.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-01T23:40:23.372958956Z","created_by":"cyou","updated_at":"2026-01-01T23:45:36.267744803Z","closed_at":"2026-01-01T23:45:36.267744803Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_finding:95b33a01ee8d","source:mala-sw4q"]}
{"id":"mala-mach","title":"[Review] 1 non-blocking finding from mala-zgl0","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-zgl0\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Diff size failure does not create remediation issue\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/infra/epic_verifier.py:572-583\n\nWhen `max_diff_size_kb` is exceeded, `verify_and_close_eligible` returns a failed verdict with `reasoning` but no `unmet_criteria`. Consequently, `create_remediation_issues` creates no issues. The epic fails to close without persistent feedback (like a blocking issue) on the board explaining why, making it hard for users to notice and resolve the \"human review required\" state.\n\nFix: Add a synthetic `UnmetCriterion` to the verdict when the size check fails.\n\n---\n\n\u003c!-- fp:7cd9b7c5da140d9d --\u003e\n\u003c!-- review_finding:2fd53f35b65a --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-10T23:53:07.22888994Z","created_by":"cyou","updated_at":"2026-01-10T23:56:46.116782678Z","closed_at":"2026-01-10T23:56:46.116782678Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-zgl0"]}
{"id":"mala-mank","title":"[Review] 1 non-blocking finding from mala-brwf.1","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-brwf.1\n**Highest priority:** P3\n\n---\n\n### Finding 1: [P3] Remove unused loop variable in extract_marker_expr\n\n**Priority:** P3\n**Reviewer:** claude\n**Location:** src/domain/validation/coverage_args.py:81\n\nThe `enumerate` call produces an unused index variable `i`. Since only `arg` is needed, use a simple `for arg in args:` loop instead:\n```python\nfor arg in args:\n```\n\n---\n","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-03T14:51:14.784716928Z","created_by":"cyou","updated_at":"2026-01-03T15:16:41.061136153Z","closed_at":"2026-01-03T15:16:41.061136153Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_findings:b0e5a943f954","source:mala-brwf.1"]}
{"id":"mala-mb29","title":"Remove real git subprocess usage from unit tests","description":"## Problem\\nUnit tests should be isolated and avoid real git/subprocess usage. tests/unit/infra/hooks/test_hooks.py uses subprocess git commands to build a real repo, which violates the testing philosophy and can be slow/flaky.\\n\\n## Scope\\n- Replace real git subprocess usage in unit tests with fakes or deterministic fixtures.\\n- Prefer in-memory or temp dir fixtures that do not call git.\\n- If git behavior must be exercised, move tests to integration with appropriate markers.\\n\\n## Acceptance Criteria\\n- No subprocess git usage in tests/unit.\\n- Equivalent behavior is tested via fakes or via an integration test marked accordingly.\\n\\n## Files\\n- tests/unit/infra/hooks/test_hooks.py\\n- tests/integration/... (if moving tests)\\n\\n## Verification\\n- rg -n git tests/unit | rg -n subprocess (should be empty for git calls)\\n- uv run pytest -m unit","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-05T14:18:14.959347721Z","created_by":"cyou","updated_at":"2026-01-05T16:08:48.932551715Z","closed_at":"2026-01-05T16:08:48.932551715Z","close_reason":"Closed"}
{"id":"mala-mfnr","title":"[Review] src/orchestration/orchestrator.py:521-544: [P2] Wrap finalization loop body in try/except","description":"## Review Finding\n\nThis issue was auto-created from a P2 review finding.\n\n**Source issue:** mala-nsjx\n**Reviewer:** gemini\n**Location:** src/orchestration/orchestrator.py:521-544\n\n## Details\n\nIn `_execute_remediation_issues`, if `_finalize_issue_result` raises an exception (e.g. during I/O), the loop will terminate early. This prevents subsequent remediation tasks in `task_pairs` from being finalized or marked as completed in the coordinator. Wrap the loop body in a `try/except` block to ensure a failure in one issue's finalization doesn't impact the cleanup of others.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-02T02:06:16.031326984Z","created_by":"cyou","updated_at":"2026-01-02T02:17:45.082995552Z","closed_at":"2026-01-02T02:17:45.082995552Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_finding:3c70c1770917","source:mala-nsjx"]}
{"id":"mala-mgfb","title":"Epic: Decompose AgentSessionRunner._run_message_iteration (CCN 32 → \u003c20)","description":"## Summary\nDecompose the 260-line `_run_message_iteration` method in `src/pipeline/agent_session_runner.py` which has CCN 32 and handles SDK client creation, query sending, response streaming, idle timeout detection, retry logic with backoff, tool result tracking, lint cache updates, and disconnect handling.\n\n## Primary Files\n- `src/pipeline/agent_session_runner.py:1232-1491`\n\n## Category\nCohesion\n\n## Source\nMultiple generators (gemini, claude)\n\n## Fix Approach\n1. Extract retry orchestration into `_retry_with_backoff()` coroutine\n2. Extract message handling into `_process_sdk_stream()`\n3. Create standalone async context manager for timeout wrapper\n\n## Non-Goals\n- Changing SDK message protocol handling\n- Modifying existing timeout durations\n\n## Acceptance Criteria\n- [ ] `_run_message_iteration` CCN reduced below 20\n- [ ] Retry logic testable without SDK mocks\n- [ ] Idle timeout behavior unchanged\n\n## Test Plan\n- Unit tests for retry policy with mocked sleep/delay\n- Integration test verifying full message iteration with test SDK client\n\n## Agent Notes\nThe idle timeout uses a sliding window; extracted helper must preserve the renewal logic on each received message.","status":"closed","priority":1,"issue_type":"epic","created_at":"2026-01-03T05:47:51.169049806Z","created_by":"cyou","updated_at":"2026-01-03T18:04:22.905711434Z","closed_at":"2026-01-03T18:04:22.905711434Z","close_reason":"Closed"}
{"id":"mala-mgfb.1","title":"Extract _apply_retry_backoff helper from _run_message_iteration","description":"## Context\nThe `_run_message_iteration` method in `src/pipeline/agent_session_runner.py` has CCN 32 and is 260 lines. This task extracts the retry backoff logic into a standalone helper.\n\n## Implementation\nExtract lines 1278-1292 into `async def _apply_retry_backoff(self, retry_count: int) -\u003e None`:\n- Calculate backoff index from `self.config.idle_retry_backoff`\n- Log the retry wait if backoff \u003e 0\n- Await asyncio.sleep(backoff)\n\n## Files\n- `src/pipeline/agent_session_runner.py` - add helper method, update caller\n\n## Tests\nExisting tests in `tests/test_agent_session_runner.py` (TestIdleTimeoutRetry class) cover this behavior through integration tests. Run:\n```bash\nuv run pytest tests/test_agent_session_runner.py::TestIdleTimeoutRetry -v\n```\n\n## Acceptance\n- [ ] Helper method extracted and called from `_run_message_iteration`\n- [ ] All existing idle timeout tests pass\n- [ ] No change in public behavior","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T05:53:02.206428992Z","created_by":"cyou","updated_at":"2026-01-03T07:15:17.727777792Z","closed_at":"2026-01-03T07:15:17.727777792Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-mgfb.1","depends_on_id":"mala-mgfb","type":"parent-child","created_at":"2026-01-03T17:57:25.636319775Z","created_by":"daemon"}]}
{"id":"mala-mgfb.2","title":"Extract IdleTimeoutStream context manager from _run_message_iteration","description":"## Context\nThe `_run_message_iteration` method has a nested `_iter_messages()` async generator (lines 1321-1343) that wraps the SDK stream with idle timeout handling. This should be a reusable async context manager.\n\n## Implementation\nCreate `class IdleTimeoutStream` as an async iterator wrapper:\n```python\nclass IdleTimeoutStream:\n    \"\"\"Wrap an async iterator with idle timeout detection.\n    \n    Raises IdleTimeoutError if no message received within timeout,\n    unless pending_tool_ids is non-empty (tool execution in progress).\n    \"\"\"\n    def __init__(\n        self,\n        stream: AsyncIterator[Any],\n        timeout_seconds: float | None,\n        pending_tool_ids: set[str],\n    ): ...\n    \n    def __aiter__(self) -\u003e Self: ...\n    async def __anext__(self) -\u003e Any: ...\n```\n\nReplace the inline `_iter_messages()` function with this class.\n\n## Files\n- `src/pipeline/agent_session_runner.py` - add class, update `_run_message_iteration`\n\n## Tests\nExisting tests cover this through `TestIdleTimeoutRetry`. Run:\n```bash\nuv run pytest tests/test_agent_session_runner.py::TestIdleTimeoutRetry -v\n```\n\n## Acceptance\n- [ ] IdleTimeoutStream class implemented\n- [ ] `_run_message_iteration` uses the new class\n- [ ] All idle timeout tests pass","notes":"Quality gate failed: Quality gate failed: No commit with bd-mala-mgfb.2 found after run baseline (stale commits from previous runs are rejected)\nLog: /home/cyou/.claude/projects/-home-cyou-mala/f44c6547-181b-4d58-862b-6f680089fcf7.jsonl","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T05:53:12.276533962Z","created_by":"cyou","updated_at":"2026-01-03T14:36:51.68535722Z","closed_at":"2026-01-03T14:36:51.68535722Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-mgfb.2","depends_on_id":"mala-mgfb.1","type":"blocks","created_at":"2026-01-03T05:53:39.763089479Z","created_by":"daemon"},{"issue_id":"mala-mgfb.2","depends_on_id":"mala-mgfb","type":"parent-child","created_at":"2026-01-03T17:57:25.677438726Z","created_by":"daemon"}]}
{"id":"mala-mgfb.3","title":"Extract _process_message_stream from _run_message_iteration","description":"## Context\nThe main message processing loop in `_run_message_iteration` (lines 1345-1416) handles AssistantMessage and ResultMessage types. This should be extracted to reduce the main method's complexity.\n\n## Implementation\nExtract into `async def _process_message_stream(...)`:\n```python\nasync def _process_message_stream(\n    self,\n    stream: AsyncIterator[Any],\n    issue_id: str,\n    state: MessageIterationState,\n    lifecycle_ctx: LifecycleContext,\n    lint_cache: LintCache,\n    query_start: float,\n    tracer: TelemetrySpan | None,\n) -\u003e None:\n    \"\"\"Process SDK message stream and update state.\n    \n    Updates state.session_id, state.tool_calls_this_turn, state.pending_tool_ids,\n    and lint_cache on successful lint commands.\n    \"\"\"\n```\n\nThis method processes messages in the stream, calling callbacks and updating state. It does not handle IdleTimeoutError - that remains in the caller.\n\n## Files\n- `src/pipeline/agent_session_runner.py` - add method, update caller\n\n## Tests\nRun full agent session runner tests:\n```bash\nuv run pytest tests/test_agent_session_runner.py -v\n```\n\n## Acceptance\n- [ ] Message processing extracted into dedicated method\n- [ ] Callback invocations preserved (on_agent_text, on_tool_use)\n- [ ] Lint cache updates preserved\n- [ ] All tests pass","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T05:53:23.698980368Z","created_by":"cyou","updated_at":"2026-01-03T15:21:01.001294585Z","closed_at":"2026-01-03T15:21:01.001294585Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-mgfb.3","depends_on_id":"mala-mgfb.2","type":"blocks","created_at":"2026-01-03T05:53:39.803705944Z","created_by":"daemon"},{"issue_id":"mala-mgfb.3","depends_on_id":"mala-mgfb","type":"parent-child","created_at":"2026-01-03T17:57:25.714984953Z","created_by":"daemon"}]}
{"id":"mala-mgfb.4","title":"Extract _prepare_idle_retry from _run_message_iteration","description":"## Context\nThe idle timeout error handling in `_run_message_iteration` (lines 1418-1485) has complex logic for:\n1. Disconnecting the client\n2. Checking retry eligibility\n3. Preparing state for retry\n\nThis should be extracted to reduce nesting and improve testability.\n\n## Implementation\nExtract into:\n```python\nasync def _disconnect_client_safely(\n    self, client: SDKClientProtocol, issue_id: str\n) -\u003e None:\n    \"\"\"Disconnect SDK client with timeout, logging any failures.\"\"\"\n\ndef _prepare_idle_retry(\n    self,\n    state: MessageIterationState,\n    lifecycle_ctx: LifecycleContext,\n    issue_id: str,\n) -\u003e str:\n    \"\"\"Prepare state for idle retry and return the next query.\n    \n    Updates state.idle_retry_count, state.pending_session_id, and clears\n    state.pending_tool_ids.\n    \n    Raises:\n        IdleTimeoutError: If retry is not possible (max retries exceeded,\n            or tool calls occurred without session context).\n    \n    Returns:\n        The query to use for the retry attempt.\n    \"\"\"\n```\n\n## Files\n- `src/pipeline/agent_session_runner.py` - add methods, update caller\n\n## Tests\nRun idle timeout retry tests:\n```bash\nuv run pytest tests/test_agent_session_runner.py::TestIdleTimeoutRetry -v\n```\n\n## Acceptance\n- [ ] Disconnect logic extracted\n- [ ] Retry preparation logic extracted\n- [ ] All error conditions preserved\n- [ ] All tests pass","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T05:53:33.527742793Z","created_by":"cyou","updated_at":"2026-01-03T16:16:29.123878301Z","closed_at":"2026-01-03T16:16:29.123878301Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-mgfb.4","depends_on_id":"mala-mgfb.3","type":"blocks","created_at":"2026-01-03T05:53:39.838402846Z","created_by":"daemon"},{"issue_id":"mala-mgfb.4","depends_on_id":"mala-mgfb","type":"parent-child","created_at":"2026-01-03T17:57:25.749444632Z","created_by":"daemon"}]}
{"id":"mala-mj8","title":"Docs: update README + Braintrust docstring to match implementation","description":"Problem: README says lock mechanism uses atomic mkdir, but scripts use hardlink+tempfile; braintrust_integration.py docstring says setup is in main.py, but it is in cli.py.\\n\\nScope/files: README.md, src/braintrust_integration.py.\\n\\nWork:\\n- Update README to reflect actual lock mechanism and current braintrust setup location.\\n- Update braintrust_integration.py docstring to refer to cli.py (or central location if changed).\\n\\nAcceptance criteria:\\n- Docs match current behavior.\\n- No functional code changes.","status":"closed","priority":3,"issue_type":"task","created_at":"2025-12-25T21:38:42.843621671Z","updated_at":"2025-12-26T00:00:38.778018843Z","closed_at":"2025-12-26T00:00:38.778018843Z","close_reason":"Closed"}
{"id":"mala-mkow","title":"refactor --validate-every command -- should be more general, including mode to run only at end","status":"closed","priority":2,"issue_type":"feature","created_at":"2026-01-05T21:44:38.151018875Z","created_by":"cyou","updated_at":"2026-01-06T04:42:11.649225758Z","closed_at":"2026-01-06T04:42:11.649225758Z","close_reason":"Closed"}
{"id":"mala-mm2","title":"BeadsClient: add parent epic lookup methods","description":"Context:\n- Need to determine parent epic for each task to enable grouping\n- `bd dep tree \u003cid\u003e --direction=down` returns parent chain (epic at depth=1)\n- Should cache results to avoid repeated subprocess calls for tasks in same epic\n\nScope:\n- In: Add `get_parent_epic_async(issue_id) -\u003e str | None` method to BeadsClient\n- In: Add batch method `get_parent_epics_async(issue_ids) -\u003e dict[str, str | None]` for efficiency\n- Out: Changes to existing `get_ready_async` signature (handled in later issue)\n\nAcceptance Criteria:\n- `get_parent_epic_async` returns parent epic ID or None for orphans\n- Batch method minimizes subprocess calls (one call per unique epic, not per task)\n- Method handles errors gracefully (returns None on failure)\n\nTest Plan:\n- Add unit tests with mocked subprocess calls\n- Test orphan detection (no parent epic)\n- Test caching behavior\n\nNotes for Agent:\n- Use existing `_run_subprocess_async` pattern\n- Epic is identified by `issue_type == \"epic\"` at depth \u003e 0 in tree output\n\nPrimary files: src/beads_client.py","status":"tombstone","priority":1,"issue_type":"task","created_at":"2025-12-27T18:14:19.162836Z","updated_at":"2025-12-27T18:19:15.404812357Z","dependencies":[{"issue_id":"mala-mm2","depends_on_id":"mala-g3h","type":"blocks","created_at":"2025-12-27T18:18:55.814652214Z","created_by":"daemon"}],"deleted_at":"2025-12-27T18:19:15.404812357Z","deleted_by":"daemon","delete_reason":"delete","original_type":"task"}
{"id":"mala-mnif","title":"[Review] 2 non-blocking findings from mala-yb5a","description":"## Review Findings\n\nThis issue consolidates 2 non-blocking findings from code review.\n\n**Source issue:** mala-yb5a\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Update docs/quality-hardening-plan.md to remove --coverage-threshold references\n\n**Priority:** P2\n**Reviewer:** claude\n**Location:** docs/quality-hardening-plan.md:248\n\nThe file `docs/quality-hardening-plan.md` still references the `--coverage-threshold` CLI flag in multiple places (lines 248, 287, 296), but this flag has been removed. This documentation is now out of sync with the CLI. Users reading this doc will be confused when the flag doesn't exist. The `docs/cli-reference.md` was updated, but this doc was missed.\n\n---\n\n### Finding 2: [P3] Remove stale --coverage-threshold docs reference\n\n**Priority:** P3\n**Reviewer:** codex\n**Location:** docs/quality-hardening-plan.md:247-249\n\n`docs/quality-hardening-plan.md` still documents `--coverage-threshold`, but this commit removes that CLI option. Anyone following this doc will now hit a CLI error (unknown option) and won’t know to set the threshold via `mala.yaml` (`coverage.threshold`).\n\n```md\n- `--coverage-threshold \u003cfloat\u003e` (default 85.0)\n```\n\n---\n\n\u003c!-- fp:6fcf774af1ef9508 --\u003e\n\u003c!-- fp:20e504efda3c1586 --\u003e\n\u003c!-- review_finding:d9259a7ee68c --\u003e","notes":"Quality gate failed: Quality gate failed: Missing validation evidence for: test, typecheck\nLog: /home/cyou/.claude/projects/-home-cyou-mala/e8bb4df4-91c7-42c9-8c92-e70bd33e9360.jsonl","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-06T03:40:45.284749881Z","created_by":"cyou","updated_at":"2026-01-06T03:44:41.095494042Z","closed_at":"2026-01-06T03:44:41.095494042Z","close_reason":"Closed","labels":["auto_generated","needs-followup","review_finding","source:mala-yb5a"]}
{"id":"mala-moty","title":"[Review] src/domain/quality_gate.py:517-520: [P2] Deduplicate failed commands for shared tool names","description":"## Review Finding\n\nThis issue was auto-created from a P2 review finding.\n\n**Source issue:** mala-fdp1.11\n**Reviewer:** codex\n**Location:** src/domain/quality_gate.py:517-520\n\n## Details\n\nWhen lint and format share the same extracted tool name (e.g., `ruff`), a single failed command matches both kinds, and `failed_commands` is built from all `kind_failed` values. This can yield duplicates like `ruff, ruff`, overstating the number of failures in the quality gate message. Consider deduping by display name or including kind-specific labels when multiple kinds map to the same tool.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-02T15:51:09.511333691Z","created_by":"cyou","updated_at":"2026-01-02T16:59:21.232804032Z","closed_at":"2026-01-02T16:59:21.232804032Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_finding:07a0520009b8","source:mala-fdp1.11"]}
{"id":"mala-mr15","title":"[P1] Don’t call getoption(\"--include-flaky-sdk\") before option exists","description":"## Review Finding\n\n**Priority:** P1\n**Trigger:** run_end\n**Baseline:** ddc86bb..3deec8ab\n**Location:** tests/e2e/conftest.py:27-47\n\n---\n\n`tests/e2e/conftest.py` calls `config.getoption(\"--include-flaky-sdk\", default=False)` inside `pytest_collection_modifyitems`. Pytest raises `ValueError: no option named '--include-flaky-sdk'` if the option isn’t registered for this config. Since `pytest_collection_modifyitems` can run before the plugin that defines `pytest_addoption` is registered (especially if this conftest isn’t in scope for a given run, or if collection happens without loading this conftest), this can crash collection.\n\nConcrete failure path: run pytest from a scope that doesn’t load `tests/e2e/conftest.py` as a conftest plugin, but still ends up importing this file or running its hooks (e.g., via plugin loading or different rootdir / testpaths configuration); then `pytest_collection_modifyitems` executes and `getoption` errors because `--include-flaky-sdk` is unknown.\n\nFix: use `hasattr(config.option, \"include_flaky_sdk\")` (option dest), or wrap `getoption` in `try/except ValueError`, or ensure the option is registered at the session level (e.g., move to `tests/conftest.py`).","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-11T02:30:41.670046814Z","created_by":"cyou","updated_at":"2026-01-11T02:35:47.515467738Z","closed_at":"2026-01-11T02:35:47.515467738Z","close_reason":"Closed","labels":["auto_generated","cumulative-review","fp:b7da4fc7332468dd","review_finding","trigger:run_end"]}
{"id":"mala-ms5f","title":"[P1] E2E disable now misses nested trigger `commands` blocks","description":"## Review Finding\n\n**Priority:** P1\n**Trigger:** session_end\n**Baseline:** 14fb49c..3438772\n**Location:** src/domain/validation/e2e.py:377-421\n**Source Issue:** mala-ptio.1\n\n---\n\nThe function was changed to only enter the mutation mode when it sees a non-indented `commands:` line (top-level). However, the fixture config used by the unit test is nested under `validation_triggers.run_end.commands` (indented), so the function will never set `in_commands=True` and will fall through to the “no top-level commands” case, appending a new top-level `commands:` section instead of disabling the nested trigger.\n\nConcrete outcome: fixture configs that specify commands only under `validation_triggers.*.commands` will keep their nested commands unchanged, and the newly appended top-level `commands.e2e: null` likely has no effect on the nested trigger execution path (depends on loader semantics). This contradicts the change intent described in the commit message (“fix e2e config disable for nested commands”).\n\nEvidence: the existing unit test input is:\n- `tests/unit/domain/validation/test_e2e.py` writes `validation_triggers: ... commands:` (indented), not a top-level `commands:`.\n- The new detection explicitly requires `not line.startswith(\" \")`.\n\nSo the updated logic does not actually “detect top-level commands: key only” for the nested case; it ignores nested commands entirely.","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-10T21:26:10.07692785Z","created_by":"cyou","updated_at":"2026-01-10T21:35:48.327412939Z","closed_at":"2026-01-10T21:35:48.327412939Z","close_reason":"Closed","labels":["auto_generated","cumulative-review","fp:9d56c9884d8be881","review_finding","trigger:session_end"]}
{"id":"mala-mut1","title":"Epic: Mala Init Evidence Checks \u0026 Triggers","description":"Add `evidence_check` and `validation_triggers` configuration to `mala init` flow with questionary-based UI, sensible defaults, and opt-out capability.\n\n**Spec**: docs/2026-01-11-init-evidence-checks-spec.md\n**Plan**: plans/2026-01-11-init-evidence-checks-plan.md\n\n## Scope\n- Evidence check configuration during init with checkbox multi-select (R1, R2, R7, R8)\n- run_end validation trigger configuration with checkbox multi-select (R3, R7, R8)\n- Migration of init() prompts to questionary (R5)\n- Config validation before writing (R6)\n- Trigger quick-reference table after every init (R4, R9)\n- Generated YAML with `ref:` syntax for trigger commands (R10)\n- Non-interactive mode with CLI flags (`--yes`, `--preset`, `--skip-evidence`, `--skip-triggers`)\n- TTY detection with actionable error message\n\n## Non-Goals\n- New preset types beyond python-uv, go, node-npm, rust\n- Triggers other than run_end during init\n- Granular per-command failure_mode/max_retries configuration during init","status":"closed","priority":1,"issue_type":"epic","created_at":"2026-01-11T04:42:41.729355013Z","created_by":"cyou","updated_at":"2026-01-11T21:48:04.086087214Z","closed_at":"2026-01-11T21:48:04.086087214Z","close_reason":"Closed"}
{"id":"mala-mut1.1","title":"[T001] [integration-path-test] Add questionary + skeleton + failing integration tests","description":"**Type**: task\n**Priority**: P1\n**Primary Files**: pyproject.toml, src/cli/cli.py, tests/integration/cli/test_init.py\n**Subsystems**: config, cli, tests\n\n## Goal\nAdd questionary dependency, create function stubs for all new init() helpers, and write failing integration tests that specify the expected behavior.\n\n## Context\n- questionary 2.x provides consistent terminal handling for confirm/checkbox/select prompts\n- The integration tests specify the behavior before implementation (TDD)\n- Tests mock questionary functions to control prompt responses\n\n## Scope\n- **In**: Add dependency, create function stubs, write failing tests\n- **Out**: Actual implementation of prompt logic, defaults, orchestration, or CLI flags (handled in T003)\n\n## Changes\n- `pyproject.toml` — [Exists] — Add `questionary\u003e=2.0.0` to `[project.dependencies]`\n- `src/cli/cli.py` — [Exists] — Add stub functions that raise NotImplementedError:\n  - `_get_preset_command_names(preset_name: str) -\u003e list[str]`\n  - `_prompt_preset_selection(presets: list[str]) -\u003e str | None`\n  - `_prompt_custom_commands_questionary() -\u003e dict[str, str]`\n  - `_compute_evidence_defaults(commands: list[str], is_preset: bool) -\u003e list[str]`\n  - `_compute_trigger_defaults(commands: list[str], is_preset: bool) -\u003e list[str]`\n  - `_prompt_evidence_check(commands: list[str], is_preset: bool) -\u003e list[str] | None`\n  - `_prompt_run_end_trigger(commands: list[str], is_preset: bool) -\u003e list[str] | None`\n  - `_build_evidence_check_dict(required: list[str]) -\u003e dict[str, list[str]]`\n  - `_build_validation_triggers_dict(commands: list[str]) -\u003e dict[str, Any]`\n  - `_print_trigger_reference_table() -\u003e None`\n- `tests/integration/cli/test_init.py` — [Exists] — Add test classes with failing tests:\n  - `TestInitEvidencePrompts` (4 tests)\n  - `TestInitTriggerPrompts` (3 tests)\n  - `TestInitNonInteractive` (6 tests)\n  - `TestInitTriggerTable` (3 tests)\n  - Add `mock_questionary` fixture that patches questionary.confirm/checkbox/select\n\n## Acceptance Criteria\n- `uv sync` succeeds with questionary installed\n- All 10 stub functions exist and raise NotImplementedError when called\n- Integration tests run but FAIL for expected reasons (NotImplementedError from stubs)\n- Test file has no import errors\n\n## Verification\n```bash\nuv sync\nuv run python -c \"import questionary; print(questionary.__version__)\"\nuv run pytest tests/integration/cli/test_init.py -v --collect-only  # Tests collect without error\nuv run pytest tests/integration/cli/test_init.py::TestInitEvidencePrompts -v  # Should fail with NotImplementedError\n```\n\n## Notes for Agent\n- Stubs should have correct signatures but only `raise NotImplementedError(\"Not implemented\")`\n- Import questionary at top of cli.py\n- Tests should mock questionary at the function level: `@patch(\"src.cli.cli.questionary.confirm\")` etc.\n- Use `pytest.fixture` for `mock_questionary` that returns a mock object with confirm/checkbox/select methods\n- Do NOT modify init() signature yet (CLI flags added in T003)","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-11T04:43:12.593380152Z","created_by":"cyou","updated_at":"2026-01-11T06:06:43.954666796Z","closed_at":"2026-01-11T06:06:43.954666796Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-mut1.1","depends_on_id":"mala-mut1","type":"parent-child","created_at":"2026-01-11T04:43:12.594044659Z","created_by":"cyou"}]}
{"id":"mala-mut1.2","title":"[T002] Implement helper functions: defaults, prompts, table, config building","description":"**Type**: task\n**Priority**: P1\n**Primary Files**: src/cli/cli.py, tests/integration/cli/test_init.py\n**Subsystems**: cli, tests\n\n## Goal\nImplement all helper functions for the init evidence/trigger flow: default computation, questionary prompts, config building, and trigger reference table printing.\n\n## Context\n- Default computation differs between preset path (intersection logic) and custom path (no defaults)\n- questionary.confirm() for Y/n, questionary.checkbox() for multi-select\n- Trigger table has semantic content requirements (key phrases, not exact formatting)\n- Commands use `ref:` syntax in generated YAML\n\n## Scope\n- **In**: Implement all 10 helper functions, add unit tests for pure functions\n- **Out**: CLI flags, init() orchestration (handled in T003)\n\n## Changes\n- `src/cli/cli.py` — [Exists] — Implement:\n  1. `_get_preset_command_names(preset_name: str) -\u003e list[str]` — Load preset via PresetRegistry.get(), return ordered command names\n  2. `_compute_evidence_defaults(commands: list[str], is_preset: bool) -\u003e list[str]` — Return `intersection({test, lint}, commands)` for preset, `[]` for custom\n  3. `_compute_trigger_defaults(commands: list[str], is_preset: bool) -\u003e list[str]` — Return `[c for c in commands if c not in {setup, e2e}]` for preset, `[]` for custom\n  4. `_prompt_preset_selection(presets: list[str]) -\u003e str | None` — questionary.select() for preset choice, return None for \"custom\"\n  5. `_prompt_custom_commands_questionary() -\u003e dict[str, str]` — questionary.text() for each BUILTIN_COMMAND_NAMES entry\n  6. `_prompt_evidence_check(commands: list[str], is_preset: bool) -\u003e list[str] | None` — questionary.confirm() + checkbox(), handle \"uncheck all\" with skip confirmation\n  7. `_prompt_run_end_trigger(commands: list[str], is_preset: bool) -\u003e list[str] | None` — questionary.confirm() + checkbox(), handle \"uncheck all\" with skip confirmation\n  8. `_build_evidence_check_dict(required: list[str]) -\u003e dict[str, list[str]]` — Build evidence_check section: `{\"required\": required}`\n  9. `_build_validation_triggers_dict(commands: list[str]) -\u003e dict[str, Any]` — Build dict with run_end trigger config, use `{\"ref\": cmd}` syntax for commands\n  10. `_print_trigger_reference_table() -\u003e None` — Print table with trigger types and descriptions\n- `tests/integration/cli/test_init.py` — [Exists] — Add unit tests for pure functions:\n  - `test_compute_evidence_defaults_preset_path`\n  - `test_compute_evidence_defaults_custom_path`\n  - `test_compute_trigger_defaults_excludes_setup_e2e`\n  - `test_compute_trigger_defaults_custom_path`\n  - `test_build_evidence_check_dict`\n  - `test_build_validation_triggers_dict_ref_syntax`\n\n## Wiring Map\n- `questionary prompts → list[str] → _build_*_dict() → config dict → validate_init_config() → dump_config_yaml() → mala.yaml`\n\n## Acceptance Criteria\n- `_compute_evidence_defaults([\"test\", \"lint\", \"format\"], True)` returns `[\"test\", \"lint\"]`\n- `_compute_evidence_defaults([\"test\", \"mypy\"], False)` returns `[]`\n- `_compute_trigger_defaults([\"setup\", \"test\", \"lint\", \"e2e\"], True)` returns `[\"test\", \"lint\"]`\n- `_build_evidence_check_dict([\"test\", \"lint\"])` returns `{\"required\": [\"test\", \"lint\"]}`\n- `_build_validation_triggers_dict([\"test\", \"lint\"])` returns dict with `run_end.commands = [{\"ref\": \"test\"}, {\"ref\": \"lint\"}]`\n- Trigger table output contains: \"epic_completion\", \"session_end\", \"periodic\", \"run_end\", and key phrases like \"verification\", \"ends\", \"N issues\", \"mala run\"\n- All unit tests for pure functions pass\n\n## Verification\n```bash\n# Unit tests for pure functions\nuv run pytest tests/integration/cli/test_init.py -v -k \"test_compute or test_build\" --tb=short\n\n# Manual verification of table output\nuv run python -c \"from src.cli.cli import _print_trigger_reference_table; _print_trigger_reference_table()\"\n```\n\n## Notes for Agent\n- Preserve command order from input lists (use list, not set)\n- Exact case-sensitive match for \"setup\" and \"e2e\" exclusion\n- For \"uncheck all\" scenario: use questionary.confirm(\"No commands selected. Skip this section?\")\n- Trigger table format is flexible; tests verify semantic content (key phrases), not exact bytes\n- Integration tests for prompts will still fail after this task (fixed in T003)","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-11T04:43:45.152409793Z","created_by":"cyou","updated_at":"2026-01-11T06:22:57.50971029Z","closed_at":"2026-01-11T06:22:57.50971029Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-mut1.2","depends_on_id":"mala-mut1","type":"parent-child","created_at":"2026-01-11T04:43:45.153197759Z","created_by":"cyou"},{"issue_id":"mala-mut1.2","depends_on_id":"mala-mut1.1","type":"blocks","created_at":"2026-01-11T04:45:04.840760226Z","created_by":"cyou"}]}
{"id":"mala-mut1.3","title":"[T003] Implement CLI flags + init() orchestration (make tests pass)","description":"**Type**: task\n**Priority**: P1\n**Primary Files**: src/cli/cli.py\n**Subsystems**: cli\n\n## Goal\nAdd CLI flags to init(), implement TTY detection, and wire the full init() flow to use all helper functions. Make all integration tests pass.\n\n## Context\n- New flags: `--yes`, `--preset`, `--skip-evidence`, `--skip-triggers`\n- TTY detection determines interactive vs non-interactive mode\n- `--skip-*` flags always take effect regardless of TTY status\n- `--preset + --skip-evidence + --skip-triggers` works in non-TTY without `--yes`\n\n## Scope\n- **In**: CLI flags, TTY detection, init() orchestration, fix failing integration tests\n- **Out**: E2E tests (next task)\n\n## Changes\n- `src/cli/cli.py` — [Exists] — Modify init() function:\n  1. Add CLI parameters:\n     ```python\n     yes: Annotated[bool, typer.Option(\"--yes\", \"-y\", help=\"Accept defaults (requires --preset)\")] = False,\n     preset: Annotated[str | None, typer.Option(\"--preset\", \"-p\", help=\"Use preset\")] = None,\n     skip_evidence: Annotated[bool, typer.Option(\"--skip-evidence\", help=\"Omit evidence_check\")] = False,\n     skip_triggers: Annotated[bool, typer.Option(\"--skip-triggers\", help=\"Omit validation_triggers\")] = False,\n     ```\n  2. Add TTY detection: `sys.stdin.isatty()` check at start\n  3. Implement flag validation:\n     - `--yes` without `--preset` → error\n     - non-TTY without `--yes` AND prompts needed → error with actionable message\n     - `--preset + --skip-* + --skip-*` in non-TTY → allowed (no prompts needed)\n  4. Orchestrate flow:\n     - Use `_prompt_preset_selection()` or `--preset` flag\n     - Get command list via `_get_preset_command_names()` or custom prompts\n     - If commands empty: print message, skip evidence/trigger prompts\n     - Call `_prompt_evidence_check()` unless `--skip-evidence` or `--yes`\n     - Call `_prompt_run_end_trigger()` unless `--skip-triggers` or `--yes`\n     - For `--yes` mode: use computed defaults directly\n     - Build config dict with evidence_check and validation_triggers sections\n     - Validate via `validate_init_config()`\n     - Handle validation errors with questionary.select() recovery options\n     - Write YAML (respects `--dry-run`)\n     - Always call `_print_trigger_reference_table()` at end\n\n## Acceptance Criteria\n- `mala init --yes --preset python-uv` generates config with evidence_check and validation_triggers\n- `mala init --yes` without `--preset` shows error: \"--yes requires --preset\"\n- `mala init` in non-TTY shows actionable error\n- `mala init --preset python-uv --skip-evidence --skip-triggers` works in non-TTY\n- All TestInitEvidencePrompts tests pass\n- All TestInitTriggerPrompts tests pass\n- All TestInitNonInteractive tests pass\n- All TestInitTriggerTable tests pass\n\n## Verification\n```bash\nuv run pytest tests/integration/cli/test_init.py -v --tb=short\nuv run pytest tests/integration/cli/test_init.py -v -k \"NonInteractive\" --tb=short\n\n# Manual verification\necho \"\" | mala init --yes --preset python-uv --dry-run 2\u003e\u00261 | head -30\n```\n\n## Notes for Agent\n- Use `sys.stdin.isatty()` for TTY detection\n- For validation errors, use questionary.select() with options: [\"Revise selections\", \"Skip section\", \"Abort init\"]\n- Preserve existing `_write_with_backup()` and `--dry-run` behavior\n- Existing `except KeyboardInterrupt` handler should still work\n- Run `uvx ruff check src/cli/cli.py` and `uvx ruff format src/cli/cli.py` before committing","notes":"Quality gate failed: External review failed: src/cli/cli.py:999: [P1] Non-interactive detection uses stdin.isatty only, breaks CLI runs; src/cli/cli.py:1247: [P1] Validation recovery loop can spin forever on repeated invalid input\nLog: /home/cyou/.claude/projects/-home-cyou-mala/62180e3d-2290-4a5d-a819-7dd8e42c2d5d.jsonl","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-11T04:44:22.845629942Z","created_by":"cyou","updated_at":"2026-01-11T14:52:33.598652087Z","closed_at":"2026-01-11T14:52:33.598652087Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-mut1.3","depends_on_id":"mala-mut1","type":"parent-child","created_at":"2026-01-11T04:44:22.846420998Z","created_by":"cyou"},{"issue_id":"mala-mut1.3","depends_on_id":"mala-mut1.2","type":"blocks","created_at":"2026-01-11T04:45:04.945935202Z","created_by":"cyou"}]}
{"id":"mala-mut1.4","title":"[T004] E2E behavioral test for init with mala run verification","description":"**Type**: task\n**Priority**: P2\n**Primary Files**: tests/e2e/test_mala_init.py\n**Subsystems**: tests\n\n## Goal\nCreate E2E tests that verify the complete init → run workflow: init generates valid config with evidence/triggers, and mala run executes commands and fires run_end trigger.\n\n## Context\n- Spec success criteria requires behavioral verification: \"mala run with a single issue produces logs showing each configured command executed and run_end validation fired\"\n- Uses fixture repo with stub commands that always exit 0\n- Tests the complete flow, not just init output\n\n## Scope\n- **In**: E2E test file with behavioral verification\n- **Out**: Integration tests (already in T001/T003)\n\n## Changes\n- `tests/e2e/test_mala_init.py` — [New] — Create file with:\n  1. `test_init_with_defaults_produces_runnable_config(tmp_path, monkeypatch)`:\n     - Create fixture repo with stub commands (test, lint, format = 'echo ok')\n     - Run `mala init --yes --preset python-uv`\n     - Assert mala.yaml contains evidence_check and validation_triggers sections\n     - Run `mala run` with single mock issue\n     - Assert logs show commands executed and run_end validation fired\n  2. `test_init_generates_correct_yaml_structure(tmp_path)`:\n     - Simpler test verifying just init output structure\n     - Run `mala init --yes --preset python-uv`\n     - Parse mala.yaml\n     - Assert correct sections, field names, and ref: syntax\n\n## Acceptance Criteria\n- Test file exists at tests/e2e/test_mala_init.py\n- Tests are marked with `@pytest.mark.e2e`\n- `test_init_with_defaults_produces_runnable_config` passes when run against fixture\n- `test_init_generates_correct_yaml_structure` passes\n- Generated YAML contains:\n  - `evidence_check.required` with expected commands\n  - `validation_triggers.run_end.commands` with `ref:` syntax\n\n## Verification\n```bash\nuv run pytest tests/e2e/test_mala_init.py -v -m e2e --tb=short\n```\n\n## Notes for Agent\n- Use existing E2E fixtures/patterns from tests/e2e/conftest.py\n- For stub commands, create shell scripts or use inline echo commands\n- Mark tests with `@pytest.mark.e2e` marker\n- Keep tests deterministic (no time-dependent assertions)\n- Log assertions should check for key phrases, not exact strings","notes":"Quality gate failed: External review failed: tests/e2e/test_mala_init.py:20: [P1] Avoid direct PyYAML import in tests (missing dependency)\nLog: /home/cyou/.claude/projects/-home-cyou-mala/7706c6c5-3bf1-47c3-a1e0-6362455c70bb.jsonl","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-11T04:44:51.271592527Z","created_by":"cyou","updated_at":"2026-01-11T21:44:17.056904052Z","closed_at":"2026-01-11T21:44:17.056904052Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-mut1.4","depends_on_id":"mala-mut1","type":"parent-child","created_at":"2026-01-11T04:44:51.272229183Z","created_by":"cyou"},{"issue_id":"mala-mut1.4","depends_on_id":"mala-mut1.3","type":"blocks","created_at":"2026-01-11T04:45:05.050064245Z","created_by":"cyou"}]}
{"id":"mala-mut1.5","title":"[Review] 1 non-blocking finding from mala-mut1.2","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-mut1.2\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Distinguish canceled preset selection from 'custom'\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/cli/cli.py:979-995\n\n`_prompt_preset_selection()` returns the raw `questionary.select(...).ask()` result unless it equals the literal string `\"custom\"`. If the user cancels the prompt, Questionary returns `None`, and this function will return `None` as well—indistinguishable from the intended “custom flow” sentinel.\n\nConcrete failure path: user hits Esc/Ctrl-C at the preset selection; `_prompt_preset_selection()` returns `None`, and upstream logic that interprets `None` as “custom” will proceed with custom config rather than aborting.\n\nCode:\n```python\nresult = questionary.select(...).ask()\nif result == \"custom\":\n    return None\nreturn result\n```\n\n---\n\n\u003c!-- fp:cfbaf35843fab507 --\u003e\n\u003c!-- review_finding:3e75151c7bf4 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-11T06:22:57.833572431Z","created_by":"cyou","updated_at":"2026-01-11T06:53:55.135429852Z","closed_at":"2026-01-11T06:53:55.135429852Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-mut1.2"],"dependencies":[{"issue_id":"mala-mut1.5","depends_on_id":"mala-mut1","type":"parent-child","created_at":"2026-01-11T06:22:57.834135389Z","created_by":"cyou"}]}
{"id":"mala-mut1.6","title":"[Review] 2 non-blocking findings from mala-mut1.4","description":"## Review Findings\n\nThis issue consolidates 2 non-blocking findings from code review.\n\n**Source issue:** mala-mut1.4\n**Highest priority:** P3\n\n---\n\n### Finding 1: [P3] Remove duplicated docstring line\n\n**Priority:** P3\n**Reviewer:** codex\n**Location:** tests/e2e/test_mala_init.py:218-225\n\nIn `TestInitConfigCompatibility.test_init_with_stub_commands_produces_runnable_config`, the docstring contains a duplicated line (`This test:` repeated twice). This is likely an edit artifact and slightly reduces clarity when reading the test intent.\n\n```py\n        \"\"\"Verify init config allows mala run (via dry-run validation).\n\n        This test:\n        This test:\n```\n\n\n---\n\n### Finding 2: [P3] Typo in git user.email configuration\n\n**Priority:** P3\n**Reviewer:** gemini\n**Location:** tests/e2e/test_mala_init.py:40\n\nThe email address 'test @test.com' contains a space. Likely intended to be 'test@test.com'.\n\n---\n\n\u003c!-- fp:d3132361db8161e1 --\u003e\n\u003c!-- fp:0de544b58f743eaf --\u003e\n\u003c!-- review_finding:9c8692062610 --\u003e","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-11T21:48:04.256230907Z","created_by":"cyou","updated_at":"2026-01-11T21:48:38.913806774Z","closed_at":"2026-01-11T21:48:38.913806774Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-mut1.4"],"dependencies":[{"issue_id":"mala-mut1.6","depends_on_id":"mala-mut1","type":"parent-child","created_at":"2026-01-11T21:48:04.256839723Z","created_by":"cyou"}]}
{"id":"mala-mxys","title":"[Review] 2 non-blocking findings from mala-6tdu","description":"## Review Findings\n\nThis issue consolidates 2 non-blocking findings from code review.\n\n**Source issue:** mala-6tdu\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Ineffective preservation of agent_id on failure\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/orchestration/orchestrator.py:361\n\nThe `get_agent_id` callback in `MalaOrchestrator` retrieves IDs from `self.agent_ids`, but `run_implementer` pops the entry from this map in its `finally` block before the task is marked as done. Since `EpicVerificationCoordinator` awaits remediation tasks before invoking the callback for failed or cancelled tasks, it will consistently receive `\"unknown\"` instead of the actual agent ID, defeating the goal of preserving agent attribution for failures.\n\n---\n\n### Finding 2: [P2] Missing agent attribution in remediation finalization warning\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/pipeline/epic_verification_coordinator.py:232-234\n\nPrevious review findings requested restoring telemetry detail in `on_warning` for finalization errors. While the commit adds the `get_agent_id` callback, the warning message in `_execute_remediation_issues` still only includes the `issue_id`, omitting the agent attribution (e.g., `result.agent_id`) needed to satisfy the requirement.\n\n---\n","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T07:42:41.893119385Z","created_by":"cyou","updated_at":"2026-01-03T08:08:24.719476355Z","closed_at":"2026-01-03T08:08:24.719476355Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_findings:41b048ccadec","source:mala-6tdu"]}
{"id":"mala-n0n","title":"Write failure handoff files on agent failure","description":"Context:\n- Track A6 requires writing .mala/handoff/\u003cissue_id\u003e.md with last commands and error summary (docs/coordination-plan-codex.md:47-48).\n- Orchestrator currently resets failed issues without a persistent handoff artifact (src/orchestrator.py:300-340).\n\nScope:\n- In: on agent failure (including quality gate failures), write .mala/handoff/\u003cissue_id\u003e.md with last commands and error summary.\n- In: ensure the handoff directory is created and file names are sanitized.\n- Out: do not change the quality gate logic beyond invoking handoff writer.\n\nAcceptance Criteria:\n- A handoff file is created for failed issues and contains last command(s) and an error summary.\n- File is created even when no commands are available (explicitly say so).\n- Handoff path is deterministic and safe for all issue IDs.\n\nTest Plan:\n- TDD: add failing unit test in a new tests/test_handoff.py.\n- Implement handoff writer and update tests to pass.\n- Run uv run pytest tests/test_handoff.py.\n\nNotes for Agent:\n- Prefer sourcing command history from JSONL logs to avoid missing data.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-26T16:44:42.712108592Z","updated_at":"2025-12-26T17:14:50.909626715Z","closed_at":"2025-12-26T17:14:50.909626715Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-n0n","depends_on_id":"mala-lo9","type":"blocks","created_at":"2025-12-26T16:44:52.444889534Z","created_by":"daemon"},{"issue_id":"mala-n0n","depends_on_id":"mala-4w7","type":"parent-child","created_at":"2025-12-26T16:47:53.018656516Z","created_by":"daemon"}]}
{"id":"mala-n1y1","title":"Epic: Custom Validation Commands","description":"Enable users to define arbitrary project-specific validation commands in mala.yaml that run as part of the validation pipeline.\n\n**Spec**: plans/2026-01-06-custom-validation-commands-spec.md\n**Plan**: plans/2026-01-06-custom-validation-commands-plan.md\n\n## Requirements (R1-R6)\n- R1: Config schema with string shorthand and object form\n- R2: Execution order (after lint/typecheck, before test)\n- R3: Advisory failures (allow_fail: true)\n- R4: Run-level override (full replace semantics)\n- R5: Evidence detection (structured markers)\n- R6: Agent awareness (prompt integration)\n\n## Implementation Phases\n1. **Config \u0026 Spec (Foundation)**: CustomCommandConfig dataclass, ValidationConfig extension, CommandKind.CUSTOM\n2. **Execution \u0026 Evidence (Runtime)**: Marker emission via agent, LogProvider extension, ValidationEvidence by-name tracking\n3. **Prompt Integration (Agent Awareness)**: PromptValidationCommands extension, implementer_prompt.md update\n\n## Key Files\n- src/domain/validation/config.py\n- src/domain/validation/spec.py\n- src/domain/validation/config_loader.py\n- src/domain/quality_gate.py\n- src/infra/io/session_log_parser.py\n- src/prompts/implementer_prompt.md","status":"closed","priority":1,"issue_type":"epic","created_at":"2026-01-06T05:20:01.457016007Z","created_by":"cyou","updated_at":"2026-01-06T15:12:20.095495079Z","closed_at":"2026-01-06T15:12:20.095495079Z","close_reason":"Closed"}
{"id":"mala-n1y1.1","title":"[T001] [integration-path-test] CustomCommandConfig dataclass + Config parsing skeleton + Failing integration test","description":"**Type**: task\n**Priority**: P2\n**Story**: R1 (Config Schema)\n**Parallel**: No (foundation)\n**Primary Files**: src/domain/validation/config.py, src/domain/validation/config_loader.py, tests/integration/domain/test_validation_config.py\n**Subsystems**: validation\n\n## Goal\nAdd CustomCommandConfig dataclass and config parsing stubs. Create failing integration test that exercises the full config load → spec build path.\n\n## Context\n- This is the foundation task for custom validation commands\n- Must establish compile-ready stubs before implementation tasks can parallelize\n- Integration test should fail because behavior is not implemented yet\n\n## Scope\n- **In**: CustomCommandConfig dataclass with from_value() stub, custom_commands field on ValidationConfig, add to _ALLOWED_TOP_LEVEL_FIELDS, CommandKind.CUSTOM enum, failing integration test\n- **Out**: Full validation logic, error handling, merge_configs support\n\n## Changes\n- `src/domain/validation/config.py` — [Exists] — Add CustomCommandConfig dataclass with from_value() stub (raises NotImplementedError), add custom_commands: dict[str, CustomCommandConfig] field to ValidationConfig\n- `src/domain/validation/spec.py` — [Exists] — Add CommandKind.CUSTOM = \"custom\" enum value\n- `src/domain/validation/config_loader.py` — [Exists] — Add \"custom_commands\" to _ALLOWED_TOP_LEVEL_FIELDS, stub parsing (returns empty dict initially)\n- `tests/integration/domain/test_validation_config.py` — [Exists] — Add test_custom_commands_config_to_spec_integration() that loads config with custom_commands and verifies they appear in ValidationSpec (will fail until impl)\n\n## Wiring Map\n- `mala.yaml custom_commands → ConfigLoader → ValidationConfig.custom_commands → build_validation_spec() → ValidationCommand(kind=CUSTOM)`\n\n## Acceptance Criteria\n- Project compiles/builds successfully after changes\n- CustomCommandConfig dataclass exists with from_value() method signature\n- CommandKind.CUSTOM enum value exists\n- Integration test exists and FAILS (behavior not implemented)\n- \"custom_commands\" recognized as valid top-level field (no ConfigError for presence)\n\n## Verification\n- `uv run pytest tests/integration/domain/test_validation_config.py -k custom_commands -v` — test exists and fails\n- `uvx ty check` — type checking passes\n- `uvx ruff check .` — lint passes\n\n## Integration Path Test\nThis task establishes the integration-path test for the Config \u0026 Spec feature:\n- Test traverses: mala.yaml → ConfigLoader → ValidationConfig → build_validation_spec() → ValidationCommand\n\n## Notes for Agent\n- ValidationCommand already has name and allow_fail fields — no changes needed there\n- Follow existing CommandConfig.from_value() pattern for CustomCommandConfig\n- PromptValidationCommands is in config.py line 486, not prompts.py\n- Integration test should use real file I/O with tmp_path fixture","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-06T05:20:57.168701392Z","created_by":"cyou","updated_at":"2026-01-06T05:53:28.470605035Z","closed_at":"2026-01-06T05:53:28.470605035Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-n1y1.1","depends_on_id":"mala-n1y1","type":"parent-child","created_at":"2026-01-06T05:20:57.182396653Z","created_by":"cyou"}]}
{"id":"mala-n1y1.10","title":"[T010] Final verification: all tests pass + integration test green","description":"**Type**: task\n**Priority**: P2\n**Story**: All (R1-R6)\n**Parallel**: No (final)\n**Primary Files**: (all test files)\n**Subsystems**: validation, quality_gate, prompts\n**Dependencies**: T007, T008, T009\n\n## Goal\nFinal verification that all unit and integration tests pass, confirming the feature is complete.\n\n## Context\n- This is the final task that confirms all implementation is complete\n- Must verify all integration tests from T001 and T006 are now passing\n- Must verify backward compatibility: repos without custom_commands behave identically\n\n## Scope\n- **In**: Run all tests, verify backward compatibility, confirm integration tests green\n- **Out**: Implementation changes (already complete)\n\n## Changes\n- No file changes — verification only\n- May add backward compatibility test if missing\n\n## Acceptance Criteria\n- All unit tests pass: `uv run pytest -m unit`\n- All integration tests pass: `uv run pytest -m integration -n auto`\n- Integration tests from T001 (config→spec) and T006 (evidence→gate) are green\n- Backward compatibility: config without custom_commands produces same ValidationSpec as before\n\n## Verification\n```bash\nuv run pytest -m unit\nuv run pytest -m integration -n auto\nuv run pytest -k custom -v  # All custom command tests\nuvx ty check\nuvx ruff check .\n```\n\n## Notes for Agent\n- This is verification-only, not implementation\n- If tests fail, fix in this task (don't create new tasks)\n- Confirm backward compatibility explicitly","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-06T05:23:35.76134097Z","created_by":"cyou","updated_at":"2026-01-06T15:08:41.990303859Z","closed_at":"2026-01-06T15:08:41.990303859Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-n1y1.10","depends_on_id":"mala-n1y1","type":"parent-child","created_at":"2026-01-06T05:23:35.761798718Z","created_by":"cyou"},{"issue_id":"mala-n1y1.10","depends_on_id":"mala-n1y1.7","type":"blocks","created_at":"2026-01-06T05:24:18.104340813Z","created_by":"cyou"},{"issue_id":"mala-n1y1.10","depends_on_id":"mala-n1y1.8","type":"blocks","created_at":"2026-01-06T05:24:18.123067684Z","created_by":"cyou"},{"issue_id":"mala-n1y1.10","depends_on_id":"mala-n1y1.9","type":"blocks","created_at":"2026-01-06T05:24:18.140138865Z","created_by":"cyou"}]}
{"id":"mala-n1y1.11","title":"[Review] 1 non-blocking finding from mala-n1y1.4","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-n1y1.4\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Move new preset guard test into unit test suite\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** tests/integration/domain/test_preset_registry.py:40-58\n\nThe new “unit” test was added to `tests/integration/domain/test_preset_registry.py`, but the task’s verification command (`uv run pytest tests/unit/domain/ -k preset -v`) won’t execute it. This can let the change pass the expected unit verification without actually covering the new prohibition; moving/adding the test to `tests/unit/domain/test_preset_registry.py` (or updating the verification/CI selection) fixes the gap.\n\n---\n\n\u003c!-- fp:ea2bf17d57507ef1 --\u003e\n\u003c!-- review_finding:ceef2700640c --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-06T05:56:29.43197409Z","created_by":"cyou","updated_at":"2026-01-06T06:00:23.008433754Z","closed_at":"2026-01-06T06:00:23.008433754Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-n1y1.4"],"dependencies":[{"issue_id":"mala-n1y1.11","depends_on_id":"mala-n1y1","type":"parent-child","created_at":"2026-01-06T05:56:29.43243655Z","created_by":"cyou"}]}
{"id":"mala-n1y1.12","title":"[Review] 2 non-blocking findings from mala-n1y1.11","description":"## Review Findings\n\nThis issue consolidates 2 non-blocking findings from code review.\n\n**Source issue:** mala-n1y1.11\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P3] Fix misleading module docstring about test type\n\n**Priority:** P3\n**Reviewer:** codex\n**Location:** tests/integration/domain/test_preset_registry.py:1\n\n`tests/integration/domain/test_preset_registry.py` now starts with an “Integration tests …” module docstring, but the file still contains a “Unit Tests” section and a unit-marked test class. This is a maintainability footgun because it makes it harder to reason about what should run under unit vs integration verification; either revert the docstring to a neutral description (e.g., “Tests for …”) or split unit tests out of the integration directory.\n\n---\n\n### Finding 2: [P2] Move remaining unit test to unit test suite\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** tests/integration/domain/test_preset_registry.py:14-37\n\nThe `test_get_unknown_preset_raises_error` test remains in `tests/integration/domain/test_preset_registry.py` within the `TestPresetRegistryUnit` class. Since the file's docstring was updated to specify it is for integration tests and a new unit test file was created, this remaining unit test should be moved to `tests/unit/domain/test_preset_registry.py`. This ensures it is executed by unit-specific verification commands (like `pytest tests/unit/domain/`) and maintains consistency with the updated file purpose and naming.\n\n---\n\n\u003c!-- fp:a780aaa23112d717 --\u003e\n\u003c!-- fp:88f7ec116d3788de --\u003e\n\u003c!-- review_finding:1fb8cf714f7a --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-06T06:00:23.055742279Z","created_by":"cyou","updated_at":"2026-01-06T06:03:16.068499605Z","closed_at":"2026-01-06T06:03:16.068499605Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-n1y1.11"],"dependencies":[{"issue_id":"mala-n1y1.12","depends_on_id":"mala-n1y1","type":"parent-child","created_at":"2026-01-06T06:00:23.056214998Z","created_by":"cyou"}]}
{"id":"mala-n1y1.13","title":"[Review] 1 non-blocking finding from mala-n1y1.2","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-n1y1.2\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Explicit `timeout: null` silently bypasses default, inconsistent with string shorthand\n\n**Priority:** P2\n**Reviewer:** claude\n**Location:** src/domain/validation/config.py:218-225\n\nWhen a user provides `{\"command\": \"...\", \"timeout\": null}`, `value.get(\"timeout\", 120)` returns `None` (not 120) because the key is present. The `if timeout is not None:` check then skips validation, and `None` is stored. This is inconsistent with the string shorthand which always sets `timeout=120`, and with the docstring example showing the default. Consider changing line 218 to:\n```python\ntimeout = value.get(\"timeout\")\nif timeout is None:\n    timeout = 120\n```\nOr decide if explicit `null` should use system default (then document this behavior).\n\n---\n\n\u003c!-- fp:4508e68b6d3ce552 --\u003e\n\u003c!-- review_finding:d05b9d11cd09 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-06T06:01:51.901472599Z","created_by":"cyou","updated_at":"2026-01-06T06:04:37.784985959Z","closed_at":"2026-01-06T06:04:37.784985959Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-n1y1.2"],"dependencies":[{"issue_id":"mala-n1y1.13","depends_on_id":"mala-n1y1","type":"parent-child","created_at":"2026-01-06T06:01:51.901870168Z","created_by":"cyou"}]}
{"id":"mala-n1y1.14","title":"[Review] 1 non-blocking finding from mala-n1y1.12","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-n1y1.12\n**Highest priority:** P3\n\n---\n\n### Finding 1: [P3] Fix misleading unit test docstring about mocking\n\n**Priority:** P3\n**Reviewer:** codex\n**Location:** tests/unit/domain/test_preset_registry.py:11-13\n\n`TestPresetRegistryUnit` claims it uses “mocked file loading”, but `test_get_unknown_preset_raises_error` does not mock `importlib.resources`/`PresetRegistry` loading behavior (it fails fast on the preset name check). This is misleading for readers trying to understand what’s actually under test; either remove the “mocked” wording or add the intended mocking so the description matches reality.\n\n---\n\n\u003c!-- fp:0b37c08d6f98ed5d --\u003e\n\u003c!-- review_finding:4f06d655731a --\u003e","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-06T06:03:16.11230973Z","created_by":"cyou","updated_at":"2026-01-06T06:07:06.225099392Z","closed_at":"2026-01-06T06:07:06.225099392Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-n1y1.12"],"dependencies":[{"issue_id":"mala-n1y1.14","depends_on_id":"mala-n1y1","type":"parent-child","created_at":"2026-01-06T06:03:16.112703789Z","created_by":"cyou"}]}
{"id":"mala-n1y1.15","title":"[Review] 1 non-blocking finding from mala-n1y1.5","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-n1y1.5\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Support programmatic run_level_custom_commands overrides\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/domain/validation/spec.py:491-493\n\n_apply_custom_commands_override relies solely on fields_set to detect overrides. For programmatic ValidationConfig instances where _fields_set is empty, a non-None run_level_custom_commands value will be ignored, falling back to repo-level commands incorrectly. It should check if override is not None as a fallback when fields_set is empty, consistent with how standard command overrides are handled in _apply_command_overrides.\n\n---\n\n\u003c!-- fp:5c774e70b74e9069 --\u003e\n\u003c!-- review_finding:3bee76ab2005 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-06T07:00:06.719067719Z","created_by":"cyou","updated_at":"2026-01-06T07:06:16.595133034Z","closed_at":"2026-01-06T07:06:16.595133034Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-n1y1.5"],"dependencies":[{"issue_id":"mala-n1y1.15","depends_on_id":"mala-n1y1","type":"parent-child","created_at":"2026-01-06T07:00:06.719508403Z","created_by":"cyou"}]}
{"id":"mala-n1y1.16","title":"[Review] 1 non-blocking finding from mala-n1y1.9","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-n1y1.9\n**Highest priority:** P3\n\n---\n\n### Finding 1: [P3] Move regression test to test_spec.py\n\n**Priority:** P3\n**Reviewer:** gemini\n**Location:** tests/unit/domain/test_validation_config.py:655-697\n\nThe new test `test_custom_commands_yaml_order_preserved` uses `build_validation_spec` and `ValidationScope`, both of which are defined in `src/domain/validation/spec.py`. Following existing project structure, tests for these components belong in `tests/unit/domain/test_spec.py` rather than `test_validation_config.py`.\n\n---\n\n\u003c!-- fp:bd986ff528ae0634 --\u003e\n\u003c!-- review_finding:94f21ceaf184 --\u003e","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-06T07:08:08.564776671Z","created_by":"cyou","updated_at":"2026-01-06T07:10:50.819487016Z","closed_at":"2026-01-06T07:10:50.819487016Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-n1y1.9"],"dependencies":[{"issue_id":"mala-n1y1.16","depends_on_id":"mala-n1y1","type":"parent-child","created_at":"2026-01-06T07:08:08.565265367Z","created_by":"cyou"}]}
{"id":"mala-n1y1.17","title":"[Review] 1 non-blocking finding from mala-n1y1.6","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-n1y1.6\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Update LogProvider mock to include extract_tool_result_content\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** tests/unit/domain/test_quality_gate.py:4121-4132\n\n`LogProvider` now includes `extract_tool_result_content()`, but `MockLogProvider` in this test is still `cast(\"LogProvider\", mock_provider)` without implementing the new method. Today the test doesn’t call parsing paths that would invoke it, but if `parse_validation_evidence_with_spec()` (or any code path that iterates entries) is called, it will fail at runtime with `AttributeError`. Add a stub `extract_tool_result_content(self, entry) -\u003e list[tuple[str, str]]: return []` (and, ideally, the other extraction methods if you want the mock to behave like a real provider).\n\n---\n\n\u003c!-- fp:29bdd8ed98517580 --\u003e\n\u003c!-- review_finding:65ef47c271a7 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-06T07:30:52.243452964Z","created_by":"cyou","updated_at":"2026-01-06T07:33:20.3358676Z","closed_at":"2026-01-06T07:33:20.3358676Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-n1y1.6"],"dependencies":[{"issue_id":"mala-n1y1.17","depends_on_id":"mala-n1y1","type":"parent-child","created_at":"2026-01-06T07:30:52.243903262Z","created_by":"cyou"}]}
{"id":"mala-n1y1.18","title":"[Review] 2 non-blocking findings from mala-n1y1.8","description":"## Review Findings\n\nThis issue consolidates 2 non-blocking findings from code review.\n\n**Source issue:** mala-n1y1.8\n**Highest priority:** P3\n\n---\n\n### Finding 1: [P3] Update prompts.py docs/comments to tuple\n\n**Priority:** P3\n**Reviewer:** codex\n**Location:** src/domain/prompts.py:26-28\n\n`custom_commands` was changed to a tuple, but `_build_custom_commands_section` still documents it as a “List” (and `format_implementer_prompt` has a comment saying “list”). This is minor, but it’s now misleading for readers and future type-driven refactors.\n\n---\n\n### Finding 2: [P3] Fix stale “list” comment in format_implementer_prompt\n\n**Priority:** P3\n**Reviewer:** codex\n**Location:** src/domain/prompts.py:134-136\n\nThis comment says “Build custom_commands_section from custom_commands list” but `validation_commands.custom_commands` is now a tuple. Update the wording to avoid confusion.\n\n---\n\n\u003c!-- fp:5561dbd713ec61c1 --\u003e\n\u003c!-- fp:d7965b860fd8a4bb --\u003e\n\u003c!-- review_finding:4ad208ef9d1e --\u003e","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-06T15:06:22.121159137Z","created_by":"cyou","updated_at":"2026-01-06T15:08:45.562235461Z","closed_at":"2026-01-06T15:08:45.562235461Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-n1y1.8"],"dependencies":[{"issue_id":"mala-n1y1.18","depends_on_id":"mala-n1y1","type":"parent-child","created_at":"2026-01-06T15:06:22.121668471Z","created_by":"cyou"}]}
{"id":"mala-n1y1.2","title":"[T002] Implement CustomCommandConfig.from_value() parsing + validation","description":"**Type**: task\n**Priority**: P2\n**Story**: R1 (Config Schema)\n**Parallel**: [P] with T003, T004\n**Primary Files**: src/domain/validation/config.py, tests/unit/domain/test_validation_config.py\n**Subsystems**: validation\n**Dependencies**: T001\n\n## Goal\nImplement full CustomCommandConfig.from_value() parsing with string shorthand and object form support, including all validation rules.\n\n## Context\n- CustomCommandConfig.from_value() must parse both string shorthand (\"uvx cmd\") and object form ({command: \"...\", timeout: 120, allow_fail: true})\n- Must validate command names match Python identifier regex\n- Must reject null values, empty commands, unknown keys\n\n## Scope\n- **In**: Complete from_value() implementation, unit tests for all parsing/validation scenarios\n- **Out**: Config loading integration (T003), preset prohibition (T004), spec building (T005)\n\n## Changes\n- `src/domain/validation/config.py` — [Exists] — Implement CustomCommandConfig.from_value() with:\n  - String shorthand parsing (defaults: timeout=120, allow_fail=False)\n  - Object form parsing (command required, timeout/allow_fail optional)\n  - Name validation regex: ^[A-Za-z_][A-Za-z0-9_]*$\n  - Reject null values with clear error\n  - Reject empty/whitespace command strings\n  - Reject unknown keys in object form\n- `tests/unit/domain/test_validation_config.py` — [Exists] — Add unit tests:\n  - test_custom_command_config_from_string_shorthand\n  - test_custom_command_config_from_object_form\n  - test_custom_command_config_defaults\n  - test_custom_command_config_invalid_name (various patterns)\n  - test_custom_command_config_null_value_error\n  - test_custom_command_config_empty_command_error\n  - test_custom_command_config_unknown_keys_error\n\n## Acceptance Criteria\n- from_value(\"uvx cmd\") returns CustomCommandConfig(command=\"uvx cmd\", timeout=120, allow_fail=False)\n- from_value({command: \"uvx cmd\", timeout: 60, allow_fail: True}) returns correct config\n- Invalid names (123abc, cmd-name, cmd.name) raise ConfigError\n- Null value raises ConfigError with \"use run-level override to disable\" message\n- Empty command raises ConfigError\n- Unknown keys raise ConfigError with key name\n\n## Verification\n- `uv run pytest tests/unit/domain/test_validation_config.py -k CustomCommandConfig -v` — all tests pass\n- Within-task TDD: write failing tests first, then implement\n\n## Notes for Agent\n- Follow existing CommandConfig.from_value() pattern\n- Name validation regex must match marker parsing regex (\\w+)\n- ConfigError messages should be user-friendly and actionable","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-06T05:21:39.261682637Z","created_by":"cyou","updated_at":"2026-01-06T06:01:51.858558155Z","closed_at":"2026-01-06T06:01:51.858558155Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-n1y1.2","depends_on_id":"mala-n1y1","type":"parent-child","created_at":"2026-01-06T05:21:39.274170544Z","created_by":"cyou"},{"issue_id":"mala-n1y1.2","depends_on_id":"mala-n1y1.1","type":"blocks","created_at":"2026-01-06T05:24:03.051087122Z","created_by":"cyou"}]}
{"id":"mala-n1y1.3","title":"[T003] Implement config_loader custom_commands parsing + merge_configs support","description":"**Type**: task\n**Priority**: P2\n**Story**: R1 (Config Schema)\n**Parallel**: [P] with T002, T004\n**Primary Files**: src/domain/validation/config_loader.py, src/domain/validation/config_merger.py, tests/unit/domain/test_validation_config.py\n**Subsystems**: validation\n**Dependencies**: T001\n\n## Goal\nImplement ConfigLoader parsing for custom_commands and ensure merge_configs preserves user custom_commands when merging with preset.\n\n## Context\n- ConfigLoader must parse custom_commands dict from YAML into ValidationConfig.custom_commands\n- merge_configs must preserve user custom_commands (presets cannot define them)\n- Follow existing YAML-to-dataclass mapping patterns\n\n## Scope\n- **In**: ConfigLoader parsing, merge_configs handling, unit tests\n- **Out**: Preset prohibition check (T004), spec building (T005)\n\n## Changes\n- `src/domain/validation/config_loader.py` — [Exists] — Parse top-level custom_commands dict:\n  - Iterate dict entries, call CustomCommandConfig.from_value() for each\n  - Build dict[str, CustomCommandConfig] for ValidationConfig\n  - Ensure _fields_set tracking for custom_commands presence\n- `src/domain/validation/config_merger.py` — [Exists] — Update merge_configs():\n  - User custom_commands always takes precedence (preset cannot define)\n  - Handle _fields_set tracking for custom_commands\n- `tests/unit/domain/test_validation_config.py` — [Exists] — Add tests:\n  - test_config_loader_parses_custom_commands\n  - test_config_loader_custom_commands_empty_dict\n  - test_merge_configs_preserves_user_custom_commands\n\n## Wiring Map\n- `mala.yaml custom_commands: {...} → ConfigLoader._load_config() → ValidationConfig.custom_commands`\n\n## Acceptance Criteria\n- ConfigLoader.load() with custom_commands YAML returns ValidationConfig with populated custom_commands dict\n- Empty custom_commands: {} results in empty dict (no commands)\n- merge_configs with preset (no custom_commands) + user (with custom_commands) → user's commands preserved\n- _fields_set correctly tracks custom_commands presence\n\n## Verification\n- `uv run pytest tests/unit/domain/test_validation_config.py -k config_loader -v` — tests pass\n- `uv run pytest tests/unit/domain/test_validation_config.py -k merge_configs -v` — tests pass\n\n## Notes for Agent\n- Follow existing pattern for parsing other top-level fields (commands, coverage, etc.)\n- Presets are forbidden from defining custom_commands (checked in T004)\n- YAML order is preserved in Python 3.7+ dicts","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-06T05:21:39.366933339Z","created_by":"cyou","updated_at":"2026-01-06T06:34:18.864975126Z","closed_at":"2026-01-06T06:34:18.864975126Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-n1y1.3","depends_on_id":"mala-n1y1","type":"parent-child","created_at":"2026-01-06T05:21:39.367546496Z","created_by":"cyou"},{"issue_id":"mala-n1y1.3","depends_on_id":"mala-n1y1.1","type":"blocks","created_at":"2026-01-06T05:24:03.06696463Z","created_by":"cyou"}]}
{"id":"mala-n1y1.4","title":"[T004] Add preset prohibition for custom_commands in PresetRegistry","description":"**Type**: task\n**Priority**: P2\n**Story**: R1 (Config Schema)\n**Parallel**: [P] with T002, T003\n**Primary Files**: src/domain/validation/preset_registry.py, tests/unit/domain/test_preset_registry.py\n**Subsystems**: validation\n**Dependencies**: T001\n\n## Goal\nAdd check in PresetRegistry.get() to reject presets that define custom_commands.\n\n## Context\n- Per spec R1: \"Presets MUST NOT define custom_commands\"\n- Check must be in PresetRegistry.get() since presets bypass _validate_schema()\n- Error message: \"presets cannot define custom_commands\"\n\n## Scope\n- **In**: Preset prohibition check, unit test\n- **Out**: Config parsing (T002, T003), spec building (T005)\n\n## Changes\n- `src/domain/validation/preset_registry.py` — [Exists] — Add check in get() method:\n  - If preset YAML contains \"custom_commands\" key, raise ConfigError\n  - Error message: \"presets cannot define custom_commands\"\n- `tests/unit/domain/test_preset_registry.py` — [Exists or New] — Add test:\n  - test_preset_with_custom_commands_raises_config_error\n\n## Acceptance Criteria\n- Preset YAML with custom_commands: {...} raises ConfigError(\"presets cannot define custom_commands\")\n- Preset YAML without custom_commands works normally\n\n## Verification\n- `uv run pytest tests/unit/domain/ -k preset -v` — tests pass\n- Edge case: preset with custom_commands: {} also rejected (any presence is forbidden)\n\n## Notes for Agent\n- Check should happen in get() before YAML is merged with user config\n- This is a simple guard — just check for key presence in parsed preset YAML","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-06T05:21:39.456245154Z","created_by":"cyou","updated_at":"2026-01-06T05:56:29.384219701Z","closed_at":"2026-01-06T05:56:29.384219701Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-n1y1.4","depends_on_id":"mala-n1y1","type":"parent-child","created_at":"2026-01-06T05:21:39.456767581Z","created_by":"cyou"},{"issue_id":"mala-n1y1.4","depends_on_id":"mala-n1y1.1","type":"blocks","created_at":"2026-01-06T05:24:03.082494489Z","created_by":"cyou"}]}
{"id":"mala-n1y1.5","title":"[T005] Implement build_validation_spec() for custom commands + run-level override","description":"**Type**: task\n**Priority**: P2\n**Story**: R2 (Execution Order), R4 (Run-Level Override)\n**Parallel**: No (depends on T002, T003, T004)\n**Primary Files**: src/domain/validation/spec.py, tests/unit/domain/test_spec.py\n**Subsystems**: validation\n**Dependencies**: T002, T003, T004\n\n## Goal\nExtend build_validation_spec() to create ValidationCommand instances for custom commands in correct pipeline position, and implement run-level override with full-replace semantics.\n\n## Context\n- Custom commands run after format/lint/typecheck, before test/e2e\n- Pipeline order: setup → format → lint → typecheck → custom_a → custom_b → test → e2e\n- Run-level override REPLACES (not merges) repo-level custom_commands\n- null vs {} semantics: null = omitted (repo-level applies), {} = disable all\n\n## Scope\n- **In**: build_validation_spec() custom command building, _apply_command_overrides() extension, pipeline position, null-vs-{} handling, unit tests\n- **Out**: Evidence detection (Phase 2), prompt integration (Phase 3)\n\n## Changes\n- `src/domain/validation/spec.py` — [Exists] — \n  - Extend build_validation_spec() to create ValidationCommand(kind=CUSTOM, name=key, command=val.command, timeout=val.timeout, allow_fail=val.allow_fail) for each custom command\n  - Insert custom commands after typecheck commands, before test commands\n  - Extend _apply_command_overrides() for custom_commands with full-replace semantics\n  - Handle null-vs-{} in _fields_set: null not tracked, {} is tracked\n- `tests/unit/domain/test_spec.py` — [Exists] — Add tests:\n  - test_build_validation_spec_custom_commands_pipeline_order\n  - test_build_validation_spec_custom_commands_insertion_order\n  - test_apply_command_overrides_custom_commands_full_replace\n  - test_apply_command_overrides_custom_commands_empty_dict_disables\n  - test_apply_command_overrides_custom_commands_null_uses_repo_level\n\n## Acceptance Criteria\n- Custom commands appear in ValidationSpec.commands after typecheck, before test\n- Dict insertion order is preserved (a, b, c → cmd_a, cmd_b, cmd_c in spec)\n- Run-level custom_commands: {cmd_a: \"...\"} only runs cmd_a (cmd_b not inherited)\n- Run-level custom_commands: {} disables all custom commands\n- Run-level custom_commands: null (or omitted) uses repo-level commands\n- Integration test from T001 passes after this task\n\n## Verification\n- `uv run pytest tests/unit/domain/test_spec.py -k custom -v` — all tests pass\n- `uv run pytest tests/integration/domain/test_validation_config.py -k custom_commands -v` — integration test passes\n\n## Notes for Agent\n- ValidationCommand already has name, allow_fail, timeout fields — use them directly\n- Use _fields_set to distinguish null (not set) from {} (explicitly empty)\n- Custom commands get kind=CommandKind.CUSTOM\n- YAML order preserved in Python 3.7+ dicts","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-06T05:22:43.98114299Z","created_by":"cyou","updated_at":"2026-01-06T07:00:06.676924722Z","closed_at":"2026-01-06T07:00:06.676924722Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-n1y1.5","depends_on_id":"mala-n1y1","type":"parent-child","created_at":"2026-01-06T05:22:43.988789846Z","created_by":"cyou"},{"issue_id":"mala-n1y1.5","depends_on_id":"mala-n1y1.2","type":"blocks","created_at":"2026-01-06T05:24:06.947936579Z","created_by":"cyou"},{"issue_id":"mala-n1y1.5","depends_on_id":"mala-n1y1.3","type":"blocks","created_at":"2026-01-06T05:24:06.965514977Z","created_by":"cyou"},{"issue_id":"mala-n1y1.5","depends_on_id":"mala-n1y1.4","type":"blocks","created_at":"2026-01-06T05:24:06.981928551Z","created_by":"cyou"}]}
{"id":"mala-n1y1.6","title":"[T006] [integration-path-test] Evidence detection skeleton + LogProvider extension + Failing integration test","description":"**Type**: task\n**Priority**: P2\n**Story**: R5 (Evidence Detection)\n**Parallel**: [P] after T005\n**Primary Files**: src/core/protocols.py, src/infra/io/session_log_parser.py, src/domain/quality_gate.py, tests/integration/domain/test_quality_gate.py\n**Subsystems**: quality_gate, protocols\n\n## Goal\nExtend LogProvider protocol with extract_tool_result_content(), extend ValidationEvidence for by-name tracking, and create failing integration test for evidence detection.\n\n## Context\n- Markers are emitted by agent via Bash tool → appear in tool_result content\n- LogProvider needs new method to extract tool_result content for marker parsing\n- ValidationEvidence needs custom_commands_ran and custom_commands_failed dicts\n- Integration test should exercise full log parse → evidence → gate check path\n\n## Scope\n- **In**: LogProvider protocol extension, SessionLogParser implementation, ValidationEvidence extension, failing integration test\n- **Out**: Marker parsing logic (T007), quality gate check logic (T007)\n\n## Changes\n- `src/core/protocols.py` — [Exists] — \n  - Add extract_tool_result_content(entry) -\u003e list[tuple[str, str]] to LogProvider protocol\n  - Add custom_commands_ran: dict[str, bool] and custom_commands_failed: dict[str, bool] to ValidationEvidenceProtocol\n- `src/infra/io/session_log_parser.py` — [Exists] — \n  - Implement extract_tool_result_content() stub (returns empty list initially)\n  - Stub should have correct signature and basic structure\n- `src/domain/quality_gate.py` — [Exists] — \n  - Add custom_commands_ran: dict[str, bool] = field(default_factory=dict) to ValidationEvidence\n  - Add custom_commands_failed: dict[str, bool] = field(default_factory=dict) to ValidationEvidence\n- `tests/integration/domain/test_quality_gate.py` — [Exists or New] — Add test:\n  - test_evidence_detection_custom_commands_integration — parses logs with custom markers, checks evidence\n\n## Wiring Map\n- `JSONL log → SessionLogParser.extract_tool_result_content() → marker regex → ValidationEvidence.custom_commands_*`\n\n## Acceptance Criteria\n- LogProvider protocol has extract_tool_result_content() method\n- SessionLogParser has stub implementation (returns [])\n- ValidationEvidence has custom_commands_ran and custom_commands_failed dicts\n- Integration test exists and FAILS (parsing not implemented)\n- Types check cleanly\n\n## Verification\n- `uv run pytest tests/integration/domain/test_quality_gate.py -k custom -v` — test exists and fails\n- `uvx ty check` — type checking passes\n\n## Integration Path Test\nThis task establishes integration-path test for Evidence Detection feature:\n- Test traverses: JSONL log → SessionLogParser → parse_validation_evidence() → ValidationEvidence → QualityGate\n\n## Notes for Agent\n- extract_tool_result_content returns list of (tool_use_id, content) tuples\n- Content extraction handles non-string types (list → concatenate strings, other → str())\n- Only extract from Bash tool results (check tool_use.name == \"Bash\")\n- ValidationEvidence does NOT store allow_fail — gate looks that up from spec","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-06T05:22:44.093819298Z","created_by":"cyou","updated_at":"2026-01-06T07:30:52.201390129Z","closed_at":"2026-01-06T07:30:52.201390129Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-n1y1.6","depends_on_id":"mala-n1y1","type":"parent-child","created_at":"2026-01-06T05:22:44.094290485Z","created_by":"cyou"},{"issue_id":"mala-n1y1.6","depends_on_id":"mala-n1y1.5","type":"blocks","created_at":"2026-01-06T05:24:09.529545903Z","created_by":"cyou"}]}
{"id":"mala-n1y1.7","title":"[T007] Implement marker parsing + quality gate custom command logic","description":"**Type**: task\n**Priority**: P2\n**Story**: R5 (Evidence Detection), R3 (Advisory Failures)\n**Parallel**: No (depends on T006)\n**Primary Files**: src/domain/quality_gate.py, src/infra/io/session_log_parser.py, tests/unit/domain/test_quality_gate.py\n**Subsystems**: quality_gate\n\n## Goal\nImplement marker parsing from tool_result content and quality gate logic for custom commands with allow_fail lookup from spec.\n\n## Context\n- Markers: [custom:\u003cname\u003e:start], [custom:\u003cname\u003e:pass], [custom:\u003cname\u003e:fail exit=\u003ccode\u003e], [custom:\u003cname\u003e:timeout]\n- Latest terminal marker wins (handles retries)\n- Terminal marker implies \"ran\" even without start marker\n- Start without terminal = failure\n- No markers for spec'd command = \"not run\"\n- Gate looks up allow_fail from ValidationSpec, not evidence\n\n## Scope\n- **In**: extract_tool_result_content() implementation, marker regex parsing, evidence population, gate check logic, unit tests\n- **Out**: Prompt integration (Phase 3)\n\n## Changes\n- `src/infra/io/session_log_parser.py` — [Exists] — Implement extract_tool_result_content():\n  - Find tool_result entries in log\n  - Filter to Bash tool results only\n  - Extract content (handle str, list, other types)\n  - Return list of (tool_use_id, content) tuples\n- `src/domain/quality_gate.py` — [Exists] — \n  - Add marker regex: \\[custom:(\\w+):(start|pass|fail exit=\\d+|timeout)\\]\n  - In parse_validation_evidence_with_spec(), extract tool_result content and scan for markers\n  - Populate custom_commands_ran[name] = True/False\n  - Populate custom_commands_failed[name] = True/False based on marker\n  - Gate check: for each spec'd custom command, check evidence, look up allow_fail from spec\n  - Advisory failure (allow_fail=True + failed) → gate passes\n  - Strict failure (allow_fail=False + failed) → gate fails\n- `tests/unit/domain/test_quality_gate.py` — [Exists] — Add tests:\n  - test_extract_tool_result_content_bash_only\n  - test_extract_tool_result_content_list_type\n  - test_parse_custom_marker_pass\n  - test_parse_custom_marker_fail_with_exit_code\n  - test_parse_custom_marker_timeout\n  - test_parse_custom_marker_start_only_is_failure\n  - test_parse_custom_marker_terminal_without_start_is_ran\n  - test_parse_custom_marker_latest_wins\n  - test_gate_advisory_failure_passes\n  - test_gate_strict_failure_fails\n  - test_gate_not_run_distinct_from_failed\n\n## Acceptance Criteria\n- Markers parsed correctly from tool_result content\n- custom_commands_ran[name] reflects whether command ran (any terminal marker or start-only)\n- custom_commands_failed[name] reflects failure (fail/timeout marker, or start-only)\n- allow_fail=True + failure → gate passes (advisory)\n- allow_fail=False + failure → gate fails\n- Integration test from T006 passes after this task\n\n## Verification\n- `uv run pytest tests/unit/domain/test_quality_gate.py -k custom -v` — all tests pass\n- `uv run pytest tests/integration/domain/test_quality_gate.py -k custom -v` — integration test passes\n\n## Notes for Agent\n- Marker regex: \\[custom:(\\w+):(start|pass|fail exit=\\d+|timeout)\\]\n- Content extraction: str → use directly, list → concatenate string elements, other → str()\n- \"not run\" is distinct status (no markers at all for spec'd command)\n- Terminal markers: pass, fail, timeout (mutually exclusive outcomes)","notes":"Quality gate failed: Quality gate failed: Missing validation evidence for: typecheck, lint, format, test\nLog: /home/cyou/.claude/projects/-home-cyou-mala/0627ce7f-ce1d-43f2-8f70-c1b51558929e.jsonl","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-06T05:22:44.179686911Z","created_by":"cyou","updated_at":"2026-01-06T15:04:34.133437295Z","closed_at":"2026-01-06T15:04:34.133437295Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-n1y1.7","depends_on_id":"mala-n1y1","type":"parent-child","created_at":"2026-01-06T05:22:44.180145549Z","created_by":"cyou"},{"issue_id":"mala-n1y1.7","depends_on_id":"mala-n1y1.6","type":"blocks","created_at":"2026-01-06T05:24:11.587352199Z","created_by":"cyou"}]}
{"id":"mala-n1y1.8","title":"[T008] Extend PromptValidationCommands + implementer_prompt.md for custom commands","description":"**Type**: task\n**Priority**: P2\n**Story**: R6 (Agent Awareness)\n**Parallel**: [P] with T007 (independent subsystem)\n**Primary Files**: src/domain/validation/config.py, src/prompts/implementer_prompt.md, tests/unit/domain/test_validation_config.py\n**Subsystems**: prompts, validation\n**Dependencies**: T005 (needs spec to build PromptValidationCommands)\n\n## Goal\nExtend PromptValidationCommands with custom_commands field and update implementer_prompt.md with marker-wrapped command patterns.\n\n## Context\n- Agents need to know about custom commands to execute them\n- PromptValidationCommands is in config.py (line 486), not prompts.py\n- Prompt template must include marker-wrapped command patterns with exit code preservation\n- Must indicate allow_fail status so agents know advisory failures don't block\n\n## Scope\n- **In**: PromptValidationCommands extension, implementer_prompt.md template update, unit tests\n- **Out**: Runtime execution (handled by agent following prompt)\n\n## Changes\n- `src/domain/validation/config.py` — [Exists] — \n  - Add to PromptValidationCommands: custom_commands: list[tuple[str, str, int, bool]]  # (name, command, timeout, allow_fail)\n  - Update factory method to populate custom_commands from ValidationSpec\n- `src/prompts/implementer_prompt.md` — [Exists] — Add custom commands section:\n  - Instructions to run custom commands after lint/typecheck, before test\n  - Marker-wrapped command patterns:\n    ```bash\n    # Strict (allow_fail=false): exit with command's status\n    echo '[custom:\u003cname\u003e:start]'; __status=0; timeout \u003ctimeout\u003e \u003ccommand\u003e || __status=$?; if [ $__status -eq 0 ]; then echo '[custom:\u003cname\u003e:pass]'; elif [ $__status -eq 124 ]; then echo '[custom:\u003cname\u003e:timeout]'; else echo \"[custom:\u003cname\u003e:fail exit=$__status]\"; fi; exit $__status\n    \n    # Advisory (allow_fail=true): always exit 0\n    echo '[custom:\u003cname\u003e:start]'; __status=0; timeout \u003ctimeout\u003e \u003ccommand\u003e || __status=$?; if [ $__status -eq 0 ]; then echo '[custom:\u003cname\u003e:pass]'; elif [ $__status -eq 124 ]; then echo '[custom:\u003cname\u003e:timeout]'; else echo \"[custom:\u003cname\u003e:fail exit=$__status]\"; fi; exit 0\n    ```\n  - Indicate which commands are advisory (allow_fail=true)\n  - Instruction: advisory failures should be noted but not fixed before proceeding\n- `tests/unit/domain/test_validation_config.py` — [Exists] — Add test:\n  - test_prompt_validation_commands_includes_custom_commands\n\n## Acceptance Criteria\n- PromptValidationCommands.custom_commands populated from ValidationSpec\n- implementer_prompt.md includes custom commands section with marker patterns\n- Advisory commands clearly indicated with allow_fail=true status\n- Command wrapper patterns preserve exit codes correctly\n\n## Verification\n- `uv run pytest tests/unit/domain/test_validation_config.py -k prompt -v` — tests pass\n- Manual review of implementer_prompt.md for clarity\n\n## Notes for Agent\n- timeout exit code 124 means timeout occurred (GNU coreutils)\n- Strict commands: exit with original status (tool_result shows error)\n- Advisory commands: always exit 0 (doesn't block validation)\n- Include timeout in PromptValidationCommands tuple for shell wrapper","notes":"Quality gate failed: Quality gate failed: Missing validation evidence for: test; Validation command(s) failed: ruff\nLog: /home/cyou/.claude/projects/-home-cyou-mala/cf504683-ac90-4a16-9296-4ea4dc5ad121.jsonl","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-06T05:23:35.568078821Z","created_by":"cyou","updated_at":"2026-01-06T15:06:22.07847548Z","closed_at":"2026-01-06T15:06:22.07847548Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-n1y1.8","depends_on_id":"mala-n1y1","type":"parent-child","created_at":"2026-01-06T05:23:35.568827147Z","created_by":"cyou"},{"issue_id":"mala-n1y1.8","depends_on_id":"mala-n1y1.5","type":"blocks","created_at":"2026-01-06T05:24:13.569326084Z","created_by":"cyou"}]}
{"id":"mala-n1y1.9","title":"[T009] Add YAML order preservation regression test","description":"**Type**: task\n**Priority**: P2\n**Story**: R2 (Execution Order)\n**Parallel**: [P] with T007, T008\n**Primary Files**: tests/unit/domain/test_validation_config.py\n**Subsystems**: validation\n**Dependencies**: T005\n\n## Goal\nAdd regression test to ensure custom_commands dict order is preserved from YAML to ValidationSpec.\n\n## Context\n- Python 3.7+ guarantees dict insertion order\n- PyYAML preserves mapping order\n- Critical that custom_commands: {a: ..., b: ..., c: ...} results in execution order a, b, c\n\n## Scope\n- **In**: Regression test for YAML order preservation\n- **Out**: Implementation changes (already covered)\n\n## Changes\n- `tests/unit/domain/test_validation_config.py` — [Exists] — Add test:\n  - test_custom_commands_yaml_order_preserved\n  - Parse custom_commands: {cmd_a: \"...\", cmd_b: \"...\", cmd_c: \"...\"}\n  - Assert spec.commands order for CUSTOM kind is [cmd_a, cmd_b, cmd_c]\n\n## Acceptance Criteria\n- Test verifies YAML key order matches ValidationSpec command order\n- Test uses at least 3 commands to catch ordering bugs\n\n## Verification\n- `uv run pytest tests/unit/domain/test_validation_config.py -k yaml_order -v` — test passes\n\n## Notes for Agent\n- Simple test, but important for correctness\n- Document in config.py that execution order follows YAML key order","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-06T05:23:35.673132842Z","created_by":"cyou","updated_at":"2026-01-06T07:08:08.51828798Z","closed_at":"2026-01-06T07:08:08.51828798Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-n1y1.9","depends_on_id":"mala-n1y1","type":"parent-child","created_at":"2026-01-06T05:23:35.673998077Z","created_by":"cyou"},{"issue_id":"mala-n1y1.9","depends_on_id":"mala-n1y1.5","type":"blocks","created_at":"2026-01-06T05:24:15.572955482Z","created_by":"cyou"}]}
{"id":"mala-n28","title":"Make terminal log colors brighter","description":"## Context\nThe terminal logging colors in src/logging/console.py could be more visible, especially on dark terminal themes. Currently using ANSI bright codes (91-97) but some outputs still use DIM which reduces visibility.\n\n## Scope\n- In: Review and adjust color codes in src/logging/console.py for better visibility\n- In: Consider replacing DIM with lighter shades or removing DIM from key output\n- In: Evaluate GRAY color (currently 37) - may need brighter alternative\n- Out: Changing the logging format or structure\n\n## Files\n- src/logging/console.py\n\n## Acceptance Criteria\n- Log output is more visible on dark terminal backgrounds\n- Color choices remain distinguishable for different log types\n- Agent ID colors remain distinct when multiple agents run concurrently\n\n## Notes for Agent\n- Test with both light and dark terminal themes\n- Keep semantic color meanings (e.g., red for errors, yellow for warnings)\n- Consider using bold + color combinations for emphasis instead of DIM","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-27T17:56:31.759827685Z","updated_at":"2025-12-28T04:07:54.067848191Z","closed_at":"2025-12-28T04:07:54.067848191Z","close_reason":"Closed"}
{"id":"mala-n37j","title":"[Review] src/domain/validation/tool_name_extractor.py:130-138: [P2] Skip built-in arguments when selecting the next command","description":"## Review Finding\n\nThis issue was auto-created from a P2 review finding.\n\n**Source issue:** mala-fdp1.5\n**Reviewer:** codex\n**Location:** src/domain/validation/tool_name_extractor.py:130-138\n\n## Details\n\n`_extract_from_tokens` skips built-in tokens but only skips env assignments afterward. For inputs like `set -e \u0026\u0026 pytest` or `source venv/bin/activate \u0026\u0026 pytest`, the first segment extracts `-e` or `activate` and is treated as meaningful, so `extract_tool_name` returns the wrong tool instead of `pytest`. This is because the built-in’s arguments aren’t consumed, so a non-command token becomes `first`. Consider skipping the next token(s) (or until a shell operator) after a built-in in the per-segment parse so you don’t treat flags/paths as tools.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-02T03:17:47.859119917Z","created_by":"cyou","updated_at":"2026-01-02T04:08:34.419627583Z","closed_at":"2026-01-02T04:08:34.419627583Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_finding:977fb1e1bf74","source:mala-fdp1.5"]}
{"id":"mala-n4av","title":"[Review] tests/test_epic_verification_retry.py:374-398: [P3] Test assertion only verifies one task was awaited, not all","description":"## Review Finding\n\nThis issue was auto-created from a P3 review finding.\n\n**Source issue:** mala-l072\n**Reviewer:** claude\n**Location:** tests/test_epic_verification_retry.py:374-398\n\n## Details\n\nThe test uses a single `task_was_awaited` boolean shared across two spawned issues (`rem-1`, `rem-2`). Each spawn overwrites the same boolean, so the test only confirms at least one task ran to completion, not that both tasks were actually awaited. Consider using a counter or set of issue IDs to verify all tasks were awaited:\n```python\ntasks_awaited: set[str] = set()\n```","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-02T01:58:59.977985801Z","created_by":"cyou","updated_at":"2026-01-02T02:00:34.187861129Z","closed_at":"2026-01-02T02:00:34.187861129Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_finding:01a2c588c525","source:mala-l072"]}
{"id":"mala-n5du","title":"[Remediation] Evidence extraction in testable pure functions","description":"## Context\nThis issue was auto-created by epic verification for epic `mala-29eu`.\n\n## Unmet Criterion\nEvidence extraction in testable pure functions\n\n## Evidence\nNo changes to _finalize_issue_result method, no GateMetadata dataclass, no _build_gate_metadata functions. Task mala-29eu.3 status is still 'open'.\n\n## Severity\ncritical\n\n## Resolution\nAddress this criterion to unblock epic closure. When complete, close this issue.\n","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-01T03:29:57.017547941Z","created_by":"cyou","updated_at":"2026-01-01T05:53:45.062130279Z","closed_at":"2026-01-01T05:53:45.062130279Z","close_reason":"Closed","labels":["auto_generated","epic_remediation:mala-29eu:b8d18be73d9c1e880bcb8f2528257407d2a626b6c9f656d0c9de669548c680d5"],"dependencies":[{"issue_id":"mala-n5du","depends_on_id":"mala-29eu","type":"parent-child","created_at":"2026-01-01T03:37:25.287374598Z","created_by":"daemon"}]}
{"id":"mala-n5or","title":"Add timeout_minutes (agent timeout) to mala.yaml","description":"Currently DEFAULT_AGENT_TIMEOUT_MINUTES=60 is hardcoded. Should be configurable in mala.yaml as a top-level agent timeout setting.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-10T21:24:44.563575441Z","created_by":"cyou","updated_at":"2026-01-10T22:46:12.355350409Z","closed_at":"2026-01-10T22:46:12.355350409Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-n5or","depends_on_id":"mala-ii08","type":"blocks","created_at":"2026-01-10T21:26:18.041633307Z","created_by":"cyou"}]}
{"id":"mala-nab0","title":"[Review] src/domain/validation/config.py:93-95: [P3] Inconsistent empty string validation","description":"## Review Finding\n\nThis issue was auto-created from a P3 review finding.\n\n**Source issue:** mala-fdp1.1\n**Reviewer:** gemini\n**Location:** src/domain/validation/config.py:93-95\n\n## Details\n\n`CommandConfig.from_value` checks for empty command strings when parsing a dict but permits them when parsing a string directly. Both paths should enforce the \"Command cannot be empty string\" rule to ensure consistency.","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-02T03:19:13.909781322Z","created_by":"cyou","updated_at":"2026-01-02T03:45:18.946777326Z","closed_at":"2026-01-02T03:45:18.946777326Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_finding:39bbb21d83d1","source:mala-fdp1.1"]}
{"id":"mala-ng3","title":"Update EvidenceGate for scope-aware evidence + no-op","description":"Context:\n- EvidenceGate must be scope-aware and allow no-op/obsolete issues without forced commits.\n- Plan requires log markers for no-change/obsolete outcomes.\n\nScope:\n- In: update EvidenceGate to derive expected evidence from ValidationSpec per scope; parse ISSUE_NO_CHANGE/ISSUE_OBSOLETE markers with rationale; skip Gate 2/3 when applicable.\n- Out: orchestrator sequencing changes (separate task).\n\nAcceptance Criteria:\n- EvidenceGate accepts no-op/obsolete issues with clean working tree and rationale.\n- Per-issue EvidenceGate never requires E2E evidence.\n- Clear failure messages when evidence is missing.\n\nTest Plan:\n- TDD: add tests for log parsing of evidence and no-op markers.\n- Run quality_gate unit tests.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-26T23:00:01.716896014Z","updated_at":"2025-12-27T00:46:55.821216427Z","closed_at":"2025-12-27T00:46:55.821216427Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-ng3","depends_on_id":"mala-5us","type":"blocks","created_at":"2025-12-26T23:00:26.558341387Z","created_by":"daemon"}]}
{"id":"mala-nsjx","title":"[Review] src/orchestration/orchestrator.py:495-503: [P2] Finalize remediation task results","description":"## Review Finding\n\nThis issue was auto-created from a P2 review finding.\n\n**Source issue:** mala-l072\n**Reviewer:** codex\n**Location:** src/orchestration/orchestrator.py:495-503\n\n## Details\n\n`_execute_remediation_issues` now only awaits the returned task and never registers it with the coordinator, but `finalize_callback` is the only path that calls `_finalize_issue_result` and `mark_completed`. That means remediation issues can succeed yet never be closed or recorded (e.g., epic verification spawns remediation issues → tasks complete → no `beads.close_async` or run metadata). Consider registering these tasks (so run_loop finalizes them) or explicitly finalizing their results here.\n\nSnippet:\n```\ntask = await self.spawn_agent(issue_id)\nif task:\n    tasks.append(task)\n```","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-02T01:58:59.991365765Z","created_by":"cyou","updated_at":"2026-01-02T02:06:15.996497605Z","closed_at":"2026-01-02T02:06:15.996497605Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_finding:fe8ac1fa5584","source:mala-l072"]}
{"id":"mala-ntpr","title":"add handle deadlocks","description":"## Epic: Deadlock Handling for Multi-Agent Coordination\n\nDetect and auto-resolve deadlocks between concurrent agents in real-time.\n\n### Goals\n- Detect deadlocks on lock contention (not timeout-based)\n- Auto-resolution: kill youngest agent, release its locks, add dependency\n- Surface deadlock details to orchestrator via event sink\n\n### Technical Approach\n- Centralized Wait-For Graph (WFG) in orchestrator\n- PostToolUse hook emits lock events after lock commands complete\n- DFS cycle detection on WAITING events (O(n) per event)\n- Feature gated via `--disable-validations=deadlock-detection`\n\n### Plan Reference\nSee: plans/2026-01-03-deadlock-handling-plan.md","status":"closed","priority":2,"issue_type":"epic","created_at":"2026-01-03T19:19:05.032222587Z","created_by":"cyou","updated_at":"2026-01-04T01:12:48.119957597Z","closed_at":"2026-01-04T01:12:48.119957597Z","close_reason":"Closed"}
{"id":"mala-ntpr.1","title":"[T001] Add Deadlock Domain Model","description":"## Goal\nImplement WaitForGraph and DeadlockMonitor with cycle detection.\n\n## Context\nThis is the core domain model for deadlock detection. The WaitForGraph tracks which agents hold which locks and which agents are waiting for which locks. The DeadlockMonitor orchestrates event handling and deadlock detection.\n\n## Scope\n**In:** \n- LockEventType enum, LockEvent dataclass\n- WaitForGraph with hold/wait tracking and DFS cycle detection\n- DeadlockInfo dataclass for cycle metadata\n- DeadlockMonitor with agent registration and event handling\n\n**Out:**\n- Hook implementation (T003)\n- Orchestrator wiring (T004)\n\n## Changes\n- `src/domain/deadlock.py` — **New** — Create domain model with:\n  - `LockEventType` enum: ACQUIRED, WAITING, RELEASED\n  - `LockEvent` dataclass: event_type, agent_id, lock_path, timestamp\n  - `WaitForGraph` with: add_hold(), add_wait(), remove_hold(), remove_agent(), get_holder(), detect_cycle()\n  - `DeadlockInfo` dataclass: cycle, victim_id, victim_issue_id, blocked_on, blocker_id, blocker_issue_id\n  - `DeadlockMonitor` with: register_agent(), unregister_agent(), handle_event()\n- `tests/domain/test_deadlock.py` — **New** — Unit tests for cycle detection and state management\n\n## Acceptance Criteria\n- [ ] detect_cycle() returns None when no cycle exists\n- [ ] detect_cycle() returns cycle agents for A\u003c-\u003eB deadlock\n- [ ] detect_cycle() handles multi-party cycles (A-\u003eB-\u003eC-\u003eA)\n- [ ] Victim selection picks youngest (max start_time in cycle)\n- [ ] remove_agent() clears all state for agent\n- [ ] All unit tests pass\n\n## Verification\n```bash\nuv run pytest tests/domain/test_deadlock.py -v -m unit\n```\n\n## Rollback\nDelete `src/domain/deadlock.py` and `tests/domain/test_deadlock.py`\n\n## Notes for Agent\n- DFS cycle detection should be O(n) where n = number of agents\n- Use dataclasses with field(default_factory=...) for mutable defaults\n- Cycle can be length 2+ (A\u003c-\u003eB or A-\u003eB-\u003eC-\u003eA)","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-03T21:29:01.200006586Z","created_by":"cyou","updated_at":"2026-01-03T23:33:03.444505296Z","closed_at":"2026-01-03T23:33:03.444505296Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-ntpr.1","depends_on_id":"mala-ntpr","type":"parent-child","created_at":"2026-01-03T21:29:01.207052363Z","created_by":"daemon"}]}
{"id":"mala-ntpr.10","title":"[T010] Handle lock-wait timeout (exit 1) in lock event hook","description":"Plan calls for clearing wait status on lock-wait.sh timeout (exit 1). Update make_lock_event_hook to emit a WAITING-clearing event (e.g., RELEASED or explicit clear) and add unit test to ensure waits_for is cleared to avoid stale waits.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T22:54:07.233662237Z","created_by":"cyou","updated_at":"2026-01-03T23:37:22.149285442Z","closed_at":"2026-01-03T23:37:22.149285442Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-ntpr.10","depends_on_id":"mala-ntpr","type":"parent-child","created_at":"2026-01-03T22:54:07.242051481Z","created_by":"daemon"}]}
{"id":"mala-ntpr.11","title":"[Review] 1 non-blocking finding from mala-ntpr.1","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-ntpr.1\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Make cycle detection linear-time per requirement\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/domain/deadlock.py:183-185\n\n`detect_cycle` runs a fresh walk from every waiting agent, so a long acyclic chain (A waits on B, B waits on C, …, last waits on an unlocked lock) is traversed almost in full for each start. That makes deadlock checks O(n^2) per WAITING event, which conflicts with the stated O(n) requirement and can become a hotspot as agent count grows. Consider a single-pass DFS with global colors/visited or memoizing visited nodes to keep total work linear.\n\n---\n\n\u003c!-- fp:3015dcdc3742dce1 --\u003e\n\u003c!-- review_finding:34ba6f566506 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T23:33:03.473158879Z","created_by":"cyou","updated_at":"2026-01-03T23:38:03.905369339Z","closed_at":"2026-01-03T23:38:03.905369339Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-ntpr.1"],"dependencies":[{"issue_id":"mala-ntpr.11","depends_on_id":"mala-ntpr","type":"parent-child","created_at":"2026-01-03T23:33:03.473515507Z","created_by":"daemon"}]}
{"id":"mala-ntpr.12","title":"[Review] 3 non-blocking findings from mala-ntpr.3","description":"## Review Findings\n\nThis issue consolidates 3 non-blocking findings from code review.\n\n**Source issue:** mala-ntpr.3\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P3] _get_exit_code guard prevents regex match for 'exit code:N' without space\n\n**Priority:** P3\n**Reviewer:** claude\n**Location:** src/infra/hooks/deadlock.py:100-103\n\nThe guard check `if \"exit code: \" in tool_result.lower()` requires a space after the colon, but the regex `r\"exit code:\\s*(\\d+)\"` uses `\\s*` which allows zero spaces. If the tool result contains `exit code:0` (no space), the guard early-returns `None` before the regex is tried. Consider removing the early guard or making it consistent with the regex pattern `if \"exit code:\" in tool_result.lower()`.\n\n---\n\n### Finding 2: [P2] Preserve lock command order when extracting matches\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/infra/hooks/deadlock.py:62-67\n\n`_extract_all_lock_paths` groups matches by command type, not by position in the command string. That means a command like `lock-release.sh a \u0026\u0026 lock-try.sh b` is emitted as try→release, reversing the real order and potentially corrupting lock state transitions. Consider collecting matches with their `start()` positions and sorting, or using a single alternation regex so iteration preserves order.\n\nExample source order:\n```\nfor match in _LOCK_TRY_PATTERN.finditer(command):\nfor match in _LOCK_WAIT_PATTERN.finditer(command):\n```\n\n---\n\n### Finding 3: [P2] Don’t treat single exit code as success for all batched commands\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/infra/hooks/deadlock.py:166-175\n\nFor batched commands, you use one `exit_code` to decide events for every match. With operators like `;`, `||`, `|`, or `\u0026`, a zero exit code only reflects the last command (or pipeline status), so earlier lock commands may have failed or not run. Example: `lock-try.sh a || lock-try.sh b` with exit 0 will emit ACQUIRED for both, even though `a` failed. This can create false lock events. Consider only emitting for the last command in a batch, or restrict multi-command emission to `\u0026\u0026` chains where success implies all commands ran.\n\n---\n\n\u003c!-- fp:7324d9f7dceb53c4 --\u003e\n\u003c!-- fp:ad0f6b66a604a333 --\u003e\n\u003c!-- fp:29f2f67bec3a0f54 --\u003e\n\u003c!-- review_finding:adc6439fbb43 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T23:53:53.721159029Z","created_by":"cyou","updated_at":"2026-01-04T00:02:04.012237054Z","closed_at":"2026-01-04T00:02:04.012237054Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-ntpr.3"],"dependencies":[{"issue_id":"mala-ntpr.12","depends_on_id":"mala-ntpr","type":"parent-child","created_at":"2026-01-03T23:53:53.721563835Z","created_by":"daemon"}]}
{"id":"mala-ntpr.13","title":"[Review] 1 non-blocking finding from mala-ntpr.12","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-ntpr.12\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Don’t treat redirection \u0026 as unsafe operator\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/infra/hooks/deadlock.py:82-89\n\n`_is_safe_batch_command` marks any `\u0026` outside quotes as unsafe unless it’s part of `\u0026\u0026`. In bash, `\u0026` is also used in redirections like `2\u003e\u00261` and `\u0026\u003efile`, which are very common and do **not** imply backgrounding or short‑circuiting. With a command like `lock-try.sh a.py 2\u003e\u00261 \u0026\u0026 lock-try.sh b.py`, the new logic will classify the batch as unsafe and only emit the last lock event, dropping the first even though the chain is safe. This is a behavior regression introduced by the new heuristic. Consider ignoring `\u0026` when it’s part of redirection syntax (e.g., preceded by `\u003e`), or requiring whitespace around `\u0026` to treat it as a background operator.\n\n---\n\n\u003c!-- fp:de5ff0066948c34c --\u003e\n\u003c!-- review_finding:a60255650c84 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-04T00:02:04.044588858Z","created_by":"cyou","updated_at":"2026-01-04T00:04:48.630176626Z","closed_at":"2026-01-04T00:04:48.630176626Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-ntpr.12"],"dependencies":[{"issue_id":"mala-ntpr.13","depends_on_id":"mala-ntpr","type":"parent-child","created_at":"2026-01-04T00:02:04.045038584Z","created_by":"daemon"}]}
{"id":"mala-ntpr.14","title":"[Review] 3 non-blocking findings from mala-ntpr.13","description":"## Review Findings\n\nThis issue consolidates 3 non-blocking findings from code review.\n\n**Source issue:** mala-ntpr.13\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Digit before \u0026 incorrectly treated as redirection\n\n**Priority:** P2\n**Reviewer:** claude\n**Location:** src/infra/hooks/deadlock.py:95\n\nThe check `command[i - 1] in \"\u003e0123456789\"` is too permissive. It includes digits, which means `lock-try.sh 1\u0026` (backgrounding a command) would be incorrectly treated as safe redirection. In `2\u003e\u00261`, the character immediately before `\u0026` is `\u003e`, not `2`. The check should be `command[i - 1] == \"\u003e\"` to correctly identify only redirection patterns like `\u003e\u00261` or `2\u003e\u00261` where `\u003e` directly precedes `\u0026`.\n\n---\n\n### Finding 2: [P2] Don’t treat digit‑preceded \u0026 as redirection\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/infra/hooks/deadlock.py:95-98\n\nThe new rule treats any `\u0026` preceded by a digit as a redirection token, but shell backgrounding does not require whitespace. For example, `lock-try.sh a.py 1\u0026 lock-try.sh b.py` is valid backgrounding, yet this logic now marks it safe because the `\u0026` follows `1`. This allows unsafe backgrounded commands to pass the safety check. Consider tightening the redirection detection to cases where `\u0026` is immediately preceded by `\u003e` (e.g., `2\u003e\u00261`) or followed by `\u003e` (e.g., `\u0026\u003efile`), rather than any digit.\n\n---\n\n### Finding 3: [P2] Exclude digits from redirection check to prevent unsafe backgrounding\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/infra/hooks/deadlock.py:95\n\nThe check `command[i - 1] in \"\u003e0123456789\"` incorrectly classifies commands like `sleep 1\u0026` as safe redirections, allowing them to run in the background. In valid file descriptor redirections (e.g., `2\u003e\u00261`), the `\u0026` always follows `\u003e` (or `\u003c`), never the digit directly. Remove digits from this check to ensure unsafe background operators are correctly detected. Consider adding `\u003c` to support input duplication (`\u003c\u0026`).\n\n---\n\n\u003c!-- fp:c96a3eb3d17192b7 --\u003e\n\u003c!-- fp:6a292acd8dfdd612 --\u003e\n\u003c!-- fp:bc821dc3493a2242 --\u003e\n\u003c!-- review_finding:d437ab50d16d --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-04T00:04:48.660525115Z","created_by":"cyou","updated_at":"2026-01-04T00:08:37.572996075Z","closed_at":"2026-01-04T00:08:37.572996075Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-ntpr.13"],"dependencies":[{"issue_id":"mala-ntpr.14","depends_on_id":"mala-ntpr","type":"parent-child","created_at":"2026-01-04T00:04:48.660896402Z","created_by":"daemon"}]}
{"id":"mala-ntpr.15","title":"[Review] 2 non-blocking findings from mala-ntpr.4","description":"## Review Findings\n\nThis issue consolidates 2 non-blocking findings from code review.\n\n**Source issue:** mala-ntpr.4\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Swallowed CancelledError in self-cancel case hides real issues\n\n**Priority:** P2\n**Reviewer:** claude\n**Location:** src/orchestration/orchestrator.py:484-487\n\nWhen `is_self_cancel` is true and `asyncio.shield(self._resolve_deadlock(info))` raises `CancelledError` from an unrelated cancellation, the error is silently swallowed. This could mask legitimate cancellation requests from other sources (e.g., timeout or abort). Consider re-raising after the deferred cancellation is scheduled, or tracking whether the cancellation was expected.\n\n---\n\n### Finding 2: [P2] Double cleanup of victim locks when victim is self\n\n**Priority:** P2\n**Reviewer:** claude\n**Location:** src/orchestration/orchestrator.py:478-479\n\nWhen `_handle_deadlock` is called and `is_self_cancel` is true, `_cleanup_agent_locks(info.victim_id)` is called at line 479. Later, when the deferred cancellation triggers and the task eventually completes, the `finally` block in `run_implementer` (line 753) calls `_cleanup_agent_locks` again for the same agent. The second call is idempotent (locks already cleaned), but it also calls `deadlock_monitor.unregister_agent` twice, which is harmless but wasteful. More importantly, the agent may already be unregistered when the second cleanup runs.\n\n---\n\n\u003c!-- fp:dd758db3f9de66e4 --\u003e\n\u003c!-- fp:faaaee5cece03424 --\u003e\n\u003c!-- review_finding:2920e9294087 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-04T00:35:27.019532992Z","created_by":"cyou","updated_at":"2026-01-04T00:41:44.815722685Z","closed_at":"2026-01-04T00:41:44.815722685Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-ntpr.4"],"dependencies":[{"issue_id":"mala-ntpr.15","depends_on_id":"mala-ntpr","type":"parent-child","created_at":"2026-01-04T00:35:27.020058133Z","created_by":"daemon"}]}
{"id":"mala-ntpr.2","title":"[T002] Add PostToolUse Hook Type","description":"## Goal\nEnable PostToolUse hooks in the hook infrastructure.\n\n## Context\nCurrently only PreToolUse and Stop hooks exist. PostToolUse hooks are needed to inspect bash command results (exit codes) after execution completes.\n\n## Scope\n**In:**\n- PostToolUseHook type alias\n- Hook infrastructure updates to support PostToolUse\n- SDK options wiring for PostToolUse hooks\n\n**Out:**\n- Actual deadlock hook implementation (T003)\n\n## Changes\n- `src/infra/hooks/dangerous_commands.py` — Exists — Add `PostToolUseHook` type alias (mirror PreToolUseHook pattern)\n- `src/infra/hooks/__init__.py` — Exists — Export `PostToolUseHook`\n- `src/pipeline/agent_session_runner.py` — Exists — Update `_build_hooks()` to return `post_tool_hooks` list; Update `_build_sdk_options()` to wire `PostToolUse` hooks into SDK HookMatcher\n\n## Acceptance Criteria\n- [ ] PostToolUseHook type is exported from hooks module\n- [ ] _build_hooks() can return post_tool_hooks\n- [ ] PostToolUse hooks are registered in SDK options\n- [ ] Existing PreToolUse and Stop hooks continue to work\n\n## Verification\n```bash\nuv run pytest tests/test_hooks.py tests/test_agent_session_runner.py -v\n```\n\n## Rollback\nRevert changes to hooks and session runner files.\n\n## Notes for Agent\n- Look at existing PreToolUseHook pattern in dangerous_commands.py\n- Check SDK documentation for PostToolUse hook signature\n- Ensure backward compatibility with existing hooks","notes":"PRE-REQ VERIFIED (docs): Claude Agent SDK PostToolUse hook input includes tool_response; for Bash, tool_response includes exitCode per SDK docs. Thus PostToolUse can see command + exit code. Sources: platform.claude.com/en/docs/agent-sdk/hooks and docs.claude.com/en/docs/agent-sdk/python.","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-03T21:29:16.161258659Z","created_by":"cyou","updated_at":"2026-01-03T23:37:29.361855833Z","closed_at":"2026-01-03T23:37:29.361855833Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-ntpr.2","depends_on_id":"mala-ntpr","type":"parent-child","created_at":"2026-01-03T21:29:16.168012287Z","created_by":"daemon"}]}
{"id":"mala-ntpr.3","title":"[T003] Emit Lock Events from PostToolUse Hook","description":"## Goal\nCapture lock command outcomes and emit LockEvent to orchestrator.\n\n## Context\nWhen agents run lock commands (lock-try.sh, lock-wait.sh, lock-release.sh), we need to capture the exit code and emit appropriate LockEvents for the deadlock monitor.\n\n## Scope\n**In:**\n- make_lock_event_hook function\n- Lock command parsing (extract file path)\n- Exit code interpretation and event emission\n- Path canonicalization\n\n**Out:**\n- Orchestrator integration (T004)\n- Hook wiring (T005)\n\n## Changes\n- `src/infra/hooks/deadlock.py` — **New** — Create PostToolUse hook:\n  - `make_lock_event_hook(agent_id, emit_event, repo_namespace)` returning PostToolUse hook\n  - `_extract_lock_path(command)` helper\n  - `_get_exit_code(tool_result)` helper\n  - Detect lock-try.sh, lock-wait.sh, lock-release.sh in bash command\n  - Map exit codes: try exit 0 -\u003e ACQUIRED, try exit 1 -\u003e WAITING, release exit 0 -\u003e RELEASED\n  - Canonicalize lock path using locking._canonicalize_path or expose as public helper\n- `src/infra/hooks/__init__.py` — Exists — Export `make_lock_event_hook`\n- `tests/infra/hooks/test_deadlock_hook.py` — **New** — Unit tests for hook\n\n## Acceptance Criteria\n- [ ] lock-try.sh with exit 0 emits ACQUIRED event\n- [ ] lock-try.sh with exit 1 emits WAITING event\n- [ ] lock-release.sh with exit 0 emits RELEASED event\n- [ ] lock-wait.sh with exit 0 emits ACQUIRED event\n- [ ] Non-lock bash commands are ignored (no event)\n- [ ] Exit code 2 logs warning, no event emitted\n- [ ] Lock paths are canonicalized correctly\n\n## Verification\n```bash\nuv run pytest tests/infra/hooks/test_deadlock_hook.py -v -m unit\n```\n\n## Rollback\nDelete `src/infra/hooks/deadlock.py` and `tests/infra/hooks/test_deadlock_hook.py`\n\n## Notes for Agent\n- Need to create tests/infra/hooks/ directory\n- Check locking.py for _canonicalize_path function - may need to expose as public\n- Exit codes: 0 = success, 1 = blocked/timeout, 2 = error\n- Command format: \"lock-try.sh /path/to/file\" or similar","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-03T21:29:35.473524782Z","created_by":"cyou","updated_at":"2026-01-03T23:53:53.690507899Z","closed_at":"2026-01-03T23:53:53.690507899Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-ntpr.3","depends_on_id":"mala-ntpr","type":"parent-child","created_at":"2026-01-03T21:29:35.480542189Z","created_by":"daemon"},{"issue_id":"mala-ntpr.3","depends_on_id":"mala-ntpr.1","type":"blocks","created_at":"2026-01-03T21:31:00.725578379Z","created_by":"daemon"},{"issue_id":"mala-ntpr.3","depends_on_id":"mala-ntpr.2","type":"blocks","created_at":"2026-01-03T21:31:00.741276366Z","created_by":"daemon"}]}
{"id":"mala-ntpr.4","title":"[T004] Wire DeadlockMonitor into Orchestrator","description":"## Goal\nMaintain WFG state in orchestrator, detect cycles, and resolve deadlocks.\n\n## Context\nThe orchestrator needs to own the DeadlockMonitor instance, register/unregister agents as they start/stop, and handle deadlock resolution when cycles are detected.\n\n## Scope\n**In:**\n- DeadlockMonitor attribute in orchestrator\n- Agent registration on start\n- Agent cleanup on stop\n- Deadlock resolution handler\n\n**Out:**\n- Hook wiring into agent sessions (T005)\n\n## Changes\n- `src/orchestration/orchestrator.py` — Exists — Add:\n  - `deadlock_monitor: DeadlockMonitor | None` attribute\n  - Initialize in `_init_runtime_state()` if deadlock detection enabled\n  - In `run_implementer()`: call `deadlock_monitor.register_agent(agent_id, issue_id)`\n  - In `_cleanup_agent_locks()`: call `deadlock_monitor.unregister_agent(agent_id)`\n  - Add `_handle_deadlock(info: DeadlockInfo)` async method:\n    - Log warning with cycle and victim info\n    - Cancel victim task if still running\n    - Call `_cleanup_agent_locks(victim_id)`\n    - Add dependency via beads API\n    - Mark needs-followup via beads API\n    - Emit deadlock event via event_sink\n  - Set `deadlock_monitor.on_deadlock = self._handle_deadlock`\n  - Guard resolution with asyncio.Lock to prevent race conditions\n\n## Acceptance Criteria\n- [ ] DeadlockMonitor initialized when deadlock detection enabled\n- [ ] Agents registered when they start working on issues\n- [ ] Agents unregistered during cleanup\n- [ ] Deadlock resolution cancels victim task\n- [ ] Deadlock resolution adds dependency victim -\u003e blocker\n- [ ] Deadlock resolution marks victim issue needs-followup\n- [ ] Deadlock event emitted to event sink\n\n## Verification\nIntegration tests in T008\n\n## Rollback\nRevert orchestrator changes\n\n## Notes for Agent\n- Check active_tasks dict for task cancellation pattern\n- Use asyncio.Lock to guard resolution\n- Check task.done() before cancel to avoid errors\n- Beads API: add_dependency_async, mark_needs_followup_async","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-03T21:29:53.693767582Z","created_by":"cyou","updated_at":"2026-01-04T00:35:26.988268624Z","closed_at":"2026-01-04T00:35:26.988268624Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-ntpr.4","depends_on_id":"mala-ntpr","type":"parent-child","created_at":"2026-01-03T21:29:53.701508667Z","created_by":"daemon"},{"issue_id":"mala-ntpr.4","depends_on_id":"mala-ntpr.1","type":"blocks","created_at":"2026-01-03T21:31:00.749702257Z","created_by":"daemon"},{"issue_id":"mala-ntpr.4","depends_on_id":"mala-ntpr.3","type":"blocks","created_at":"2026-01-03T21:31:00.758092399Z","created_by":"daemon"}]}
{"id":"mala-ntpr.5","title":"[T005] Hook Wiring in Orchestration","description":"## Goal\nInstall lock event hook for all agent sessions.\n\n## Context\nThe lock event hook needs to be wired into every agent session so lock commands emit events to the orchestrator's DeadlockMonitor.\n\n## Scope\n**In:**\n- Update WiringDependencies\n- Wire hook into session config\n- Connect hook callback to DeadlockMonitor\n\n**Out:**\n- Event protocol updates (T006)\n- Config/CLI (T007)\n\n## Changes\n- `src/orchestration/orchestration_wiring.py` — Exists — Update:\n  - `WiringDependencies` to include `deadlock_monitor: DeadlockMonitor | None`\n  - `build_session_config()` to accept `deadlock_monitor` and `deadlock_detection_enabled`\n  - Create `make_lock_event_hook` with callback that calls `deadlock_monitor.handle_event()`\n- `src/pipeline/agent_session_runner.py` — Exists — Update:\n  - Accept `post_tool_hooks` in `SessionConfig`\n  - Update `_build_hooks()` to include deadlock hook if enabled\n\n## Acceptance Criteria\n- [ ] WiringDependencies accepts deadlock_monitor\n- [ ] Lock event hook installed in agent sessions when enabled\n- [ ] Hook callback invokes deadlock_monitor.handle_event()\n- [ ] Hook not installed when deadlock detection disabled\n\n## Verification\nIntegration tests in T008\n\n## Rollback\nRevert wiring and session runner changes\n\n## Notes for Agent\n- Follow existing patterns for hook wiring in orchestration_wiring.py\n- Hook should be conditional on deadlock_detection_enabled flag\n- Make sure hook captures correct agent_id for each session","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-03T21:30:04.837061496Z","created_by":"cyou","updated_at":"2026-01-04T00:40:45.242811776Z","closed_at":"2026-01-04T00:40:45.242811776Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-ntpr.5","depends_on_id":"mala-ntpr","type":"parent-child","created_at":"2026-01-03T21:30:04.843850614Z","created_by":"daemon"},{"issue_id":"mala-ntpr.5","depends_on_id":"mala-ntpr.2","type":"blocks","created_at":"2026-01-03T21:31:00.766327421Z","created_by":"daemon"},{"issue_id":"mala-ntpr.5","depends_on_id":"mala-ntpr.3","type":"blocks","created_at":"2026-01-03T21:31:00.774562543Z","created_by":"daemon"},{"issue_id":"mala-ntpr.5","depends_on_id":"mala-ntpr.4","type":"blocks","created_at":"2026-01-03T21:31:00.782648166Z","created_by":"daemon"}]}
{"id":"mala-ntpr.6","title":"[T006] Event Protocol Updates","description":"## Goal\nExpose deadlock detection/resolution via event sink for observability.\n\n## Context\nWhen a deadlock is detected and resolved, we need to emit an event that can be logged, tracked, and acted upon by the orchestrator and any observers.\n\n## Scope\n**In:**\n- Add on_deadlock_detected to MalaEventSink protocol\n- Implement in console_sink\n- Implement in null_sink if exists\n\n**Out:**\n- N/A\n\n## Changes\n- `src/infra/io/event_sink.py` — Exists — Add `on_deadlock_detected(info: DeadlockInfo) -\u003e None` to MalaEventSink protocol\n- `src/infra/io/console_sink.py` — Exists — Implement `on_deadlock_detected` with warning log including cycle and victim info\n- `src/infra/io/base_sink.py` — Exists — Add no-op implementation if base class pattern used\n\n## Acceptance Criteria\n- [ ] on_deadlock_detected method in event sink protocol\n- [ ] Console sink logs deadlock info as warning\n- [ ] All sink implementations handle deadlock event\n\n## Verification\n```bash\nuv run pytest tests/test_event_sink.py -v\n```\n\n## Rollback\nRevert event sink changes\n\n## Notes for Agent\n- Follow existing event method patterns in event_sink.py\n- Log format should include: cycle agents, victim, blocker, blocked resource\n- Check if base_sink.py needs update for inheritance","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T21:30:17.370498702Z","created_by":"cyou","updated_at":"2026-01-04T00:48:33.036098599Z","closed_at":"2026-01-04T00:48:33.036098599Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-ntpr.6","depends_on_id":"mala-ntpr","type":"parent-child","created_at":"2026-01-03T21:30:17.377356389Z","created_by":"daemon"},{"issue_id":"mala-ntpr.6","depends_on_id":"mala-ntpr.4","type":"blocks","created_at":"2026-01-03T21:31:00.790815189Z","created_by":"daemon"}]}
{"id":"mala-ntpr.7","title":"[T007] Feature Flag and Config Integration","description":"## Goal\nAllow disabling deadlock detection via its own dedicated CLI flag.\n\n## Context\nDeadlock detection should have its OWN flag, NOT be part of --disable-validations. This makes it independently controllable and clearer for users.\n\n## Scope\n**In:**\n- Add dedicated --no-deadlock-detection flag to CLI\n- Add config option\n- Gate DeadlockMonitor initialization\n\n**Out:**\n- Do NOT add to VALID_DISABLE_VALUES / --disable-validations\n\n## Changes\n- `src/cli/cli.py` — Exists — Add `--no-deadlock-detection` flag (boolean, default False meaning detection is enabled by default)\n- `src/infra/io/config.py` — Exists — Add `deadlock_detection_enabled: bool = True` to config class; wire --no-deadlock-detection to set this to False\n- `src/orchestration/orchestrator.py` — Exists — Check config flag before initializing DeadlockMonitor\n\n## Acceptance Criteria\n- [ ] --no-deadlock-detection flag exists on CLI\n- [ ] Config has deadlock_detection_enabled option\n- [ ] --no-deadlock-detection sets flag to False\n- [ ] DeadlockMonitor not initialized when disabled\n- [ ] Default is enabled (no flag = detection on)\n\n## Verification\n```bash\nuv run pytest tests/test_cli.py tests/test_config.py -v -k \"deadlock\"\n```\n\n## Rollback\nRevert CLI and config changes\n\n## Notes for Agent\n- This is a STANDALONE flag, not part of --disable-validations\n- Follow existing flag patterns in CLI (e.g., --no-summary style)\n- Default should be enabled (detection ON)","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T21:30:28.465452983Z","created_by":"cyou","updated_at":"2026-01-04T01:01:13.068773559Z","closed_at":"2026-01-04T01:01:13.068773559Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-ntpr.7","depends_on_id":"mala-ntpr","type":"parent-child","created_at":"2026-01-03T21:30:28.465988401Z","created_by":"daemon"},{"issue_id":"mala-ntpr.7","depends_on_id":"mala-ntpr.4","type":"blocks","created_at":"2026-01-03T21:31:00.798901121Z","created_by":"daemon"},{"issue_id":"mala-ntpr.7","depends_on_id":"mala-ntpr.5","type":"blocks","created_at":"2026-01-03T21:31:00.807289823Z","created_by":"daemon"}]}
{"id":"mala-ntpr.8","title":"[T008] Integration Tests for Deadlock Detection","description":"## Goal\nValidate end-to-end deadlock detection and resolution.\n\n## Context\nIntegration tests should verify the complete flow: lock events -\u003e cycle detection -\u003e resolution -\u003e cleanup.\n\n## Scope\n**In:**\n- Integration tests for deadlock scenarios\n- Mock BeadsClient and agent sessions\n- Verify all resolution steps\n\n**Out:**\n- E2E tests (explicitly out of scope per plan)\n\n## Changes\n- `tests/integration/test_deadlock_integration.py` — **New** — Create:\n  - Test setup with mock BeadsClient and mock agent sessions\n  - Simulate 2 agents: A holds L1, B holds L2\n  - Trigger deadlock: A waits on L2, B waits on L1\n  - Assert youngest agent cancelled\n  - Assert cleanup_agent_locks called for victim\n  - Assert add_dependency called correctly\n  - Assert mark_needs_followup called\n  - Assert on_deadlock_detected event emitted\n  - Test: Single agent never triggers deadlock\n  - Test: 3-agent cycle (A-\u003eB-\u003eC-\u003eA) detected and resolved\n\n## Acceptance Criteria\n- [ ] 2-agent deadlock detected and resolved correctly\n- [ ] Youngest agent selected as victim\n- [ ] Victim task cancelled\n- [ ] Victim locks cleaned up\n- [ ] Dependency added between victim and blocker\n- [ ] Needs-followup marked on victim issue\n- [ ] Deadlock event emitted\n- [ ] Single agent never triggers false positive\n- [ ] 3-agent cycle handled correctly\n- [ ] Coverage threshold met (85%)\n\n## Verification\n```bash\nuv run pytest tests/integration/test_deadlock_integration.py -v -m integration\nuv run pytest -m \"unit or integration\" --cov --cov-fail-under=85\n```\n\n## Rollback\nDelete tests/integration/test_deadlock_integration.py\n\n## Notes for Agent\n- Need to create tests/integration/ directory if not exists\n- Mock BeadsClient add_dependency_async and mark_needs_followup_async\n- Mock task cancellation\n- Use asyncio for async test patterns\n- Follow existing integration test patterns in codebase","notes":"CHANGE: Deadlock detection should be controlled by its own dedicated flag, NOT as part of --validations. Need a separate --deadlock-detection or similar flag for enabling/disabling this feature independently.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T21:30:42.849539101Z","created_by":"cyou","updated_at":"2026-01-04T01:09:18.438553693Z","closed_at":"2026-01-04T01:09:18.438553693Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-ntpr.8","depends_on_id":"mala-ntpr","type":"parent-child","created_at":"2026-01-03T21:30:42.856611548Z","created_by":"daemon"},{"issue_id":"mala-ntpr.8","depends_on_id":"mala-ntpr.1","type":"blocks","created_at":"2026-01-03T21:31:00.815653795Z","created_by":"daemon"},{"issue_id":"mala-ntpr.8","depends_on_id":"mala-ntpr.2","type":"blocks","created_at":"2026-01-03T21:31:00.824012467Z","created_by":"daemon"},{"issue_id":"mala-ntpr.8","depends_on_id":"mala-ntpr.3","type":"blocks","created_at":"2026-01-03T21:31:00.832630088Z","created_by":"daemon"},{"issue_id":"mala-ntpr.8","depends_on_id":"mala-ntpr.4","type":"blocks","created_at":"2026-01-03T21:31:00.841523768Z","created_by":"daemon"},{"issue_id":"mala-ntpr.8","depends_on_id":"mala-ntpr.5","type":"blocks","created_at":"2026-01-03T21:31:00.84985568Z","created_by":"daemon"},{"issue_id":"mala-ntpr.8","depends_on_id":"mala-ntpr.6","type":"blocks","created_at":"2026-01-03T21:31:00.858876019Z","created_by":"daemon"},{"issue_id":"mala-ntpr.8","depends_on_id":"mala-ntpr.7","type":"blocks","created_at":"2026-01-03T21:31:00.867659719Z","created_by":"daemon"}]}
{"id":"mala-ntpr.9","title":"[T009] Propagate DeadlockMonitor into wiring deps","description":"Ensure orchestrator passes deadlock_monitor into WiringDependencies so orchestration_wiring can install hooks. Update _build_wiring_dependencies to include deadlock_monitor (and any enabled flag) so T005 can wire hooks end-to-end.","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-03T22:54:01.681331288Z","created_by":"cyou","updated_at":"2026-01-03T23:26:23.752985619Z","closed_at":"2026-01-03T23:26:23.752985619Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-ntpr.9","depends_on_id":"mala-ntpr","type":"parent-child","created_at":"2026-01-03T22:54:01.691313142Z","created_by":"daemon"}]}
{"id":"mala-nuoq","title":"Epic: CLI Redesign - mala run Command","description":"Redesign the `mala run` CLI to improve discoverability and fix ordering bugs.\n\n## Goals\n- Replace 5 interacting flags (`--wip`, `--focus`, `--epic`, `--only`, `--orphans-only`) with 3 orthogonal knobs (`--scope`, `--order`, `--resume`)\n- Fix bug where `--only` loses input order by converting to a `set`\n- Group options logically using `rich_help_panel`\n- Support repeatable flags instead of comma-separated strings\n- Rename internal \"cerberus\" naming to user-facing \"review\"\n\n## Phases\n1. **Phase 1**: Non-breaking additions (help panels, short flags, `--fail-on-empty`, order fix, cerberus→review rename)\n2. **Phase 2**: Deprecation warnings for old flags\n3. **Phase 3**: Remove deprecated options (future, not part of this epic)\n\n## Key Decisions\n- Orthogonal knobs (not single `--mode`)\n- Keep old flags as hidden aliases during migration\n- `--order input` only valid for `--scope ids:`\n- Conflict resolution: new flags take precedence with warning\n- Duplicate IDs deduplicated with warning\n- `--scope ids:` defaults to `--order focus`\n\nSee: plans/2026-01-05-cli-redesign.md","status":"closed","priority":1,"issue_type":"epic","created_at":"2026-01-05T02:35:57.623662369Z","created_by":"cyou","updated_at":"2026-01-05T21:03:46.880026278Z","closed_at":"2026-01-05T21:03:46.880026278Z","close_reason":"Closed"}
{"id":"mala-nuoq.1","title":"[T001] [integration-path-test] Skeleton + Integration: Scope parsing and type definitions","description":"**Type**: task\n**Priority**: P2\n**Story**: Phase 1 - Foundation\n**Parallel**: No (foundation)\n**Primary Files**: src/cli/cli.py, src/orchestration/types.py, tests/unit/cli/test_cli.py\n**Subsystems**: cli, orchestration\n\n## Goal\nAdd the new `--scope` option skeleton with compile-ready stubs and failing integration tests that verify the parsing logic.\n\n## Context\nThis is the foundation task that establishes the new scope parsing infrastructure. All subsequent implementation tasks depend on this.\n\n## Scope\n**In:**\n- Add `--scope` option to CLI with type annotation\n- Add `ScopeConfig` dataclass to hold parsed scope values\n- Update `ValidatedRunArgs.only_ids` from `set[str] | None` to `list[str] | None`\n- Add stub `parse_scope()` function that raises `NotImplementedError`\n- Add failing integration test that calls CLI with `--scope ids:T-1,T-2`\n\n**Out:**\n- Actual scope parsing implementation (T002)\n- Deprecation warnings (T005)\n\n## Changes\n- `src/cli/cli.py` — [Exists] — Add `--scope` option, `ScopeConfig` dataclass, `parse_scope()` stub\n- `src/orchestration/types.py` — [Exists] — Update `OrchestratorConfig.only_ids` and `IssueFilterConfig.only_ids` to `list[str] | None`\n- `tests/unit/cli/test_cli.py` — [Exists] — Add failing integration test for scope parsing\n\n## Wiring Map\n- `--scope` CLI arg → `parse_scope()` → `ScopeConfig` → `ValidatedRunArgs.only_ids` → `OrchestratorConfig.only_ids`\n\n## Acceptance Criteria\n- `--scope` option appears in `mala run --help`\n- Integration test exists and fails with `NotImplementedError` (red state)\n- Type definitions compile (no type errors)\n\n## Verification\n- `uvx ty check` passes (types valid)\n- `uv run pytest tests/unit/cli/test_cli.py -k scope -x` shows failing test for expected reason\n- `mala run --help | grep scope` shows the new option\n\n## Notes for Agent\n- This is a TDD \"red\" task - tests SHOULD fail\n- The stub should raise `NotImplementedError` so tests fail for the right reason\n- Keep `--only` working (don't break it yet)","notes":"Quality gate failed: Quality gate failed: No commit with bd-mala-nuoq.1 found after run baseline (stale commits from previous runs are rejected)\nLog: /home/cyou/.claude/projects/-home-cyou-mala/8730a8fa-958e-437a-81a3-70dac698cf89.jsonl","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-05T02:36:16.877702079Z","created_by":"cyou","updated_at":"2026-01-05T14:27:45.128068202Z","closed_at":"2026-01-05T14:27:45.128068202Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-nuoq.1","depends_on_id":"mala-nuoq","type":"parent-child","created_at":"2026-01-05T02:36:16.888411813Z","created_by":"cyou"}]}
{"id":"mala-nuoq.10","title":"[T010] Make --disable and --epic-override repeatable","description":"**Type**: task\n**Priority**: P2\n**Story**: Phase 1 - UX Improvements\n**Parallel**: [P] (after T001)\n**Primary Files**: src/cli/cli.py, tests/unit/cli/test_cli.py\n**Subsystems**: cli\n**Dependencies**: T001\n\n## Goal\nMake `--disable` and `--epic-override` options repeatable instead of comma-separated.\n\n## Context\nRepeatable flags are more user-friendly: `--disable coverage --disable review` vs `--disable \"coverage,review\"`.\n\n## Scope\n**In:**\n- Change `--disable-validations` to `--disable` (repeatable)\n- Keep comma-separated input working for backward compat\n- Change `--epic-override` to repeatable\n- Keep comma-separated input working for backward compat\n\n**Out:**\n- Deprecation warning for comma-separated (could add)\n\n## Changes\n- `src/cli/cli.py` — [Exists] — Change option types to `list[str]`, handle both formats\n- `tests/unit/cli/test_cli.py` — [Exists] — Add tests for repeatable options\n\n## Acceptance Criteria\n- `--disable coverage --disable review` works\n- `--disable \"coverage,review\"` still works (backward compat)\n- `--epic-override E-1 --epic-override E-2` works\n- `--epic-override \"E-1,E-2\"` still works\n\n## Verification\n- `uv run pytest tests/unit/cli/test_cli.py -k \"disable or epic_override\" -v` passes\n- Manual: both formats work with `--dry-run`\n\n## Notes for Agent\n- typer supports `list[str]` type for repeatable options\n- Parse both comma-separated and repeated to support migration","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-05T02:38:31.451005033Z","created_by":"cyou","updated_at":"2026-01-05T15:09:31.75086904Z","closed_at":"2026-01-05T15:09:31.75086904Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-nuoq.10","depends_on_id":"mala-nuoq","type":"parent-child","created_at":"2026-01-05T02:38:31.461433976Z","created_by":"cyou"},{"issue_id":"mala-nuoq.10","depends_on_id":"mala-nuoq.1","type":"blocks","created_at":"2026-01-05T02:38:58.485587459Z","created_by":"cyou"}]}
{"id":"mala-nuoq.11","title":"[T011] Final integration: verify all tests pass and acceptance criteria met","description":"**Type**: task\n**Priority**: P2\n**Story**: Phase 1 - Final Verification\n**Parallel**: No (final task)\n**Primary Files**: tests/unit/cli/test_cli.py, tests/integration/\n**Subsystems**: cli, orchestration\n**Dependencies**: T005, T006, T007, T008, T009, T010\n\n## Goal\nFinal verification that all CLI redesign features work together and all acceptance criteria are met.\n\n## Context\nThis is the final task that ensures all pieces integrate correctly.\n\n## Scope\n**In:**\n- Run full test suite\n- Verify all acceptance criteria from plan\n- Manual testing of key scenarios\n- Update any documentation if needed\n\n**Out:**\n- Phase 3 (removing deprecated options - future work)\n\n## Changes\n- No new changes expected\n- May fix integration issues discovered\n\n## Acceptance Criteria\n- `uv run pytest -m \"unit or integration\"` passes\n- `uvx ty check` passes\n- Manual verification of all acceptance criteria:\n  - [ ] `--scope ids:T-1,T-2,T-3 --order input` preserves order\n  - [ ] `--scope ids:T-1,T-2,T-3` (no --order) uses focus ordering\n  - [ ] `--scope ids:T-1,T-1,T-2` deduplicates with warning\n  - [ ] `--scope epic:E-123` filters to descendants\n  - [ ] `--scope orphans` shows only orphans\n  - [ ] `--help` shows grouped panels\n  - [ ] `--wip` emits deprecation warning\n  - [ ] `--wip --resume` emits redundancy warning\n  - [ ] `--epic E-1 --scope epic:E-2` uses E-2 with warning\n\n## Verification\n- `uv run pytest -m \"unit or integration\" -v` - all pass\n- `uvx ty check` - clean\n- `uvx ruff check .` - no lint errors\n- Manual testing checklist above\n\n## Notes for Agent\n- This task may require fixing issues found during integration\n- If fixes needed, they should be small; large fixes indicate earlier tasks incomplete","notes":"Quality gate failed: Timeout after 60 minutes\nLog: /home/cyou/.claude/projects/-home-cyou-mala-wt-nuoq/3a6d39fc-c2fb-40dd-af06-a791b0ac6ed9.jsonl","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-05T02:38:48.572961306Z","created_by":"cyou","updated_at":"2026-01-05T20:41:02.451522406Z","closed_at":"2026-01-05T20:41:02.451522406Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-nuoq.11","depends_on_id":"mala-nuoq","type":"parent-child","created_at":"2026-01-05T02:38:48.581900818Z","created_by":"cyou"},{"issue_id":"mala-nuoq.11","depends_on_id":"mala-nuoq.5","type":"blocks","created_at":"2026-01-05T02:38:58.499527395Z","created_by":"cyou"},{"issue_id":"mala-nuoq.11","depends_on_id":"mala-nuoq.6","type":"blocks","created_at":"2026-01-05T02:38:58.512570333Z","created_by":"cyou"},{"issue_id":"mala-nuoq.11","depends_on_id":"mala-nuoq.7","type":"blocks","created_at":"2026-01-05T02:38:58.525354981Z","created_by":"cyou"},{"issue_id":"mala-nuoq.11","depends_on_id":"mala-nuoq.8","type":"blocks","created_at":"2026-01-05T02:38:58.539274268Z","created_by":"cyou"},{"issue_id":"mala-nuoq.11","depends_on_id":"mala-nuoq.9","type":"blocks","created_at":"2026-01-05T02:38:58.554218394Z","created_by":"cyou"},{"issue_id":"mala-nuoq.11","depends_on_id":"mala-nuoq.10","type":"blocks","created_at":"2026-01-05T02:38:58.568234751Z","created_by":"cyou"}]}
{"id":"mala-nuoq.12","title":"[Review] 2 non-blocking findings from mala-nuoq.1","description":"## Review Findings\n\nThis issue consolidates 2 non-blocking findings from code review.\n\n**Source issue:** mala-nuoq.1\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P3] Test passes set to function expecting list\n\n**Priority:** P3\n**Reviewer:** claude\n**Location:** tests/unit/cli/test_cli.py:1564\n\nThe test `test_passes_correct_filtering_params` passes `only_ids={\"id-1\", \"id-2\"}` to `_handle_dry_run`, but the function signature was changed in this commit to `only_ids: list[str] | None`. While this works at runtime since `set(only_ids)` handles both types, it's inconsistent with the type annotation and may trigger type checker warnings. Should be `only_ids=[\"id-1\", \"id-2\"]` to match the new signature.\n\n---\n\n### Finding 2: [P2] Make --scope test enforce intended (red) contract\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** tests/unit/cli/test_cli.py:2185-2190\n\n`TestScopeOption.test_scope_option_triggers_not_implemented_error` asserts that `NotImplementedError` is raised, so it *passes* with the current stub. If the intent is a TDD “red state” that fails until `parse_scope()` is implemented and wired into `only_ids`, this won’t catch missing wiring/behavior; instead assert the expected outcome (e.g. `only_ids == ['T-1','T-2']`) so the test fails until T002, or mark it `xfail` until implementation lands.\n\n---\n\n\u003c!-- fp:18c39423ec91a11f --\u003e\n\u003c!-- fp:dbe459ffca52e1e4 --\u003e\n\u003c!-- review_finding:17d9920dddbd --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-05T14:27:45.208816005Z","created_by":"cyou","updated_at":"2026-01-05T14:45:27.414854551Z","closed_at":"2026-01-05T14:45:27.414854551Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-nuoq.1"],"dependencies":[{"issue_id":"mala-nuoq.12","depends_on_id":"mala-nuoq","type":"parent-child","created_at":"2026-01-05T14:27:45.209388732Z","created_by":"cyou"}]}
{"id":"mala-nuoq.13","title":"[Review] 2 non-blocking findings from mala-nuoq.10","description":"## Review Findings\n\nThis issue consolidates 2 non-blocking findings from code review.\n\n**Source issue:** mala-nuoq.10\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Make `--order` effective or hide it\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/cli/cli.py:971-976\n\n`run()` accepts `--order`, validates it, and passes `order_preference` into `OrchestratorConfig`, but the orchestrator never reads/stores `orch_config.order_preference` (it only captures `focus`), so `--order` likely has no effect. In addition, `--order input` is guarded by `--scope ids:...`, but those IDs are never applied to `only_ids`/filters, so the “input order” mode can’t reliably do what it claims. Suggest either (a) plumb `scope_config` into the actual filters and implement `order_preference` consumption in the orchestrator, or (b) remove/hide `--order` until implemented (to avoid a silent no-op flag).\n\n---\n\n### Finding 2: [P3] Normalize `disable_validations` in `cli_args` metadata\n\n**Priority:** P3\n**Reviewer:** codex\n**Location:** src/cli/cli.py:405-407\n\n`_build_cli_args_metadata()` stores the raw `disable: list[str] | None` under the `disable_validations` key. With mixed usage like `--disable coverage --disable \"review,e2e\"`, the metadata records `['coverage', 'review,e2e']` even though runtime behavior uses the normalized set `{'coverage','review','e2e'}`. This makes logs/metadata misleading; consider storing a normalized list (e.g., `sorted(disable_set)` or `_normalize_repeatable_option(disable)`) instead of the raw CLI list.\n\n---\n\n\u003c!-- fp:1ea77ed726f01009 --\u003e\n\u003c!-- fp:c2e7e23eed173091 --\u003e\n\u003c!-- review_finding:3dd827b2a1bf --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-05T15:09:31.833347887Z","created_by":"cyou","updated_at":"2026-01-05T15:14:45.290109774Z","closed_at":"2026-01-05T15:14:45.290109774Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-nuoq.10"],"dependencies":[{"issue_id":"mala-nuoq.13","depends_on_id":"mala-nuoq","type":"parent-child","created_at":"2026-01-05T15:09:31.833800695Z","created_by":"cyou"}]}
{"id":"mala-nuoq.14","title":"[Review] 2 non-blocking findings from mala-nuoq.13","description":"## Review Findings\n\nThis issue consolidates 2 non-blocking findings from code review.\n\n**Source issue:** mala-nuoq.13\n**Highest priority:** P3\n\n---\n\n### Finding 1: [P3] Deduplicate normalized disable_validations metadata\n\n**Priority:** P3\n**Reviewer:** codex\n**Location:** src/cli/cli.py:405-409\n\n`_normalize_repeatable_option()` can produce duplicates (e.g. `--disable \"review,review\"` or repeated flags), and the new code preserves them after sorting. That makes `cli_args[\"disable_validations\"]` noisy and potentially misleading vs runtime behavior (which uses `disable_set`). Consider `sorted(set(_normalize_repeatable_option(disable)))` (or otherwise dedupe) before storing.\n\n```py\nnormalized_disable = sorted(_normalize_repeatable_option(disable))\n\"disable_validations\": normalized_disable if normalized_disable else None,\n```\n\n---\n\n### Finding 2: [P3] Normalize epic_override in metadata for consistency\n\n**Priority:** P3\n**Reviewer:** gemini\n**Location:** src/cli/cli.py:425\n\nThe `epic_override` metadata should be normalized using `_normalize_repeatable_option` and sorted, consistent with the fix applied to `disable_validations`. Currently, it remains a raw list which may contain mixed formats (e.g., `[\"E-1,E-2\"]`) instead of the flattened, sorted list of IDs actually used by the orchestrator.\n\n---\n\n\u003c!-- fp:827e368621d03f9b --\u003e\n\u003c!-- fp:8083573376aad696 --\u003e\n\u003c!-- review_finding:d151f5b50fc5 --\u003e","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-05T15:14:45.370712469Z","created_by":"cyou","updated_at":"2026-01-05T15:18:31.621397174Z","closed_at":"2026-01-05T15:18:31.621397174Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-nuoq.13"],"dependencies":[{"issue_id":"mala-nuoq.14","depends_on_id":"mala-nuoq","type":"parent-child","created_at":"2026-01-05T15:14:45.371180907Z","created_by":"cyou"}]}
{"id":"mala-nuoq.15","title":"[Review] 1 non-blocking finding from mala-nuoq.4","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-nuoq.4\n**Highest priority:** P3\n\n---\n\n### Finding 1: [P3] Rename test_run_wip_flag_passed_to_orchestrator\n\n**Priority:** P3\n**Reviewer:** gemini\n**Location:** tests/unit/cli/test_cli.py:904\n\nThe test `test_run_wip_flag_passed_to_orchestrator` has been updated to call `cli.run(resume=True)` but its name still refers to the `wip` flag. Since other tests (like `test_run_focus_composes_with_resume`) were renamed to reflect the new terminology, this one should also be renamed for consistency.\n\n---\n\n\u003c!-- fp:276815ed87d22c5f --\u003e\n\u003c!-- review_finding:704440fae3ff --\u003e","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-05T15:17:30.442090286Z","created_by":"cyou","updated_at":"2026-01-05T15:18:35.468328901Z","closed_at":"2026-01-05T15:18:35.468328901Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-nuoq.4"],"dependencies":[{"issue_id":"mala-nuoq.15","depends_on_id":"mala-nuoq","type":"parent-child","created_at":"2026-01-05T15:17:30.442497084Z","created_by":"cyou"}]}
{"id":"mala-nuoq.16","title":"[Review] 4 non-blocking findings from mala-nuoq.7","description":"## Review Findings\n\nThis issue consolidates 4 non-blocking findings from code review.\n\n**Source issue:** mala-nuoq.7\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Fix `--review-*` precedence when value is empty string\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/cli/cli.py:969-971\n\nThe coalescing uses Python `or`, which treats `\"\"` as falsey. If a user explicitly passes an empty string (e.g. to clear a configured value), the code will fall back to the old flag (or effectively “no override”), and `--review-*` may *not* take precedence when both are specified.\n\n```py\ncerberus_spawn_args=review_spawn_args or cerberus_spawn_args\n```\n\nConcrete cases:\n- `mala run --review-spawn-args \"\"` won’t clear/override a non-empty config value.\n- `mala run --review-spawn-args \"\" --cerberus-spawn-args \"--foo\"` will incorrectly pick `--foo`.\n\nUse an explicit `is not None` check instead (e.g., `review_spawn_args if review_spawn_args is not None else cerberus_spawn_args`) for `spawn_args`, `wait_args`, and `env`.\n\n---\n\n### Finding 2: [P2] Use explicit None check for coalescing CLI options\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/cli/cli.py:961-963\n\nThe current coalesce logic `review_spawn_args or cerberus_spawn_args` will incorrectly fall back to the deprecated `cerberus` option if the new `review` option is provided as an empty string (e.g., `--review-spawn-args \"\"`). This contradicts the commit message stating new options take precedence. Use `review_spawn_args if review_spawn_args is not None else cerberus_spawn_args` instead.\n\n---\n\n### Finding 3: [P2] Update metadata keys to hide implementation details\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/cli/cli.py:435-437\n\nThe `_build_cli_args_metadata` function still uses `cerberus_` prefixes for the keys in the returned dictionary. Since these keys are logged in run metadata and the stated goal is to hide 'cerberus' as an implementation detail, these keys should be renamed to match the new CLI options (e.g., `review_spawn_args`).\n\n---\n\n### Finding 4: [P2] Missing test updates\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/cli/cli.py:803-850\n\nThe author context identifies `tests/unit/cli/test_cli.py` as a primary file for this task, but the commit does not include any changes to tests. Renaming CLI options and adding aliases should be accompanied by unit test updates to verify both the new options and the backward compatibility of the hidden aliases.\n\n---\n\n\u003c!-- fp:ead0305368458681 --\u003e\n\u003c!-- fp:5bf06c7d4cbc18ea --\u003e\n\u003c!-- fp:f4e354e33eb607b2 --\u003e\n\u003c!-- fp:d8387813259e3a82 --\u003e\n\u003c!-- review_finding:bcd05973f641 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-05T17:41:47.423420703Z","created_by":"cyou","updated_at":"2026-01-05T17:47:33.467278053Z","closed_at":"2026-01-05T17:47:33.467278053Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-nuoq.7"],"dependencies":[{"issue_id":"mala-nuoq.16","depends_on_id":"mala-nuoq","type":"parent-child","created_at":"2026-01-05T17:41:47.424053199Z","created_by":"cyou"}]}
{"id":"mala-nuoq.17","title":"[Review] 4 non-blocking findings from mala-nuoq.8","description":"## Review Findings\n\nThis issue consolidates 4 non-blocking findings from code review.\n\n**Source issue:** mala-nuoq.8\n**Highest priority:** P3\n\n---\n\n### Finding 1: [P3] Remove unused nonlocal declaration in _handle_dry_run\n\n**Priority:** P3\n**Reviewer:** claude\n**Location:** src/cli/cli.py:510\n\nLine 510 declares `nonlocal issue_count` inside `_dry_run()`, but the inner function returns `len(issues)` and the outer function captures it via `issue_count = asyncio.run(_dry_run())`. The nonlocal declaration is never used since the value is returned rather than assigned via side effect. This is harmless dead code but could be removed for clarity.\n\n---\n\n### Finding 2: [P3] Remove unused `nonlocal`/redundant state in dry-run handler\n\n**Priority:** P3\n**Reviewer:** codex\n**Location:** src/cli/cli.py:507-511\n\nIn `_handle_dry_run`, `issue_count` is computed via the coroutine return value, but `_dry_run` still declares `nonlocal issue_count` without ever assigning to it. This is confusing and easy to misread as shared-state mutation.\n\n```py\nasync def _dry_run() -\u003e int:\n    nonlocal issue_count\n    beads = _lazy(\"BeadsClient\")(repo_path)\n```\n\nSuggested fix: drop `issue_count = 0` and the `nonlocal issue_count` line; rely purely on `issue_count = asyncio.run(_dry_run())`.\n\n---\n\n### Finding 3: [P3] Update test/docstring claiming `_handle_dry_run` always exits 0\n\n**Priority:** P3\n**Reviewer:** codex\n**Location:** tests/unit/cli/test_cli.py:1669-1673\n\n`TestHandleDryRun.test_raises_typer_exit_zero` documents that `_handle_dry_run` “always raises typer.Exit(0)”, but the function can now exit 1 when `fail_on_empty=True` and there are no issues. The assertion in this test is still valid for the default path, but the wording is now misleading.\n\n```py\n\"\"\"_handle_dry_run always raises typer.Exit(0).\"\"\"\n```\n\nSuggested fix: rename/update the docstring (e.g., “defaults to Exit(0) when fail_on_empty is False”).\n\n---\n\n### Finding 4: [P3] Redundant nonlocal declaration\n\n**Priority:** P3\n**Reviewer:** gemini\n**Location:** src/cli/cli.py:514\n\nThe `nonlocal issue_count` declaration inside `_dry_run` is redundant because `issue_count` is not assigned to within the coroutine. The outer `issue_count` variable is updated via the return value of `asyncio.run(_dry_run())`.\n\n---\n\n\u003c!-- fp:138a49a69565db4d --\u003e\n\u003c!-- fp:a0e6c37076717b62 --\u003e\n\u003c!-- fp:3def258c1ebdea99 --\u003e\n\u003c!-- fp:2ed02a3da24c57dd --\u003e\n\u003c!-- review_finding:901b265a90f3 --\u003e","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-05T17:46:46.059958877Z","created_by":"cyou","updated_at":"2026-01-05T17:52:44.517473427Z","closed_at":"2026-01-05T17:52:44.517473427Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-nuoq.8"],"dependencies":[{"issue_id":"mala-nuoq.17","depends_on_id":"mala-nuoq","type":"parent-child","created_at":"2026-01-05T17:46:46.060409094Z","created_by":"cyou"}]}
{"id":"mala-nuoq.18","title":"[Review] 2 non-blocking findings from mala-nuoq.9","description":"## Review Findings\n\nThis issue consolidates 2 non-blocking findings from code review.\n\n**Source issue:** mala-nuoq.9\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Avoid O(n*m) filtering by converting only_ids to a set internally\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/infra/issue_manager.py:104-111\n\n`IssueManager.apply_filters()` now does `str(i[\"id\"]) in only_ids` where `only_ids` is a `list[str]`, making filtering O(len(issues) * len(only_ids)). This is a regression from the prior `set[str]` behavior and can become noticeable for large issue lists. Convert once inside the function (keeping the original list for ordering elsewhere if needed), e.g. `only_id_set = set(only_ids) if only_ids is not None else None` and check membership against `only_id_set`.\n\n---\n\n### Finding 2: [P3] Deduplicate --only IDs (or warn) to match --scope ids: behavior\n\n**Priority:** P3\n**Reviewer:** codex\n**Location:** src/cli/cli.py:644-652\n\n`_validate_run_args()` now parses `--only` into a list without deduplication, so `--only a,a,b` propagates duplicates into config/metadata/display (even if downstream filtering effectively treats it as a set). `--scope ids:` already deduplicates with a warning, so this is an inconsistency introduced by the set→list migration. Consider deduping while preserving order (optionally warning) when parsing `--only`.\n\n---\n\n\u003c!-- fp:c9c5d79a64d8d860 --\u003e\n\u003c!-- fp:bd9a30b3d90a9cf2 --\u003e\n\u003c!-- review_finding:e9ae4d18b8d5 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-05T18:06:29.555264077Z","created_by":"cyou","updated_at":"2026-01-05T18:09:13.670573414Z","closed_at":"2026-01-05T18:09:13.670573414Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-nuoq.9"],"dependencies":[{"issue_id":"mala-nuoq.18","depends_on_id":"mala-nuoq","type":"parent-child","created_at":"2026-01-05T18:06:29.555801715Z","created_by":"cyou"}]}
{"id":"mala-nuoq.19","title":"[Review] 1 non-blocking finding from mala-nuoq.5","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-nuoq.5\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Make --no-focus deprecation warning aware of --order precedence\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/cli/cli.py:454-458\n\n`_emit_deprecation_warnings()` always emits `--no-focus → --order priority` when `focus=False`, even if the user also supplied `--order` (which later overrides `focus` in `run()`). This produces misleading guidance in cases like `mala run --no-focus --order focus` (warning recommends `--order priority` despite `--order focus` being present and taking precedence).\n\nFix options:\n- In `_emit_deprecation_warnings`, use the existing `order` parameter to emit a conflict/redundancy warning (similar to `--wip/--resume`) and avoid recommending `--order priority` when `order` is present.\n- Or restructure `run()` to validate/normalize `order` first (without losing the ability to detect deprecated usage) and then emit warnings based on the effective/precedence-resolved values.\n\nAdd a unit test covering `focus=False` with `order=\"focus\"` (and optionally `order=\"priority\"` as redundant) to lock in the intended behavior.\n\n---\n\n\u003c!-- fp:76fb98a5df2da4d3 --\u003e\n\u003c!-- review_finding:48b9ab368ff8 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-05T18:22:52.282367892Z","created_by":"cyou","updated_at":"2026-01-05T18:26:47.495650559Z","closed_at":"2026-01-05T18:26:47.495650559Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-nuoq.5"],"dependencies":[{"issue_id":"mala-nuoq.19","depends_on_id":"mala-nuoq","type":"parent-child","created_at":"2026-01-05T18:22:52.28280626Z","created_by":"cyou"}]}
{"id":"mala-nuoq.2","title":"[T002] Implement scope parsing logic","description":"**Type**: task\n**Priority**: P2\n**Story**: Phase 1 - Core Implementation\n**Parallel**: [P] (after T001)\n**Primary Files**: src/cli/cli.py, tests/unit/cli/test_cli.py\n**Subsystems**: cli\n**Dependencies**: T001\n\n## Goal\nImplement the `parse_scope()` function to parse all scope values (`all`, `epic:\u003cid\u003e`, `orphans`, `ids:\u003cid,...\u003e`) and make the integration tests pass.\n\n## Context\nWith the skeleton in place from T001, this task implements the actual parsing logic.\n\n## Scope\n**In:**\n- Implement `parse_scope()` for all scope types\n- Parse `--scope all` (default)\n- Parse `--scope epic:\u003cid\u003e` to epic filter\n- Parse `--scope orphans` to orphans-only mode\n- Parse `--scope ids:\u003cid,...\u003e` to ordered list\n- Deduplicate IDs with warning for `ids:` scope\n- Validate scope format (reject `--scope invalid:xyz`)\n\n**Out:**\n- `--order` option (T003)\n- Deprecation warnings (T005)\n\n## Changes\n- `src/cli/cli.py` — [Exists] — Implement `parse_scope()` with all scope types, error handling, duplicate detection\n- `tests/unit/cli/test_cli.py` — [Exists] — Add unit tests for each scope type and error cases\n\n## Acceptance Criteria\n- `--scope all` processes all issues (default behavior)\n- `--scope epic:E-123` filters to epic descendants\n- `--scope orphans` enables orphan-only mode\n- `--scope ids:T-1,T-2,T-3` produces ordered list `[\"T-1\", \"T-2\", \"T-3\"]`\n- `--scope ids:T-1,T-1,T-2` deduplicates to `[\"T-1\", \"T-2\"]` with warning\n- `--scope invalid:xyz` raises clear error\n\n## Verification\n- `uv run pytest tests/unit/cli/test_cli.py -k scope -v` - all scope tests pass\n- `uvx ty check` passes\n- Manual test: `mala run --scope ids:T-1,T-2 --dry-run` shows correct parsing\n\n## Notes for Agent\n- Use `list` not `set` to preserve order for ids scope\n- Emit warning to stderr for duplicates using existing `log()` function\n- Error messages should suggest valid scope formats","notes":"Quality gate failed: Aborted due to unrecoverable error\nLog: /home/cyou/.claude/projects/-home-cyou-mala-wt-nuoq/d0e6c109-c117-4c60-a883-521b9d17c54c.jsonl","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-05T02:36:33.265013143Z","created_by":"cyou","updated_at":"2026-01-05T17:39:31.097762825Z","closed_at":"2026-01-05T17:39:31.097762825Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-nuoq.2","depends_on_id":"mala-nuoq","type":"parent-child","created_at":"2026-01-05T02:36:33.274334065Z","created_by":"cyou"},{"issue_id":"mala-nuoq.2","depends_on_id":"mala-nuoq.1","type":"blocks","created_at":"2026-01-05T02:38:58.342978553Z","created_by":"cyou"},{"issue_id":"mala-nuoq.2","depends_on_id":"mala-nuoq.10","type":"blocks","created_at":"2026-01-05T14:44:05.466727148Z","created_by":"cyou"}]}
{"id":"mala-nuoq.3","title":"[T003] Implement --order option and input ordering","description":"**Type**: task\n**Priority**: P2\n**Story**: Phase 1 - Core Implementation\n**Parallel**: [P] (after T001, parallel with T002)\n**Primary Files**: src/cli/cli.py, tests/unit/cli/test_cli.py\n**Subsystems**: cli\n**Dependencies**: T001\n\n## Goal\nAdd the `--order` option with `focus` (default), `priority`, and `input` values. The `input` order preserves user-specified ID order for `--scope ids:`.\n\n## Context\nThis fixes the bug where `--only` loses input order by converting to a `set`. The `--order input` option only works with `--scope ids:`.\n\n## Scope\n**In:**\n- Add `--order` option with values: `focus`, `priority`, `input`\n- Default to `focus` ordering\n- Validate `--order input` only allowed with `--scope ids:`\n- Propagate order preference to orchestrator config\n\n**Out:**\n- Actual ordering implementation in orchestrator (already exists, just needs config propagation)\n- `--resume` option (T004)\n\n## Changes\n- `src/cli/cli.py` — [Exists] — Add `--order` option, validation for `input` + `ids` scope\n- `tests/unit/cli/test_cli.py` — [Exists] — Add unit tests for order validation\n\n## Acceptance Criteria\n- `--order focus` (default) uses epic-grouped ordering\n- `--order priority` uses global priority ordering\n- `--order input` with `--scope ids:` preserves input order\n- `--order input` without `--scope ids:` raises error\n\n## Verification\n- `uv run pytest tests/unit/cli/test_cli.py -k order -v` - all order tests pass\n- `uvx ty check` passes\n- Manual: `mala run --scope ids:T-5,T-3,T-1 --order input --dry-run` shows T-5, T-3, T-1 order\n\n## Notes for Agent\n- The ordering logic already exists in the orchestrator, this just needs to set the right config values\n- `--order input` should be mutually exclusive with non-ids scopes","notes":"Quality gate failed: Aborted due to unrecoverable error\nLog: /home/cyou/.claude/projects/-home-cyou-mala-wt-nuoq/a5768e3b-b15c-4cb8-945c-bd6d676d4458.jsonl","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-05T02:36:51.48934436Z","created_by":"cyou","updated_at":"2026-01-05T17:45:03.439741883Z","closed_at":"2026-01-05T17:45:03.439741883Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-nuoq.3","depends_on_id":"mala-nuoq","type":"parent-child","created_at":"2026-01-05T02:36:51.489859837Z","created_by":"cyou"},{"issue_id":"mala-nuoq.3","depends_on_id":"mala-nuoq.1","type":"blocks","created_at":"2026-01-05T02:38:58.362164915Z","created_by":"cyou"},{"issue_id":"mala-nuoq.3","depends_on_id":"mala-nuoq.8","type":"blocks","created_at":"2026-01-05T14:43:17.225661246Z","created_by":"cyou"}]}
{"id":"mala-nuoq.4","title":"[T004] Implement --resume option (replaces --wip)","description":"**Type**: task\n**Priority**: P2\n**Story**: Phase 1 - Core Implementation\n**Parallel**: [P] (after T001, parallel with T002/T003)\n**Primary Files**: src/cli/cli.py, tests/unit/cli/test_cli.py\n**Subsystems**: cli\n**Dependencies**: T001\n\n## Goal\nAdd the `--resume` / `-r` option as the new name for `--wip` functionality.\n\n## Context\nThe `--wip` flag name is confusing. `--resume` better describes the intent: resume in-progress work.\n\n## Scope\n**In:**\n- Add `--resume` / `-r` option\n- Map to existing `wip` config field\n- Keep `--wip` working (hidden alias, deprecated in T005)\n\n**Out:**\n- Deprecation warning for `--wip` (T005)\n\n## Changes\n- `src/cli/cli.py` — [Exists] — Add `--resume` / `-r` option, map to wip config\n- `tests/unit/cli/test_cli.py` — [Exists] — Add unit tests for `--resume`\n\n## Acceptance Criteria\n- `--resume` / `-r` enables in-progress issue prioritization\n- `--wip` still works (no warnings yet, that's T005)\n- `--resume` and `--wip` together work without error\n\n## Verification\n- `uv run pytest tests/unit/cli/test_cli.py -k resume -v` - tests pass\n- `uvx ty check` passes\n- Manual: `mala run --resume --dry-run` shows in-progress issues first\n\n## Notes for Agent\n- The underlying wip logic already exists, just need to add the new option name\n- Don't add deprecation warning yet (that's T005)","notes":"Quality gate failed: Quality gate failed: No commit with bd-mala-nuoq.4 found after run baseline (stale commits from previous runs are rejected)\nLog: /home/cyou/.claude/projects/-home-cyou-mala/b9682511-a88d-4d55-b17e-3cb4cd431bb8.jsonl","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-05T02:37:04.080827998Z","created_by":"cyou","updated_at":"2026-01-05T15:17:30.361683641Z","closed_at":"2026-01-05T15:17:30.361683641Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-nuoq.4","depends_on_id":"mala-nuoq","type":"parent-child","created_at":"2026-01-05T02:37:04.102369122Z","created_by":"cyou"},{"issue_id":"mala-nuoq.4","depends_on_id":"mala-nuoq.1","type":"blocks","created_at":"2026-01-05T02:38:58.375254302Z","created_by":"cyou"}]}
{"id":"mala-nuoq.5","title":"[T005] Add deprecation warnings for old flags","description":"**Type**: task\n**Priority**: P2\n**Story**: Phase 2 - Deprecation\n**Parallel**: No (requires T002, T003, T004)\n**Primary Files**: src/cli/cli.py, tests/unit/cli/test_cli.py\n**Subsystems**: cli\n**Dependencies**: T002, T003, T004\n\n## Goal\nAdd deprecation warnings when users use old flags, suggesting the new equivalents.\n\n## Context\nThis is Phase 2 of the migration. Old flags continue to work but emit warnings.\n\n## Scope\n**In:**\n- Warn on `--wip` → suggest `--resume`\n- Warn on `--focus/--no-focus` → suggest `--order`\n- Warn on `--epic` → suggest `--scope epic:\u003cid\u003e`\n- Warn on `--only` → suggest `--scope ids:\u003cids\u003e`\n- Warn on `--orphans-only` → suggest `--scope orphans`\n- Warn on redundant `--wip --resume` combination\n- Warn on conflict `--epic X --scope epic:Y` (new flag wins)\n\n**Out:**\n- Removing old flags (Phase 3, future work)\n\n## Changes\n- `src/cli/cli.py` — [Exists] — Add `emit_deprecation_warning()` helper, call from validation\n- `tests/unit/cli/test_cli.py` — [Exists] — Add unit tests for each deprecation warning\n\n## Acceptance Criteria\n- `--wip` emits warning mentioning `--resume`\n- `--epic E-123` emits warning mentioning `--scope epic:E-123`\n- `--only T-1,T-2` emits warning mentioning `--scope ids:T-1,T-2`\n- `--orphans-only` emits warning mentioning `--scope orphans`\n- `--focus/--no-focus` emits warning mentioning `--order`\n- `--wip --resume` emits redundancy warning\n- `--epic E-1 --scope epic:E-2` uses E-2, emits conflict warning\n- All warnings go to stderr, not stdout\n\n## Verification\n- `uv run pytest tests/unit/cli/test_cli.py -k deprecat -v` - all warning tests pass\n- Manual: `mala run --wip --dry-run 2\u003e\u00261 | grep -i deprecat` shows warning\n- `uvx ty check` passes\n\n## Notes for Agent\n- Use `warnings.warn()` with `DeprecationWarning` category, or use existing `log()` to stderr\n- Warnings should include exact replacement command\n- Old flags must continue to work (functionality preserved)","notes":"Quality gate failed: Quality gate failed: No commit with bd-mala-nuoq.5 found after run baseline (stale commits from previous runs are rejected)\nLog: /home/cyou/.claude/projects/-home-cyou-mala/a16ba49f-d34e-4b51-aebb-ace1de6a05e5.jsonl","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-05T02:37:18.726722217Z","created_by":"cyou","updated_at":"2026-01-05T18:22:52.200897793Z","closed_at":"2026-01-05T18:22:52.200897793Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-nuoq.5","depends_on_id":"mala-nuoq","type":"parent-child","created_at":"2026-01-05T02:37:18.736531807Z","created_by":"cyou"},{"issue_id":"mala-nuoq.5","depends_on_id":"mala-nuoq.2","type":"blocks","created_at":"2026-01-05T02:38:58.38824124Z","created_by":"cyou"},{"issue_id":"mala-nuoq.5","depends_on_id":"mala-nuoq.3","type":"blocks","created_at":"2026-01-05T02:38:58.401179708Z","created_by":"cyou"},{"issue_id":"mala-nuoq.5","depends_on_id":"mala-nuoq.4","type":"blocks","created_at":"2026-01-05T02:38:58.416357284Z","created_by":"cyou"}]}
{"id":"mala-nuoq.6","title":"[T006] Add rich_help_panel groupings to --help","description":"**Type**: task\n**Priority**: P2\n**Story**: Phase 1 - UX Improvements\n**Parallel**: [P] (after T001, parallel with T002-T004)\n**Primary Files**: src/cli/cli.py\n**Subsystems**: cli\n**Dependencies**: T001\n\n## Goal\nOrganize CLI options into logical groups using typer's `rich_help_panel` feature.\n\n## Context\nCurrently all 20 options are dumped together in `--help`. Grouping improves discoverability.\n\n## Scope\n**In:**\n- Group options into panels:\n  - \"Scope \u0026 Ordering\" (`--scope`, `--order`, `--resume`)\n  - \"Execution Limits\" (`--max-agents`, `--timeout`, `--max-issues`)\n  - \"Quality Gates\" (`--disable`, `--coverage-threshold`, `--max-gate-retries`, etc.)\n  - \"Epic Verification\" (`--epic-override`, `--max-epic-retries`)\n  - \"Review Backend\" (`--review-timeout`, `--review-spawn-args`, etc.)\n  - \"Debugging\" (`--dry-run`, `--verbose`, `--fail-on-empty`, etc.)\n\n**Out:**\n- New options (handled by other tasks)\n- Deprecation warnings (T005)\n\n## Changes\n- `src/cli/cli.py` — [Exists] — Add `rich_help_panel` parameter to all option definitions\n\n## Acceptance Criteria\n- `mala run --help` shows grouped options with panel headers\n- \"Scope \u0026 Ordering\" panel exists with `--scope`, `--order`, `--resume`\n- \"Execution Limits\" panel exists with `--max-agents`, `--timeout`, `--max-issues`\n\n## Verification\n- `mala run --help` shows panel headers in output\n- Visual inspection confirms logical groupings\n\n## Notes for Agent\n- typer \u003e= 0.9.0 is already required (confirmed in pyproject.toml)\n- `rich_help_panel` is a string parameter on `typer.Option()`\n- Put deprecated/hidden options in a separate panel or hide them","notes":"Quality gate failed: Quality gate failed: No commit with bd-mala-nuoq.6 found after run baseline (stale commits from previous runs are rejected)\nLog: /home/cyou/.claude/projects/-home-cyou-mala/6177215b-02db-4c57-96fc-a7572790ebab.jsonl","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-05T02:37:32.908080778Z","created_by":"cyou","updated_at":"2026-01-05T14:35:46.769196041Z","closed_at":"2026-01-05T14:35:46.769196041Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-nuoq.6","depends_on_id":"mala-nuoq","type":"parent-child","created_at":"2026-01-05T02:37:32.927351486Z","created_by":"cyou"},{"issue_id":"mala-nuoq.6","depends_on_id":"mala-nuoq.1","type":"blocks","created_at":"2026-01-05T02:38:58.430030041Z","created_by":"cyou"}]}
{"id":"mala-nuoq.7","title":"[T007] Rename cerberus options to review","description":"**Type**: task\n**Priority**: P2\n**Story**: Phase 1 - Naming\n**Parallel**: [P] (no file overlap with T002-T006)\n**Primary Files**: src/cli/cli.py, tests/unit/cli/test_cli.py\n**Subsystems**: cli\n**Dependencies**: T001\n\n## Goal\nRename `--cerberus-*` options to `--review-*` to hide implementation details.\n\n## Context\nUsers shouldn't need to know \"cerberus\" is the internal review system name.\n\n## Scope\n**In:**\n- Rename `--cerberus-spawn-args` → `--review-spawn-args`\n- Rename `--cerberus-wait-args` → `--review-wait-args`\n- Rename `--cerberus-env` → `--review-env`\n- Keep old names as hidden aliases\n\n**Out:**\n- Deprecation warnings for old names (could add in T005 or separate)\n\n## Changes\n- `src/cli/cli.py` — [Exists] — Rename options, add hidden aliases for old names\n\n## Acceptance Criteria\n- `--review-spawn-args`, `--review-wait-args`, `--review-env` work\n- `--cerberus-*` still work (hidden aliases)\n- `mala run --help` shows `--review-*` not `--cerberus-*`\n\n## Verification\n- `mala run --help | grep review` shows new names\n- `mala run --help | grep cerberus` shows nothing (hidden)\n- Manual: `mala run --review-spawn-args \"--foo\" --dry-run` works\n\n## Notes for Agent\n- Use `hidden=True` on typer.Option for the old cerberus names\n- The underlying config field names can stay the same (just CLI option names change)","notes":"Quality gate failed: Aborted due to unrecoverable error\nLog: /home/cyou/.claude/projects/-home-cyou-mala-wt-nuoq/71a9877c-ede3-417d-9874-87caa9b4202f.jsonl","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-05T02:37:45.83418484Z","created_by":"cyou","updated_at":"2026-01-05T17:41:47.341063664Z","closed_at":"2026-01-05T17:41:47.341063664Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-nuoq.7","depends_on_id":"mala-nuoq","type":"parent-child","created_at":"2026-01-05T02:37:45.83478259Z","created_by":"cyou"},{"issue_id":"mala-nuoq.7","depends_on_id":"mala-nuoq.1","type":"blocks","created_at":"2026-01-05T02:38:58.443615998Z","created_by":"cyou"},{"issue_id":"mala-nuoq.7","depends_on_id":"mala-nuoq.10","type":"blocks","created_at":"2026-01-05T14:44:11.308366031Z","created_by":"cyou"}]}
{"id":"mala-nuoq.8","title":"[T008] Add short flags and --fail-on-empty","description":"**Type**: task\n**Priority**: P2\n**Story**: Phase 1 - UX Improvements\n**Parallel**: [P] (after T001, parallel with others)\n**Primary Files**: src/cli/cli.py, tests/unit/cli/test_cli.py\n**Subsystems**: cli\n**Dependencies**: T001\n\n## Goal\nAdd missing short flags (`-d`, `-s`) and the new `--fail-on-empty` option.\n\n## Context\nCommon options like `--dry-run` and `--scope` should have short forms.\n\n## Scope\n**In:**\n- Add `-d` for `--dry-run`\n- Add `-s` for `--scope`\n- Add `--fail-on-empty` option (exit non-zero if no issues to process)\n\n**Out:**\n- Other short flags (already exist: `-n`, `-t`, `-i`, `-v`, `-e`, `-r`)\n\n## Changes\n- `src/cli/cli.py` — [Exists] — Add short flags, add `--fail-on-empty` option\n- `tests/unit/cli/test_cli.py` — [Exists] — Add tests for `--fail-on-empty`\n\n## Acceptance Criteria\n- `-d` works as alias for `--dry-run`\n- `-s` works as alias for `--scope`\n- `--fail-on-empty` causes exit code 1 when no issues found (normally exit 0)\n\n## Verification\n- `mala run -d` works like `mala run --dry-run`\n- `mala run -s ids:T-1` works like `mala run --scope ids:T-1`\n- Manual: `mala run --fail-on-empty --scope ids:NONEXISTENT; echo $?` returns non-zero\n\n## Notes for Agent\n- Short flags are added via typer.Option parameter\n- `--fail-on-empty` needs to propagate to orchestrator and affect exit code","notes":"Quality gate failed: Aborted due to unrecoverable error\nLog: /home/cyou/.claude/projects/-home-cyou-mala-wt-nuoq/b68f8983-2e94-4a8a-9ce9-206d47629904.jsonl","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-05T02:37:58.05063509Z","created_by":"cyou","updated_at":"2026-01-05T17:46:45.981193718Z","closed_at":"2026-01-05T17:46:45.981193718Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-nuoq.8","depends_on_id":"mala-nuoq","type":"parent-child","created_at":"2026-01-05T02:37:58.059389177Z","created_by":"cyou"},{"issue_id":"mala-nuoq.8","depends_on_id":"mala-nuoq.1","type":"blocks","created_at":"2026-01-05T02:38:58.458179724Z","created_by":"cyou"},{"issue_id":"mala-nuoq.8","depends_on_id":"mala-nuoq.10","type":"blocks","created_at":"2026-01-05T14:44:01.885932524Z","created_by":"cyou"}]}
{"id":"mala-nuoq.9","title":"[T009] Update only_ids type across codebase (set → list)","description":"**Type**: task\n**Priority**: P2\n**Story**: Phase 1 - Type Migration\n**Parallel**: No (touches many files, do after T001-T002 stable)\n**Primary Files**: src/orchestration/types.py, src/pipeline/issue_execution_coordinator.py, src/core/protocols.py, src/orchestration/run_config.py, src/infra/issue_manager.py, src/orchestration/orchestrator.py\n**Subsystems**: orchestration, pipeline, core, infra\n**Dependencies**: T002\n\n## Goal\nUpdate all `only_ids` type definitions from `set[str]` to `list[str]` to support order preservation.\n\n## Context\nThe scope parsing now produces ordered lists. All downstream consumers need to accept `list[str]`.\n\n## Scope\n**In:**\n- Update type definitions in:\n  - `src/orchestration/types.py` - `OrchestratorConfig.only_ids`, `IssueFilterConfig.only_ids`\n  - `src/pipeline/issue_execution_coordinator.py` - `IssueExecutionCoordinatorConfig.only_ids`\n  - `src/core/protocols.py` - protocol definitions\n  - `src/orchestration/run_config.py` - function signatures\n- Update usage sites that need `set()` conversion for membership checks:\n  - `src/infra/issue_manager.py` - `find_missing_ids()` set subtraction\n  - `src/orchestration/orchestrator.py` - membership checks\n  - `src/infra/clients/beads_client.py` - any set operations\n  - `src/orchestration/orchestration_wiring.py` - wiring between layers\n\n**Out:**\n- Changing ordering logic (just type changes)\n\n## Changes\n- `src/orchestration/types.py` — [Exists] — Change `only_ids: set[str] | None` to `list[str] | None`\n- `src/pipeline/issue_execution_coordinator.py` — [Exists] — Update type\n- `src/core/protocols.py` — [Exists] — Update protocol type\n- `src/orchestration/run_config.py` — [Exists] — Update function signatures\n- `src/infra/issue_manager.py` — [Exists] — Add `set()` conversion in `find_missing_ids()`\n- `src/orchestration/orchestrator.py` — [Exists] — Update any set operations\n- `src/infra/clients/beads_client.py` — [Exists] — Update any set operations\n- `src/orchestration/orchestration_wiring.py` — [Exists] — Update wiring\n- `src/infra/io/console_sink.py` — [Exists] — Update display logic if needed\n- `src/infra/io/log_output/run_metadata.py` — [Exists] — Update serialization\n\n## Wiring Map\n- `ValidatedRunArgs.only_ids` (list) → `OrchestratorConfig.only_ids` (list) → `IssueFilterConfig.only_ids` (list) → `IssueManager.find_missing_ids()` (converts to set internally)\n\n## Acceptance Criteria\n- `uvx ty check` passes with no type errors\n- All existing tests pass\n- `find_missing_ids()` still works (uses set internally)\n- Run metadata serializes `only_ids` as list\n\n## Verification\n- `uvx ty check` - no errors\n- `uv run pytest -m \"unit or integration\" -x` - all tests pass\n- Review git diff to confirm all `set[str]` → `list[str]` changes\n\n## Notes for Agent\n- This is a mechanical type change across many files\n- Functions that need set operations should convert: `set(only_ids) if only_ids else set()`\n- Keep membership checks efficient with set conversion","notes":"Quality gate failed: Quality gate failed: No commit with bd-mala-nuoq.9 found after run baseline (stale commits from previous runs are rejected)\nLog: /home/cyou/.claude/projects/-home-cyou-mala/4d1db22e-91b6-49b3-864f-ec9660251d1e.jsonl","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-05T02:38:18.920165037Z","created_by":"cyou","updated_at":"2026-01-05T18:06:29.475299528Z","closed_at":"2026-01-05T18:06:29.475299528Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-nuoq.9","depends_on_id":"mala-nuoq","type":"parent-child","created_at":"2026-01-05T02:38:18.928775522Z","created_by":"cyou"},{"issue_id":"mala-nuoq.9","depends_on_id":"mala-nuoq.2","type":"blocks","created_at":"2026-01-05T02:38:58.471885892Z","created_by":"cyou"}]}
{"id":"mala-ny1s","title":"[Review] 5 non-blocking findings from mala-axhy","description":"## Review Findings\n\nThis issue consolidates 5 non-blocking findings from code review.\n\n**Source issue:** mala-axhy\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Make SyntheticReviewOutcome.issues compatible with ReviewOutcome\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/pipeline/lifecycle_effect_handler.py:53-57\n\n`ImplementerLifecycle.on_review_result(...)` expects a `src.domain.lifecycle.ReviewOutcome`, whose protocol requires `issues` to be `list[ReviewIssue]` (see `src/domain/lifecycle.py`), but `SyntheticReviewOutcome` now declares `issues: list[ReviewIssueProtocol]`. Because `list[...]` is invariant, `list[ReviewIssueProtocol]` won’t be accepted as `list[ReviewIssue]` even if the element protocols are structurally similar, so `ty`/type checking can fail at the call site where `synthetic` is passed as a `ReviewOutcome`. Consider changing the annotation to `list[ReviewIssue]` (domain protocol) or relaxing `ReviewOutcome.issues` to a covariant type like `Sequence[...]` if that’s acceptable.\n\n---\n\n### Finding 2: [P2] Incompatible issues type in SyntheticReviewOutcome\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/pipeline/lifecycle_effect_handler.py:54\n\nThe commit types `issues` as `list[ReviewIssueProtocol]`, but `SyntheticReviewOutcome` is used in `check_review_no_progress` where `ReviewOutcome` is expected (e.g., in `on_review_result`). Since `ReviewOutcome.issues` is typed as `list[ReviewIssue]` and `list` is invariant, this creates a static type mismatch despite the structural similarity of the protocols. It should use `list[ReviewIssue]` to maintain compatibility with the domain lifecycle layer, or the domain protocols should be updated to use the core protocol.\n\n---\n\n### Finding 3: [P3] Redundant import of ReviewIssueProtocol\n\n**Priority:** P3\n**Reviewer:** gemini\n**Location:** src/pipeline/lifecycle_effect_handler.py:26\n\n`ReviewIssueProtocol` is added to the `TYPE_CHECKING` block while already being imported at runtime on line 15. This is redundant and can trigger lint errors (e.g., Ruff TCH004). If the runtime import is truly necessary for the dataclass, it should not be repeated in the `TYPE_CHECKING` block.\n\n---\n\n### Finding 4: [P3] Redundant type: ignore on format_review_issues call\n\n**Priority:** P3\n**Reviewer:** gemini\n**Location:** src/pipeline/lifecycle_effect_handler.py:229\n\nThe `# type: ignore[arg-type]` is now unnecessary because `format_review_issues` has been updated to accept a covariant `Sequence[ReviewIssueProtocol]`, with which `list[ReviewIssue]` is compatible. Removing it would improve the codebase's type safety and cleanliness.\n\n---\n\n### Finding 5: [P3] Redundant Path construction in _to_relative_path\n\n**Priority:** P3\n**Reviewer:** gemini\n**Location:** src/pipeline/review_formatter.py:45-51\n\n`Path(file_path)` is constructed twice: once for the absolute check and again for the relativization attempt. The result of the first construction should be stored and reused for better efficiency.\n\n---\n\n\u003c!-- fp:1f3d4359320b19cf --\u003e\n\u003c!-- fp:fd928cb5a95533b5 --\u003e\n\u003c!-- fp:17e230469c695f4d --\u003e\n\u003c!-- fp:2d78ff03b5916a1c --\u003e\n\u003c!-- fp:d4e3bdcacf329ae4 --\u003e\n\u003c!-- review_finding:592970d6f238 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-05T01:49:59.727769308Z","created_by":"cyou","updated_at":"2026-01-05T01:58:30.559815237Z","closed_at":"2026-01-05T01:58:30.559815237Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-axhy"]}
{"id":"mala-nzk6","title":"hide waiting for session log, log file ready in default log mode","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-01T22:54:32.553091653Z","created_by":"cyou","updated_at":"2026-01-02T01:08:22.730646474Z","closed_at":"2026-01-02T01:08:22.730646474Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-nzk6","depends_on_id":"mala-cw6h","type":"blocks","created_at":"2026-01-01T23:32:33.320375764Z","created_by":"daemon"}]}
{"id":"mala-nzr9","title":"Epic: Fix import-linter contract regressions (2026-01-03)","description":"Import-linter/grimp now report Layered Architecture + Hooks isolated violations:\\n- src.infra -\u003e src.domain (EpicScopeAnalyzer + hooks lint_cache)\\n- src.pipeline -\u003e src.orchestration (issue_finalizer + epic_verification_coordinator)\\n- src.infra.hooks -\u003e src.domain/core (hooks isolated)\\n\\nGoal: restore all contracts with minimal churn while preserving current behavior.","status":"closed","priority":1,"issue_type":"epic","created_at":"2026-01-03T18:14:20.507448842Z","created_by":"cyou","updated_at":"2026-01-03T21:22:44.41856105Z","closed_at":"2026-01-03T21:22:44.41856105Z","close_reason":"Closed"}
{"id":"mala-nzr9.1","title":"Resolve infra-\u003edomain dependency: EpicScopeAnalyzer","description":"Layered Architecture contract broken: src.infra.epic_verifier imports src.domain.epic.scope. Decide on correct ownership (infra vs core vs domain) and refactor to remove infra-\u003edomain import while keeping EpicVerifier behavior unchanged.\\n\\nAcceptance: import-linter Layered Architecture passes; grimp layers shows no infra-\u003edomain edge.","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-03T18:14:33.556222216Z","created_by":"cyou","updated_at":"2026-01-03T19:35:35.872713376Z","closed_at":"2026-01-03T19:35:35.872713376Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-nzr9.1","depends_on_id":"mala-nzr9","type":"parent-child","created_at":"2026-01-03T18:14:33.563328679Z","created_by":"daemon"}]}
{"id":"mala-nzr9.2","title":"Fix hooks isolated violation (infra.hooks.lint_cache -\u003e domain/tool_name_extractor)","description":"Hooks isolated contract broken: src.infra.hooks.lint_cache imports src.domain.validation.tool_name_extractor (which reaches src.core.models). Refactor so infra.hooks has no dependency on src.domain or src.core (e.g., move extractor to infra/core or inject via protocol).\\n\\nAcceptance: import-linter Hooks isolated passes; no src.infra.hooks -\u003e src.domain/core edges.","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-03T18:14:46.358806Z","created_by":"cyou","updated_at":"2026-01-03T18:58:55.846634753Z","closed_at":"2026-01-03T18:58:55.846634753Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-nzr9.2","depends_on_id":"mala-nzr9","type":"parent-child","created_at":"2026-01-03T18:14:46.366920333Z","created_by":"daemon"}]}
{"id":"mala-nzr9.3","title":"Remove pipeline -\u003e orchestration imports (issue_result/gate_metadata)","description":"Layered Architecture contract broken: src.pipeline.issue_finalizer and src.pipeline.epic_verification_coordinator import src.orchestration.issue_result and src.orchestration.gate_metadata. Relocate shared types to pipeline or core, or introduce protocol to invert dependency.\\n\\nAcceptance: import-linter Layered Architecture passes; grimp layers shows no pipeline-\u003eorchestration edge.","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-03T18:14:59.675747798Z","created_by":"cyou","updated_at":"2026-01-03T19:14:45.143062675Z","closed_at":"2026-01-03T19:14:45.143062675Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-nzr9.3","depends_on_id":"mala-nzr9","type":"parent-child","created_at":"2026-01-03T18:14:59.68386421Z","created_by":"daemon"}]}
{"id":"mala-nzr9.4","title":"[Remediation] Fix hooks isolation contract violation (src.infra.hooks -\u003e s...","description":"## Context\nThis issue was auto-created by epic verification for epic `mala-nzr9`.\n\n## Epic Description / Acceptance Criteria\nTitle: Epic: Fix import-linter contract regressions (2026-01-03)\n\nImport-linter/grimp now report Layered Architecture + Hooks isolated violations:\\n- src.infra -\u003e src.domain (EpicScopeAnalyzer + hooks lint_cache)\\n- src.pipeline -\u003e src.orchestration (issue_finalizer + epic_verification_coordinator)\\n- src.infra.hooks -\u003e src.domain/core (hooks isolated)\\n\\nGoal: restore all contracts with minimal churn while preserving current behavior.\n\n## Commit Scope\n- Range hint: ada25f0449762ba33a37571173c8949a14193aa4^..2cc21200f520638a5f9dd194eef616ebd4198d17\n- Commits:\n- 2cc21200f520638a5f9dd194eef616ebd4198d17 bd-mala-nzr9.1: Fix EpicVerifier tests missing command_runner and run_async\n- 8fcf45453c7cf10ea47f956ca7fe33a6014d8563 bd-mala-nzr9.1: Move EpicScopeAnalyzer from domain to infra\n- 07e6888c507cb50ac8063c6ed92a0238501d3c3f bd-mala-nzr9.3: Move issue_result and gate_metadata from orchestration to pipeline\n- ada25f0449762ba33a37571173c8949a14193aa4 bd-mala-nzr9.2: Fix Hooks isolated contract violation\n\n## Child Issues\n- mala-nzr9.1\n- mala-nzr9.2\n- mala-nzr9.3\n\n## Unmet Criterion\nFix hooks isolation contract violation (src.infra.hooks -\u003e src.domain)\n\n## Evidence\nImport-linter still reports 'Hooks isolated BROKEN' with src.infra.tool_name_extractor importing from src.domain.validation.spec (line 18). The extract_lint_tools_from_spec function creates a dependency chain: src.infra.hooks.lint_cache -\u003e src.infra.tool_name_extractor -\u003e src.domain.validation.spec -\u003e src.core.models\n\n## Priority\nP1 (blocking)\n\n## Resolution\nAddress this criterion to unblock epic closure. When complete, close this issue.\n","notes":"Quality gate failed: External review failed: src/domain/quality_gate.py:25: [P1] Update imports after deleting tool_name_extractor module; src/domain/quality_gate.py:28: [P1] Broken imports after tool_name_extractor deletion; src/pipeline/agent_session_runner.py:1614: [P1] ContextPressureError incorrectly handled as hard failure\nLog: /home/cyou/.claude/projects/-home-cyou-mala/f32445c5-1c9d-41e4-878c-bb9dbb34e1ca.jsonl","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-03T19:37:37.836028856Z","created_by":"cyou","updated_at":"2026-01-03T21:20:03.971758419Z","closed_at":"2026-01-03T21:20:03.971758419Z","close_reason":"Closed","labels":["auto_generated","epic_remediation:mala-nzr9:cc472ce2ca881e24494eef89146659de38a1fdfa048f9a112c56d7a2074ed15c","needs-followup"],"dependencies":[{"issue_id":"mala-nzr9.4","depends_on_id":"mala-nzr9","type":"parent-child","created_at":"2026-01-03T19:37:37.836590572Z","created_by":"daemon"}]}
{"id":"mala-o15f","title":"[Review] src/infra/clients/cerberus_review.py:280-282: [P2] Missing timeout check after retry spawn","description":"## Review Finding\n\nThis issue was auto-created from a P2 review finding.\n\n**Source issue:** mala-m70v\n**Reviewer:** claude\n**Location:** src/infra/clients/cerberus_review.py:280-282\n\n## Details\n\nThe retry spawn after stale gate resolution (lines 280-282) doesn't check `spawn_result.timed_out`, unlike the original spawn (line 264). If the retry times out, it will report a generic 'spawn failed after gate resolve' error instead of 'spawn timeout'. Add a timeout check after line 282:\n```python\nif spawn_result.timed_out:\n    return ReviewResult(...parse_error=\"spawn timeout\"...)\n```","notes":"Quality gate failed: External review failed: spawn failed: Error: --exclude expects git exclude pathspec syntax (e.g. :!path or :(exclude,glob)path): .beads/\nLog: /home/cyou/.claude/projects/-home-cyou-mala/59d97fb4-a8a0-49df-a330-7dc847b8bbfd.jsonl","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-01T23:45:36.288319868Z","created_by":"cyou","updated_at":"2026-01-02T01:00:52.747591791Z","closed_at":"2026-01-02T01:00:52.747591791Z","close_reason":"Closed","labels":["auto_generated","needs-followup","review_finding","review_finding:c7c7974506a0","source:mala-m70v"]}
{"id":"mala-o17","title":"Test issue: add a comment to README.md","status":"tombstone","priority":2,"issue_type":"task","created_at":"2025-12-25T18:49:43.062524854Z","updated_at":"2025-12-25T18:53:44.873613587Z","close_reason":"Closed","deleted_at":"2025-12-25T18:53:44.873613587Z","deleted_by":"daemon","delete_reason":"delete","original_type":"task"}
{"id":"mala-o3z","title":"Test braintrust tracing","description":"Add a comment to the README TODOs section noting that Braintrust LLM tracing is now implemented","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-25T19:22:15.933197275Z","updated_at":"2025-12-25T19:23:21.922382429Z","closed_at":"2025-12-25T19:23:21.922382429Z","close_reason":"Closed"}
{"id":"mala-o4tr","title":"[Review] src/domain/validation/presets/python-uv.yaml:8: [P2] Regression: format command uses --check in implementer prompt","description":"## Review Finding\n\nThis issue was auto-created from a P2 review finding.\n\n**Source issue:** mala-vnzl\n**Reviewer:** gemini\n**Location:** src/domain/validation/presets/python-uv.yaml:8\n\n## Details\n\nThe `python-uv` preset uses `ruff format --check .` for the `format` command. Since this command is now substituted into the `implementer_prompt.md`, the agent is no longer instructed to automatically fix formatting issues (it previously used `uvx ruff format $CHANGED_FILES`). Instead, the agent will encounter a check failure and be forced to manually fix formatting, which is less efficient and prone to error compared to allowing the tool to format the files directly.","notes":"Quality gate failed: Quality gate failed: Missing validation evidence for: format, test, typecheck, lint\nLog: /home/cyou/.claude/projects/-home-cyou-mala/bd13973a-def6-4d7c-858e-edbd2fd197b2.jsonl","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T00:31:02.195943585Z","created_by":"cyou","updated_at":"2026-01-03T02:39:11.591065813Z","closed_at":"2026-01-03T02:39:11.591065813Z","close_reason":"Closed","labels":["auto_generated","needs-followup","review_finding","review_finding:da35e533ee98","source:mala-vnzl"]}
{"id":"mala-o8cr","title":"[Review] 1 non-blocking finding from mala-s6tn","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-s6tn\n**Highest priority:** P3\n\n---\n\n### Finding 1: [P3] Fix misleading “non-blocking” comment on WAITING emission\n\n**Priority:** P3\n**Reviewer:** codex\n**Location:** src/infra/tools/locking_mcp.py:225-227\n\n`lock_acquire` still awaits `asyncio.gather(...)`, so emitting WAITING events is parallelized but not non-blocking. The current comment can mislead readers into thinking `emit_lock_event` cost won’t add to `lock_acquire` latency; consider rewording to just “in parallel” (or note it’s awaited).\n\n---\n\n\u003c!-- fp:d3202077f6434e19 --\u003e\n\u003c!-- review_finding:23667683dfd2 --\u003e","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-04T23:28:52.822273087Z","created_by":"cyou","updated_at":"2026-01-04T23:30:20.87188253Z","closed_at":"2026-01-04T23:30:20.87188253Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-s6tn"]}
{"id":"mala-o9mf","title":"[P3] Use public API for pytest Item type","description":"## Review Finding\n\n**Priority:** P3\n**Trigger:** run_end\n**Baseline:** bf7d0ad2..102e65c8\n**Location:** tests/e2e/conftest.py:13\n\n---\n\nImporting `Item` from `_pytest.nodes` relies on private API. Use `from pytest import Item` instead for better stability.","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-11T06:40:16.647027476Z","created_by":"cyou","updated_at":"2026-01-11T07:06:18.222973175Z","closed_at":"2026-01-11T07:06:18.222973175Z","close_reason":"Closed","labels":["auto_generated","cumulative-review","fp:d736a4ade48b40a5","review_finding","trigger:run_end"]}
{"id":"mala-o9zf","title":"[Review] 3 non-blocking findings from mala-ari7.3","description":"## Review Findings\n\nThis issue consolidates 3 non-blocking findings from code review.\n\n**Source issue:** mala-ari7.3\n**Highest priority:** P3\n\n---\n\n### Finding 1: [P3] Missed de-duplication in _handle_gate_effect\n\n**Priority:** P3\n**Reviewer:** gemini\n**Location:** src/pipeline/agent_session_runner.py:1034-1045\n\nThe newly extracted `_emit_gate_passed_events` helper can be used to replace the identical event emission logic in `_handle_gate_effect` (lines 1034-1045), which still manually triggers `on_gate_passed` and `on_validation_result`.\n\n---\n\n### Finding 2: [P3] Architectural inconsistency with existing helpers\n\n**Priority:** P3\n**Reviewer:** gemini\n**Location:** src/pipeline/agent_session_runner.py:1321\n\nThe extracted helper is implemented as a class method, whereas other similar helpers in this file (e.g., `_emit_review_result_events`) are module-level functions. Adopting the module-level function pattern would keep the `AgentSessionRunner` class smaller and simplify unit testing by removing the need for a full runner instance in the tests.\n\n---\n\n### Finding 3: [P3] Hardcoded log path in unit tests\n\n**Priority:** P3\n**Reviewer:** gemini\n**Location:** tests/test_agent_session_runner.py:2722\n\nThe `runner` fixture in `TestEmitGatePassedEvents` uses a hardcoded path `/tmp/test.jsonl` in its `get_log_path` callback. It is better practice to use the `tmp_path` fixture to ensure isolation and avoid potential permission issues during concurrent test execution.\n\n---\n","notes":"Quality gate failed: Quality gate failed: No commit with bd-mala-o9zf found after run baseline (stale commits from previous runs are rejected)\nLog: /home/cyou/.claude/projects/-home-cyou-mala/cb65b178-982a-4f72-9371-2dec34510720.jsonl","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-03T08:51:26.937792767Z","created_by":"cyou","updated_at":"2026-01-03T15:12:07.575232468Z","closed_at":"2026-01-03T15:12:07.575232468Z","close_reason":"Closed","labels":["auto_generated","needs-followup","review_finding","review_findings:0c3fd33b2386","source:mala-ari7.3"]}
{"id":"mala-od6m","title":"[Review] 1 non-blocking finding from mala-au9g.3","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-au9g.3\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Preserve late-bound dependency overrides in callbacks\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/pipeline/session_callback_factory.py:91-100\n\n`SessionCallbackFactory` captures `event_sink`, `quality_gate`, `log_provider`, etc. at construction and callbacks dereference those cached fields. Previously, the closures read `self.event_sink`/`self.quality_gate` at call time, so patches or runtime swaps on the orchestrator took effect. With the factory, tests that patch `orchestrator.quality_gate` or `orchestrator.event_sink` after construction will still hit the original instances (e.g., real gate logic/log I/O), which is a behavioral regression. Consider deferring lookup via callables (like `_create_finalizer_callbacks` does) or rebuilding the factory when dependencies change.\n\n---\n","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T07:28:23.71951752Z","created_by":"cyou","updated_at":"2026-01-03T07:36:54.517168695Z","closed_at":"2026-01-03T07:36:54.517168695Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_findings:f6e34efaf64b","source:mala-au9g.3"]}
{"id":"mala-onst","title":"add instructions to implementer to use subagents","description":"Context:\nAdd instructions to implementer prompt to use subagents for exploration and specialized tasks.\n\nLocation:\n- src/prompts/implementer_prompt.md (implementer instructions)\n- src/orchestration/prompts.py (prompt loading)\n\nScope:\n- Needs decision on what specific guidance to add\n- Potential: Task tool for exploration, specialized agents\n\nAcceptance Criteria:\n- TBD after determining specific guidance\n\nTest Plan:\n- Manual: Run agent, observe subagent usage\n\nNotes for Agent:\n- GROOMING: Needs decision on what subagent guidance to include\n- Currently implementer doesn't mention Task/Explore tools\n- Consider examples of when subagents are beneficial","status":"closed","priority":2,"issue_type":"feature","created_at":"2026-01-01T22:11:55.295882901Z","created_by":"cyou","updated_at":"2026-01-03T19:06:12.753832147Z","closed_at":"2026-01-03T19:06:12.753832147Z","close_reason":"Closed"}
{"id":"mala-or0r","title":"AgentSDKReviewer ignores commit_shas, uses diff_range instead","description":"## Summary\nWhen reviewing code, `AgentSDKReviewer` receives `commit_shas` (the specific commits for an issue) but the reviewer prompt still tells the agent to run `git diff {diff_range}` instead of reviewing the specific commits.\n\n## Impact\nWhen multiple agents work in parallel on main, the diff_range includes commits from ALL agents, not just the issue being reviewed. This causes:\n1. Reviewer sees 2500+ lines across 30 files instead of ~100 lines across 5 files\n2. Reviewer flags issues in code from OTHER issues as belonging to current issue\n3. False positive review failures (agent correctly implements something, but reviewer can't find it in the noise)\n\n## Root Cause\nIn `src/infra/clients/agent_sdk_review.py`:\n- Line 212: `query = self._create_review_query(diff_range, context, commit_shas)`\n- `_create_review_query` adds commit_shas as info: `## Specific Commits\\n{commit_shas}`\n- But prompt template (`src/prompts/review_agent.md`) says: `git diff {diff_range}`\n\nThe reviewer runs the wrong command and reviews the wrong diff.\n\n## Fix\nWhen `commit_shas` is provided, the prompt should instruct:\n```bash\n# Review these specific commits only:\ngit show \u003csha1\u003e\ngit show \u003csha2\u003e\n# etc.\n```\n\nOr generate a combined diff of only those commits.\n\n## Evidence\nRun 2026-01-08T19-57-54 failed mala-s9p8.5 and mala-smaj.10 due to reviewer seeing wrong diff.","status":"closed","priority":1,"issue_type":"bug","created_at":"2026-01-08T22:20:18.635514851Z","created_by":"cyou","updated_at":"2026-01-09T18:24:11.671873929Z","closed_at":"2026-01-09T18:24:11.671873929Z","close_reason":"Closed"}
{"id":"mala-oste","title":"separate module used by both reviewer and epic verifier that has smarter ticket creation: description writing, dependency aware, and deduplication","description":"When mala creates review tickets or epic verification tickets, it should check if a similar issue already exists to avoid duplicates.\n\nCurrent behavior:\n- Review failures may create new tickets even if the same issue was already filed\n- Epic verification may create duplicate tickets for the same problem\n\nProposed solution:\n- Before creating a new review/verification issue, search existing open issues for similar titles or descriptions\n- If a potential duplicate is found, either:\n  - Skip creation and log a warning\n  - Add a note to the existing issue instead\n  - Prompt for confirmation (in interactive mode)\n\nThis prevents issue sprawl and keeps the backlog clean.","status":"tombstone","priority":2,"issue_type":"feature","created_at":"2026-01-02T15:21:54.071683268Z","created_by":"cyou","updated_at":"2026-01-06T22:52:10.761723406Z","deleted_at":"2026-01-06T22:52:10.761723406Z","deleted_by":"batch delete","delete_reason":"batch delete","original_type":"feature"}
{"id":"mala-oxnm","title":"[Review] src/domain/validation/config.py:93-94: [P2] CommandConfig.from_value doesn't validate empty string shorthand","description":"## Review Finding\n\nThis issue was auto-created from a P2 review finding.\n\n**Source issue:** mala-fdp1.1\n**Reviewer:** claude\n**Location:** src/domain/validation/config.py:93-94\n\n## Details\n\nWhen a command is provided as a string shorthand (not dict form), empty strings are accepted:\n```python\nif isinstance(value, str):\n    return cls(command=value)\n```\nThis allows `CommandConfig.from_value(\"\")` to succeed, creating a config with an empty command. The dict form validates for empty strings, but the string shorthand does not. Add `if not value: raise ConfigError(...)` before returning.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-02T03:19:13.852820893Z","created_by":"cyou","updated_at":"2026-01-02T03:42:25.765872349Z","closed_at":"2026-01-02T03:42:25.765872349Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_finding:e61656e78d0c","source:mala-fdp1.1"]}
{"id":"mala-p3qs","title":"Wire interrupt_event into implementer AgentSessionRunner","description":"Ensure MalaOrchestrator.run_implementer passes interrupt_event into AgentSessionRunner.run_session so implementer SDK flows are interrupt-aware. Verify propagation path with tests.","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-09T16:52:46.801815182Z","created_by":"cyou","updated_at":"2026-01-09T17:03:39.62976032Z","closed_at":"2026-01-09T17:03:39.62976032Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-p3qs","depends_on_id":"mala-s9p8","type":"parent-child","created_at":"2026-01-09T16:54:28.566895554Z","created_by":"cyou"}]}
{"id":"mala-p42f","title":"Epic: Trigger Code Review Terminal Logging","description":"## Summary\nAdd terminal logging for trigger-based code reviews, which currently run silently with no start/progress/result output.\n\n## Spec\n`docs/2026-01-12-code-review-refinement-spec.md`\n\n## Plan\n`docs/2026-01-12-code-review-logging-plan.md`\n\n## Requirements\n- R1: Emit start event before trigger code review\n- R2: Emit exactly one end event (passed/failed/skipped/error)\n- R3: Emit fixer events during remediation loop\n- R4: Emit error event on execution failure\n\n## Technical Approach\n- Add 5 new MalaEventSink events for trigger code review lifecycle\n- Implement ConsoleEventSink handlers for terminal output\n- Wire up event emission in RunCoordinator._run_trigger_validation()\n- Single emission boundary pattern with try/finally for exactly-one-end-event invariant\n\n## Key Constraints\n- Events MUST be emitted from RunCoordinator only (CumulativeReviewRunner has no event_sink access)\n- Use TriggerType.value for event trigger_type parameter\n- Fixer events per remediation attempt, not per overall remediation","status":"closed","priority":1,"issue_type":"epic","created_at":"2026-01-12T04:05:58.453744177Z","created_by":"cyou","updated_at":"2026-01-12T04:24:03.394914843Z","closed_at":"2026-01-12T04:24:03.394914843Z","close_reason":"Closed"}
{"id":"mala-p42f.1","title":"[T001] [integration-path-test] Protocol + Sink Skeleton + Failing Integration Test","description":"## Goal\nAdd 5 new event methods to MalaEventSink protocol and all sink implementations (stubs), plus a failing integration test that validates events are recorded.\n\n## Context\nThis task establishes the skeleton that makes the project compile with new event infrastructure. The integration test will fail until T002 wires up event emission in RunCoordinator.\n\n## Scope\n**In:**\n- Add 5 abstract methods to MalaEventSink protocol\n- Add 5 no-op implementations to BaseEventSink\n- Add 5 stub implementations to ConsoleEventSink (no-op, just `pass`)\n- Add 5 recording implementations to FakeEventSink using `_record()` pattern\n- Add failing integration test in test_validation_triggers.py\n\n**Out:**\n- Actual console formatting (T003)\n- RunCoordinator wiring (T002)\n\n## Changes\n\n### `src/core/protocols.py` — Exists\nAdd 5 new abstract methods to MalaEventSink protocol:\n```python\ndef on_trigger_code_review_started(self, trigger_type: str) -\u003e None: ...\ndef on_trigger_code_review_skipped(self, trigger_type: str, reason: str) -\u003e None: ...\ndef on_trigger_code_review_passed(self, trigger_type: str) -\u003e None: ...\ndef on_trigger_code_review_failed(self, trigger_type: str, blocking_count: int) -\u003e None: ...\ndef on_trigger_code_review_error(self, trigger_type: str, error: str) -\u003e None: ...\n```\n\n### `src/infra/io/base_sink.py` — Exists\nAdd 5 no-op implementations (just `pass`):\n```python\ndef on_trigger_code_review_started(self, trigger_type: str) -\u003e None:\n    pass  # No-op default\n\n# ... same for other 4 methods\n```\n\n### `src/infra/io/console_sink.py` — Exists\nAdd 5 stub implementations (no formatting yet, just `pass`):\n```python\ndef on_trigger_code_review_started(self, trigger_type: str) -\u003e None:\n    pass  # Stub - formatting added in T003\n\n# ... same for other 4 methods\n```\n\n### `tests/fakes/event_sink.py` — Exists\nAdd 5 recording implementations using `_record()` pattern:\n```python\ndef on_trigger_code_review_started(self, trigger_type: str) -\u003e None:\n    self._record(\"trigger_code_review_started\", trigger_type=trigger_type)\n\ndef on_trigger_code_review_skipped(self, trigger_type: str, reason: str) -\u003e None:\n    self._record(\"trigger_code_review_skipped\", trigger_type=trigger_type, reason=reason)\n\ndef on_trigger_code_review_passed(self, trigger_type: str) -\u003e None:\n    self._record(\"trigger_code_review_passed\", trigger_type=trigger_type)\n\ndef on_trigger_code_review_failed(self, trigger_type: str, blocking_count: int) -\u003e None:\n    self._record(\"trigger_code_review_failed\", trigger_type=trigger_type, blocking_count=blocking_count)\n\ndef on_trigger_code_review_error(self, trigger_type: str, error: str) -\u003e None:\n    self._record(\"trigger_code_review_error\", trigger_type=trigger_type, error=error)\n```\n\n### `tests/integration/test_validation_triggers.py` — Exists\nAdd integration test that:\n1. Sets up a trigger with code review enabled\n2. Runs the trigger validation\n3. Asserts FakeEventSink recorded expected event sequence\n4. Initially FAILS because RunCoordinator doesn't emit events yet\n\n```python\ndef test_trigger_code_review_emits_lifecycle_events(fake_event_sink, ...):\n    \"\"\"Verify code review lifecycle events are emitted during trigger validation.\"\"\"\n    # Setup trigger with code review enabled\n    # Run trigger validation\n    # Assert event sequence: started → (passed|failed|skipped|error)\n    # This test will FAIL until T002 wires up event emission\n```\n\n## Acceptance Criteria\n- [ ] All 5 event methods exist in MalaEventSink protocol as abstract methods\n- [ ] BaseEventSink provides no-op implementations (project compiles)\n- [ ] ConsoleEventSink has stub implementations (no-op, compiles)\n- [ ] FakeEventSink records all 5 event types with correct parameters\n- [ ] Integration test exists and FAILS with \"expected events not recorded\" (not import/compile errors)\n\n## Verification\n```bash\n# Compile check\nuv run python -c \"from src.core.protocols import MalaEventSink; from src.infra.io.console_sink import ConsoleEventSink\"\n\n# Run integration test (expected to FAIL)\nuv run pytest tests/integration/test_validation_triggers.py::test_trigger_code_review_emits_lifecycle_events -v\n# Should fail with assertion error, not import error\n```\n\n## Notes for Agent\n- Follow existing trigger event pattern in protocols.py (search for `on_trigger_validation_`)\n- Use `_record()` pattern in FakeEventSink (search for existing usages)\n- The integration test setup can copy from existing trigger tests in the file\n- Console stub methods should just `pass` - formatting added in T003","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-12T04:06:27.693488953Z","created_by":"cyou","updated_at":"2026-01-12T04:13:18.133821232Z","closed_at":"2026-01-12T04:13:18.133821232Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-p42f.1","depends_on_id":"mala-p42f","type":"parent-child","created_at":"2026-01-12T04:06:27.694241174Z","created_by":"cyou"}]}
{"id":"mala-p42f.2","title":"[T002] RunCoordinator Event Wiring + Unit Tests","description":"## Goal\nWire up event emission in RunCoordinator._run_trigger_validation() with try/finally pattern for exactly-one-end-event invariant, plus comprehensive unit tests.\n\n## Context\nThis is the core implementation task. After T001 establishes the protocol skeleton, this task makes the integration test pass by emitting events from the coordinator.\n\n**Key constraint**: ALL events MUST be emitted within `_run_trigger_validation()` only. The `_run_trigger_code_review()` helper returns a result but does NOT emit events.\n\n## Scope\n**In:**\n- Wire up event emission in `_run_trigger_validation()`\n- Use try/finally pattern with status variable\n- Emit fixer events per remediation attempt\n- Add comprehensive unit tests mocking CumulativeReviewRunner\n\n**Out:**\n- Console formatting (T003)\n- Protocol/sink changes (T001)\n\n## Changes\n\n### `src/pipeline/run_coordinator.py` — Exists\n\nAdd event emission in `_run_trigger_validation()` following this pattern:\n\n```python\nasync def _run_trigger_validation(self, trigger_type: TriggerType, ...):\n    # ... existing enabled check ...\n    if not code_review_enabled:\n        return  # No events when disabled\n    \n    # Emit started AFTER enabled check passes\n    if self.event_sink is not None:\n        self.event_sink.on_trigger_code_review_started(trigger_type.value)\n    \n    end_status: Literal[\"skipped\", \"passed\", \"failed\"] | None = None\n    try:\n        result = await self._run_trigger_code_review(...)  # Returns result, NO events\n        \n        # Map result to status (precedence rule)\n        if result.status == \"skipped\":\n            end_status = \"skipped\"\n        elif result.status == \"failed\":\n            # Review execution failed internally - treat as error\n            raise RuntimeError(f\"Code review failed: {result.skip_reason}\")\n        elif not blocking_findings:\n            end_status = \"passed\"\n        else:\n            # Blocking findings - enter remediation loop\n            for attempt in range(1, max_retries + 1):\n                if self.event_sink is not None:\n                    self.event_sink.on_fixer_started(attempt, max_retries)\n                \n                try:\n                    await self._run_code_review_remediation(...)\n                    if self.event_sink is not None:\n                        self.event_sink.on_fixer_completed()\n                except Exception as e:\n                    if self.event_sink is not None:\n                        self.event_sink.on_fixer_failed(str(e))\n                    # ... continue or break based on error handling\n                \n                # Re-run review\n                result = await self._run_trigger_code_review(...)\n                if not has_blocking_findings(result):\n                    end_status = \"passed\"\n                    break\n            else:\n                end_status = \"failed\"\n    \n    except Exception as e:\n        # Exception bubbles up - emit error event\n        if self.event_sink is not None:\n            self.event_sink.on_trigger_code_review_error(trigger_type.value, str(e))\n        raise\n    \n    finally:\n        # Emit exactly one end event based on status\n        if self.event_sink is not None and end_status is not None:\n            if end_status == \"skipped\":\n                self.event_sink.on_trigger_code_review_skipped(trigger_type.value, result.skip_reason or \"unknown\")\n            elif end_status == \"passed\":\n                self.event_sink.on_trigger_code_review_passed(trigger_type.value)\n            elif end_status == \"failed\":\n                self.event_sink.on_trigger_code_review_failed(trigger_type.value, blocking_count)\n```\n\n### `tests/unit/pipeline/test_run_coordinator.py` — Exists\n\nAdd unit tests with mocked CumulativeReviewRunner:\n\n```python\nclass TestTriggerCodeReviewEvents:\n    \"\"\"Test trigger code review lifecycle events.\"\"\"\n    \n    def test_enabled_passing_review_emits_started_passed(self, fake_event_sink, mock_review_runner):\n        \"\"\"started → passed when review has no blocking findings.\"\"\"\n        mock_review_runner.return_value = CumulativeReviewResult(status=\"success\", findings=(), ...)\n        # Run trigger validation\n        # Assert: started, passed events (in order)\n    \n    def test_enabled_failing_review_emits_started_failed(self, fake_event_sink, mock_review_runner):\n        \"\"\"started → failed when blocking findings remain.\"\"\"\n        mock_review_runner.return_value = CumulativeReviewResult(status=\"success\", findings=blocking_findings, ...)\n        # Assert: started, failed(blocking_count=N) events\n    \n    def test_empty_diff_emits_started_skipped(self, fake_event_sink, mock_review_runner):\n        \"\"\"started → skipped when result.status == 'skipped'.\"\"\"\n        mock_review_runner.return_value = CumulativeReviewResult(status=\"skipped\", skip_reason=\"empty_diff\", ...)\n        # Assert: started, skipped(reason=\"empty_diff\") events\n    \n    def test_exception_emits_started_error(self, fake_event_sink, mock_review_runner):\n        \"\"\"started → error when exception bubbles up.\"\"\"\n        mock_review_runner.side_effect = RuntimeError(\"test error\")\n        # Assert: started, error(error=\"test error\") events\n    \n    def test_disabled_emits_no_events(self, fake_event_sink):\n        \"\"\"No events when code review is disabled.\"\"\"\n        # Configure code review disabled\n        # Assert: no events recorded\n    \n    def test_remediation_success_emits_fixer_events_then_passed(self, fake_event_sink, mock_review_runner):\n        \"\"\"started → fixer_started → fixer_completed → passed.\"\"\"\n        # First call: blocking findings\n        # Second call (after remediation): no blocking findings\n        # Assert: started, fixer_started(1, max), fixer_completed, passed\n    \n    def test_remediation_exhausted_emits_fixer_events_then_failed(self, fake_event_sink, mock_review_runner):\n        \"\"\"started → fixer_started → fixer_completed → ... → failed.\"\"\"\n        # All calls return blocking findings\n        # Assert: started, fixer events for each attempt, failed(blocking_count=N)\n    \n    def test_exactly_one_end_event_invariant(self, fake_event_sink, ...):\n        \"\"\"Verify exactly one end event is emitted per execution.\"\"\"\n        # Run various scenarios\n        # Assert: count of (passed + failed + skipped + error) == 1\n```\n\n## Acceptance Criteria\n- [ ] Events emitted only from `_run_trigger_validation()` (single emission boundary)\n- [ ] No events when code review is disabled\n- [ ] `started` event emitted after enabled check passes\n- [ ] Exactly one end event per execution (passed|failed|skipped|error)\n- [ ] Fixer events emitted per remediation attempt (not per overall remediation)\n- [ ] Unit tests cover all 7 scenarios listed in plan\n- [ ] Integration test from T001 now PASSES\n\n## Verification\n```bash\n# Unit tests\nuv run pytest tests/unit/pipeline/test_run_coordinator.py::TestTriggerCodeReviewEvents -v\n\n# Integration test (should now pass)\nuv run pytest tests/integration/test_validation_triggers.py::test_trigger_code_review_emits_lifecycle_events -v\n\n# All tests\nuv run pytest tests/unit/pipeline/test_run_coordinator.py tests/integration/test_validation_triggers.py -v\n```\n\n## Notes for Agent\n- Check `if self.event_sink is not None:` before emitting (see existing pattern in file)\n- Use `trigger_type.value` for the string form (TriggerType is an enum)\n- Fixer events use existing methods: on_fixer_started(attempt, max_attempts), on_fixer_completed(), on_fixer_failed(error)\n- Result status → end event mapping (precedence rule):\n  | Condition | End Event |\n  |-----------|-----------|\n  | Exception bubbles up | error |\n  | result.status == \"skipped\" | skipped |\n  | result.status == \"success\" AND blocking_count == 0 | passed |\n  | result.status == \"success\" AND blocking_count \u003e 0 after remediation | failed |\n  | result.status == \"failed\" | error (internal failure) |","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-12T04:07:10.099038155Z","created_by":"cyou","updated_at":"2026-01-12T04:20:49.186170436Z","closed_at":"2026-01-12T04:20:49.186170436Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-p42f.2","depends_on_id":"mala-p42f","type":"parent-child","created_at":"2026-01-12T04:07:10.099844866Z","created_by":"cyou"},{"issue_id":"mala-p42f.2","depends_on_id":"mala-p42f.1","type":"blocks","created_at":"2026-01-12T04:07:41.206641833Z","created_by":"cyou"}]}
{"id":"mala-p42f.3","title":"[T003] ConsoleEventSink Formatting","description":"## Goal\nImplement actual log() calls with symbols and colors in ConsoleEventSink, replacing the stub implementations from T001.\n\n## Context\nThis task adds user-visible terminal output formatting. It's independent of T002 (coordinator wiring) and can run in parallel after T001.\n\n## Scope\n**In:**\n- Replace stub implementations with formatted log() calls\n- Follow existing trigger event pattern (symbols, colors, agent_id)\n\n**Out:**\n- Protocol changes (T001)\n- Event emission logic (T002)\n\n## Changes\n\n### `src/infra/io/console_sink.py` — Exists\n\nReplace stub implementations with formatted output following existing `on_trigger_validation_*` pattern:\n\n```python\ndef on_trigger_code_review_started(self, trigger_type: str) -\u003e None:\n    \"\"\"Emitted when trigger code review begins.\"\"\"\n    self.log(\"→\", f\"[{trigger_type}] code_review_started\", agent_id=\"trigger\")\n\ndef on_trigger_code_review_skipped(self, trigger_type: str, reason: str) -\u003e None:\n    \"\"\"Emitted when code review is skipped.\"\"\"\n    self.log(\"◦\", f\"{Colors.MUTED}[{trigger_type}] code_review_skipped: {reason}{Colors.RESET}\", agent_id=\"trigger\")\n\ndef on_trigger_code_review_passed(self, trigger_type: str) -\u003e None:\n    \"\"\"Emitted when code review passes with no blocking findings.\"\"\"\n    self.log(\"✓\", f\"{Colors.GREEN}[{trigger_type}] code_review_passed{Colors.RESET}\", agent_id=\"trigger\")\n\ndef on_trigger_code_review_failed(self, trigger_type: str, blocking_count: int) -\u003e None:\n    \"\"\"Emitted when code review fails with blocking findings.\"\"\"\n    self.log(\"✗\", f\"{Colors.RED}[{trigger_type}] code_review_failed ({blocking_count} blocking){Colors.RESET}\", agent_id=\"trigger\")\n\ndef on_trigger_code_review_error(self, trigger_type: str, error: str) -\u003e None:\n    \"\"\"Emitted when code review execution fails.\"\"\"\n    self.log(\"✗\", f\"{Colors.RED}[{trigger_type}] code_review_error: {error}{Colors.RESET}\", agent_id=\"trigger\")\n```\n\n**Output format reference table:**\n| Event | Symbol | Output Format | Color |\n|-------|--------|---------------|-------|\n| started | → | [trigger_type] code_review_started | default |\n| skipped | ◦ | [trigger_type] code_review_skipped: {reason} | MUTED |\n| passed | ✓ | [trigger_type] code_review_passed | GREEN |\n| failed | ✗ | [trigger_type] code_review_failed ({blocking_count} blocking) | RED |\n| error | ✗ | [trigger_type] code_review_error: {error} | RED |\n\n## Acceptance Criteria\n- [ ] All 5 event handlers use log() (not log_verbose())\n- [ ] Symbols match existing trigger event pattern (→ ✓ ✗ ◦)\n- [ ] Colors applied correctly (MUTED for skipped, GREEN for passed, RED for failed/error)\n- [ ] agent_id=\"trigger\" used for all events\n- [ ] Message format matches table above\n\n## Verification\n```bash\n# Verify console output formatting manually\n# 1. Enable trigger code review in a test project\n# 2. Run mala to trigger code review\n# 3. Observe terminal output matches expected format\n\n# Or run existing trigger tests with verbose output to see formatting\nuv run pytest tests/integration/test_validation_triggers.py -v -s 2\u003e\u00261 | head -50\n```\n\n## Notes for Agent\n- Use log() not log_verbose() - for normal-mode visibility\n- Search for `on_trigger_validation_skipped` in console_sink.py for reference pattern\n- Colors class is already imported in console_sink.py\n- Symbols: → (start), ◦ (skip/neutral), ✓ (success), ✗ (failure/error)","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-12T04:07:34.528878709Z","created_by":"cyou","updated_at":"2026-01-12T04:15:44.977166072Z","closed_at":"2026-01-12T04:15:44.977166072Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-p42f.3","depends_on_id":"mala-p42f","type":"parent-child","created_at":"2026-01-12T04:07:34.529666769Z","created_by":"cyou"},{"issue_id":"mala-p42f.3","depends_on_id":"mala-p42f.1","type":"blocks","created_at":"2026-01-12T04:07:41.320477019Z","created_by":"cyou"}]}
{"id":"mala-p6h","title":"Allow sending messages to a specific agent","description":"Context:\n- Users should be able to send instructions/messages to individual agents mid-run.\n- Enables steering agents without restarting the entire run.\n\nScope:\n- In: CLI command to send message to agent by ID; agent receives and processes message.\n- Out: bidirectional chat; agent UI.\n\nAcceptance Criteria:\n- `mala send \u003cagent-id\u003e \"message\"` delivers message to running agent.\n- Agent acknowledges receipt in its log.\n- Clear error if agent not found or not running.\n\nTest Plan:\n- Manual: run mala, send message to agent, verify it appears in agent context.\n- Unit: mock IPC mechanism.","status":"closed","priority":4,"issue_type":"feature","created_at":"2025-12-27T01:15:17.401172887Z","updated_at":"2026-01-04T02:46:08.575596091Z","closed_at":"2026-01-04T02:46:08.575596091Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-p6h","depends_on_id":"mala-bdu","type":"blocks","created_at":"2025-12-27T01:15:21.10029576Z","created_by":"daemon"}]}
{"id":"mala-p9kv","title":"[Remediation] All tests pass: `uv run pytest -m \"unit or integration\"`","description":"## Context\nThis issue was auto-created by epic verification for epic `mala-bp3h`.\n\n## Unmet Criterion\nAll tests pass: `uv run pytest -m \"unit or integration\"`\n\n## Evidence\nNo evidence in the diff that the complete test suite has been run and verified to pass after all architectural changes\n\n## Severity\ncritical\n\n## Resolution\nAddress this criterion to unblock epic closure. When complete, close this issue.\n","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-01T03:35:30.889863765Z","created_by":"cyou","updated_at":"2026-01-01T03:38:42.263497558Z","closed_at":"2026-01-01T03:38:42.263497558Z","close_reason":"Closed","labels":["auto_generated","epic_remediation:mala-bp3h:328be6059ec286e5f33b19718d4e2ef96f684df091acda9dda1f5a6c92914008"],"dependencies":[{"issue_id":"mala-p9kv","depends_on_id":"mala-bp3h","type":"parent-child","created_at":"2026-01-01T03:37:52.072226819Z","created_by":"daemon"}]}
{"id":"mala-pc7u","title":"[Review] src/pipeline/agent_session_runner.py:557: [P3] Task says to wire ValidationSpec but only default lint_tools are used","description":"## Review Finding\n\nThis issue was auto-created from a P3 review finding.\n\n**Source issue:** mala-fdp1.10\n**Reviewer:** claude\n**Location:** src/pipeline/agent_session_runner.py:557\n\n## Details\n\nThe acceptance criteria states \"LintCache uses tool names from ValidationSpec\", but the callers in `agent_session_runner.py:557` and `run_coordinator.py:408` still instantiate `LintCache(repo_path=...)` without passing `lint_tools`. The `lint_tools` parameter exists but is unused in the actual integration points. This may be intentional if ValidationSpec wiring is deferred to another task, but the AC as written is not met.","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-02T15:43:51.267892994Z","created_by":"cyou","updated_at":"2026-01-02T17:05:46.327399774Z","closed_at":"2026-01-02T17:05:46.327399774Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_finding:24e893db0753","source:mala-fdp1.10"]}
{"id":"mala-pco2","title":"Move .mala-cache to ~/.config/mala instead of project root","description":"The .mala-cache directory currently lives in the project root, which pollutes the working directory. It contains lint_cache.json for caching validation run results.\n\nMove the cache to ~/.config/mala/.cache/ or similar XDG-compliant location:\n- Update cache path resolution to use ~/.config/mala/\n- Ensure the directory is created if it doesn't exist\n- Consider respecting XDG_CONFIG_HOME if set\n- Remove .mala-cache from .gitignore (if present) after migration\n- Clean up any references to the old location","status":"closed","priority":3,"issue_type":"task","created_at":"2025-12-30T02:46:26.436150057Z","created_by":"cyou","updated_at":"2025-12-30T03:15:25.503901177Z","closed_at":"2025-12-30T03:15:25.503901177Z","close_reason":"Closed"}
{"id":"mala-pd8v","title":"Clear pending_lint_commands on idle retry","description":"IdleTimeoutRetryPolicy clears pending_tool_ids on retry but leaves pending_lint_commands; stale tool IDs can accumulate and be misapplied after retries.\\n\\nGoal: clear pending_lint_commands alongside pending_tool_ids when preparing an idle retry.\\n\\nFiles: src/pipeline/idle_retry_policy.py (execute_iteration loop), src/pipeline/message_stream_processor.py (state).\\n\\nAcceptance: pending_lint_commands cleared on retry; add unit test to guard against regression.","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-05T01:26:52.877288576Z","created_by":"cyou","updated_at":"2026-01-05T01:31:56.101049687Z","closed_at":"2026-01-05T01:31:56.101049687Z","close_reason":"Closed"}
{"id":"mala-pid0","title":"Epic: Centralize config parsing/normalization","description":"## Summary\nCLI pulls in underscore helpers (`_parse_cerberus_args/_parse_cerberus_env`) directly, while derived fields are computed in run_config.py. This spreads configuration logic across layers and makes it easy for CLI vs env config to diverge.\n\n## Primary Files\n- `src/cli/cli.py:527-555`\n- `src/infra/io/config.py:32-67,340-395`\n- `src/orchestration/run_config.py:1-80`\n\n## Category\nAbstraction\n\n## Source\ncodex\n\n## Fix Approach\nIntroduce a `ResolvedConfig` builder in `infra/io/config.py` (or a small `config_resolver.py`) that consumes raw CLI overrides + env and returns a single validated object for orchestrator + event sink. CLI should not import private parse helpers.\n\n## Non-Goals\n- Adding configuration file format\n- Changing environment variable names\n\n## Acceptance Criteria\n- [ ] CLI no longer imports private `_parse_*` helpers\n- [ ] Run config/event sink uses a single resolved config object for derived fields\n\n## Test Plan\n- Unit tests for ResolvedConfig builder with various env/CLI combinations\n- Integration test verifying CLI and programmatic API produce equivalent configs\n\n## Agent Notes\nConsider making `_parse_cerberus_args` public as `parse_cerberus_args` if external callers need it.","status":"closed","priority":2,"issue_type":"epic","created_at":"2026-01-03T05:49:54.857653158Z","created_by":"cyou","updated_at":"2026-01-03T18:23:51.99303936Z","closed_at":"2026-01-03T18:23:51.99303936Z","close_reason":"Closed"}
{"id":"mala-pid0.1","title":"Create ResolvedConfig dataclass and builder in infra/io/config.py","description":"## Summary\nCreate a `ResolvedConfig` dataclass and a `build_resolved_config()` function that consolidates CLI overrides and environment variables into a single validated configuration object.\n\n## Primary Files\n- `src/infra/io/config.py` - Add ResolvedConfig class and builder function\n\n## Implementation Details\n1. Create `ResolvedConfig` dataclass that contains:\n   - All fields from `MalaConfig` that are needed by orchestrator\n   - All CLI override fields (cerberus_spawn_args, cerberus_wait_args, cerberus_env, review_timeout, max_epic_verification_retries)\n   - Derived fields currently computed in run_config.py (morph_disabled_reason, braintrust_disabled_reason)\n\n2. Create `build_resolved_config(base_config: MalaConfig, cli_overrides: CLIOverrides) -\u003e ResolvedConfig` that:\n   - Takes a base MalaConfig from environment\n   - Applies CLI overrides (parsing cerberus args/env using existing helpers)\n   - Computes derived fields (morph_disabled_reason, braintrust_disabled_reason)\n   - Returns a frozen, validated ResolvedConfig\n\n3. Consider making `_parse_cerberus_args` and `_parse_cerberus_env` public (remove underscore prefix) since they will be used by the builder\n\n4. Create a `CLIOverrides` dataclass or TypedDict to represent the raw CLI input that needs parsing\n\n## Acceptance Criteria\n- [ ] ResolvedConfig dataclass exists with all required fields\n- [ ] build_resolved_config function handles all current CLI override logic\n- [ ] Existing _parse_* helpers are reused (not duplicated)\n- [ ] Type annotations are complete\n\n## Test Plan\n- Unit test build_resolved_config with various combinations of base config and overrides","notes":"Quality gate failed: External review failed: src/cli/cli.py:569: [P1] review_enabled override lost in CLI\nLog: /home/cyou/.claude/projects/-home-cyou-mala/124c8577-ab8e-4406-af5d-7cfceb9d25eb.jsonl","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T05:53:50.239243228Z","created_by":"cyou","updated_at":"2026-01-03T14:51:21.778170189Z","closed_at":"2026-01-03T14:51:21.778170189Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-pid0.1","depends_on_id":"mala-pid0","type":"parent-child","created_at":"2026-01-03T17:57:23.344424661Z","created_by":"daemon"}]}
{"id":"mala-pid0.2","title":"Migrate CLI to use ResolvedConfig builder","description":"## Summary\nRefactor `src/cli/cli.py` to use the new `build_resolved_config()` function instead of directly importing and calling private `_parse_*` helpers.\n\n## Primary Files\n- `src/cli/cli.py` (lines 527-572) - Remove direct imports of private helpers, use ResolvedConfig builder\n\n## Implementation Details\n1. Remove direct imports of private helpers:\n   - Remove: `from src.infra.io.config import _parse_cerberus_args`\n   - Remove: `from src.infra.io.config import _normalize_cerberus_env, _parse_cerberus_env`\n\n2. Replace the manual config override logic (lines 519-572) with:\n   ```python\n   from src.infra.io.config import build_resolved_config, CLIOverrides\n   \n   cli_overrides = CLIOverrides(\n       review_timeout=review_timeout,\n       cerberus_spawn_args=cerberus_spawn_args,\n       cerberus_wait_args=cerberus_wait_args,\n       cerberus_env=cerberus_env,\n       max_epic_verification_retries=max_epic_verification_retries,\n       no_morph=no_morph,\n   )\n   \n   try:\n       resolved = build_resolved_config(config, cli_overrides)\n   except ValueError as exc:\n       log(\"✗\", str(exc), Colors.RED)\n       raise typer.Exit(1)\n   ```\n\n3. Update `cli_args` dict to use values from resolved config\n\n4. Pass `resolved` (or extract needed fields) to OrchestratorConfig and factory\n\n## Dependencies\nRequires: mala-pid0.1 (ResolvedConfig dataclass must exist)\n\n## Acceptance Criteria\n- [ ] CLI no longer imports `_parse_cerberus_args`, `_parse_cerberus_env`, `_normalize_cerberus_env`\n- [ ] All CLI override logic consolidated into single build_resolved_config call\n- [ ] Error handling preserved (user-friendly error messages)\n- [ ] CLI behavior unchanged (same exit codes, same effective configuration)\n\n## Test Plan\n- E2E test verifying CLI arguments produce expected config values\n- Manual test: `mala run --cerberus-spawn-args=\"--verbose\"` still works","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T05:54:02.9366325Z","created_by":"cyou","updated_at":"2026-01-03T15:08:17.557710122Z","closed_at":"2026-01-03T15:08:17.557710122Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-pid0.2","depends_on_id":"mala-pid0.1","type":"blocks","created_at":"2026-01-03T05:54:35.176679324Z","created_by":"daemon"},{"issue_id":"mala-pid0.2","depends_on_id":"mala-pid0","type":"parent-child","created_at":"2026-01-03T17:57:23.373786627Z","created_by":"daemon"}]}
{"id":"mala-pid0.3","title":"Consolidate run_config.py derived field logic into ResolvedConfig","description":"## Summary\nMove the derived field computation (morph_disabled_reason, braintrust_disabled_reason) from `run_config.py` into the ResolvedConfig builder so all configuration resolution happens in one place.\n\n## Primary Files\n- `src/orchestration/run_config.py` (lines 63-76) - Remove derived field computation\n- `src/infra/io/config.py` - Add derived field computation to ResolvedConfig builder (if not done in mala-pid0.1)\n\n## Implementation Details\n1. Analyze `build_event_run_config()` in run_config.py:\n   - Lines 63-71: Computes `morph_disabled_reason` based on morph_enabled, cli_args, and mala_config.morph_api_key\n   - Lines 73-76: Computes `braintrust_disabled_reason` based on braintrust_enabled\n\n2. Move this logic into ResolvedConfig:\n   - Add `morph_disabled_reason: str | None` field\n   - Add `braintrust_disabled_reason: str | None` field\n   - Compute these in `build_resolved_config()` based on input parameters\n\n3. Update `build_event_run_config()` to:\n   - Accept ResolvedConfig as parameter (or extract the needed fields)\n   - Use pre-computed disabled reasons from ResolvedConfig\n\n4. Consider whether `build_event_run_config()` and `build_run_metadata()` can be simplified or consolidated now that ResolvedConfig exists\n\n## Dependencies\nRequires: mala-pid0.1 (ResolvedConfig must exist)\nRequires: mala-pid0.2 (CLI must pass ResolvedConfig or equivalent)\n\n## Acceptance Criteria\n- [ ] morph_disabled_reason computed in ResolvedConfig, not run_config.py\n- [ ] braintrust_disabled_reason computed in ResolvedConfig, not run_config.py\n- [ ] run_config.py functions simplified to use pre-computed values\n- [ ] EventRunConfig and RunMetadata creation still works correctly\n\n## Test Plan\n- Unit test ResolvedConfig contains correct disabled reasons for various scenarios\n- Integration test verifying event sink receives correct disabled reasons","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T05:54:16.29521556Z","created_by":"cyou","updated_at":"2026-01-03T15:23:37.205666791Z","closed_at":"2026-01-03T15:23:37.205666791Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-pid0.3","depends_on_id":"mala-pid0.1","type":"blocks","created_at":"2026-01-03T05:54:35.242507276Z","created_by":"daemon"},{"issue_id":"mala-pid0.3","depends_on_id":"mala-pid0.2","type":"blocks","created_at":"2026-01-03T05:54:35.295762258Z","created_by":"daemon"},{"issue_id":"mala-pid0.3","depends_on_id":"mala-pid0","type":"parent-child","created_at":"2026-01-03T17:57:23.405887424Z","created_by":"daemon"}]}
{"id":"mala-pid0.4","title":"Add unit tests for ResolvedConfig builder","description":"## Summary\nAdd comprehensive unit tests for the `build_resolved_config()` function to ensure CLI and programmatic API produce equivalent configurations.\n\n## Primary Files\n- `tests/test_config.py` or `tests/test_resolved_config.py` (new or existing test file)\n\n## Implementation Details\n1. Test `build_resolved_config()` with various input combinations:\n   - Base config only (no CLI overrides)\n   - CLI overrides only (default base config)\n   - Combined base config + CLI overrides (CLI wins)\n   - Invalid CLI input (error handling)\n\n2. Test specific parsing scenarios:\n   - `cerberus_spawn_args`: shell-style parsing (quotes, escapes)\n   - `cerberus_env`: JSON format `{\"KEY\": \"value\"}`\n   - `cerberus_env`: comma-separated format `KEY=value,KEY2=value2`\n   - Invalid formats produce clear error messages\n\n3. Test derived field computation:\n   - `morph_disabled_reason` when --no-morph flag set\n   - `morph_disabled_reason` when MORPH_API_KEY not set\n   - `braintrust_disabled_reason` when BRAINTRUST_API_KEY not set\n\n4. Test idempotency and immutability:\n   - ResolvedConfig is frozen (immutable)\n   - Same inputs produce same outputs\n\n## Dependencies\nRequires: mala-pid0.1 (ResolvedConfig must exist to test)\n\n## Acceptance Criteria\n- [ ] Tests cover all CLI override fields\n- [ ] Tests cover all derived field scenarios\n- [ ] Tests cover error cases with clear assertions\n- [ ] Test coverage for config.py ≥85%\n\n## Test Plan\n- Run `uv run pytest tests/test_config.py -v` (or equivalent)","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T05:54:27.720326967Z","created_by":"cyou","updated_at":"2026-01-03T15:10:24.142110425Z","closed_at":"2026-01-03T15:10:24.142110425Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-pid0.4","depends_on_id":"mala-pid0.1","type":"blocks","created_at":"2026-01-03T05:54:35.353550221Z","created_by":"daemon"},{"issue_id":"mala-pid0.4","depends_on_id":"mala-pid0","type":"parent-child","created_at":"2026-01-03T17:57:23.466246894Z","created_by":"daemon"}]}
{"id":"mala-pm8q","title":"add architecture linting to run level validations","status":"tombstone","priority":2,"issue_type":"feature","created_at":"2026-01-05T03:33:59.121135328Z","created_by":"cyou","updated_at":"2026-01-06T22:52:10.761723406Z","deleted_at":"2026-01-06T22:52:10.761723406Z","deleted_by":"batch delete","delete_reason":"batch delete","original_type":"feature"}
{"id":"mala-pmpi","title":"[Review] 1 non-blocking finding from mala-ii08","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-ii08\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] CerberusConfig.timeout is ignored\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/orchestration/factory.py:386-403\n\nThe `CerberusConfig.timeout` field (default 300) is extracted from `mala.yaml` but never used. `DefaultReviewer` receives its timeout from `ReviewRunner` (via `MalaConfig.review_timeout`) during `__call__`. As a result, setting `cerberus.timeout` in `mala.yaml` has no effect unless the user also manually adds `--timeout` to `wait_args`.\n\nConsider injecting it into `wait_args` in `_create_code_reviewer` or updating `DefaultReviewer` to respect a configured default timeout.\n\n---\n\n\u003c!-- fp:7faa3d7040385742 --\u003e\n\u003c!-- review_finding:206f45a74611 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-10T22:23:29.459785052Z","created_by":"cyou","updated_at":"2026-01-10T22:45:13.227127541Z","closed_at":"2026-01-10T22:45:13.227127541Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-ii08"]}
{"id":"mala-pnil","title":"Fix import-linter violations from skxg refactor","description":"## Problem\n\nThe skxg architecture refactor introduced 3 import-linter contract violations:\n\n### Violations\n\n1. **Layered Architecture BROKEN**\n   - `src.infra.hooks.deadlock -\u003e src.domain.deadlock`\n   - `src.infra.agent_runtime -\u003e src.domain.deadlock`\n\n2. **SDK confined to infra BROKEN**\n   - Transitive import: `src.pipeline.agent_session_runner -\u003e src.infra.hooks -\u003e src.infra.hooks.locking -\u003e claude_agent_sdk`\n   - Transitive import: `src.pipeline.run_coordinator -\u003e src.infra.agent_runtime -\u003e src.infra.hooks.deadlock -\u003e claude_agent_sdk`\n\n3. **Hooks isolated BROKEN**\n   - `src.infra.hooks.deadlock -\u003e src.domain.deadlock`\n\n## Root Cause\n\nThe deadlock detection logic in `src/domain/deadlock.py` is imported by infra hooks, breaking the layered architecture (infra should not import domain).\n\n## Fix Approach\n\n1. Move `DeadlockDetector` protocol/interface to `src/core/protocols.py`\n2. Keep implementation in domain, but have hooks depend only on protocol\n3. Use lazy/local imports for SDK types in hooks to break transitive chain\n4. Inject deadlock detector via dependency injection rather than direct import\n\n## Acceptance Criteria\n\n- [ ] `uv run lint-imports` passes (all 10 contracts kept)\n- [ ] No behavioral changes to deadlock detection\n- [ ] Existing tests pass\n\n## References\n\n- Parent refactor: mala-skxg\n- Architecture review: plans/2026-01-03-architecture-review.md","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-04T04:07:54.869647435Z","created_by":"cyou","updated_at":"2026-01-04T04:39:37.442674364Z","closed_at":"2026-01-04T04:39:37.442674364Z","close_reason":"Closed"}
{"id":"mala-porw","title":"in the cli log, at the start of a run, enumerate all configs","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-07T16:56:19.207993472Z","created_by":"cyou","updated_at":"2026-01-07T18:48:48.308704063Z","closed_at":"2026-01-07T18:48:48.308704063Z","close_reason":"Closed"}
{"id":"mala-pqtl","title":"remove unnecessary unit tests","status":"closed","priority":2,"issue_type":"feature","created_at":"2026-01-05T01:39:22.183128933Z","created_by":"cyou","updated_at":"2026-01-06T02:58:03.81302237Z","closed_at":"2026-01-06T02:58:03.81302237Z","close_reason":"Closed"}
{"id":"mala-psmf","title":"[Review] 2 non-blocking findings from mala-b79o.2","description":"## Review Findings\n\nThis issue consolidates 2 non-blocking findings from code review.\n\n**Source issue:** mala-b79o.2\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Remove dead code: _log_message method and logger field\n\n**Priority:** P2\n**Reviewer:** claude\n**Location:** src/domain/validation/spec_executor.py:439-449\n\nThe second commit removed all `_log_message()` calls but left the `_log_message()` method itself (lines 439-449) and the `logger: LoggerPort | None = None` field in `ExecutorConfig` (line 55). These are now dead code since no code path calls `_log_message()` anymore. The `LoggerPort` import in the TYPE_CHECKING block is also unused.\n\n---\n\n### Finding 2: [P2] Preserve step logging when event_sink is None\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/domain/validation/spec_executor.py:149-152\n\nAfter removing `_log_message` calls, the step status is emitted only through `event_sink`. If current callers keep `event_sink=None` (as noted in the goal), there is no output for running/skip/pass/fail, making validation appear silent or hung. Consider keeping `_log_message` as a fallback when no sink is provided or wiring a default sink that logs.\n\n---\n","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T15:46:06.277525552Z","created_by":"cyou","updated_at":"2026-01-03T16:05:42.518687029Z","closed_at":"2026-01-03T16:05:42.518687029Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_findings:1cd6bea274f1","source:mala-b79o.2"]}
{"id":"mala-pt5b","title":"[Review] 1 non-blocking finding from mala-eeof","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-eeof\n**Highest priority:** P3\n\n---\n\n### Finding 1: [P3] Log agent_id instead of issue_id in on_agent_completed\n\n**Priority:** P3\n**Reviewer:** codex\n**Location:** src/infra/io/console_sink.py:138-143\n\n`on_agent_completed` now formats the message as \"Agent {issue_id} completed...\" while it receives both `agent_id` and `issue_id`. This prints the issue ID under an “Agent” label, so log readers can’t correlate the message to the actual agent that finished. This shows up any time agents complete and can confuse troubleshooting. Use `agent_id` in the message, or change the label to “Issue” if you intend to log `issue_id`.\n\n---\n","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-03T16:48:01.300600518Z","created_by":"cyou","updated_at":"2026-01-03T16:49:32.274939096Z","closed_at":"2026-01-03T16:49:32.274939096Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_findings:224fa1e49f1d","source:mala-eeof"]}
{"id":"mala-ptio","title":"Epic: mala init command","description":"Implement the `mala init` CLI command for interactive config generation.\n\n**Spec**: docs/2026-01-10-mala-init-spec.md\n**Plan**: plans/2026-01-10-mala-init-plan.md\n\n## Features\n- Numbered menu UI (1-5): presets + custom mode\n- Custom command entry for 7 standard commands\n- File backup to `.bak` before overwrite\n- Validation via helper function before writing\n- `--dry-run` flag support\n- Success output with file contents and next steps tip\n\n## Requirements Coverage\n- R1: Command availability\n- R2: Preset selection UI\n- R3: Custom command entry\n- R4: File backup\n- R5: Output validation\n- R6: Dry-run support\n- R7: Success output","status":"closed","priority":1,"issue_type":"epic","created_at":"2026-01-10T19:57:34.466784354Z","created_by":"cyou","updated_at":"2026-01-10T23:08:02.924817151Z","closed_at":"2026-01-10T23:08:02.924817151Z","close_reason":"Closed"}
{"id":"mala-ptio.1","title":"[T001] [integration-path-test] Skeleton + Validation Helper + Integration Tests","description":"## Goal\nCreate the foundation for the `mala init` command: validation helper, command stub, and failing integration tests that will drive implementation.\n\n## Context\nThis task follows TDD ordering: create the skeleton and integration tests first, then implementation makes them pass. The validation helper is simple (2-line wrapper) so we implement it here to avoid a separate task.\n\n## Scope\n- **In**: validation helper + unit tests, command stub, integration test file\n- **Out**: full command implementation (T002)\n\n## Changes\n\n### `src/domain/validation/config_loader.py` — Exists\nAdd `validate_generated_config(data: dict) -\u003e None` helper:\n```python\ndef validate_generated_config(data: dict[str, Any]) -\u003e None:\n    \"\"\"Validates a dictionary against the config schema and semantic rules.\"\"\"\n    config = ValidationConfig.from_dict(data)  # raises ConfigError\n    _validate_config(config)                   # raises ConfigError\n```\n\n### `tests/unit/domain/validation/test_config_loader.py` — Exists\nAdd unit tests for `validate_generated_config`:\n- Valid preset dict: `{\"preset\": \"python-uv\"}` → no error\n- Valid custom dict: `{\"commands\": {\"test\": \"pytest\"}}` → no error\n- Empty commands dict: `{\"commands\": {}}` → raises ConfigError\n\n### `src/cli/cli.py` — Exists\nAdd stub `init` command:\n```python\n@app.command()\ndef init(dry_run: bool = typer.Option(False, \"--dry-run\", help=\"Preview without writing\")) -\u003e None:\n    \"\"\"Initialize mala.yaml configuration interactively.\"\"\"\n    raise typer.Exit(1)  # Stub - to be implemented in T002\n```\n\n### `tests/integration/cli/test_init.py` — **New**\nCreate integration tests using `CliRunner(mix_stderr=False)`:\n\n**Test scenarios (all should fail initially except help):**\n1. `test_dry_run_preset` — Input \"1\", `--dry-run`. Assert exit 0, parse stdout YAML as `{\"preset\": \"python-uv\"}`\n2. `test_custom_flow` — Input \"5\", provide commands. Assert exit 0, verify `mala.yaml` content\n3. `test_backup` — Create existing `mala.yaml`. Assert `.bak` created with old content\n4. `test_validation_fail` — Input \"5\", skip all commands. Assert exit 1, error in stderr\n5. `test_invalid_input_loop` — Input \"0\\n6\\nabc\\n1\". Assert exit 0, valid preset output\n6. `test_help` — Run `--help`. Assert exit 0, \"--dry-run\" in output (should pass immediately)\n7. `test_dry_run_no_backup` — Existing file + `--dry-run`. Assert file unchanged\n\n## Acceptance Criteria\n- [ ] `validate_generated_config` passes unit tests\n- [ ] `mala init --help` works (exit 0, shows --dry-run)\n\n## Verification\n```bash\nuv run pytest tests/unit/domain/validation/test_config_loader.py -k validate_generated -v\nuv run pytest tests/integration/cli/test_init.py::test_help -v  # Should pass\nuv run pytest tests/integration/cli/test_init.py -v  # Most will fail (expected)\n```\n\n## Notes for Agent\n- Use `CliRunner(mix_stderr=False)` to separate stdout/stderr in tests\n- Prompts route to stderr, YAML routes to stdout\n- Test assertions should parse YAML from output, not match exact strings\n- `click.exceptions.Abort` needs to be imported for EOF handling","notes":"Quality gate failed: Interrupted: Interrupted by user\nLog: /home/cyou/.claude/projects/-home-cyou-mala/dbb1310d-4c76-4847-a194-073acd95f65c.jsonl","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-10T19:57:58.699648756Z","created_by":"cyou","updated_at":"2026-01-10T21:30:32.286161649Z","closed_at":"2026-01-10T21:30:32.286161649Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-ptio.1","depends_on_id":"mala-ptio","type":"parent-child","created_at":"2026-01-10T19:57:58.700457519Z","created_by":"cyou"}]}
{"id":"mala-ptio.2","title":"[T002] Implement menu display + input flows","description":"## Goal\nImplement the menu display and input gathering flows (preset selection + custom command prompts).\n\n## Context\nT001 created the validation helper and failing integration tests. This task implements the user interaction logic. File operations are handled in T003.\n\n## Scope\n- **In**: Menu display, input validation loop, preset flow, custom command prompts\n- **Out**: File operations, exception handling (T003)\n\n## Changes\n\n### `src/cli/cli.py` — Exists\nImplement the input gathering portion of the init command:\n\n**Menu display:**\n- Get presets from `PresetRegistry().list_presets()` → `['go', 'node-npm', 'python-uv', 'rust']`\n- Display numbered menu to stderr: `1) go  2) node-npm  3) python-uv  4) rust  5) custom`\n\n**Input validation loop:**\n```python\ndef _prompt_menu_selection(presets: list[str]) -\u003e int:\n    \"\"\"While-loop prompt returning 1-5.\"\"\"\n    while True:\n        typer.echo(\"Select configuration:\", err=True)\n        for i, preset in enumerate(presets, 1):\n            typer.echo(f\"  {i}) {preset}\", err=True)\n        typer.echo(f\"  {len(presets) + 1}) custom\", err=True)\n        \n        choice = typer.prompt(\"Enter choice (1-5)\", default=\"\")\n        try:\n            num = int(choice)\n            if 1 \u003c= num \u003c= len(presets) + 1:\n                return num\n        except ValueError:\n            pass\n        typer.echo(\"Invalid choice, please enter 1-5:\", err=True)\n```\n\n**Preset flow (options 1-4):**\n- Construct `{\"preset\": presets[selection - 1]}`\n\n**Custom flow (option 5):**\n```python\ndef _prompt_custom_commands() -\u003e dict[str, str]:\n    \"\"\"Prompt for 7 commands, return non-empty ones.\"\"\"\n    commands = {}\n    command_names = [\"setup\", \"build\", \"test\", \"lint\", \"format\", \"typecheck\", \"e2e\"]\n    for name in command_names:\n        value = typer.prompt(f\"Command for '{name}' (Enter to skip)\", default=\"\", show_default=False)\n        value = value.strip()\n        if value:\n            commands[name] = value\n    return commands\n```\n- Construct `{\"commands\": commands_dict}` (empty values omitted)\n\n**Call validation:**\n- After building config dict, call `validate_generated_config(data)`\n- If validation fails, ConfigError propagates (caught in T003)\n\n## Acceptance Criteria\n- [ ] Menu displays all 4 presets + custom option to stderr\n- [ ] Invalid input (0, 6, \"abc\") re-prompts until valid 1-5 received\n- [ ] Preset selection (1-4) builds correct dict; custom (5) prompts for 7 commands and builds commands dict\n\n## Verification\n```bash\n# Manual verification - run interactively\nuv run mala init --dry-run\n# Select option 1, verify it attempts to output (may fail on file ops - that's T003)\n\n# Integration tests for input flows (may still fail on file ops)\nuv run pytest tests/integration/cli/test_init.py::test_invalid_input_loop -v\n```\n\n## Notes for Agent\n- Prompts MUST use `typer.echo(..., err=True)` to route to stderr\n- `typer.prompt()` for input gathering\n- Import PresetRegistry from the validation module\n- Whitespace-only input should be treated as empty after strip\n- Don't implement file operations or exception handling - that's T003","notes":"Quality gate failed: External review failed: tests/integration/cli/test_init.py:101: [P1] Fix xfail tests asserting success while CLI exits 1\nLog: /home/cyou/.claude/projects/-home-cyou-mala/77bf8de1-cc49-4272-8eec-37b2fead14bf.jsonl","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-10T19:58:21.164877496Z","created_by":"cyou","updated_at":"2026-01-10T22:52:50.516164868Z","closed_at":"2026-01-10T22:52:50.516164868Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-ptio.2","depends_on_id":"mala-ptio","type":"parent-child","created_at":"2026-01-10T19:58:21.16552805Z","created_by":"cyou"},{"issue_id":"mala-ptio.2","depends_on_id":"mala-ptio.1","type":"blocks","created_at":"2026-01-10T19:58:26.167195612Z","created_by":"cyou"}]}
{"id":"mala-ptio.3","title":"[T003] Implement file operations + exception handling","description":"## Goal\nComplete the init command by implementing file operations, exception handling, and output routing. All integration tests must pass.\n\n## Context\nT001 created the foundation (validation helper, stubs, failing tests). T002 implemented input gathering (menu, prompts). This task completes the command and verifies everything works.\n\n## Scope\n- **In**: File backup, file write, dry-run logic, exception handling, output stream routing\n- **Out**: Input gathering logic (done in T002)\n\n## Changes\n\n### `src/cli/cli.py` — Exists\nComplete the init command with file operations and exception handling:\n\n**YAML generation:**\n```python\nimport yaml\nyaml_content = yaml.dump(data, default_flow_style=False, sort_keys=True)\n```\n\n**File operations:**\n```python\nimport shutil\nfrom pathlib import Path\n\ndef _write_with_backup(path: Path, content: str) -\u003e None:\n    \"\"\"Backup existing file and write new content.\"\"\"\n    if path.exists():\n        backup_path = path.with_suffix(\".yaml.bak\")\n        shutil.copy(path, backup_path)\n    path.write_text(content)\n```\n\n**Dry-run logic:**\n```python\nconfig_path = Path(\"mala.yaml\")\nif not dry_run:\n    _write_with_backup(config_path, yaml_content)\n```\n\n**Success output (to stdout):**\n```python\ntyper.echo(yaml_content)  # YAML to stdout\ntyper.echo()  # blank line\ntyper.echo(\"Run `mala run` to start\")  # tip to stdout\n```\n\n**Exception handling (wrap entire command body):**\n```python\nimport click.exceptions\n\n@app.command()\ndef init(dry_run: bool = typer.Option(False, \"--dry-run\", help=\"Preview without writing\")) -\u003e None:\n    \"\"\"Initialize mala.yaml configuration interactively.\"\"\"\n    try:\n        # ... menu selection from T002 ...\n        # ... validation call from T002 ...\n        # ... file operations ...\n        # ... success output ...\n    except ConfigError as e:\n        typer.echo(f\"Error: {e}\", err=True)\n        raise typer.Exit(1)\n    except OSError as e:\n        typer.echo(f\"Error writing file: {e}\", err=True)\n        raise typer.Exit(1)\n    except KeyboardInterrupt:\n        raise typer.Exit(130)\n    except click.exceptions.Abort:\n        raise typer.Exit(1)\n```\n\n**Required imports at top of cli.py:**\n```python\nimport shutil\nimport yaml\nimport click.exceptions\nfrom pathlib import Path\n```\n\n## Wiring Map\n- Config dict → yaml.dump() → yaml_content → stdout + file write\n\n## Acceptance Criteria\n- [ ] `--dry-run` prints YAML to stdout without creating/modifying any files\n- [ ] Existing `mala.yaml` backed up to `.bak` before overwrite (overwrites existing .bak)\n- [ ] Exit codes correct: 0 (success), 1 (validation/file error), 130 (Ctrl+C)\n\n## Verification\n**Final task verification — ALL tests must pass:**\n```bash\n# All integration tests\nuv run pytest tests/integration/cli/test_init.py -v\n\n# All unit tests for validation helper\nuv run pytest tests/unit/domain/validation/test_config_loader.py -k validate_generated -v\n\n# Full test suite (confirm no regressions)\nuv run pytest -m \"unit or integration\" --cov-fail-under=72\n```\n\n## Notes for Agent\n- Import `click.exceptions` (not just `click`) to catch Abort\n- File backup uses `.yaml.bak` suffix (not just `.bak`)\n- shutil.copy() overwrites existing backup silently\n- Both dry-run and non-dry-run print YAML to stdout (per spec R7)\n- The tip \"Run `mala run` to start\" goes to stdout, not stderr\n- KeyboardInterrupt must be caught explicitly - typer/click defaults vary","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-10T20:02:24.243236865Z","created_by":"cyou","updated_at":"2026-01-10T23:05:17.300092916Z","closed_at":"2026-01-10T23:05:17.300092916Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-ptio.3","depends_on_id":"mala-ptio","type":"parent-child","created_at":"2026-01-10T20:02:24.24389732Z","created_by":"cyou"},{"issue_id":"mala-ptio.3","depends_on_id":"mala-ptio.2","type":"blocks","created_at":"2026-01-10T20:02:28.456227463Z","created_by":"cyou"}]}
{"id":"mala-pvdn","title":"[Review] 2 non-blocking findings from mala-78np","description":"## Review Findings\n\nThis issue consolidates 2 non-blocking findings from code review.\n\n**Source issue:** mala-78np\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Hash legacy fingerprints for deduplication compatibility\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/orchestration/review_tracking.py:91-96\n\nThe `_extract_existing_fingerprints` function returns plain legacy strings (e.g., `file.py:10:10:title`), but the deduplication logic in `create_review_tracking_issues` compares current findings against hex hashes. This mismatch causes findings previously tracked in legacy format to be treated as new, leading to duplicate entries in the tracking issue. Legacy matches should be hashed (e.g., `hashlib.sha256(m.encode()).hexdigest()[:16]`) before being returned to ensure they can be matched against new findings.\n\n---\n\n### Finding 2: [P2] Update test to expect hashed legacy fingerprints\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** tests/test_orchestration_helpers.py:424-433\n\nThe test `test_extracts_legacy_fingerprints` incorrectly asserts that legacy fingerprints are extracted as plain strings. It should be updated to verify that they are converted to hex hashes, matching the format required by the deduplication logic in `create_review_tracking_issues`.\n\n---\n","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T06:50:47.843572955Z","created_by":"cyou","updated_at":"2026-01-03T07:05:39.560566363Z","closed_at":"2026-01-03T07:05:39.560566363Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_findings:22f550d7fd28","source:mala-78np"]}
{"id":"mala-pwmg","title":"add fixer sessions + cerberus reviews to the logs search","status":"tombstone","priority":2,"issue_type":"feature","created_at":"2026-01-06T03:09:34.985529431Z","created_by":"cyou","updated_at":"2026-01-06T22:52:10.761723406Z","deleted_at":"2026-01-06T22:52:10.761723406Z","deleted_by":"batch delete","delete_reason":"batch delete","original_type":"feature"}
{"id":"mala-pyug","title":"[Review] src/domain/validation/config.py:144-162: [P3] YamlCoverageConfig validates in both from_dict and __post_init__","description":"## Review Finding\n\nThis issue was auto-created from a P3 review finding.\n\n**Source issue:** mala-fdp1.1\n**Reviewer:** claude\n**Location:** src/domain/validation/config.py:144-162\n\n## Details\n\nThe `file` field is validated for emptiness in both `from_dict()` (line 192-196 type check, but not emptiness) and `__post_init__()` (line 161-162). However, direct instantiation like `YamlCoverageConfig(format=\"xml\", file=\"\", threshold=80)` triggers only `__post_init__` validation. The `from_dict` validates type but relies on `__post_init__` for the emptiness check. This dual validation path works but may cause confusion if someone expects `from_dict` to catch all validation errors before object creation.","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-02T03:19:13.867324171Z","created_by":"cyou","updated_at":"2026-01-02T03:50:01.005058307Z","closed_at":"2026-01-02T03:50:01.005058307Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_finding:2759f6fc5b47","source:mala-fdp1.1"]}
{"id":"mala-pzrg","title":"[P2] E2E test now allows missing run validation metadata","description":"## Review Finding\n\n**Priority:** P2\n**Trigger:** run_end\n**Baseline:** ddc86bb..3deec8ab\n**Location:** tests/e2e/test_e2e.py:414-426\n\n---\n\n`tests/e2e/test_e2e.py` changed from requiring `run_validation.coverage_percent` to only asserting it when `run_validation` exists. This can hide regressions where `run_validation` is unexpectedly omitted (e.g., run_end trigger not firing, metadata schema drift), while the test still passes.\n\nGiven the surrounding test name and intent (“run real fixture” integration), this reduces coverage of the run_end validation behavior introduced in this diff.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-11T02:30:41.944730809Z","created_by":"cyou","updated_at":"2026-01-11T02:37:43.137747709Z","closed_at":"2026-01-11T02:37:43.137747709Z","close_reason":"Closed","labels":["auto_generated","cumulative-review","fp:6272f037c6d0afba","review_finding","trigger:run_end"]}
{"id":"mala-q1y","title":"Quality gate: require commit created during current run","description":"Context:\n- src/quality_gate.py: check_commit_exists() accepts any bd-\u003cissue_id\u003e commit in the last 30 days; orchestrator uses this for gating.\n- This can accept stale commits from earlier runs, allowing the gate to pass without a new commit in the current run.\n- Needs verification: reproduce by reopening an issue that already has a bd-\u003cid\u003e commit and confirm the gate passes without a new commit.\n\nScope:\n- In: capture a per-run baseline (timestamp or commit hash) and require the matching commit to be newer than that baseline; update gate/orchestrator and tests.\n- Out: changing commit message format or redesigning the retry loop.\n\nAcceptance Criteria:\n- Gate fails when only pre-run commits exist for the issue.\n- Gate passes when a new bd-\u003cissue_id\u003e commit is created after run start.\n- Tests cover stale vs new commit behavior.\n\nTest Plan:\n- TDD: add failing test(s) (tests/test_orchestrator.py or tests/test_quality_gate.py) that simulate stale commit acceptance, implement fix, run targeted pytest.\n\nNotes for Agent:\n- Baseline should be captured once per run to avoid false positives in retries.","status":"closed","priority":1,"issue_type":"bug","created_at":"2025-12-26T21:43:52.640540776Z","updated_at":"2025-12-26T22:52:42.793516615Z","closed_at":"2025-12-26T22:52:42.793516615Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-q1y","depends_on_id":"mala-53v","type":"blocks","created_at":"2025-12-26T21:43:58.82161634Z","created_by":"daemon"}]}
{"id":"mala-q24","title":"Pretty print tool call arguments in logs","description":"Currently log_tool() in src/logging/console.py only shows tool name and a brief description. Tool call arguments (like file paths, search patterns, etc.) are not displayed.\n\nWhen --no-truncate is used, tool arguments MUST be pretty-printed in the logs:\n- Add 'arguments' parameter to log_tool()\n- When truncation is disabled, format arguments nicely (e.g., key=value pairs)\n- Use multi-line formatting for complex nested arguments (JSON)\n- When truncation is enabled, can show abbreviated/truncated version\n\nThis ties into mala-59n which added the --no-truncate option. The full output mode should include full tool arguments for debugging.\n\nReference: src/logging/console.py:103","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-26T17:12:19.753573776Z","updated_at":"2025-12-26T17:16:20.717304732Z","closed_at":"2025-12-26T17:16:20.717304732Z","close_reason":"Closed"}
{"id":"mala-q28l","title":"add config cli command, q\u0026a style","status":"tombstone","priority":2,"issue_type":"feature","created_at":"2026-01-06T05:45:43.809545983Z","created_by":"cyou","updated_at":"2026-01-06T22:52:10.761723406Z","deleted_at":"2026-01-06T22:52:10.761723406Z","deleted_by":"batch delete","delete_reason":"batch delete","original_type":"feature"}
{"id":"mala-q2af","title":"[Review] tests/test_event_sink.py:511-605: [P3] Missing unit test for `on_gate_result` issue_id propagation","description":"## Review Finding\n\nThis issue was auto-created from a P3 review finding.\n\n**Source issue:** mala-40pg.3\n**Reviewer:** gemini\n**Location:** tests/test_event_sink.py:511-605\n\n## Details\n\nThe commit adds comprehensive tests for `issue_id` propagation in most updated methods, but `on_gate_result` is missing a corresponding test case in `tests/test_event_sink.py`.","notes":"Quality gate failed: External review failed: spawn failed: Artifact written: /home/cyou/.claude/projects/-home-cyou-mala/cerberus/d2632f15-6bff-41ff-b973-41ed15b71162/latest.md\nReview gate already active (status: pending, age: 48s)\nUse /home/cyou/.claude/plugins/cache/cerberus/cerberus/1.10.6/bin/review-gate resolve to complete the existing gate first.\nLog: /home/cyou/.claude/projects/-home-cyou-mala/d2632f15-6bff-41ff-b973-41ed15b71162.jsonl","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-01T19:41:09.716421152Z","created_by":"cyou","updated_at":"2026-01-01T23:55:29.976592003Z","closed_at":"2026-01-01T23:53:06.940105448Z","close_reason":"Closed","labels":["auto_generated","needs-followup","review_finding","review_finding:d7263695bc3a","source:mala-40pg.3"],"dependencies":[{"issue_id":"mala-q2af","depends_on_id":"mala-apsz","type":"blocks","created_at":"2026-01-01T23:32:33.361886548Z","created_by":"daemon"}]}
{"id":"mala-q2xb","title":"[Review] 5 non-blocking findings from mala-c90n","description":"## Review Findings\n\nThis issue consolidates 5 non-blocking findings from code review.\n\n**Source issue:** mala-c90n\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Missing error handling for update_issue_async failure\n\n**Priority:** P2\n**Reviewer:** claude\n**Location:** src/orchestration/review_tracking.py:207-211\n\nThe `update_issue_async` call on lines 207-211 returns a boolean indicating success/failure, but the result is not checked. If updating the title/priority fails after successfully updating the description, the issue will have an updated description but a stale title and priority, creating an inconsistent state. Consider checking the return value and logging a warning on failure.\n\n---\n\n### Finding 2: [P3] Missing tests for new helper functions\n\n**Priority:** P3\n**Reviewer:** claude\n**Location:** tests/test_orchestration_helpers.py:320\n\nThe new helper functions `_get_finding_fingerprint`, `_extract_existing_fingerprints`, and `_update_header_count` have no direct unit tests. These are the core building blocks for individual finding dedup and count updates. While they're exercised indirectly through integration tests, direct unit tests would catch regressions in the regex patterns and fingerprint logic. Consider adding tests for edge cases like malformed fingerprints or descriptions without the expected header.\n\n---\n\n### Finding 3: [P2] Check return value of update_issue_async\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/orchestration/review_tracking.py:208-212\n\nThe call to `update_issue_async` on line 208 returns a boolean indicating success, but it is ignored. For consistency with the fix for Finding 1 (which now checks `update_issue_description_async`), this result should also be checked and handled, for example by logging a warning if the title/priority update fails.\n\n---\n\n### Finding 4: [P2] Over-eager regex in _update_header_count\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/orchestration/review_tracking.py:84-88\n\nThe regex `\\d+ non-blocking findings?` on line 85 is too broad. If a finding's title or body contains a similar pattern (e.g., \"Found 2 non-blocking findings in this file\"), it will be incorrectly replaced during the update. Consider making the regex more specific to the header line (e.g., by matching \"consolidates \\d+ non-blocking finding\").\n\n---\n\n### Finding 5: [P2] Fragile fingerprint extraction regex\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/orchestration/review_tracking.py:76-77\n\nThe regex `r\"\u003c!-- fp:(.+?) --\u003e\"` on line 76 is fragile. The `.+?` non-greedy match will terminate prematurely if a finding title contains the HTML comment closer ` --\u003e` (e.g., in a code snippet). Additionally, it won't match fingerprints spanning multiple lines if a title contains a newline. Consider using a more robust pattern or escaping the title.\n\n---\n","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T06:42:13.004870585Z","created_by":"cyou","updated_at":"2026-01-03T06:46:56.273398165Z","closed_at":"2026-01-03T06:46:56.273398165Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_findings:79ef7ab82dc2","source:mala-c90n"]}
{"id":"mala-q7vx","title":"[Review] 1 non-blocking finding from mala-qh40","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-qh40\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Treat `eligible_for_close` as a strict boolean when deriving reason\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/infra/epic_verifier.py:633-636\n\n`_get_ineligibility_reason` uses a truthiness check:\n\n```py\nif row.get(\"eligible_for_close\"):\n    return None\n```\n\nIf the external `bd epic status --json` ever emits non-boolean values (e.g. the string `\"false\"`, or `\"0\"`), this will incorrectly treat an ineligible epic as eligible and return `None`, which means the CLI won’t show the new ineligibility reason. Since this method is specifically meant to surface eligibility failures, it should interpret `eligible_for_close` strictly (e.g. `is True` / normalize to a real bool) rather than by truthiness.\n\n---\n\n\u003c!-- fp:63e0c269454eee6f --\u003e\n\u003c!-- review_finding:ebf40e139573 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-06T19:55:38.303498888Z","created_by":"cyou","updated_at":"2026-01-06T19:57:58.984331984Z","closed_at":"2026-01-06T19:57:58.984331984Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-qh40"]}
{"id":"mala-qh40","title":"in epic verify if it does not pass eligibility checks, show the checks it does not meet","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-06T15:26:51.620204518Z","created_by":"cyou","updated_at":"2026-01-06T19:55:38.272136086Z","closed_at":"2026-01-06T19:55:38.272136086Z","close_reason":"Closed"}
{"id":"mala-qhny","title":"[Review] src/orchestration/orchestrator.py:954: [P1] Debug logging is cut off before run-level validation and finalization","description":"## Review Finding\n\nThis issue was auto-created from a P3 review finding.\n\n**Source issue:** mala-dhry\n**Reviewer:** gemini\n**Location:** src/orchestration/orchestrator.py:954\n\n## Details\n\nThe `run_metadata.cleanup()` call in `MalaOrchestrator.run` is placed in a `finally` block that only covers `_run_main_loop`. This causes debug logging to be disabled and the log file to be closed before run-level validation, issue committing, and final metadata saving occur. Since these finalization steps are critical for troubleshooting (e.g., understanding why run-level validation failed or if issues were correctly committed), they should be captured in the debug log. Moving the `cleanup()` call to the end of the `run` method or wrapping the entire method in a `try...finally` would resolve this.","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-01T22:54:29.961648244Z","created_by":"cyou","updated_at":"2026-01-02T01:48:15.376251199Z","closed_at":"2026-01-02T01:48:15.376251199Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_finding:dd0929635c04","source:mala-dhry"],"dependencies":[{"issue_id":"mala-qhny","depends_on_id":"mala-cw6h","type":"blocks","created_at":"2026-01-01T23:03:58.870671665Z","created_by":"daemon"}]}
{"id":"mala-qhrl","title":"[Review] src/pipeline/issue_execution_coordinator.py:269-284: [P2] Unused utility method `abort_active_tasks` in coordinator","description":"## Review Finding\n\nThis issue was auto-created from a P2 review finding.\n\n**Source issue:** mala-cavk.3\n**Reviewer:** gemini\n**Location:** src/pipeline/issue_execution_coordinator.py:269-284\n\n## Details\n\nThe `IssueExecutionCoordinator.abort_active_tasks` method is defined as a utility but is not used by the current `MalaOrchestrator` implementation, which continues to use its own `_abort_active_tasks` logic. This contributes to code duplication and dead code.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-01T22:23:07.451805927Z","created_by":"cyou","updated_at":"2026-01-02T02:02:40.212012256Z","closed_at":"2026-01-02T02:02:40.212012256Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_finding:c49ef24c6fc6","source:mala-cavk.3"],"dependencies":[{"issue_id":"mala-qhrl","depends_on_id":"mala-i8ke","type":"blocks","created_at":"2026-01-01T23:31:14.986865876Z","created_by":"daemon"},{"issue_id":"mala-qhrl","depends_on_id":"mala-l072","type":"blocks","created_at":"2026-01-01T23:32:50.801086863Z","created_by":"daemon"}]}
{"id":"mala-qt6d","title":"Fix async WAITING event emission in locking MCP","description":"Problem: lock_acquire emits WAITING via emit_lock_event synchronously; when emit_lock_event is async (DeadlockMonitor.handle_event), the coroutine is never awaited/scheduled so WAITING is dropped and deadlock detection misses waits.\n\nAcceptance Criteria:\n- emit_lock_event supports async callbacks for WAITING (await or schedule safely) without blocking lock_acquire.\n- Errors from async emission are handled/logged without crashing tool handlers.\n- Unit test covers async emit_lock_event for WAITING and verifies it is awaited/scheduled.\n- (If needed) integration test proves WAITING reaches DeadlockMonitor via async callback.","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-04T21:50:52.732742976Z","created_by":"cyou","updated_at":"2026-01-04T23:20:11.77757832Z","closed_at":"2026-01-04T23:20:11.77757832Z","close_reason":"Closed"}
{"id":"mala-r2ll","title":"Move epic verification to run after each issue is closed","description":"Currently epic verification runs at the end of a mala run. It should be moved to run incrementally after each issue is closed, so verification happens sooner and problems are caught earlier.\n\nScope:\n- Move epic verification trigger from end-of-run to post-issue-close hook\n- Verify only the epic(s) affected by the closed issue\n- Maintain existing verification logic and event sink integration\n- Update orchestrator to call verification after issue close instead of batch at end\n\nAcceptance Criteria:\n- Epic verification runs immediately after an issue is closed\n- Only affected epics are verified (not all eligible)\n- Verification results are still emitted via event sink\n- No duplicate verifications for the same epic in a single run","notes":"Quality gate failed: External review failed: CLAUDE_SESSION_ID missing; must be provided by agent session id\nLog: /home/cyou/.claude/projects/-home-cyou-mala/017aff67-c1d3-4098-9b74-1d01ac1ca6e9.jsonl","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-30T23:11:43.855833054Z","created_by":"cyou","updated_at":"2025-12-31T03:40:00.802865075Z","closed_at":"2025-12-31T03:40:00.802865075Z","close_reason":"Closed","labels":["needs-followup"]}
{"id":"mala-rba","title":"Add timeout to MCP server subprocess execution in validation","description":"## Context\nDuring a validation run, a pytest test spawned a claude agent that uses the morphllm MCP server. The MCP server's ripgrep subprocess got stuck searching from `/` (root filesystem) instead of the project directory, running for 3+ hours before being manually killed.\n\n## Root Cause\n1. `get_mcp_servers()` in `src/orchestrator.py` receives `repo_path` but doesn't use it\n2. The morphllm MCP config lacks a `cwd` field, so it inherits an unexpected working directory\n3. No timeout on MCP server operations or their subprocesses\n\n## Scope\n- In: Add `cwd` parameter to morphllm MCP config using the passed `repo_path`\n- In: Add timeout protection for MCP server subprocess operations\n- In: Consider adding `WORKSPACE_PATH` env var if morphllm supports it\n- Out: Changes to morphllm itself\n\n## Acceptance Criteria\n- morphllm MCP config includes `cwd: str(repo_path)` \n- MCP server operations have reasonable timeouts\n- Stuck MCP subprocesses don't hang validation indefinitely\n- Tests pass\n\n## Test Plan\n- Run validation with morphllm enabled, verify ripgrep operates in correct directory\n- Verify timeout triggers if subprocess hangs\n\n## Notes for Agent\n- Fix location: `src/orchestrator.py:181-191` in `get_mcp_servers()`\n- The `repo_path` parameter is already passed but unused","status":"closed","priority":2,"issue_type":"bug","created_at":"2025-12-28T18:57:54.324738643Z","updated_at":"2025-12-28T20:51:06.78258887Z","closed_at":"2025-12-28T20:51:06.78258887Z","close_reason":"Closed"}
{"id":"mala-rboc","title":"[Review] src/infra/hooks/lint_cache.py:117-122: [P2] Normalization of tool name without normalizing lint_tools set","description":"## Review Finding\n\nThis issue was auto-created from a P2 review finding.\n\n**Source issue:** mala-4313\n**Reviewer:** gemini\n**Location:** src/infra/hooks/lint_cache.py:117-122\n\n## Details\n\nNormalizing `tool_name` to lowercase while keeping `lint_tools` as-is introduces a regression for mixed-case tool names. If `lint_tools` contains uppercase strings (e.g. from project configuration like `command: RUFF check .`), they will no longer match because `\"ruff\" in {\"RUFF\"}` is False. Both the command-derived name and the `lint_tools` set should be normalized to ensure true case-insensitive matching.","notes":"Quality gate failed: External review failed: src/domain/validation/spec.py:305: [P1] Missing imports for CoverageConfig and E2EConfig in build_validation_spec\nLog: /home/cyou/.claude/projects/-home-cyou-mala/4bba92bf-39f6-4497-b71a-6dfe60ac8147.jsonl","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-02T16:58:51.130615669Z","created_by":"cyou","updated_at":"2026-01-02T19:15:00.761141047Z","closed_at":"2026-01-02T19:15:00.761141047Z","close_reason":"False positive review failure - CoverageConfig and E2EConfig are defined in the same file (spec.py lines 89, 108), no import needed","labels":["auto_generated","needs-followup","review_finding","review_finding:cb33a2480f35","source:mala-4313"]}
{"id":"mala-rdre","title":"[Review] 5 non-blocking findings from mala-w1js","description":"## Review Findings\n\nThis issue consolidates 5 non-blocking findings from code review.\n\n**Source issue:** mala-w1js\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Missing newline at end of file after test deletion\n\n**Priority:** P2\n**Reviewer:** claude\n**Location:** tests/integration/pipeline/test_agent_session_runner.py:4079\n\nThe deletion of `TestIdleTimeoutResumeIntegration` left the file without a trailing newline. The diff shows `\\ No newline at end of file` marker. Most style guides (including POSIX) expect files to end with a newline. This can cause issues with some tools and diffs.\n\n---\n\n### Finding 2: [P2] Remove unnecessary runtime import and `# noqa` for type-only protocol\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/pipeline/lifecycle_effect_handler.py:17\n\n`src/pipeline/lifecycle_effect_handler.py` already has `from __future__ import annotations`, so `ReviewIssueProtocol` is only needed for typing and doesn’t need a runtime import. Keeping it at runtime (and suppressing TC001) adds avoidable import-time coupling and could contribute to circular-import pain later; prefer moving the import back under `TYPE_CHECKING` (or using a forward reference) and dropping the suppression.\n\n---\n\n### Finding 3: [P3] Restore trailing newline at end of file\n\n**Priority:** P3\n**Reviewer:** codex\n**Location:** tests/integration/pipeline/test_agent_session_runner.py:4079\n\nThe file is now missing a final newline, which can cause noisy diffs and can fail format/lint checks that enforce POSIX newlines at EOF.\n\n---\n\n### Finding 4: [P2] Unnecessary runtime import with TC001 bypass\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/pipeline/lifecycle_effect_handler.py:17\n\nIn `src/pipeline/lifecycle_effect_handler.py`, `ReviewIssueProtocol` is imported at runtime with a `# noqa: TC001` bypass. Since the file uses `from __future__ import annotations`, type hints in dataclass fields are postponed and do not require types to be available at runtime. Other types used in dataclass fields in the same file (like `Path` and `MalaEventSink`) are correctly kept in the `TYPE_CHECKING` block. This import should be moved to the `TYPE_CHECKING` block to maintain consistency and avoid unnecessary runtime dependencies.\n\n---\n\n### Finding 5: [P3] Missing newline at end of file\n\n**Priority:** P3\n**Reviewer:** gemini\n**Location:** tests/integration/pipeline/test_agent_session_runner.py:4080\n\nThe cleanup of duplicate tests in `tests/integration/pipeline/test_agent_session_runner.py` resulted in the removal of the trailing newline at the end of the file. This violates POSIX standards and common Python styling conventions (PEP 8).\n\n---\n\n\u003c!-- fp:2c34d01ec1f2ffb1 --\u003e\n\u003c!-- fp:741df0c8ffd546db --\u003e\n\u003c!-- fp:4c3dc8c16dcd036f --\u003e\n\u003c!-- fp:9a567b8927fa70bc --\u003e\n\u003c!-- fp:84cbdabaa5813f7f --\u003e\n\u003c!-- review_finding:d931af67fe1b --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-05T01:51:19.136794015Z","created_by":"cyou","updated_at":"2026-01-05T02:01:29.902905136Z","closed_at":"2026-01-05T02:01:29.902905136Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-w1js"]}
{"id":"mala-rds5","title":"[Review] src/domain/validation/config_loader.py:121-125: [P2] Handle non-string top-level keys without TypeError","description":"## Review Finding\n\nThis issue was auto-created from a P2 review finding.\n\n**Source issue:** mala-fdp1.2\n**Reviewer:** codex\n**Location:** src/domain/validation/config_loader.py:121-125\n\n## Details\n\n`_validate_schema` sorts the set of unknown keys. If a YAML file has multiple non-string top-level keys of different types (e.g. `null:` and `1:`), `sorted(unknown_fields)` raises `TypeError`, so users see an unexpected exception instead of the intended `ConfigError`. This is triggered by malformed YAML like `null: foo\\n1: bar\\n` (with any allowed string keys present). Consider normalizing keys to strings for sorting or avoiding sorting altogether to keep error handling consistent.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-02T03:28:47.881606184Z","created_by":"cyou","updated_at":"2026-01-02T03:36:02.231140104Z","closed_at":"2026-01-02T03:36:02.231140104Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_finding:aace070b99fc","source:mala-fdp1.2"]}
{"id":"mala-rdsq","title":"[Review] 1 non-blocking finding from mala-kyq1.4","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-kyq1.4\n**Highest priority:** P3\n\n---\n\n### Finding 1: [P3] Preserve actual Braintrust availability in cli_args metadata\n\n**Priority:** P3\n**Reviewer:** codex\n**Location:** src/cli/cli.py:288-290\n\n`cli_args['braintrust']` now uses `resolved.braintrust_enabled`, which is derived from config/API key, not whether Braintrust actually initialized. If the API key is present but `braintrust` isn’t installed (ImportError during `bootstrap()`), `_braintrust_enabled` stays `False` but `resolved.braintrust_enabled` will be `True`, so `EventSink._log_cli_args()` will log `--braintrust` even though tracing is disabled. This is a behavioral regression introduced by the refactor; consider passing `_braintrust_enabled` (or `is_braintrust_enabled()`) into `_build_cli_args_metadata` instead.\n\n---\n","notes":"Quality gate failed: Quality gate failed: No commit with bd-mala-rdsq found after run baseline (stale commits from previous runs are rejected)\nLog: /home/cyou/.claude/projects/-home-cyou-mala/6703cd4d-c60b-45ff-ba27-94900823768c.jsonl","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-03T07:42:00.694534376Z","created_by":"cyou","updated_at":"2026-01-03T15:02:46.310698186Z","closed_at":"2026-01-03T15:02:46.310698186Z","close_reason":"Closed","labels":["auto_generated","needs-followup","review_finding","review_findings:1c5a63720cfc","source:mala-kyq1.4"]}
{"id":"mala-rf5l","title":"[Review] 3 non-blocking findings from mala-7ya5","description":"## Review Findings\n\nThis issue consolidates 3 non-blocking findings from code review.\n\n**Source issue:** mala-7ya5\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P3] Fix type annotation for canonicalize_fn field\n\n**Priority:** P3\n**Reviewer:** claude\n**Location:** tests/fakes/locking_backend.py:56\n\nThe `canonicalize_fn` field is typed as `None` instead of `Callable[[str, str | None], str] | None`. This forces all 7+ assignment sites in tests to use `# type: ignore[assignment]`. The fix is straightforward:\n\n```python\ncanonicalize_fn: Callable[[str, str | None], str] | None = None\n```\n\n---\n\n### Finding 2: [P2] Avoid truthiness when selecting locking backend\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/infra/tools/locking_mcp.py:161\n\n`backend = locking_backend or DefaultLockingBackend()` will silently discard a provided backend if it’s falsy (e.g., implements `__bool__`/`__len__`), which is surprising in DI and can lead to using the real locking backend unintentionally. Prefer an explicit `None` check, e.g. `backend = locking_backend if locking_backend is not None else DefaultLockingBackend()`. Same pattern also appears in tests’ `_create_handlers()` (`backend = locking_backend or FakeLockingBackend()`).\n\n---\n\n### Finding 3: [P2] Fix `FakeLockingBackend.canonicalize_fn` type to avoid ignores\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** tests/fakes/locking_backend.py:55-63\n\n`canonicalize_fn` is annotated as `None` but is used as a callable, which forces `# type: ignore` in the fake and `# type: ignore[assignment]` at call sites, undermining `ty` signal. Annotate it as a callable (or remove it and override `canonicalize_path` in tests), e.g. `canonicalize_fn: Callable[[str, str | None], str] | None = None`, then you can drop the ignores around `canonicalize_fn` usage.\n\n---\n\n\u003c!-- fp:bf57a89c7a2cd6a7 --\u003e\n\u003c!-- fp:518cf9717e26f01d --\u003e\n\u003c!-- fp:4525379698505e89 --\u003e\n\u003c!-- review_finding:0ad48dac7f71 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-06T00:04:46.227366581Z","created_by":"cyou","updated_at":"2026-01-06T00:07:17.522270884Z","closed_at":"2026-01-06T00:07:17.522270884Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-7ya5"]}
{"id":"mala-rfbn","title":"[Review] src/pipeline/agent_session_runner.py:904-920: [P2] pending_tool_ids not cleared on idle retry can cause infinite hang","description":"## Review Finding\n\nThis issue was auto-created from a P2 review finding.\n\n**Source issue:** mala-cavk.2\n**Reviewer:** claude\n**Location:** src/pipeline/agent_session_runner.py:904-920\n\n## Details\n\n`state.pending_tool_ids` is only cleared at line 902, before the retry while-loop. After an idle timeout, stale tool IDs from the previous attempt remain in the set. On retry, at lines 956-958, `current_timeout = None if state.pending_tool_ids else idle_timeout_seconds` - meaning if pending_tool_ids is non-empty, the timeout is disabled. If the new client also hangs immediately, we wait forever on `stream.__anext__()` with no timeout. The original code cleared `pending_tool_ids` at the start of each iteration attempt. Fix: clear `state.pending_tool_ids` inside the while loop before creating a new client.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-01T21:33:48.199582418Z","created_by":"cyou","updated_at":"2026-01-01T23:03:54.623369234Z","closed_at":"2026-01-01T23:03:54.623369234Z","close_reason":"Duplicate of mala-actw - both describe the same bug: pending_tool_ids not cleared on idle retry causing infinite hang","labels":["auto_generated","review_finding","review_finding:b9ab0068d7b6","source:mala-cavk.2"]}
{"id":"mala-ri3a","title":"[Review] 2 non-blocking findings from mala-mgfb.4","description":"## Review Findings\n\nThis issue consolidates 2 non-blocking findings from code review.\n\n**Source issue:** mala-mgfb.4\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Idle retry safety check bypassed by premature state reset\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/pipeline/agent_session_runner.py:1475-1485\n\nIn `_prepare_idle_retry`, `state.tool_calls_this_turn` is reset to `0` before it is checked in the `elif state.tool_calls_this_turn == 0` block. This makes the `else` branch (which prevents retrying when side-effecting tool calls occurred without a session ID) unreachable, potentially allowing unsafe retries of operations that cannot be correctly resumed.\n\n---\n\n### Finding 2: [P3] Morph tool removed from pretty-printing\n\n**Priority:** P3\n**Reviewer:** gemini\n**Location:** src/infra/io/log_output/console.py:69-71\n\nThe tool `mcp__morphllm__edit_file` was removed from `EDIT_TOOLS` in `console.py`. While it remains in `FILE_TOOLS` for path display, this change prevents its code-related fields (like `new_string`) from being pretty-printed in verbose mode, which may degrade the debugging experience for this tool.\n\n---\n","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T16:16:29.146442041Z","created_by":"cyou","updated_at":"2026-01-03T16:20:06.878059937Z","closed_at":"2026-01-03T16:20:06.878059937Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_findings:34341a4181b8","source:mala-mgfb.4"]}
{"id":"mala-rkk2","title":"[Review] 1 non-blocking finding from mala-1vhh","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-1vhh\n**Highest priority:** P3\n\n---\n\n### Finding 1: [P3] Missing '!' icon from KNOWN_ICONS allowlist\n\n**Priority:** P3\n**Reviewer:** claude\n**Location:** src/infra/io/log_output/console.py:410-414\n\nThe original finding listed 6 missing icons: `◐`, `◌`, `▶`, `!`, `🧹`, `🔍`. This commit added 5 of them but missed `!`. The `!` icon is used in `event_sink.py` at lines 785, 929, and 933 for warning messages. When `ConsoleLoggerAdapter` is wired up, messages using `!` would have their icon replaced with the default `→`.\n\n---\n","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-03T16:12:00.487687464Z","created_by":"cyou","updated_at":"2026-01-03T16:27:28.14822678Z","closed_at":"2026-01-03T16:27:28.14822678Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_findings:a7fe414aa91e","source:mala-1vhh"]}
{"id":"mala-rmhn","title":"remove deprecated flags","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-06T03:20:16.485717906Z","created_by":"cyou","updated_at":"2026-01-06T03:37:29.395222121Z","closed_at":"2026-01-06T03:37:29.395222121Z","close_reason":"Closed"}
{"id":"mala-rmxf","title":"[Review] src/pipeline/agent_session_runner.py:939-952: [P2] Missing on_validation_result emission in SEND_GATE_RETRY path","description":"## Review Finding\n\nThis issue was auto-created from a P2 review finding.\n\n**Source issue:** mala-40pg.4\n**Reviewer:** claude\n**Location:** src/pipeline/agent_session_runner.py:939-952\n\n## Details\n\nWhen the gate fails but retries are available (SEND_GATE_RETRY effect), `on_validation_result` is not emitted. This means validation cycles that trigger retries will emit `on_validation_started` but never a corresponding result event until the final success/failure. While technically validation continues across retries, this creates asymmetric event pairs - listeners tracking `started`/`result` pairs will see orphaned start events.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-01T19:56:47.316003571Z","created_by":"cyou","updated_at":"2026-01-02T01:52:47.956910615Z","closed_at":"2026-01-02T01:52:47.956910615Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_finding:2ea3af3d802e","source:mala-40pg.4"],"dependencies":[{"issue_id":"mala-rmxf","depends_on_id":"mala-actw","type":"blocks","created_at":"2026-01-01T23:31:44.60161055Z","created_by":"daemon"}]}
{"id":"mala-robf","title":"publish to pypi","status":"tombstone","priority":2,"issue_type":"feature","created_at":"2026-01-04T03:29:47.44789743Z","created_by":"cyou","updated_at":"2026-01-06T22:52:10.761723406Z","deleted_at":"2026-01-06T22:52:10.761723406Z","deleted_by":"batch delete","delete_reason":"batch delete","original_type":"feature"}
{"id":"mala-rs33","title":"make cerberus use optional, fall back to claude","status":"tombstone","priority":2,"issue_type":"feature","created_at":"2026-01-05T14:12:09.350660425Z","created_by":"cyou","updated_at":"2026-01-06T22:52:10.761723406Z","deleted_at":"2026-01-06T22:52:10.761723406Z","deleted_by":"batch delete","delete_reason":"batch delete","original_type":"feature"}
{"id":"mala-rsg","title":"Honor --no-truncate for completion summaries","description":"Completion log lines still hard-truncate summaries to 50 chars. Apply the global truncation setting so --no-truncate shows full completion summaries.","status":"closed","priority":3,"issue_type":"task","created_at":"2025-12-26T15:34:15.335284655Z","updated_at":"2025-12-26T17:11:44.411821976Z","closed_at":"2025-12-26T17:11:44.411821976Z","close_reason":"Closed"}
{"id":"mala-rsq","title":"Refactor: split CLI into cli.py and make main.py a shim","description":"## Context\nWe will parallelize by creating new modules in separate issues, then do a single integration pass to wire everything together. This issue is the integration/wiring step.\n\n## Scope\nWire all refactor modules into the app, update tests, and make `src/main.py` a true shim. This is the **only** issue that should edit `src/main.py` and tests.\n\n### Files\n- Add: `src/cli.py`\n- Modify: `src/main.py`\n- Modify: `tests/test_lock_integration.py`\n- Modify: `tests/test_lock_sdk_integration.py`\n- Modify: any files needed to update imports/paths after the prompt move\n\n## Requirements\n- Create `src/cli.py` with the Typer app + commands currently defined in `src/main.py`.\n- Move CLI-adjacent logic out of `src/main.py` so it becomes a tiny shim that only exposes `app`.\n- Update imports to use the new modules:\n  - `src/tools/env.py`\n  - `src/logging/console.py`\n  - `src/logging/jsonl.py`\n  - `src/agent_loop.py`\n  - `src/tools/locking.py`\n- Update prompt loading to the new path: `src/prompts/implementer_prompt.md`.\n- Update tests to import from new module paths (locking + prompt template).\n- Keep `pyproject.toml` entrypoint as `src.main:app`.\n\n## TDD Guidance\n- Update tests first (imports/paths), run relevant tests:\n  - `tests/test_lock_integration.py`\n  - `tests/test_lock_sdk_integration.py`\n  - `tests/test_lock_scripts.py`\n- Then implement wiring changes and re-run the tests.\n\n## Acceptance Criteria\n- `src/main.py` is a thin shim (import + expose `app`).\n- CLI behavior is unchanged (`mala --help` identical).\n- Prompt loads from the new location.\n- Locking + prompt tests pass with updated imports.\n- All refactor modules are used by the CLI/orchestrator.\n","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-25T19:54:51.18845258Z","updated_at":"2025-12-25T20:24:59.79953084Z","closed_at":"2025-12-25T20:24:59.79953084Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-rsq","depends_on_id":"mala-9ki","type":"blocks","created_at":"2025-12-25T20:06:47.478081301Z","created_by":"daemon"},{"issue_id":"mala-rsq","depends_on_id":"mala-1by","type":"blocks","created_at":"2025-12-25T20:06:47.48915539Z","created_by":"daemon"},{"issue_id":"mala-rsq","depends_on_id":"mala-lm0","type":"blocks","created_at":"2025-12-25T20:06:47.499510627Z","created_by":"daemon"},{"issue_id":"mala-rsq","depends_on_id":"mala-wd1","type":"blocks","created_at":"2025-12-25T20:06:47.510195051Z","created_by":"daemon"},{"issue_id":"mala-rsq","depends_on_id":"mala-5aa","type":"blocks","created_at":"2025-12-25T20:06:47.520878684Z","created_by":"daemon"},{"issue_id":"mala-rsq","depends_on_id":"mala-vrm","type":"blocks","created_at":"2025-12-25T20:06:47.530900735Z","created_by":"daemon"}]}
{"id":"mala-rsy","title":"Add --wip flag to prioritize in_progress issues","description":"Add a CLI flag (--wip) to mala run that prioritizes issues with status 'in_progress' before processing 'open' issues.\n\n## Current Behavior\n- mala run processes issues from bd ready which returns both open and in_progress issues\n- No priority given to in_progress issues that may have been started but not completed\n\n## Desired Behavior\nWhen --wip flag is provided:\n1. First process all in_progress issues\n2. Then process open issues (if any slots remain)\n\nThis helps resume work that was previously started before picking up new tasks.\n\n## Implementation Notes\n- Add --wip flag to CLI (src/cli.py)\n- Modify orchestrator to filter/sort issues by status when flag is set\n- Consider: bd ready may need status filtering or sorting in get_ready()\n\n## Acceptance Criteria\n- --wip flag added to mala run\n- When set, in_progress issues are processed before open issues\n- Flag is documented in --help output\n- Unit tests cover the prioritization logic","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-27T00:19:15.20123002Z","updated_at":"2025-12-27T06:35:24.049205106Z","closed_at":"2025-12-27T06:35:24.049205106Z","close_reason":"Closed"}
{"id":"mala-rt8","title":"Reduce max_review_retries from 5 to 2-3","description":"## Problem\nHigh review_attempts strongly correlate with duration (Pearson r ≈ 0.95) but not with success. Issues that fail after 5 review attempts would have failed after 2-3 as well.\n\n## Evidence from recent runs\n- mala-869.11: 5 review attempts → failed (31.5 min wasted)\n- mala-51q.2: 5 review attempts → failed (39.5 min wasted)\n- mala-869.7: 3 review attempts → failed (15.2 min)\n- mala-51q.9: 3 review attempts → failed (22.8 min)\n\nSuccessful issues typically complete in 1-2 review attempts.\n\n## Solution\nReduce `max_review_retries` default from 5 to 2-3. If no progress after 2-3 attempts, it won't suddenly succeed.\n\n## Expected Impact\n~40% wall time reduction on failing issues","status":"closed","priority":0,"issue_type":"task","created_at":"2025-12-29T00:43:22.845882742Z","created_by":"cyou","updated_at":"2025-12-29T01:17:02.745340749Z","closed_at":"2025-12-29T01:17:02.745340749Z","close_reason":"Closed"}
{"id":"mala-rv3q","title":"[Review] src/domain/validation/tool_name_extractor.py:63-65: [P3] Add 'export' and 'set' to _SKIP_REST_BUILTINS","description":"## Review Finding\n\nThis issue was auto-created from a P3 review finding.\n\n**Source issue:** mala-b8g4\n**Reviewer:** gemini\n**Location:** src/domain/validation/tool_name_extractor.py:63-65\n\n## Details\n\nThe `export` and `set` shell built-ins typically operate on variable assignments or shell options rather than executing a command passed as an argument. Adding them to `_SKIP_REST_BUILTINS` would prevent their arguments (like variable names or flags) from being incorrectly extracted as tool names in multi-command strings.","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-02T17:20:28.899212822Z","created_by":"cyou","updated_at":"2026-01-02T17:27:15.454916171Z","closed_at":"2026-01-02T17:27:15.454916171Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_finding:e200d5301740","source:mala-b8g4"]}
{"id":"mala-rwtm","title":"fixer logs are not truncated in non verbose mode","status":"closed","priority":2,"issue_type":"bug","created_at":"2026-01-05T05:30:28.527767068Z","created_by":"cyou","updated_at":"2026-01-06T15:25:44.958466343Z","closed_at":"2026-01-06T15:25:44.958466343Z","close_reason":"Closed"}
{"id":"mala-s0ai","title":"[Review] 1 non-blocking finding from mala-x5mg","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-x5mg\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Stale 'first_message_received' status in multi-turn sessions\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/pipeline/agent_session_runner.py:1616-1618\n\nThe `state.first_message_received` flag is not reset at the beginning of `_run_message_iteration`. Since the `state` object (which is `msg_state` from `SessionExecutionState`) is persisted across multiple turns in `_run_lifecycle_loop` (e.g., during gate or review retries), a subsequent turn will inherit the `True` value from a previous successful turn. This causes the first-message latency log to be skipped for subsequent turns and makes the `IdleTimeoutError` log misleadingly report `first_msg=True` even if no messages were received in the current turn.\n\n---\n","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T15:34:52.753371767Z","created_by":"cyou","updated_at":"2026-01-03T15:49:51.230148164Z","closed_at":"2026-01-03T15:49:51.230148164Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_findings:039564ef1b7d","source:mala-x5mg"]}
{"id":"mala-s20g","title":"Epic verifier: Use project LLM infrastructure instead of direct Anthropic SDK","description":"## Context\n\nThe `ClaudeEpicVerificationModel` instantiates the Anthropic SDK directly rather than using project-standard provider abstractions like `CodexProvider` or other LLM wrappers used elsewhere in the codebase.\n\n## Problem\n\n- Bypasses established patterns for observability and logging\n- Misses centralized routing configuration (e.g., MorphLLM)\n- Inconsistent with how other LLM calls are made in the orchestrator\n- May miss shared error handling and telemetry\n\n## Proposed Solution\n\n1. Investigate existing LLM provider abstractions in the codebase (e.g., `CodexProvider`, `LLM` classes)\n2. Refactor `ClaudeEpicVerificationModel` to use the same infrastructure\n3. Or create a shared Anthropic client factory that handles configuration consistently\n\n## Acceptance Criteria\n\n- [ ] Epic verification uses the same LLM infrastructure as other components\n- [ ] Observability/logging is consistent with rest of system\n- [ ] MorphLLM routing works automatically when configured\n- [ ] No direct Anthropic SDK instantiation in epic verifier\n\n## References\n\n- File: `src/epic_verifier.py` - `ClaudeEpicVerificationModel`\n- Compare with: `src/codex_review.py` for LLM usage patterns\n- From code review iteration 3","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-30T15:50:11.429590042Z","created_by":"cyou","updated_at":"2025-12-30T17:38:55.313679513Z","closed_at":"2025-12-30T17:38:55.313679513Z","close_reason":"Closed"}
{"id":"mala-s6tn","title":"Align implementer prompt with locking tool contract","description":"Problem: implementer prompt documents lock_acquire/lock_release behavior that does not match tool implementation (timeout default, result shape, release-all invocation). This causes agents to block unexpectedly and fail to release locks.\n\nAcceptance Criteria:\n- Decide on the source-of-truth contract (prompt vs tool) for: default timeout_seconds, lock_acquire response schema, and lock_release release-all semantics.\n- Update prompt and/or tool implementation so they are consistent.\n- Update examples in the prompt to match real responses and required inputs.\n- Update/add tests if tool behavior changes (no separate test ticket).","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-04T21:51:04.674169933Z","created_by":"cyou","updated_at":"2026-01-04T23:28:52.796314004Z","closed_at":"2026-01-04T23:28:52.796314004Z","close_reason":"Closed"}
{"id":"mala-s9p8","title":"Epic: Unified SIGINT Handling for Agent SDK Flows","description":"Unify SIGINT handling across all Agent SDK flows in the mala orchestrator.\n\n## Problem\nThe orchestrator's existing 3-stage SIGINT escalation (drain → abort → hard abort) produces an `interrupt_event`, but this event isn't propagated to SDK flows (fixer, reviewer, epic verification, implementer sessions). A recent bug caused validation to report \"passed\" when the fixer was interrupted.\n\n## Goals\n1. Consistent SIGINT behavior across all SDK flows\n2. Never mark validation as passed when interrupted\n3. No orphaned tasks or agent processes on interrupt\n4. Centralize interrupt logic in a shared helper\n\n## Technical Approach\n- **Phase 1 (Foundation)**: Create `src/infra/sigint_guard.py` with `InterruptGuard`, `FlowInterruptedError`, and utility functions\n- **Phase 2 (Result Types)**: Add `interrupted: bool = False` to result types\n- **Phase 3 (Flow Integration)**: Thread `interrupt_event` to all SDK-driven code paths\n- **Phase 4 (Wiring)**: Wire orchestrator to propagate interrupt_event\n- **Phase 5 (Testing)**: Integration tests for unified SIGINT propagation\n\n## Key Decisions\n- Strict abort contract: Interrupt always aborts retries immediately\n- `interrupted=True` implies `success=None` (canonical contract)\n- Add `validation_ran: bool` to disambiguate skipped vs interrupted\n\n## Plan Reference\nSee `plans/2026-01-08-sigint-unified-tech-design.md` for full technical design.\n\n## Acceptance Criteria\n- [ ] All SDK flows (fixer, reviewer, implementer, epic verification) check `interrupt_event`\n- [ ] Validation never reports \"passed\" when interrupted (returns `None`)\n- [ ] No orphaned SDK subprocess processes after interrupt (verified via `ps`)\n- [ ] Shared `InterruptGuard` used across all flows","status":"closed","priority":0,"issue_type":"epic","created_at":"2026-01-08T16:03:29.879188841Z","created_by":"cyou","updated_at":"2026-01-09T06:01:43.799194801Z","closed_at":"2026-01-09T06:01:43.799194801Z","close_reason":"Closed"}
{"id":"mala-s9p8.1","title":"[T001] Create sigint_guard.py module with interrupt helpers","description":"## Type\ntask\n\n## Priority\nP2\n\n## Story\nFoundation - Workstream 1 (Shared interrupt helper)\n\n## Primary Files\n- `src/infra/sigint_guard.py` — **New**\n- `tests/unit/infra/test_sigint_guard.py` — **New**\n\n## Subsystems\ninfra\n\n## Goal\nCreate the shared interrupt helper module that all SDK flows will use for consistent interrupt handling.\n\n## Context\nThe plan requires a centralized module for interrupt logic. This includes:\n- `InterruptGuard` class for checking/raising on interrupt\n- `FlowInterruptedError` exception (named to avoid shadowing Python's InterruptedError)\n- `await_interruptible()` for interruptible sleep\n- `run_with_interrupt_checks()` for retry loops with interrupt awareness\n- `run_with_timeout_and_interrupt()` for timeout + interrupt handling\n\n## Scope\n**In:**\n- Create `src/infra/sigint_guard.py` with all helper classes/functions\n- Create comprehensive unit tests\n\n**Out:**\n- pgid registration (use existing CommandRunner API)\n- Integration with flows (separate tasks)\n\n## Changes\n- `src/infra/sigint_guard.py` — **New** — Create module with:\n  - `FlowInterruptedError` exception class\n  - `InterruptGuard` class with `is_interrupted()`, `raise_if_interrupted()`\n  - `await_interruptible(delay, interrupt_event)` async function\n  - `run_with_interrupt_checks(attempt_fn, interrupt_event, max_retries)` async function\n  - `run_with_timeout_and_interrupt(coro, timeout, interrupt_event)` async function\n- `tests/unit/infra/test_sigint_guard.py` — **New** — Unit tests for all helpers\n\n## Acceptance Criteria\n- `InterruptGuard.is_interrupted()` returns True when event is set\n- `InterruptGuard.raise_if_interrupted()` raises `FlowInterruptedError` when event is set\n- `await_interruptible()` returns immediately when event is set\n- `await_interruptible()` waits full duration when not interrupted\n- `run_with_interrupt_checks()` aborts between retries when interrupted\n- `run_with_timeout_and_interrupt()` returns `(None, False, True)` when interrupted\n- `run_with_timeout_and_interrupt()` returns `(None, True, False)` on timeout\n\n## Verification\n```bash\nuv run pytest tests/unit/infra/test_sigint_guard.py -v\nuvx ruff check src/infra/sigint_guard.py\nuvx ty check src/infra/sigint_guard.py\n```\n\n## Notes for Agent\n- `FlowInterruptedError` is named specifically to avoid shadowing Python's built-in `InterruptedError` (OSError with errno.EINTR)\n- Do NOT re-export pgid functions from CommandRunner - callers should use `CommandRunner.register_sigint_pgid()` directly\n- Use `asyncio.Event | None` pattern - None means no interrupt checking\n- Ensure all helpers are properly typed for mypy/ty","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-08T16:04:04.364194665Z","created_by":"cyou","updated_at":"2026-01-08T16:32:34.40344386Z","closed_at":"2026-01-08T16:32:34.40344386Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-s9p8.1","depends_on_id":"mala-s9p8","type":"parent-child","created_at":"2026-01-08T16:04:04.364751341Z","created_by":"cyou"}]}
{"id":"mala-s9p8.10","title":"[Review] 2 non-blocking findings from mala-s9p8.9","description":"## Review Findings\n\nThis issue consolidates 2 non-blocking findings from code review.\n\n**Source issue:** mala-s9p8.9\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Capture fixer log path using agent_id, not session_id\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/pipeline/run_coordinator.py:537-542\n\nThe implementation still relies on `session_id` from `ResultMessage` to determine the log path, as explicitly noted in the added comment. This contradicts the requirement to use `get_claude_log_path(agent_id)`, which is necessary to align with the documented wiring map and ensure logs are correctly associated with the fixer agent.\n\n---\n\n### Finding 2: [P3] Log path not captured on interrupt or error\n\n**Priority:** P3\n**Reviewer:** gemini\n**Location:** src/pipeline/run_coordinator.py:542\n\nBecause `log_path` is currently dependent on receiving a `ResultMessage` (which contains the `session_id`), it remains `None` if the fixer process is interrupted or fails. By deriving the path from `agent_id` before the message loop starts, the `log_path` can be populated in `FixerResult` for all termination scenarios.\n\n---\n\n\u003c!-- fp:87340f90fde43ef4 --\u003e\n\u003c!-- fp:0222bbe6ee0a254b --\u003e\n\u003c!-- review_finding:170eb9fde414 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-08T17:43:06.44442528Z","created_by":"cyou","updated_at":"2026-01-08T17:51:24.939182518Z","closed_at":"2026-01-08T17:51:24.939182518Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-s9p8.9"],"dependencies":[{"issue_id":"mala-s9p8.10","depends_on_id":"mala-s9p8","type":"parent-child","created_at":"2026-01-08T17:43:06.444958677Z","created_by":"cyou"}]}
{"id":"mala-s9p8.11","title":"[Review] 3 non-blocking findings from mala-s9p8.8","description":"## Review Findings\n\nThis issue consolidates 3 non-blocking findings from code review.\n\n**Source issue:** mala-s9p8.8\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Log real pid/pgid for sdk_subprocess_spawned\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/infra/clients/agent_sdk_review.py:413-425\n\nThe task context requires a structured log event `sdk_subprocess_spawned pid=%s pgid=%s flow=reviewer` “when the SDK subprocess is spawned”, and specifically mentions monitoring/test PID capture. The diff adds the log, but it hardcodes `pid` and `pgid` as the string `\"unknown\"`, so it does not actually capture the spawned process identifiers.\n\nConcrete impact: any downstream consumer that expects numeric pid/pgid (alerts, debugging, tests that parse these fields) cannot use this log to identify or manage the subprocess.\n\nIf the reviewer cannot directly access the subprocess details, consider either (a) emitting this log at the layer that actually spawns the subprocess (where pid/pgid are known), or (b) exposing pid/pgid from `sdk_client_factory`/transport so this call site can log real values (or omit the event entirely until real values are available).\n\n---\n\n### Finding 2: [P2] Use of 'unknown' for pid/pgid in structured log\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/infra/clients/agent_sdk_review.py:421-425\n\nThe structured log `sdk_subprocess_spawned` uses `\"unknown\"` placeholders for `pid` and `pgid`. Finding 3 in the task context explicitly notes that these fields are required for monitoring and 'test PID capture'. Using placeholders will likely cause tests that capture and verify PIDs from logs to fail. If the PID is not directly available in `AgentSDKReviewer`, it should be exposed by the SDK client/transport or the logging should be handled where the process is spawned.\n\n---\n\n### Finding 3: [P2] Missing asyncio runtime import in protocols.py\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/core/protocols.py:31\n\nIn `src/core/protocols.py`, `asyncio` is only imported within a `TYPE_CHECKING` block, but it is used as a type hint `asyncio.Event` in the `CodeReviewer.__call__` method signature. Unless `from __future__ import annotations` is present in the file (not visible in the diff), this will cause a `NameError` at runtime when the module is loaded or when type hints are inspected.\n\n---\n\n\u003c!-- fp:ace67c3dadb74f09 --\u003e\n\u003c!-- fp:790c49af796a9741 --\u003e\n\u003c!-- fp:dfcbfc06dbc12cf4 --\u003e\n\u003c!-- review_finding:677b4359c15f --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-08T17:44:36.734875417Z","created_by":"cyou","updated_at":"2026-01-08T17:53:21.19410549Z","closed_at":"2026-01-08T17:53:21.19410549Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-s9p8.8"],"dependencies":[{"issue_id":"mala-s9p8.11","depends_on_id":"mala-s9p8","type":"parent-child","created_at":"2026-01-08T17:44:36.735436664Z","created_by":"cyou"}]}
{"id":"mala-s9p8.12","title":"[Review] 3 non-blocking findings from mala-s9p8.10","description":"## Review Findings\n\nThis issue consolidates 3 non-blocking findings from code review.\n\n**Source issue:** mala-s9p8.10\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Test uses incorrect pattern to mock __str__ return value\n\n**Priority:** P2\n**Reviewer:** claude\n**Location:** tests/unit/pipeline/test_run_coordinator.py:206\n\nSetting `mock_uuid.__str__ = MagicMock(return_value=...)` creates an instance attribute, but Python's `str()` calls the class-level method, not the instance attribute. Use `mock_uuid.__str__.return_value = \"test-session-uuid-1234\"` instead. With the current pattern, `str(mock_uuid)` returns the default MagicMock representation, causing assertions like `assert_called_once_with(tmp_path, \"test-session-uuid-1234\")` to fail. Same issue at line 332.\n\n---\n\n### Finding 2: [P2] Derive fixer log path from agent_id, not generated session_id\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/pipeline/run_coordinator.py:536-544\n\nTask context requires wiring logs to the fixer agent via `get_claude_log_path(agent_id)`. The diff instead generates a new `session_id` (`uuid.uuid4()`), derives `log_path` from that, and passes the same `session_id` into `client.query`.\n\nThis keeps the implementation anchored on a session identifier rather than the fixer `agent_id`, so it still doesn't meet the stated requirement (and risks mis-associating logs if the system expects log paths to be keyed by agent identity).\n\n```py\nsession_id = str(uuid.uuid4())\nlog_path = str(get_claude_log_path(self.config.repo_path, session_id))\n```\n\n\n---\n\n### Finding 3: [P2] Use agent_id for log path instead of new session_id\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/pipeline/run_coordinator.py:539-542\n\nFinding 1 explicitly requested using `agent_id` for the log path to ensure logs are correctly associated with the fixer agent. The current implementation generates a new random `session_id` via `uuid.uuid4()` and uses that instead. It should use the existing `agent_id` variable (which is already unique) and pass it to both `get_claude_log_path` and `client.query(..., session_id=agent_id)`.\n\n---\n\n\u003c!-- fp:ef04df2c559e3b63 --\u003e\n\u003c!-- fp:15e1c76efe413716 --\u003e\n\u003c!-- fp:7aa81751b3f0677c --\u003e\n\u003c!-- review_finding:f1983bde87de --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-08T17:51:25.238418164Z","created_by":"cyou","updated_at":"2026-01-08T17:54:07.442614618Z","closed_at":"2026-01-08T17:54:07.442614618Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-s9p8.10"],"dependencies":[{"issue_id":"mala-s9p8.12","depends_on_id":"mala-s9p8","type":"parent-child","created_at":"2026-01-08T17:51:25.23903053Z","created_by":"cyou"}]}
{"id":"mala-s9p8.13","title":"[Review] 1 non-blocking finding from mala-s9p8.5","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-s9p8.5\n**Highest priority:** P3\n\n---\n\n### Finding 1: [P3] Remove duplicate import of PromptValidationCommands\n\n**Priority:** P3\n**Reviewer:** claude\n**Location:** tests/integration/pipeline/test_agent_session_runner.py:34-35\n\nThe same import `from src.domain.validation.config import PromptValidationCommands` appears twice consecutively. While Python handles duplicate imports gracefully at runtime, this is clearly unintentional and should be cleaned up.\n\n---\n\n\u003c!-- fp:896bb6e48f52c91b --\u003e\n\u003c!-- review_finding:385c619b0684 --\u003e","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-09T05:24:54.463655064Z","created_by":"cyou","updated_at":"2026-01-09T05:26:01.274123087Z","closed_at":"2026-01-09T05:26:01.274123087Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-s9p8.5"],"dependencies":[{"issue_id":"mala-s9p8.13","depends_on_id":"mala-s9p8","type":"parent-child","created_at":"2026-01-09T05:24:54.464246171Z","created_by":"cyou"}]}
{"id":"mala-s9p8.14","title":"[Review] 3 non-blocking findings from mala-s9p8.7","description":"## Review Findings\n\nThis issue consolidates 3 non-blocking findings from code review.\n\n**Source issue:** mala-s9p8.7\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Avoid manual event-loop management in pytest unit tests\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** tests/unit/orchestration/test_interrupt_wiring.py:1-274\n\n`tests/unit/orchestration/test_interrupt_wiring.py` creates and drives its own loops (`asyncio.new_event_loop()` + `run_until_complete()`). This is a known source of flakiness depending on how pytest (and plugins like `pytest-asyncio`) manage the event loop policy.\n\nPrefer `@pytest.mark.asyncio` tests and `await ...` directly, or use the project’s existing async-test conventions/fixtures (e.g., `event_loop` fixture if present) so pytest owns loop lifecycle.\n\n---\n\n### Finding 2: [P2] Avoid `asyncio.run()` in subprocess test scripts\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** tests/integration/orchestration/test_sigint_unified.py:1-684\n\nMultiple integration tests write subprocess scripts that call `asyncio.run(main())`. If these scripts are ever executed in an environment that already has an active event loop (or with altered loop policy), this can fail with runtime errors and become flaky.\n\nSince these scripts are specifically used for SIGINT propagation tests, consider using the same loop-management pattern consistently across the suite (whatever the repo standard is), and avoid nesting/starting new loops when not necessary.\n\n---\n\n### Finding 3: [P3] Manual event loops in unit tests\n\n**Priority:** P3\n**Reviewer:** gemini\n**Location:** tests/unit/orchestration/test_interrupt_wiring.py:124-128\n\nThe file `tests/unit/orchestration/test_interrupt_wiring.py` uses `asyncio.new_event_loop()` instead of standard `pytest-asyncio` patterns. While this may be intentional to avoid conflicts, it repeats a pattern previously flagged as an issue. Standard `async def test_...` with `pytest-asyncio` is preferred.\n\n---\n\n\u003c!-- fp:c06419876c397beb --\u003e\n\u003c!-- fp:3e8113b5c8e22c9a --\u003e\n\u003c!-- fp:b12a0b9cc791f7b1 --\u003e\n\u003c!-- review_finding:b128bd694aab --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-09T06:01:43.96247114Z","created_by":"cyou","updated_at":"2026-01-09T06:04:59.643925306Z","closed_at":"2026-01-09T06:04:59.643925306Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-s9p8.7"],"dependencies":[{"issue_id":"mala-s9p8.14","depends_on_id":"mala-s9p8","type":"parent-child","created_at":"2026-01-09T06:01:43.963134705Z","created_by":"cyou"}]}
{"id":"mala-s9p8.2","title":"[T002] Add interrupted field to result types","description":"## Type\ntask\n\n## Priority\nP2\n\n## Story\nFoundation - Workstream 2 (Result shape standardization)\n\n## Primary Files\n- `src/pipeline/review_runner.py` — Exists\n- `src/pipeline/gate_runner.py` — Exists\n- `src/pipeline/run_coordinator.py` — Exists\n- `src/core/models.py` — Exists (if EpicVerificationResult lives here)\n\n## Subsystems\npipeline, core\n\n## Dependencies\nT001 (foundation must be in place before adding types that will use the helpers)\n\n## Goal\nAdd `interrupted: bool = False` field to all result types that can be interrupted.\n\n## Context\nThe plan defines a canonical contract:\n- `interrupted=False, success=True` → completed successfully\n- `interrupted=False, success=False` → completed with failure\n- `interrupted=True, success=None` → did not complete (interrupted)\n- `interrupted=True, success=True` → **INVALID**\n- `interrupted=True, success=False` → **INVALID**\n\n## Scope\n**In:**\n- Add `interrupted: bool = False` to existing result types\n- Add `FixerResult` dataclass to run_coordinator.py\n\n**Out:**\n- Actually wiring interrupt_event to flows (separate tasks)\n- Changing behavior (just adding fields)\n\n## Changes\n- `src/pipeline/review_runner.py` — Exists — Add `interrupted: bool = False` to `ReviewOutput`\n- `src/pipeline/gate_runner.py` — Exists — Add `interrupted: bool = False` to `PerSessionGateOutput`\n- `src/pipeline/run_coordinator.py` — Exists — Add `FixerResult` dataclass with `success: bool | None`, `interrupted: bool = False`, `log_path: str | None = None`\n- `src/core/models.py` or appropriate location — Add `interrupted: bool = False` to `EpicVerificationResult` if not already present\n\n## Acceptance Criteria\n- `ReviewOutput` has `interrupted: bool = False` field\n- `PerSessionGateOutput` has `interrupted: bool = False` field\n- `FixerResult` dataclass exists with `success`, `interrupted`, `log_path` fields\n- `EpicVerificationResult` has `interrupted: bool = False` field\n- All fields have `= False` default (backward compatible)\n- Type checker passes (`uvx ty check`)\n\n## Verification\n```bash\nuvx ty check src/pipeline/review_runner.py src/pipeline/gate_runner.py src/pipeline/run_coordinator.py\nuvx ruff check src/pipeline/\n```\n\n## Notes for Agent\n- All new fields MUST have `= False` default for backward compatibility\n- Do not change any behavior yet - just add the fields\n- Check if EpicVerificationResult already exists and where it's defined\n- The `success: bool | None` pattern allows None for \"not evaluated\"","notes":"Quality gate failed: External review failed: src/pipeline/run_coordinator.py:174: [P1] Syntax error in FixerResult definition\nLog: /home/cyou/.claude/projects/-home-cyou-mala/6da0ed89-fe00-4747-804f-eb6605e8bd21.jsonl","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-08T16:05:22.310857455Z","created_by":"cyou","updated_at":"2026-01-08T17:20:42.068667791Z","closed_at":"2026-01-08T17:20:42.068667791Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-s9p8.2","depends_on_id":"mala-s9p8","type":"parent-child","created_at":"2026-01-08T16:05:22.311470091Z","created_by":"cyou"},{"issue_id":"mala-s9p8.2","depends_on_id":"mala-s9p8.1","type":"blocks","created_at":"2026-01-08T16:08:21.237162185Z","created_by":"cyou"}]}
{"id":"mala-s9p8.3","title":"[T003] [integration-path-test] Fixer interrupt handling","description":"## Type\ntask\n\n## Priority\nP2\n\n## Story\nFlow Integration - Workstream 3 (Thread interrupt_event to fixer)\n\n## Parallel\n[P]\n\n## Primary Files\n- `src/pipeline/run_coordinator.py` — Exists\n- `tests/integration/orchestration/test_sigint_escalation.py` — Exists\n- `tests/unit/pipeline/test_run_coordinator.py` — **New**\n\n## Subsystems\npipeline, tests\n\n## Dependencies\nT002 (need FixerResult type)\n\n## Goal\nAdd interrupt handling to the fixer flow in RunCoordinator, with a failing integration test that exercises the full SIGINT propagation path.\n\n## Context\nThis is the critical path that caused the original bug - Ctrl-C during fixer caused validation to incorrectly report \"passed\". The fixer must:\n1. Accept `interrupt_event` parameter\n2. Check interrupt before starting and between retries\n3. Return `FixerResult(success=None, interrupted=True)` when interrupted\n\n## Scope\n**In:**\n- Add `interrupt_event` parameter to `run_validation()` and `_run_fixer_agent()`\n- Change `_run_fixer_agent` return type from `bool` to `FixerResult`\n- Add interrupt checks before/during fixer execution\n- Add log path capture using `get_claude_log_path()` (Workstream 6)\n- Add structured log event for subprocess spawn (for test PID capture)\n- Write failing integration test for SIGINT during fixer\n- Write unit tests for fixer interrupt behavior\n\n**Out:**\n- Wiring from orchestrator (T007)\n\n## Changes\n- `src/pipeline/run_coordinator.py` — Exists — Modify:\n  - `run_validation()`: Add `interrupt_event: asyncio.Event | None = None` parameter, pass to `_run_fixer_agent()`\n  - `_run_fixer_agent()`:\n    - Add `interrupt_event: asyncio.Event | None = None` parameter\n    - Change return type to `FixerResult`\n    - Check `interrupt_event.is_set()` before starting\n    - Use `InterruptGuard` for checks during execution\n    - Return `FixerResult(success=None, interrupted=True)` when interrupted\n    - Capture log path using `get_claude_log_path(agent_id)` and include in FixerResult\n  - Add structured log: `logger.info(\"sdk_subprocess_spawned pid=%s pgid=%s flow=fixer\", pid, pgid)`\n- `tests/integration/orchestration/test_sigint_escalation.py` — Exists — Add:\n  - `test_sigint_during_fixer_marks_validation_not_passed()` - **must fail initially**\n- `tests/unit/pipeline/test_run_coordinator.py` — **New** — Add:\n  - `test_fixer_returns_interrupted_when_event_set()`\n  - `test_fixer_checks_interrupt_before_starting()`\n  - `test_fixer_captures_log_path()`\n\n## Wiring Map\n`interrupt_event → RunCoordinator.run_validation() → _run_fixer_agent() → InterruptGuard.is_interrupted() → FixerResult.interrupted`\n\n## Acceptance Criteria\n- `run_validation()` accepts `interrupt_event` parameter and passes to fixer\n- `_run_fixer_agent()` accepts `interrupt_event` parameter\n- Returns `FixerResult` instead of `bool`\n- When `interrupt_event.is_set()` before start: returns `FixerResult(success=None, interrupted=True)` immediately\n- When `interrupt_event.is_set()` during execution: returns interrupted result\n- Log path is captured using `get_claude_log_path()` and returned in FixerResult\n- Integration test `test_sigint_during_fixer_marks_validation_not_passed` exists and fails (will pass after T007 wires orchestrator)\n\n## Verification\n```bash\n# Unit tests should pass\nuv run pytest tests/unit/pipeline/test_run_coordinator.py -v\n\n# Integration test should FAIL (not wired yet) - this is expected\nuv run pytest tests/integration/orchestration/test_sigint_escalation.py::TestUnifiedSIGINTHandling::test_sigint_during_fixer_marks_validation_not_passed -v\n# Expected: FAIL (interrupt_event not wired from orchestrator yet)\n```\n\n## Integration Path Test\nThis task includes the integration-path-test for the unified SIGINT feature. The test exercises:\n- `MalaOrchestrator._handle_sigint()` → sets `interrupt_event`\n- `IssueExecutionCoordinator.run_loop()` receives event\n- `RunCoordinator.run_validation()` passes to fixer\n- `_run_fixer_agent()` checks and returns interrupted\n\n## Notes for Agent\n- The integration test MUST fail initially - it proves the behavior is not yet wired\n- Use mock SDK client for unit tests to avoid actual subprocess spawning\n- Check both \"interrupt before start\" and \"interrupt during execution\" paths\n- Do not modify orchestrator.py yet - that's T007\n- Use `get_claude_log_path()` from `src/infra/tools/env.py` for log path capture\n- Add structured log event for test PID capture (see plan's Monitoring section)","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-08T16:05:43.950077094Z","created_by":"cyou","updated_at":"2026-01-08T17:37:21.514006123Z","closed_at":"2026-01-08T17:37:21.514006123Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-s9p8.3","depends_on_id":"mala-s9p8","type":"parent-child","created_at":"2026-01-08T16:05:43.950750709Z","created_by":"cyou"},{"issue_id":"mala-s9p8.3","depends_on_id":"mala-s9p8.2","type":"blocks","created_at":"2026-01-08T16:08:21.815581491Z","created_by":"cyou"}]}
{"id":"mala-s9p8.4","title":"[T004] Reviewer interrupt handling","description":"## Type\ntask\n\n## Priority\nP2\n\n## Story\nFlow Integration - Workstream 3 (Thread interrupt_event to reviewer)\n\n## Parallel\n[P]\n\n## Primary Files\n- `src/pipeline/review_runner.py` — Exists\n- `src/infra/clients/agent_sdk_review.py` — Exists\n- `tests/unit/infra/test_agent_sdk_review.py` — Exists\n\n## Subsystems\npipeline, infra\n\n## Dependencies\nT002 (need interrupted field on ReviewOutput)\n\n## Goal\nAdd interrupt handling to ReviewRunner and AgentSDKReviewer so reviews can be interrupted cleanly.\n\n## Context\nThe reviewer is called from the orchestration layer. When SIGINT arrives:\n1. ReviewRunner should pass interrupt_event to AgentSDKReviewer\n2. AgentSDKReviewer should check interrupt during SDK session\n3. Both should return `interrupted=True` in their result\n\n## Scope\n**In:**\n- Add `interrupt_event` parameter to `ReviewRunner.run_review()`\n- Add `interrupt_event` parameter to `AgentSDKReviewer.__call__()`\n- Add interrupt checks at appropriate points\n- Add structured log event for subprocess spawn\n- Extend unit tests\n\n**Out:**\n- Wiring from orchestrator (T007)\n\n## Changes\n- `src/pipeline/review_runner.py` — Exists — Modify `run_review()`:\n  - Add `interrupt_event: asyncio.Event | None = None` parameter\n  - Pass to AgentSDKReviewer\n  - Return `ReviewOutput(interrupted=True, ...)` when interrupted\n- `src/infra/clients/agent_sdk_review.py` — Exists — Modify `__call__()`:\n  - Add `interrupt_event: asyncio.Event | None = None` parameter\n  - Check `interrupt_event.is_set()` before starting review\n  - Check during SDK session iteration (if applicable)\n  - Add structured log: `logger.info(\"sdk_subprocess_spawned pid=%s pgid=%s flow=reviewer\", pid, pgid)`\n- `tests/unit/infra/test_agent_sdk_review.py` — Exists — Add:\n  - `test_reviewer_returns_interrupted_when_event_set()`\n  - `test_reviewer_checks_interrupt_before_starting()`\n\n## Wiring Map\n`interrupt_event → ReviewRunner.run_review() → AgentSDKReviewer.__call__() → ReviewOutput.interrupted`\n\n## Acceptance Criteria\n- `ReviewRunner.run_review()` accepts `interrupt_event` parameter\n- `AgentSDKReviewer.__call__()` accepts `interrupt_event` parameter\n- When `interrupt_event.is_set()`: returns `ReviewOutput` with `interrupted=True`\n- Structured log event emitted for SDK subprocess spawn\n- Existing tests continue to pass (backward compatible via `= None` default)\n\n## Verification\n```bash\nuv run pytest tests/unit/infra/test_agent_sdk_review.py -v\nuv run pytest tests/unit/pipeline/test_review_runner.py -v\nuvx ty check src/pipeline/review_runner.py src/infra/clients/agent_sdk_review.py\n```\n\n## Notes for Agent\n- The reviewer is simpler than the fixer (one-shot, no retries)\n- Use `InterruptGuard` from sigint_guard.py for consistent checks\n- The `= None` default maintains backward compatibility\n- Check if there's an existing test file for review_runner.py\n- Add structured log event for test PID capture (see plan's Monitoring section)","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-08T16:05:58.651194735Z","created_by":"cyou","updated_at":"2026-01-08T17:35:46.149450273Z","closed_at":"2026-01-08T17:35:46.149450273Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-s9p8.4","depends_on_id":"mala-s9p8","type":"parent-child","created_at":"2026-01-08T16:05:58.65193508Z","created_by":"cyou"},{"issue_id":"mala-s9p8.4","depends_on_id":"mala-s9p8.2","type":"blocks","created_at":"2026-01-08T16:08:22.38648715Z","created_by":"cyou"}]}
{"id":"mala-s9p8.5","title":"[T005] Epic verification interrupt handling","description":"## Type\ntask\n\n## Priority\nP2\n\n## Story\nFlow Integration - Workstream 3 (Thread interrupt_event to epic verification)\n\n## Parallel\n[P]\n\n## Primary Files\n- `src/pipeline/epic_verification_coordinator.py` — Exists\n- `tests/unit/pipeline/test_epic_verification_coordinator.py` — **New**\n\n## Subsystems\npipeline, tests\n\n## Dependencies\nT002 (need interrupted field on EpicVerificationResult if applicable)\n\n## Goal\nAdd interrupt handling to EpicVerificationCoordinator so epic verification and remediation can be interrupted cleanly.\n\n## Context\nEpic verification has a retry loop for remediation. When SIGINT arrives:\n1. Cancel any in-flight remediation tasks immediately\n2. Do not spawn new remediation attempts\n3. Return with interrupted state (remediation issues may be left open - acceptable since user interrupted intentionally)\n\n## Scope\n**In:**\n- Add `interrupt_event` parameter to `check_epic_closure()`\n- Add interrupt checks in retry loop\n- Cancel remediation tasks on interrupt\n- Add structured log event for remediation subprocess spawns\n- Create unit tests\n\n**Out:**\n- Wiring from orchestrator (T007)\n\n## Changes\n- `src/pipeline/epic_verification_coordinator.py` — Exists — Modify:\n  - `check_epic_closure()`: Add `interrupt_event: asyncio.Event | None = None` parameter\n  - Check `interrupt_event.is_set()` before each retry iteration\n  - Cancel `asyncio.gather` for remediation tasks when interrupted\n  - Return early if interrupted (remediation incomplete is acceptable)\n  - Add structured log for remediation spawns: `logger.info(\"sdk_subprocess_spawned pid=%s pgid=%s flow=epic_remediation\", pid, pgid)`\n- `tests/unit/pipeline/test_epic_verification_coordinator.py` — **New** — Add:\n  - `test_epic_verification_checks_interrupt_before_retry()`\n  - `test_epic_verification_cancels_remediation_on_interrupt()`\n\n## Wiring Map\n`interrupt_event → EpicVerificationCoordinator.check_epic_closure() → _execute_remediation_issues() → task cancellation`\n\n## Acceptance Criteria\n- `check_epic_closure()` accepts `interrupt_event` parameter\n- When `interrupt_event.is_set()` before retry: exits loop immediately\n- When `interrupt_event.is_set()` during remediation: cancels tasks via `asyncio.gather` cancellation\n- Remediation issues may be left open when interrupted (acceptable per design)\n- Structured log event emitted for remediation subprocess spawns\n\n## Verification\n```bash\nuv run pytest tests/unit/pipeline/test_epic_verification_coordinator.py -v\nuvx ty check src/pipeline/epic_verification_coordinator.py\n```\n\n## Notes for Agent\n- Use `InterruptGuard` for consistent checks\n- `asyncio.gather()` with `return_exceptions=True` may need adjustment for cancellation\n- Per the plan: \"Remediation issues may be left open (acceptable - user interrupted intentionally)\"\n- The `= None` default maintains backward compatibility\n- Add structured log event for test PID capture (see plan's Monitoring section)","notes":"Quality gate failed: External review failed: src/pipeline/epic_verification_coordinator.py:3: [P1] Diff contains only docstring updates, not the required implementation; src/pipeline/epic_verification_coordinator.py:1: [P1] Implement interrupt handling + remediation spawn log in coordinator; src/pipeline/epic_verification_coordinator.py:3: [P1] Missing interrupt handling logic in coordinator\nLog: /home/cyou/.claude/projects/-home-cyou-mala/0b822c9c-77aa-45d5-aa55-752b61cd59e3.jsonl","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-08T16:06:17.519780549Z","created_by":"cyou","updated_at":"2026-01-09T05:24:54.167668262Z","closed_at":"2026-01-09T05:24:54.167668262Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-s9p8.5","depends_on_id":"mala-s9p8","type":"parent-child","created_at":"2026-01-08T16:06:17.520361254Z","created_by":"cyou"},{"issue_id":"mala-s9p8.5","depends_on_id":"mala-s9p8.2","type":"blocks","created_at":"2026-01-08T16:08:22.959423495Z","created_by":"cyou"}]}
{"id":"mala-s9p8.6","title":"[T006] Agent session + gate/lifecycle interrupt handling","description":"## Type\ntask\n\n## Priority\nP2\n\n## Story\nFlow Integration - Workstream 3 (Thread interrupt_event to session, gate, lifecycle)\n\n## Parallel\n[P]\n\n## Primary Files\n- `src/pipeline/agent_session_runner.py` — Exists\n- `src/pipeline/gate_runner.py` — Exists\n- `src/pipeline/lifecycle_effect_handler.py` — Exists\n\n## Subsystems\npipeline\n\n## Dependencies\nT002 (need interrupted field on PerSessionGateOutput)\n\n## Goal\nAdd interrupt handling to AgentSessionRunner, GateRunner, and LifecycleEffectHandler for complete coverage of SDK session lifecycle.\n\n## Context\nThese components form the implementer session pipeline:\n- `AgentSessionRunner`: Runs the main SDK session\n- `GateRunner`: Runs post-session gates\n- `LifecycleEffectHandler`: Orchestrates effects (gates, reviews)\n\nAll need interrupt awareness for consistent behavior.\n\n## Scope\n**In:**\n- Add `interrupt_event` parameter to `AgentSessionRunner.run_session()`\n- Add `interrupt_event` parameter to `GateRunner.run_gate()`\n- Add `interrupt_event` parameter to `LifecycleEffectHandler.run_effects()`\n- Add interrupt checks at appropriate points\n- Add structured log event for implementer subprocess spawn\n\n**Out:**\n- Wiring from orchestrator (T007)\n- Comprehensive tests (defer to T007's integration tests)\n\n## Changes\n- `src/pipeline/agent_session_runner.py` — Exists — Modify `run_session()`:\n  - Add `interrupt_event: asyncio.Event | None = None` parameter\n  - Check interrupt at session start and between major operations\n  - Return `AgentSessionOutput` with appropriate interrupted state\n  - Add structured log: `logger.info(\"sdk_subprocess_spawned pid=%s pgid=%s flow=implementer\", pid, pgid)`\n- `src/pipeline/gate_runner.py` — Exists — Modify `run_gate()`:\n  - Add `interrupt_event: asyncio.Event | None = None` parameter\n  - Check interrupt before gate execution\n  - Return `PerSessionGateOutput(interrupted=True)` when interrupted\n- `src/pipeline/lifecycle_effect_handler.py` — Exists — Modify `run_effects()`:\n  - Add `interrupt_event: asyncio.Event | None = None` parameter\n  - Check interrupt before each effect\n  - Return partial results when interrupted (remaining effects skipped)\n\n## Wiring Map\n```\ninterrupt_event → AgentSessionRunner.run_session() → SDK session\ninterrupt_event → GateRunner.run_gate() → PerSessionGateOutput.interrupted\ninterrupt_event → LifecycleEffectHandler.run_effects() → partial EffectResult list\n```\n\n## Acceptance Criteria\n- All three components accept `interrupt_event` parameter\n- `AgentSessionRunner.run_session()` checks interrupt at session boundaries\n- `GateRunner.run_gate()` returns interrupted=True when event set\n- `LifecycleEffectHandler.run_effects()` stops effect iteration when event set\n- Structured log event emitted for implementer subprocess spawn\n- Existing tests continue to pass (backward compatible via `= None` default)\n\n## Verification\n```bash\nuvx ty check src/pipeline/agent_session_runner.py src/pipeline/gate_runner.py src/pipeline/lifecycle_effect_handler.py\nuv run pytest tests/unit/pipeline/test_gate_runner.py tests/unit/pipeline/test_lifecycle_effect_handler.py -v\n```\n\n## Notes for Agent\n- Use `InterruptGuard` for consistent checks\n- `LifecycleEffectHandler.run_effects()` should return partial results (list of completed effects) not raise\n- The `= None` default maintains backward compatibility\n- These are lower-risk changes since fixer/reviewer are higher priority\n- Add structured log event for test PID capture (see plan's Monitoring section)","notes":"Quality gate failed: External review failed: src/pipeline/agent_session_runner.py:993: [P1] NameError: 'agent_id' is not defined in early interrupt path; src/pipeline/agent_session_runner.py:887: [P1] interrupt_event not passed to gate and review callbacks\nLog: /home/cyou/.claude/projects/-home-cyou-mala/ac59271d-1247-42ec-b92c-72911e594be8.jsonl","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-08T16:06:33.48606865Z","created_by":"cyou","updated_at":"2026-01-08T20:03:20.99373633Z","closed_at":"2026-01-08T20:03:20.99373633Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-s9p8.6","depends_on_id":"mala-s9p8","type":"parent-child","created_at":"2026-01-08T16:06:33.486701626Z","created_by":"cyou"},{"issue_id":"mala-s9p8.6","depends_on_id":"mala-s9p8.2","type":"blocks","created_at":"2026-01-08T16:08:23.547448314Z","created_by":"cyou"}]}
{"id":"mala-s9p8.7","title":"[T007] Orchestrator wiring + complete integration tests","description":"## Type\ntask\n\n## Priority\nP2\n\n## Story\nWiring Phase - Workstreams 3, 5, 8\n\n## Primary Files\n- `src/pipeline/issue_execution_coordinator.py` — Exists\n- `src/orchestration/orchestrator.py` — Exists\n- `tests/integration/orchestration/test_sigint_escalation.py` — Exists\n- `tests/integration/orchestration/test_sigint_unified.py` — **New**\n- `tests/unit/orchestration/test_interrupt_wiring.py` — **New**\n\n## Subsystems\norchestration, pipeline, tests\n\n## Dependencies\nT003, T004, T005, T006 (all flow implementations must be complete)\n\n## Goal\nWire interrupt_event from orchestrator through all flows and make integration tests pass. This is the final task that connects all the pieces.\n\n## Context\nAfter T003-T006, all flows have interrupt_event parameters but they're not wired from the orchestrator. This task:\n1. Wires interrupt_event through IssueExecutionCoordinator callbacks\n2. Computes and passes `validation_ran` to on_run_completed\n3. Completes and verifies all integration tests pass\n4. Adds audit tests to prevent regression\n\n## Scope\n**In:**\n- Wire interrupt_event through IssueExecutionCoordinator\n- Update orchestrator to pass interrupt_event to all flows\n- Compute `validation_ran` and pass to `on_run_completed()`\n- Complete integration test suite\n- Add interrupt wiring audit tests\n\n**Out:**\n- Flow implementations (done in T003-T006)\n\n## Changes\n- `src/pipeline/issue_execution_coordinator.py` — Exists — Modify:\n  - Ensure `interrupt_event` is passed to validation callbacks\n  - Pass to `run_validation()` which passes to fixer\n  - Track whether validation actually ran (for `validation_ran` parameter)\n- `src/orchestration/orchestrator.py` — Exists — Modify:\n  - Wire `interrupt_event` to `IssueExecutionCoordinator`\n  - Wire to `ReviewRunner` calls\n  - Wire to `EpicVerificationCoordinator` calls\n  - Update `on_run_completed()` signature to accept `validation_ran: bool = True`\n  - Compute `validation_ran`: `True` if validation was attempted, `False` if skipped (e.g., no issues to validate)\n  - Pass both `run_validation_passed` and `validation_ran` to sink\n- `src/infra/sdk_transport.py` — Exists — **Audit only**: Verify pgid registration works correctly (lines 83-84, 107, 119, 128)\n- `tests/integration/orchestration/test_sigint_escalation.py` — Exists — Verify/extend:\n  - `test_sigint_during_fixer_marks_validation_not_passed()` — **must now pass**\n  - `test_sigint_during_reviewer_returns_interrupted()`\n  - `test_double_sigint_during_sdk_session_kills_subprocess()`\n  - `test_no_orphaned_processes_after_interrupt()` — use structured logs for PID capture\n- `tests/integration/orchestration/test_sigint_unified.py` — **New** — Add:\n  - `test_sigint_during_epic_verification_cancels_remediation()`\n  - `test_sigint_during_implementer_session_returns_interrupted()`\n  - `test_validation_ran_false_when_skipped()` — verify exit 0 when validation skipped\n  - `test_validation_ran_true_with_none_when_interrupted()` — verify exit 130\n- `tests/unit/orchestration/test_interrupt_wiring.py` — **New** — Add audit tests:\n  - `test_run_coordinator_passes_interrupt_event()` — verify non-None\n  - `test_issue_coordinator_passes_interrupt_event()` — verify non-None\n  - `test_orchestrator_passes_interrupt_event_to_all_flows()` — verify wiring\n\n## Wiring Map\n```\nOrchestrator._handle_sigint()\n  ↓ sets interrupt_event\nIssueExecutionCoordinator.run_loop(interrupt_event)\n  ↓ passes to callbacks\n  ↓ tracks validation_ran\nRunCoordinator.run_validation(interrupt_event)\n  ↓ passes to fixer\n_run_fixer_agent(interrupt_event) → FixerResult\n\nOrchestrator → ReviewRunner.run_review(interrupt_event)\nOrchestrator → EpicVerificationCoordinator.check_epic_closure(interrupt_event)\n\nIssueExecutionCoordinator → on_run_completed(run_validation_passed, validation_ran)\n```\n\n## validation_ran Computation\n```python\n# In IssueExecutionCoordinator or orchestrator:\nvalidation_ran = True  # Default: validation was attempted\n\n# Set to False only when validation is explicitly skipped:\nif no_issues_to_validate:\n    validation_ran = False\n    run_validation_passed = None  # Skipped, not interrupted\n\n# Pass to event sink:\nself.event_sink.on_run_completed(\n    run_validation_passed=result,\n    validation_ran=validation_ran,\n)\n```\n\n## Acceptance Criteria\n- All integration tests pass (no more \"expected failures\")\n- `test_sigint_during_fixer_marks_validation_not_passed` **passes** (was failing before wiring)\n- No orphaned SDK processes after interrupt (verified by test using structured logs)\n- `validation_ran` parameter is computed and passed to `on_run_completed()`\n- `validation_ran=False` + `run_validation_passed=None` → exit 0 (skipped)\n- `validation_ran=True` + `run_validation_passed=None` → exit 130 (interrupted)\n- Audit tests verify orchestrator passes non-None interrupt_event to flows\n\n## Verification\n```bash\n# All integration tests must pass\nuv run pytest tests/integration/orchestration/test_sigint_escalation.py -v\nuv run pytest tests/integration/orchestration/test_sigint_unified.py -v\n\n# Audit tests must pass\nuv run pytest tests/unit/orchestration/test_interrupt_wiring.py -v\n\n# Full test suite\nuv run pytest -m \"unit or integration\" -n auto\n\n# Type check\nuvx ty check src/orchestration/orchestrator.py src/pipeline/issue_execution_coordinator.py\n```\n\n**Final verification that all tests pass**:\n```bash\nuv run pytest tests/unit/infra/test_sigint_guard.py tests/unit/pipeline/test_run_coordinator.py tests/unit/orchestration/test_interrupt_wiring.py tests/integration/orchestration/test_sigint_escalation.py tests/integration/orchestration/test_sigint_unified.py -v\n```\n\n## Notes for Agent\n- The integration test from T003 should now PASS after this wiring\n- Use the test assertion strategy from the plan: black-box assertions on exit codes, log messages, result fields\n- For \"no orphaned processes\" test: parse `sdk_subprocess_spawned pid=N` from logs to capture PIDs deterministically\n- This is the final task - all acceptance criteria for the epic should be verifiable after this\n- Run the full test suite at the end to verify no regressions\n- Audit sdk_transport.py to confirm pgid registration is correct (no changes needed, just verify)","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-08T16:08:04.395005183Z","created_by":"cyou","updated_at":"2026-01-09T05:59:22.293364152Z","closed_at":"2026-01-09T05:59:22.293364152Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-s9p8.7","depends_on_id":"mala-s9p8","type":"parent-child","created_at":"2026-01-08T16:08:04.395587079Z","created_by":"cyou"},{"issue_id":"mala-s9p8.7","depends_on_id":"mala-s9p8.3","type":"blocks","created_at":"2026-01-08T16:08:24.131302344Z","created_by":"cyou"},{"issue_id":"mala-s9p8.7","depends_on_id":"mala-s9p8.4","type":"blocks","created_at":"2026-01-08T16:08:24.722587752Z","created_by":"cyou"},{"issue_id":"mala-s9p8.7","depends_on_id":"mala-s9p8.5","type":"blocks","created_at":"2026-01-08T16:08:25.277969481Z","created_by":"cyou"},{"issue_id":"mala-s9p8.7","depends_on_id":"mala-s9p8.6","type":"blocks","created_at":"2026-01-08T16:08:25.855420066Z","created_by":"cyou"}]}
{"id":"mala-s9p8.8","title":"[Review] 5 non-blocking findings from mala-s9p8.4","description":"## Review Findings\n\nThis issue consolidates 5 non-blocking findings from code review.\n\n**Source issue:** mala-s9p8.4\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Emit required structured log on SDK subprocess spawn\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/infra/clients/agent_sdk_review.py:123-170\n\nTask acceptance requires a structured log event `sdk_subprocess_spawned pid=%s pgid=%s flow=reviewer` when the SDK subprocess is spawned. This diff adds no such log statement in the reviewer path, so the requirement is unmet.\n\n---\n\n### Finding 2: [P2] Ensure reviewer result surfaces `interrupted=True` rather than parse_error\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/infra/clients/agent_sdk_review.py:123-165\n\nThe task goal/criteria say `AgentSDKReviewer` should return a result with `interrupted=True` when `interrupt_event.is_set()`. The diff returns a `ReviewResult` with `parse_error=\"Review interrupted\"` and does not set any interrupted flag on the result object, so the wiring map `... → ReviewOutput.interrupted` is not satisfied at the reviewer layer (it relies on downstream heuristics instead of an explicit field).\n\n---\n\n### Finding 3: [P2] Missing required structured log for subprocess spawn\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/infra/clients/agent_sdk_review.py:136-162\n\nThe task specifies adding a structured log: `logger.info(\"sdk_subprocess_spawned pid=%s pgid=%s flow=reviewer\", pid, pgid)` in `AgentSDKReviewer.__call__`. This log is missing from the diff. It is required for monitoring and test PID capture as outlined in the task context.\n\n---\n\n### Finding 4: [P2] Missing interrupt check during SDK session iteration\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/infra/clients/agent_sdk_review.py:145-158\n\nThe task requires checking for interrupts during the SDK session iteration in `AgentSDKReviewer`. The diff only adds an early check before the session starts. To ensure a timely interruption, `guard.is_interrupted()` should also be checked inside the loop that processes messages from the SDK session.\n\n---\n\n### Finding 5: [P2] Update CodeReviewer protocol to include interrupt_event\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/pipeline/review_runner.py:223\n\nThe call to `self.code_reviewer` in `ReviewRunner.run_review` uses a `# type: ignore[call-arg]` because `interrupt_event` was added to the implementation but presumably not to the `CodeReviewer` protocol in `src/core/protocols.py`. Updating the protocol is necessary for type safety and matches the 'Flow Integration' goal.\n\n---\n\n\u003c!-- fp:52f2f2c48ee9579c --\u003e\n\u003c!-- fp:c393ed2aa468b2ba --\u003e\n\u003c!-- fp:5c0711bcd8b8d459 --\u003e\n\u003c!-- fp:52d4ffce9b35af7c --\u003e\n\u003c!-- fp:8e8291db30753040 --\u003e\n\u003c!-- review_finding:706adccb0be0 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-08T17:35:46.446361354Z","created_by":"cyou","updated_at":"2026-01-08T17:44:36.408166531Z","closed_at":"2026-01-08T17:44:36.408166531Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-s9p8.4"],"dependencies":[{"issue_id":"mala-s9p8.8","depends_on_id":"mala-s9p8","type":"parent-child","created_at":"2026-01-08T17:35:46.446953801Z","created_by":"cyou"}]}
{"id":"mala-s9p8.9","title":"[Review] 4 non-blocking findings from mala-s9p8.3","description":"## Review Findings\n\nThis issue consolidates 4 non-blocking findings from code review.\n\n**Source issue:** mala-s9p8.3\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Capture fixer log path using agent_id, not session_id\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/pipeline/run_coordinator.py:480-606\n\nTask context/acceptance criteria require capturing the log path via `get_claude_log_path(agent_id)` and returning it in `FixerResult`. The diff instead derives `log_path` from `result_session_id` (from `ResultMessage.session_id`), which does not match the documented wiring map and may point at a different log naming scheme than the fixer’s `agent_id`.\n\nThis is a concrete mismatch between spec and implementation; if tests/assertions later depend on `agent_id`-based paths, this will fail even when interrupts are correctly handled.\n\n---\n\n### Finding 2: [P2] Integration test is xfail but spec requires it to fail initially\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** tests/integration/orchestration/test_sigint_escalation.py:1372-1450\n\nThe task explicitly requires `test_sigint_during_fixer_marks_validation_not_passed` to exist and **fail** until T007 wires the orchestrator. The diff marks the test `xfail(strict=True)`, which converts the intended failing signal into an expected outcome, reducing the usefulness of the integration-path-test as a “red” gate.\n\nIf the goal is to keep CI red for this test until wiring is done, it should be a normal failing test (or otherwise enforced to fail) rather than xfail.\n\n---\n\n### Finding 3: [P2] Missing structured log for subprocess spawn\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/pipeline/run_coordinator.py:536-540\n\nThe task context explicitly requires adding a structured log: `logger.info(\"sdk_subprocess_spawned pid=%s pgid=%s flow=fixer\", pid, pgid)` in `_run_fixer_agent`. This log is missing from the implementation, and there is no code to extract the `pid` or `pgid` from the created SDK client.\n\n---\n\n### Finding 4: [P3] Log path not captured on interrupt or error\n\n**Priority:** P3\n**Reviewer:** gemini\n**Location:** src/pipeline/run_coordinator.py:545-612\n\nThe `log_path` is only computed after the message loop finishes successfully. If the fixer is interrupted (line 548) or an exception occurs (lines 604, 608), the returned `FixerResult` will have `log_path=None`. If `get_claude_log_path` can be derived from `agent_id` (as mentioned in the task description), it should be computed before the loop to ensure visibility into logs for failed or interrupted attempts.\n\n---\n\n\u003c!-- fp:a41007313466590d --\u003e\n\u003c!-- fp:676bbaaef6ab5d09 --\u003e\n\u003c!-- fp:39b44193171e2808 --\u003e\n\u003c!-- fp:0554cdc20eb12583 --\u003e\n\u003c!-- review_finding:9f009c1fe953 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-08T17:37:21.804825141Z","created_by":"cyou","updated_at":"2026-01-08T17:43:06.116261558Z","closed_at":"2026-01-08T17:43:06.116261558Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-s9p8.3"],"dependencies":[{"issue_id":"mala-s9p8.9","depends_on_id":"mala-s9p8","type":"parent-child","created_at":"2026-01-08T17:37:21.805446738Z","created_by":"cyou"}]}
{"id":"mala-sb1q","title":"[Review] 1 non-blocking finding from mala-b79o.4","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-b79o.4\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Pass event_sink into DefaultReviewer\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/orchestration/factory.py:246-254\n\nDefaultReviewer now emits review warnings only via the event sink, but the default factory path still constructs it without a sink. That means mismatched exit code/JSON verdict or stale-gate resolution warnings will be silently lost in the common orchestrator setup, reducing diagnostics compared to the prior console log. Consider threading `event_sink` into the DefaultReviewer constructor (or explicitly using a NullEventSink) so warnings still flow by default.\n\n---\n","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T06:56:55.388077939Z","created_by":"cyou","updated_at":"2026-01-03T06:59:40.898627037Z","closed_at":"2026-01-03T06:59:40.898627037Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_findings:af686afb5ec1","source:mala-b79o.4"]}
{"id":"mala-scxp","title":"[Review] src/domain/quality_gate.py:322-353: [P3] Remove unused _match_spec_pattern method","description":"## Review Finding\n\nThis issue was auto-created from a P3 review finding.\n\n**Source issue:** mala-fdp1.11\n**Reviewer:** claude\n**Location:** src/domain/quality_gate.py:322-353\n\n## Details\n\nThe `_match_spec_pattern` method (lines 322-353) is now dead code after introducing `_match_spec_pattern_with_kinds`. The new method is called in `parse_validation_evidence_with_spec` but the old method is never called anywhere. This adds unnecessary code to maintain.","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-02T15:51:09.482926057Z","created_by":"cyou","updated_at":"2026-01-02T17:03:52.62189039Z","closed_at":"2026-01-02T17:03:52.62189039Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_finding:0ad3d5e1a149","source:mala-fdp1.11"],"dependencies":[{"issue_id":"mala-scxp","depends_on_id":"mala-moty","type":"blocks","created_at":"2026-01-02T16:44:12.021266386Z","created_by":"daemon"},{"issue_id":"mala-scxp","depends_on_id":"mala-e0sn","type":"blocks","created_at":"2026-01-02T16:44:12.136133092Z","created_by":"daemon"}]}
{"id":"mala-sdh1","title":"[Review] src/domain/validation/spec.py:271-277: [P2] Handle missing/invalid config without hard failure","description":"## Review Finding\n\nThis issue was auto-created from a P2 review finding.\n\n**Source issue:** mala-b8g4\n**Reviewer:** codex\n**Location:** src/domain/validation/spec.py:271-277\n\n## Details\n\n`build_validation_spec` previously caught `ConfigError` from `load_config` (e.g., no `mala.yaml` present) and returned an empty spec. The new fail-fast path means repos without a config file—or with a temporarily invalid one—will now throw and abort validation instead of proceeding with defaults. If optional config is still a supported use case, restore the fallback or distinguish missing-file vs invalid-config errors.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-02T17:20:28.884771834Z","created_by":"cyou","updated_at":"2026-01-02T17:43:47.262879595Z","closed_at":"2026-01-02T17:43:47.262879595Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_finding:76436723cde1","source:mala-b8g4"]}
{"id":"mala-sj18","title":"run run-level validations every x issues completed","status":"closed","priority":2,"issue_type":"feature","created_at":"2026-01-03T19:19:23.105615886Z","created_by":"cyou","updated_at":"2026-01-06T05:19:55.776253286Z","closed_at":"2026-01-06T05:19:55.776253286Z","close_reason":"Closed"}
{"id":"mala-skxg","title":"Epic: Architecture Refactor 2026-01-03 (SDK boundary, god classes, testability)","description":"## Summary\n\nArchitecture refactoring based on 10 issues identified in architecture review:\n- **3 High Priority**: SDK imports in pipeline, duplicated agent runtime setup, DefaultReviewer complexity  \n- **5 Medium Priority**: AgentSessionRunner god class, shared mutable log dicts, session config wiring, duplicate PromptProvider names, locking CLI complexity\n- **2 Low Priority**: Deferred (hook building in pipeline, config parsing in domain)\n\n## Goals\n\n- Both testability AND maintainability equally prioritized\n- Issue-by-issue phasing (complete each end-to-end before starting next)\n\n## Success Metrics\n\n- `AgentSessionRunner` delegates all stream processing to `MessageStreamProcessor`; no SDK iteration calls remain\n- `ContextPressureHandler` exposes checkpoint/restart logic independently testable\n- `DefaultReviewer.__call__` orchestrates only; all subprocess and parsing logic in dedicated components\n- `_cli_main` only parses/dispatches; command handlers perform all lock operations\n- Import-linter passes with new SDK boundary rules\n- All new components have unit tests\n\n## Constraints\n\n- API breaks allowed freely for internal APIs\n- No backward-compatibility shims or re-exports\n- Lazy SDK imports must be preserved (local to methods)\n- 85% coverage threshold maintained\n\n## Plan Reference\n\nSee `plans/2026-01-03-architecture-refactor-plan.md` for full details.","status":"closed","priority":1,"issue_type":"epic","created_at":"2026-01-03T23:17:17.609804621Z","created_by":"cyou","updated_at":"2026-01-04T03:42:06.170934388Z","closed_at":"2026-01-04T03:42:06.170934388Z","close_reason":"Closed"}
{"id":"mala-skxg.1","title":"[T001] Create SDKClientFactory and SDK boundary enforcement","description":"## Goal\n\nMove all Claude SDK usage behind cross-layer protocols; enforce SDK boundary with import-linter.\n\n## Context\n\n- SDK imports currently exist in `src/pipeline/` which violates architectural boundaries\n- Pipeline layer should only use protocols from `src/core/protocols.py`\n- Lazy local imports must be preserved for bootstrap order\n\n## Scope\n\n**In**: Create SDK adapter, update pipeline imports, add import-linter rule\n**Out**: No changes to SDK behavior or API\n\n## Changes\n\n| File | Status | Changes |\n|------|--------|---------|\n| `src/core/protocols.py` | Modify | Add SDKClientProtocol if needed for cross-layer |\n| `src/infra/sdk_adapter.py` | New | SDKClientFactory with lazy SDK imports |\n| `src/pipeline/agent_session_runner.py` | Modify | Remove SDK imports, use factory |\n| `src/pipeline/run_coordinator.py` | Modify | Inject factory, remove SDK imports |\n| `src/orchestration/orchestration_wiring.py` | Modify | Construct and pass factory |\n| `pyproject.toml` | Modify | Add `claude_agent_sdk` to forbidden_modules in Contract 2 |\n\n## Wiring Map\n\n`SDKClientFactory` (infra) → `orchestration_wiring.py` → `AgentSessionRunner` / `RunCoordinator`\n\n## Acceptance Criteria\n\n- [ ] No `claude_agent_sdk` imports in `src/pipeline/`, `src/domain/`, `src/core/`\n- [ ] SDKClientFactory uses lazy local imports only (inside methods, not module-level)\n- [ ] Import-linter passes with new rule (`uv run import-linter`)\n- [ ] `tests/test_lazy_imports.py` passes\n- [ ] Existing tests pass\n\n## Verification\n\n```bash\n# Verify no SDK imports in forbidden layers\ngrep -r \"claude_agent_sdk\" src/pipeline/ src/domain/ src/core/ || echo \"Clean!\"\n\n# Run import-linter\nuv run import-linter\n\n# Run lazy import test\nuv run pytest tests/test_lazy_imports.py -v\n\n# Run full test suite\nuv run pytest\n```\n\n## Rollback\n\nGit revert this commit if issues found.\n\n## Notes for Agent\n\n- Use `TYPE_CHECKING` imports for SDK types in infra (no runtime dependency)\n- Local imports inside methods, never at module level\n- Check `tests/test_lazy_imports.py` to understand existing lazy import guarantees","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-03T23:17:43.853632368Z","created_by":"cyou","updated_at":"2026-01-04T01:54:54.378005694Z","closed_at":"2026-01-04T01:54:54.378005694Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-skxg.1","depends_on_id":"mala-skxg","type":"parent-child","created_at":"2026-01-03T23:17:43.871712885Z","created_by":"daemon"}]}
{"id":"mala-skxg.10","title":"[T010] Refactor locking CLI to command dispatch pattern","description":"## Goal\n\nSimplify `_cli_main` using command dispatch pattern.\n\n## Context\n\n- `_cli_main` currently has complex if/else chain for command handling\n- Each command handler contains lock operations mixed with arg parsing\n- Command dispatch pattern separates concerns and makes adding commands easier\n\n## Scope\n\n**In**: Refactor _cli_main to dispatch pattern\n**Out**: No changes to locking behavior\n\n## Changes\n\n| File | Status | Changes |\n|------|--------|---------|\n| `src/infra/tools/locking.py` | Modify | Create CliContext, per-command handlers, COMMANDS dispatch dict |\n\n## Design\n\n```python\n@dataclass\nclass CliContext:\n    lock_base: Path\n    lock_key: str\n    # ... parsed env\n\nCOMMANDS = {\n    'try': _cmd_try,\n    'wait': _cmd_wait,\n    'check': _cmd_check,\n    'holder': _cmd_holder,\n    'release': _cmd_release,\n    'release-all': _cmd_release_all,\n}\n\ndef _cli_main() -\u003e int:\n    ctx = _parse_context()  # validate env/args once\n    return COMMANDS[ctx.command](ctx)\n```\n\n## Acceptance Criteria\n\n- [ ] `_cli_main` only validates env/args and dispatches to command handlers; no lock operations in main\n- [ ] Each command handler (`_cmd_try`, `_cmd_wait`, etc.) performs exactly one lock operation\n- [ ] Command dispatch uses `COMMANDS` dict; adding new commands requires only adding handler + dict entry\n- [ ] `CliContext` dataclass holds parsed env (lock_base, lock_key, etc.)\n- [ ] Existing lock script tests pass\n- [ ] Existing integration tests pass\n\n## Verification\n\n```bash\n# Run lock script tests\nuv run pytest tests/test_lock_scripts.py -v\n\n# Run lock integration tests\nuv run pytest tests/test_lock_integration.py -v\n\n# Run full test suite\nuv run pytest\n```\n\n## Rollback\n\nGit revert this commit if issues found.\n\n## Notes for Agent\n\n- Each `_cmd_*` function takes CliContext, returns exit code (int)\n- Move all lock operations out of _cli_main into handlers\n- Keep error handling (invalid command, missing env) in _cli_main\n- CliContext is frozen dataclass (immutable)","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T23:20:47.674408264Z","created_by":"cyou","updated_at":"2026-01-04T01:24:33.411560318Z","closed_at":"2026-01-04T01:24:33.411560318Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-skxg.10","depends_on_id":"mala-skxg","type":"parent-child","created_at":"2026-01-03T23:20:47.682935826Z","created_by":"daemon"}]}
{"id":"mala-skxg.11","title":"[Review] 1 non-blocking finding from mala-skxg.10","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-skxg.10\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Reject extra args for non-wait commands\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/infra/tools/locking.py:653-660\n\n`_cli_main` now only checks `len(sys.argv) \u003c 3` for commands that take a single `\u003cfilepath\u003e`, so calls like `try \u003cfilepath\u003e extra` or `check \u003cfilepath\u003e extra` silently ignore the extra arguments and proceed. The pre-refactor code required an exact arg count and returned usage errors; this change can hide CLI misuse and is a behavior change outside the stated scope. Consider enforcing exact arg counts for commands without optional arguments (e.g., `len == 3` for `try/check/holder/release`).\n\n---\n\n\u003c!-- fp:a6a579f27c424696 --\u003e\n\u003c!-- review_finding:fa680448d2e5 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-04T01:24:33.444469974Z","created_by":"cyou","updated_at":"2026-01-04T01:27:30.195493817Z","closed_at":"2026-01-04T01:27:30.195493817Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-skxg.10"],"dependencies":[{"issue_id":"mala-skxg.11","depends_on_id":"mala-skxg","type":"parent-child","created_at":"2026-01-04T01:24:33.444822821Z","created_by":"daemon"}]}
{"id":"mala-skxg.12","title":"[Review] 3 non-blocking findings from mala-skxg.4","description":"## Review Findings\n\nThis issue consolidates 3 non-blocking findings from code review.\n\n**Source issue:** mala-skxg.4\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Re-export shim violates documented code migration rules\n\n**Priority:** P2\n**Reviewer:** claude\n**Location:** src/infra/clients/cerberus_review.py:33-42\n\nThe `__all__` list with comment \"Re-export for backward compatibility\" and the imports from `review_output_parser` that are re-exported violate CLAUDE.md which explicitly states: \"No backward-compatibility shims\" and \"No re-exports: Don't create modules that just import and re-export from elsewhere. If code moves, fix the imports.\" The ~20 files importing `ReviewIssue`, `ReviewResult`, etc. from `cerberus_review` should be updated to import from `review_output_parser` instead.\n\n---\n\n### Finding 2: [P2] Remove re-export shim in cerberus_review\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/infra/clients/cerberus_review.py:33-43\n\nThis change adds an `__all__` re-export for `ReviewIssue`, `ReviewResult`, `parse_cerberus_json`, and `map_exit_code_to_result`. The project’s migration rules explicitly say “No backward-compatibility shims” and “No re-exports”; keeping these symbols re-exported in `cerberus_review` hides the move and undermines the intended direct imports. Update callers to import from `review_output_parser` and drop the re-export list instead.\n\n---\n\n### Finding 3: [P2] Missing runtime import for Path\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/infra/clients/review_output_parser.py:8-9\n\n`Path` is only imported inside `if TYPE_CHECKING:`, but is used in `ReviewResult` fields and method signatures. While `from __future__ import annotations` postpones evaluation, this will cause `NameError` if `typing.get_type_hints(ReviewResult)` is called at runtime (common in serialization/validation libs). Move `from pathlib import Path` to top-level imports.\n\n---\n\n\u003c!-- fp:5526c12a69b24256 --\u003e\n\u003c!-- fp:8449809b0390c368 --\u003e\n\u003c!-- fp:5e2e899c141d9c56 --\u003e\n\u003c!-- review_finding:e72f3217284c --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-04T01:35:12.10587832Z","created_by":"cyou","updated_at":"2026-01-04T01:59:57.338693326Z","closed_at":"2026-01-04T01:59:57.338693326Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-skxg.4"],"dependencies":[{"issue_id":"mala-skxg.12","depends_on_id":"mala-skxg","type":"parent-child","created_at":"2026-01-04T01:35:12.106319257Z","created_by":"daemon"}]}
{"id":"mala-skxg.13","title":"[Review] 2 non-blocking findings from mala-skxg.1","description":"## Review Findings\n\nThis issue consolidates 2 non-blocking findings from code review.\n\n**Source issue:** mala-skxg.1\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Make SDKClientFactory required in dataclasses\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/pipeline/agent_session_runner.py:648\n\nThe `sdk_client_factory` field is typed as optional (`| None = None`) in `AgentSessionRunner` and `RunCoordinator`, but runtime checks in methods like `_build_agent_env` will raise `RuntimeError` if it is missing. This makes the classes unsafe to instantiate with defaults. You should make this field required in `__init__` (adjusting field order if necessary) to reflect that it is now a mandatory dependency.\n\n---\n\n### Finding 2: [P3] Verify import-linter configuration\n\n**Priority:** P3\n**Reviewer:** gemini\n**Location:** pyproject.toml:173-178\n\nYou added `claude_agent_sdk` to `forbidden_modules` in `pyproject.toml`. Ensure that the `source_modules` for this contract does not include `src.infra` (or specifically `src.infra.sdk_adapter`), otherwise the new adapter itself will violate the linter rule since it legitimately imports the SDK.\n\n---\n\n\u003c!-- fp:5fd19bc4e1491b64 --\u003e\n\u003c!-- fp:5e6db4cd73d3ebcf --\u003e\n\u003c!-- review_finding:46e8bfc02c70 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-04T01:54:54.408304934Z","created_by":"cyou","updated_at":"2026-01-04T02:32:46.800471805Z","closed_at":"2026-01-04T02:32:46.800471805Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-skxg.1"],"dependencies":[{"issue_id":"mala-skxg.13","depends_on_id":"mala-skxg","type":"parent-child","created_at":"2026-01-04T01:54:54.408708721Z","created_by":"daemon"}]}
{"id":"mala-skxg.14","title":"[Review] 2 non-blocking findings from mala-skxg.5","description":"## Review Findings\n\nThis issue consolidates 2 non-blocking findings from code review.\n\n**Source issue:** mala-skxg.5\n**Highest priority:** P3\n\n---\n\n### Finding 1: [P3] Remove unused FakeLifecycleContext class\n\n**Priority:** P3\n**Reviewer:** claude\n**Location:** tests/test_message_stream_processor.py:101-107\n\nThe `FakeLifecycleContext` class is defined but never used in the tests. All test methods use the real `LifecycleContext` fixture (`lifecycle_ctx`) instead. This is dead code that should be removed for clarity.\n\n---\n\n### Finding 2: [P3] Correct inaccurate line number in comment\n\n**Priority:** P3\n**Reviewer:** gemini\n**Location:** src/pipeline/message_stream_processor.py:372\n\nThe comment `# session_id was already extracted at line 331` in `_process_result_message` is inaccurate as the extraction occurs a few lines above (around line 370) and refers to a line number from the original file or a previous draft.\n\n---\n\n\u003c!-- fp:bd492726827577ef --\u003e\n\u003c!-- fp:9a85e3ff20e9d908 --\u003e\n\u003c!-- review_finding:22e1eba768e8 --\u003e","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-04T03:07:24.570773507Z","created_by":"cyou","updated_at":"2026-01-04T03:09:47.420817451Z","closed_at":"2026-01-04T03:09:47.420817451Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-skxg.5"],"dependencies":[{"issue_id":"mala-skxg.14","depends_on_id":"mala-skxg","type":"parent-child","created_at":"2026-01-04T03:07:24.571146174Z","created_by":"daemon"}]}
{"id":"mala-skxg.15","title":"[Review] 1 non-blocking finding from mala-skxg.7","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-skxg.7\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Preserve session log path on abort/error results\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/orchestration/orchestrator.py:681-686\n\n`_finalize_issue_result` now relies on `IssueResult.session_log_path`, but the abort/exception IssueResults are constructed without it (e.g. `IssueResult(issue_id=issue_id, agent_id=..., success=False, summary=...)`). If a session had already produced a log path via `_on_session_log_path`, finalization will now pass `None` and drop logs for aborted/failed runs, which is a regression from the previous dict-based behavior. Consider setting `session_log_path=self._active_session_log_paths.get(issue_id)` when creating these IssueResults (also in `finalize_callback`), or having `_finalize_issue_result` fall back to `_active_session_log_paths` when the result field is missing.\n\n---\n\n\u003c!-- fp:108bbcb2e161ac8a --\u003e\n\u003c!-- review_finding:cdee7ea2eed7 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-04T03:34:53.423540782Z","created_by":"cyou","updated_at":"2026-01-04T03:38:58.533622694Z","closed_at":"2026-01-04T03:38:58.533622694Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-skxg.7"],"dependencies":[{"issue_id":"mala-skxg.15","depends_on_id":"mala-skxg","type":"parent-child","created_at":"2026-01-04T03:34:53.424077619Z","created_by":"daemon"}]}
{"id":"mala-skxg.2","title":"[T002] Create AgentRuntimeBuilder for centralized runtime config","description":"## Goal\n\nCentralize duplicated agent runtime configuration using builder pattern.\n\n## Context\n\n- Agent runtime setup (hooks, env, options) is duplicated across `AgentSessionRunner` and `RunCoordinator`\n- A single `AgentRuntimeBuilder` provides single source of truth\n- Builder must use local SDK imports inside `build()` to preserve lazy-import guarantees\n\n## Scope\n\n**In**: Create builder, replace duplicated setup in both consumers\n**Out**: No behavior changes to agent runtime\n\n## Changes\n\n| File | Status | Changes |\n|------|--------|---------|\n| `src/infra/agent_runtime.py` | New | AgentRuntimeBuilder class with fluent API |\n| `src/pipeline/agent_session_runner.py` | Modify | Replace `_build_agent_env`, `_build_hooks`, `_build_sdk_options` with builder |\n| `src/pipeline/run_coordinator.py` | Modify | Replace duplicated setup with builder |\n| `tests/test_agent_runtime.py` | New | Unit tests for builder |\n\n## Wiring Map\n\n`AgentRuntimeBuilder` (infra) → `orchestration_wiring.py` → `AgentSessionRunner` / `RunCoordinator`\n\n## Acceptance Criteria\n\n- [ ] `AgentRuntimeBuilder` with fluent API: `.with_hooks()`, `.with_env()`, `.with_mcp()`, `.with_disallowed_tools()`, `.build()`\n- [ ] Both implementer and fixer sessions use builder (no duplicated setup code)\n- [ ] Builder uses local imports inside `build()` only\n- [ ] Unit tests pass for builder (env composition, hook ordering, disallowed tools)\n- [ ] Existing tests pass\n\n## Verification\n\n```bash\n# Run new unit tests\nuv run pytest tests/test_agent_runtime.py -v\n\n# Run lazy import test\nuv run pytest tests/test_lazy_imports.py -v\n\n# Run full test suite\nuv run pytest\n```\n\n## Rollback\n\nGit revert this commit if issues found.\n\n## Notes for Agent\n\n- Builder pattern: each `.with_*()` returns `self` for chaining\n- `.build()` returns bundle: `AgentRuntime` dataclass with options, env, caches, hooks\n- Preserve hook ordering - order matters for SDK\n- Local imports only inside `build()` method","notes":"Quality gate failed: External review failed: tests/test_agent_session_runner.py:1: [P0] Existing tests in test_agent_session_runner.py are broken\nLog: /home/cyou/.claude/projects/-home-cyou-mala/0b25114a-2619-4dfd-92fa-3edcdc053083.jsonl","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-03T23:18:06.792735541Z","created_by":"cyou","updated_at":"2026-01-04T02:51:59.890017014Z","closed_at":"2026-01-04T02:51:59.890017014Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-skxg.2","depends_on_id":"mala-skxg","type":"parent-child","created_at":"2026-01-03T23:18:06.800439359Z","created_by":"daemon"},{"issue_id":"mala-skxg.2","depends_on_id":"mala-skxg.1","type":"blocks","created_at":"2026-01-03T23:21:29.137876081Z","created_by":"daemon"}]}
{"id":"mala-skxg.3","title":"[T003] Extract CerberusGateCLI for subprocess management","description":"## Goal\n\nExtract CLI subprocess management from DefaultReviewer into independently testable component.\n\n## Context\n\n- `DefaultReviewer` currently handles subprocess spawn/wait/resolve, binary validation, env merge, timeout handling\n- Extracting `CerberusGateCLI` makes subprocess logic independently testable\n- DefaultReviewer becomes thin orchestrator\n\n## Scope\n\n**In**: Create CerberusGateCLI, move subprocess logic\n**Out**: No changes to parsing logic (separate task)\n\n## Changes\n\n| File | Status | Changes |\n|------|--------|---------|\n| `src/infra/clients/cerberus_gate_cli.py` | New | CLI subprocess management |\n| `src/infra/clients/cerberus_review.py` | Modify | Use CerberusGateCLI for subprocess calls |\n| `tests/test_cerberus_gate_cli.py` | New | Unit tests for CLI component |\n| `tests/test_cerberus_review.py` | Modify | Adjust imports to new modules |\n\n## Acceptance Criteria\n\n- [ ] `CerberusGateCLI` handles all subprocess spawn/wait/resolve; `DefaultReviewer` has no subprocess calls\n- [ ] `CerberusGateCLI` handles binary validation, env merge, timeout handling\n- [ ] Component independently testable with mocked subprocess\n- [ ] Stale gate flow tested\n- [ ] Existing Cerberus tests pass\n\n## Verification\n\n```bash\n# Run new unit tests\nuv run pytest tests/test_cerberus_gate_cli.py -v\n\n# Run existing Cerberus tests\nuv run pytest tests/test_cerberus_review.py -v\n\n# Run full test suite\nuv run pytest\n```\n\n## Rollback\n\nGit revert this commit if issues found.\n\n## Notes for Agent\n\n- CLI should handle: subprocess.run/Popen, exit code interpretation, timeout, env dict merge\n- Keep existing behavior - just extract, don't refactor\n- Test stale gate flow (where gate file is outdated)","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-03T23:18:36.673213041Z","created_by":"cyou","updated_at":"2026-01-04T01:27:47.16285331Z","closed_at":"2026-01-04T01:27:47.16285331Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-skxg.3","depends_on_id":"mala-skxg","type":"parent-child","created_at":"2026-01-03T23:18:36.673771002Z","created_by":"daemon"}]}
{"id":"mala-skxg.4","title":"[T004] Extract ReviewOutputParser for JSON parsing","description":"## Goal\n\nExtract JSON parsing and issue mapping from DefaultReviewer into independently testable component.\n\n## Context\n\n- `DefaultReviewer` currently handles JSON parsing, issue mapping, exit-code mapping, formatting\n- Extracting `ReviewOutputParser` makes parsing logic independently testable\n- Combined with T003, DefaultReviewer becomes thin orchestrator\n\n## Scope\n\n**In**: Create ReviewOutputParser, move parsing logic\n**Out**: No changes to subprocess logic (covered by T003)\n\n## Changes\n\n| File | Status | Changes |\n|------|--------|---------|\n| `src/infra/clients/review_output_parser.py` | New | JSON parsing + issue mapping |\n| `src/infra/clients/cerberus_review.py` | Modify | Use ReviewOutputParser, thin adapter using CLI + parser |\n| `tests/test_review_output_parser.py` | New | Unit tests for parser component |\n| `tests/test_cerberus_review.py` | Modify | Point to new modules |\n\n## Acceptance Criteria\n\n- [ ] `ReviewOutputParser` handles all JSON parsing and exit-code mapping; `DefaultReviewer` has no parsing logic\n- [ ] Each component independently testable with mocked dependencies\n- [ ] `DefaultReviewer.__call__` orchestrates only (calls CLI, passes output to parser)\n- [ ] Golden JSON fixtures used for parser tests\n- [ ] Existing Cerberus tests pass\n\n## Verification\n\n```bash\n# Run new unit tests\nuv run pytest tests/test_review_output_parser.py -v\n\n# Run existing Cerberus tests\nuv run pytest tests/test_cerberus_review.py -v\n\n# Run full test suite\nuv run pytest\n```\n\n## Rollback\n\nGit revert this commit if issues found.\n\n## Notes for Agent\n\n- Parser should handle: JSON decode, issue object mapping, exit-code → result mapping\n- Use golden JSON fixtures (sample valid/invalid outputs)\n- Test all exit code mappings (0=pass, 1=findings, 2+=error)","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-03T23:18:36.739786631Z","created_by":"cyou","updated_at":"2026-01-04T01:35:12.071748061Z","closed_at":"2026-01-04T01:35:12.071748061Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-skxg.4","depends_on_id":"mala-skxg","type":"parent-child","created_at":"2026-01-03T23:18:36.740235622Z","created_by":"daemon"},{"issue_id":"mala-skxg.4","depends_on_id":"mala-skxg.3","type":"blocks","created_at":"2026-01-03T23:21:29.152495922Z","created_by":"daemon"}]}
{"id":"mala-skxg.5","title":"[T005] Extract MessageStreamProcessor from AgentSessionRunner","description":"## Goal\n\nExtract SDK stream processing from AgentSessionRunner into independently testable component.\n\n## Context\n\n- `AgentSessionRunner` is a god class (~1000+ lines)\n- Stream processing (SDK iteration, idle timeout, lint cache updates) can be extracted\n- `MessageStreamProcessor` handles all `receive_response()` iteration\n\n## Scope\n\n**In**: Create MessageStreamProcessor, move stream iteration logic\n**Out**: Context pressure logic (separate task T006)\n\n## Changes\n\n| File | Status | Changes |\n|------|--------|---------|\n| `src/pipeline/message_stream_processor.py` | New | IdleTimeoutStream, IdleTimeoutError, MessageIterationState/Result, stream processing |\n| `src/pipeline/agent_session_runner.py` | Modify | Delegate stream processing to MessageStreamProcessor |\n| `tests/test_message_stream_processor.py` | New | Stream parsing, idle retry, lint cache tests |\n| `tests/test_agent_session_runner.py` | Modify | Update to new entry points |\n\n## Wiring Map\n\n`MessageStreamProcessor` (pipeline) ← `AgentSessionRunner` delegates iteration\n\n## Acceptance Criteria\n\n- [ ] `MessageStreamProcessor` handles all SDK stream iteration; no `receive_response()` loops in `AgentSessionRunner`\n- [ ] `IdleTimeoutStream` wraps SDK stream with timeout logic\n- [ ] Lint cache updates happen in processor\n- [ ] Unit tests pass for extracted component\n- [ ] Existing tests pass\n\n## Verification\n\n```bash\n# Run new unit tests\nuv run pytest tests/test_message_stream_processor.py -v\n\n# Run existing tests\nuv run pytest tests/test_agent_session_runner.py -v\n\n# Run full test suite\nuv run pytest\n```\n\n## Rollback\n\nGit revert this commit if issues found.\n\n## Notes for Agent\n\n- Processor iterates SDK stream, yields processed messages\n- IdleTimeoutStream monitors time between messages, raises IdleTimeoutError\n- MessageIterationState tracks current position/mode\n- MessageIterationResult contains processed message data\n- Use protocol-based message/block checks for testability","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-03T23:19:14.481720714Z","created_by":"cyou","updated_at":"2026-01-04T03:07:24.538185593Z","closed_at":"2026-01-04T03:07:24.538185593Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-skxg.5","depends_on_id":"mala-skxg","type":"parent-child","created_at":"2026-01-03T23:19:14.489926715Z","created_by":"daemon"},{"issue_id":"mala-skxg.5","depends_on_id":"mala-skxg.2","type":"blocks","created_at":"2026-01-03T23:21:29.16189634Z","created_by":"daemon"}]}
{"id":"mala-skxg.6","title":"[T006] Extract ContextPressureHandler from AgentSessionRunner","description":"## Goal\n\nExtract checkpoint/restart logic from AgentSessionRunner into independently testable component.\n\n## Context\n\n- Context pressure handling (checkpoint fetch, restart loop, continuation prompts) is complex\n- `ContextPressureHandler` makes this logic independently testable\n- AgentSessionRunner becomes coordinator delegating to extracted components\n\n## Scope\n\n**In**: Create ContextPressureHandler, move checkpoint/restart logic\n**Out**: Stream processing (covered by T005)\n\n## Changes\n\n| File | Status | Changes |\n|------|--------|---------|\n| `src/pipeline/context_pressure_handler.py` | New | ContextPressureError, checkpoint/restart loop, continuation prompt logic |\n| `src/pipeline/agent_session_runner.py` | Modify | Delegate context pressure to handler; reduce to coordinator (~500 lines target) |\n| `tests/test_context_pressure_handler.py` | New | Checkpoint fetch, continuation prompts tests |\n| `tests/test_agent_session_runner.py` | Modify | Update to new entry points |\n\n## Wiring Map\n\n`ContextPressureHandler` (pipeline) ← `AgentSessionRunner` delegates checkpoint logic\n\n## Acceptance Criteria\n\n- [ ] `ContextPressureHandler` handles all checkpoint fetch and restart logic; independently testable without `AgentSessionRunner`\n- [ ] `ContextPressureError` raised when pressure threshold hit\n- [ ] Continuation prompts generated by handler\n- [ ] `AgentSessionRunner` only coordinates lifecycle transitions and delegates to extracted components\n- [ ] `AgentSessionRunner` reduced to ~500 lines (from 1000+)\n- [ ] Unit tests pass for extracted component\n\n## Verification\n\n```bash\n# Run new unit tests\nuv run pytest tests/test_context_pressure_handler.py -v\n\n# Check line count reduction\nwc -l src/pipeline/agent_session_runner.py\n\n# Run existing tests\nuv run pytest tests/test_agent_session_runner.py -v\n\n# Run full test suite\nuv run pytest\n```\n\n## Rollback\n\nGit revert this commit if issues found.\n\n## Notes for Agent\n\n- Handler monitors context token count vs threshold\n- Checkpoint fetch logic: call SDK checkpoint API, handle timeouts\n- Continuation prompts: generate prompts for agent to continue after restart\n- Keep lifecycle state machine in AgentSessionRunner (don't over-extract)","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-03T23:19:14.555956823Z","created_by":"cyou","updated_at":"2026-01-04T03:16:44.623551633Z","closed_at":"2026-01-04T03:16:44.623551633Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-skxg.6","depends_on_id":"mala-skxg","type":"parent-child","created_at":"2026-01-03T23:19:14.556390353Z","created_by":"daemon"},{"issue_id":"mala-skxg.6","depends_on_id":"mala-skxg.5","type":"blocks","created_at":"2026-01-03T23:21:29.170818918Z","created_by":"daemon"}]}
{"id":"mala-skxg.7","title":"[T007] Remove shared mutable log path dictionaries","description":"## Goal\n\nRemove shared mutable `session_log_paths`/`review_log_paths` dictionaries; flow log paths via return values.\n\n## Context\n\n- Shared mutable dicts are passed through multiple layers for logging\n- This makes code harder to reason about and test\n- Log paths should flow via `IssueResult` return values instead\n\n## Scope\n\n**In**: Add log path fields to IssueResult, remove shared dicts\n**Out**: No changes to actual logging behavior\n\n## Changes\n\n| File | Status | Changes |\n|------|--------|---------|\n| `src/pipeline/issue_result.py` | Modify | Add session_log_path, review_log_path fields |\n| `src/orchestration/orchestrator.py` | Modify | Remove dicts, use IssueResult paths for finalization |\n| `src/pipeline/session_callback_factory.py` | Modify | Remove dict dependencies |\n| `src/orchestration/orchestration_wiring.py` | Modify | Remove dicts from WiringDependencies |\n\n## Acceptance Criteria\n\n- [ ] No shared mutable log path dictionaries in codebase\n- [ ] `IssueResult` has `session_log_path` and `review_log_path` Optional fields\n- [ ] Log paths correctly propagate through IssueResult to finalization\n- [ ] Orchestrator reads paths from IssueResult, not shared dicts\n- [ ] Existing tests pass\n\n## Verification\n\n```bash\n# Search for removed patterns\ngrep -r \"session_log_paths\\|review_log_paths\" src/ || echo \"Clean!\"\n\n# Run orchestrator tests\nuv run pytest tests/test_orchestrator.py -v\n\n# Run full test suite\nuv run pytest\n```\n\n## Rollback\n\nGit revert this commit if issues found.\n\n## Notes for Agent\n\n- IssueResult is a dataclass, add Optional[Path] fields\n- Update all code that reads from shared dicts to read from IssueResult\n- Remove dict cleanup helper from orchestrator\n- Session runner sets log path on result before returning","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T23:19:39.276481977Z","created_by":"cyou","updated_at":"2026-01-04T03:34:53.391142906Z","closed_at":"2026-01-04T03:34:53.391142906Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-skxg.7","depends_on_id":"mala-skxg","type":"parent-child","created_at":"2026-01-03T23:19:39.284430766Z","created_by":"daemon"},{"issue_id":"mala-skxg.7","depends_on_id":"mala-skxg.6","type":"blocks","created_at":"2026-01-03T23:21:29.180064996Z","created_by":"daemon"}]}
{"id":"mala-skxg.8","title":"[T008] Fix session config wiring for context thresholds","description":"## Goal\n\nEnsure `context_restart_threshold` and `context_limit` properly propagate from OrchestratorConfig to AgentSessionConfig.\n\n## Context\n\n- These config values may not be properly wired through the layer boundaries\n- Need to verify and fix the wiring path\n- Add unit test to prevent future regressions\n\n## Scope\n\n**In**: Verify and fix config wiring, add regression test\n**Out**: No changes to config behavior\n\n## Changes\n\n| File | Status | Changes |\n|------|--------|---------|\n| `src/orchestration/orchestration_wiring.py` | Modify | Ensure explicit wiring of context fields |\n| `src/orchestration/orchestrator.py` | Modify | Verify wiring deps include fields |\n| `src/orchestration/types.py` | Modify (if needed) | Only if fields missing/mis-typed |\n| `tests/test_orchestrator.py` | Modify | Add unit check verifying propagation |\n\n## Wiring Map\n\n`OrchestratorConfig.context_restart_threshold` → `_build_wiring_dependencies` → `build_session_config` → `AgentSessionConfig.context_restart_threshold`\n`OrchestratorConfig.context_limit` → `_build_wiring_dependencies` → `build_session_config` → `AgentSessionConfig.context_limit`\n\n## Acceptance Criteria\n\n- [ ] Config values propagate from OrchestratorConfig to AgentSessionConfig\n- [ ] Unit test verifies propagation: set non-default value in OrchestratorConfig, verify it reaches AgentSessionConfig\n- [ ] [integration-path-test] Test exercises `build_session_config` with overridden values\n- [ ] Existing tests pass\n\n## Verification\n\n```bash\n# Run orchestrator tests with new test\nuv run pytest tests/test_orchestrator.py -v -k config\n\n# Run full test suite\nuv run pytest\n```\n\n## Rollback\n\nGit revert this commit if issues found.\n\n## Notes for Agent\n\n- Trace the config path manually first to identify gaps\n- Add explicit field pass-through if currently missing\n- Unit test: construct OrchestratorConfig with non-default threshold, call build functions, assert AgentSessionConfig has same value\n- This is [integration-path-test] for config wiring","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T23:20:02.041505871Z","created_by":"cyou","updated_at":"2026-01-04T03:39:23.924394812Z","closed_at":"2026-01-04T03:39:23.924394812Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-skxg.8","depends_on_id":"mala-skxg","type":"parent-child","created_at":"2026-01-03T23:20:02.050499337Z","created_by":"daemon"},{"issue_id":"mala-skxg.8","depends_on_id":"mala-skxg.7","type":"blocks","created_at":"2026-01-03T23:21:29.189379364Z","created_by":"daemon"}]}
{"id":"mala-skxg.9","title":"[T009] Rename pipeline PromptProvider to SessionPrompts","description":"## Goal\n\nDisambiguate naming - rename pipeline's `PromptProvider` to `SessionPrompts` to avoid collision with domain `PromptProvider`.\n\n## Context\n\n- Both pipeline and domain have a class called `PromptProvider`\n- This causes confusion and requires aliasing in orchestration_wiring.py\n- Renaming pipeline's version to `SessionPrompts` clarifies purpose\n\n## Scope\n\n**In**: Rename class, update all imports\n**Out**: No behavior changes\n\n## Changes\n\n| File | Status | Changes |\n|------|--------|---------|\n| `src/pipeline/agent_session_runner.py` | Modify | Rename class from PromptProvider to SessionPrompts |\n| `src/orchestration/orchestration_wiring.py` | Modify | Import SessionPrompts directly, remove alias workaround |\n| `tests/test_agent_session_runner.py` | Modify | Update imports to use SessionPrompts |\n\n## Acceptance Criteria\n\n- [ ] Class renamed from `PromptProvider` to `SessionPrompts` in pipeline\n- [ ] No name collision between pipeline and domain PromptProvider\n- [ ] All imports updated (no aliases needed)\n- [ ] Existing tests pass\n\n## Verification\n\n```bash\n# Search for old name in pipeline\ngrep -r \"class PromptProvider\" src/pipeline/ || echo \"Renamed!\"\n\n# Search for alias workaround\ngrep -r \"as.*PromptProvider\" src/orchestration/ || echo \"Alias removed!\"\n\n# Run affected tests\nuv run pytest tests/test_agent_session_runner.py -v\n\n# Run full test suite\nuv run pytest\n```\n\n## Rollback\n\nGit revert this commit if issues found.\n\n## Notes for Agent\n\n- Simple rename refactor\n- Find all imports of pipeline's PromptProvider and update\n- Domain PromptProvider (in src/domain/prompts.py) stays unchanged\n- Check orchestration_wiring.py for any alias patterns to remove","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T23:20:24.449397948Z","created_by":"cyou","updated_at":"2026-01-04T03:38:36.858634283Z","closed_at":"2026-01-04T03:38:36.858634283Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-skxg.9","depends_on_id":"mala-skxg","type":"parent-child","created_at":"2026-01-03T23:20:24.460859841Z","created_by":"daemon"},{"issue_id":"mala-skxg.9","depends_on_id":"mala-skxg.6","type":"blocks","created_at":"2026-01-03T23:21:29.198191373Z","created_by":"daemon"},{"issue_id":"mala-skxg.9","depends_on_id":"mala-skxg.7","type":"blocks","created_at":"2026-01-03T23:29:56.253921061Z","created_by":"daemon"}]}
{"id":"mala-slxa","title":"Missing sdk_subprocess_spawned log in epic_verification_coordinator","description":"In src/pipeline/epic_verification_coordinator.py, the remediation subprocess spawns lack the required structured log event:\n```\nlogger.info(\"sdk_subprocess_spawned pid=%s pgid=%s flow=epic_remediation\", pid, pgid)\n```\nThis was flagged repeatedly in code review for mala-s9p8.5 but never resolved. The current implementation logs `remediation_task_spawned` with `issue_id` instead of the required pid/pgid fields.\n\n## Acceptance Criteria\n- Add sdk_subprocess_spawned log with pid and pgid parameters\n- May require updating spawn_remediation callback to provide PID/PGID info\n\n## Context\nBlocked mala-s9p8.5 from passing review after 3 attempts.","status":"closed","priority":1,"issue_type":"bug","created_at":"2026-01-08T21:02:20.588512355Z","created_by":"cyou","updated_at":"2026-01-08T21:37:08.291134323Z","closed_at":"2026-01-08T21:37:08.291134323Z","close_reason":"FALSE POSITIVE: Agent correctly implemented this. The sdk_subprocess_spawned log IS emitted at sdk_transport.py:91-96 with pid/pgid/flow. The flow is passed via MALA_SDK_FLOW env var through spawn_remediation → spawn_agent → run_implementer → AgentSessionInput → with_env. Reviewer didn't see the full chain."}
{"id":"mala-smab","title":"Epic: Orchestrator Architecture Refactor","description":"## Goals\nAddress two High-severity architecture issues from architecture review:\n1. **God Object Orchestrator**: Complete factory pattern by moving `_build_runtime_deps()` to factory\n2. **Callback Lambda Capture**: Replace 10-parameter lambda soup with `SessionRunContext` dataclass\n\n## Scope\n- Move `CommandRunner`, `EnvConfig`, `LockManager` construction to `create_orchestrator()` factory\n- Eliminate `MalaOrchestrator._build_runtime_deps()` method\n- Create `SessionRunContext` frozen dataclass\n- Refactor `build_session_callback_factory()` from 10 getter params to context object\n- Update `SessionCallbackFactory` to accept/use `SessionRunContext`\n- Add/update unit tests\n\n## Out of Scope\n- Medium/Low severity issues from architecture review\n- Creating Protocol for `SessionRunContext`\n- Changes to public CLI interface\n\n## Constraints\n- Maintain backward compatibility with `create_orchestrator()` API\n- Follow repo's \"no re-export shims\" policy\n- Use frozen dataclasses for config/context objects\n- 72% minimum test coverage\n\n## Plan Reference\n`plans/2026-01-12-orchestrator-refactor-plan.md`","status":"closed","priority":1,"issue_type":"epic","created_at":"2026-01-12T04:10:53.071558905Z","created_by":"cyou","updated_at":"2026-01-12T04:50:10.471048902Z","closed_at":"2026-01-12T04:50:10.471048902Z","close_reason":"Closed"}
{"id":"mala-smab.1","title":"[T001] Add SessionRunContext frozen dataclass","description":"## Type: task | Priority: P2 | Phase: Foundation\n\n## Goal\nDefine the `SessionRunContext` frozen dataclass that bundles the 9 late-bound getters currently passed individually to `build_session_callback_factory()`.\n\n## Context\nThe callback wiring currently passes 10 individual getter parameters, creating \"lambda soup\" that's hard to maintain and test. This task creates the Parameter Object that consolidates these into a single typed context.\n\n## Scope\n**In**: Define `SessionRunContext` in `types.py` with all 9 getter fields\n**Out**: Updating consumers - that's T002's responsibility\n\n## Changes\n- `src/orchestration/types.py` — [Exists] — Add `SessionRunContext` frozen dataclass with these fields:\n  - `log_provider_getter: Callable[[], LogProvider]`\n  - `evidence_check_getter: Callable[[], GateChecker]`\n  - `on_session_log_path: Callable[[str, Path], None]`\n  - `on_review_log_path: Callable[[str, str], None]`\n  - `interrupt_event_getter: Callable[[], asyncio.Event | None]`\n  - `get_base_sha: Callable[[str], str | None]`\n  - `get_run_metadata: Callable[[], RunMetadata | None]`\n  - `on_abort: Callable[[str], None]`\n  - `abort_event_getter: Callable[[], asyncio.Event | None]`\n\n## Acceptance Criteria\n- `SessionRunContext` is a frozen dataclass (immutable)\n- All 9 fields are typed with `Callable` signatures matching existing usage in `orchestration_wiring.py:249-263`\n- Dataclass can be instantiated with getter lambdas\n- Uses `TYPE_CHECKING` imports to avoid circular import issues\n\n## Verification\n- `uvx ruff check src/orchestration/types.py` passes\n- `uvx ty check src/orchestration/types.py` passes\n- Can instantiate: `SessionRunContext(log_provider_getter=lambda: ..., ...)`\n- Frozen test: `ctx.log_provider_getter = x` raises `FrozenInstanceError`\n\n## Notes for Agent\n- Follow existing patterns in types.py (see RuntimeDeps, PipelineConfig)\n- Import types under `if TYPE_CHECKING:` to avoid circular imports\n- Use `Callable[[], X | None]` for optional getters, not `Callable[[], X] | None`","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-12T04:11:08.174540152Z","created_by":"cyou","updated_at":"2026-01-12T04:24:03.560765374Z","closed_at":"2026-01-12T04:24:03.560765374Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-smab.1","depends_on_id":"mala-smab","type":"parent-child","created_at":"2026-01-12T04:11:08.17533288Z","created_by":"cyou"}]}
{"id":"mala-smab.2","title":"[T002] [integration-path-test] Refactor callback wiring to use SessionRunContext","description":"## Type: task | Priority: P1 | Phase: Feature | Parallel: No\n\n## Goal\nRefactor `build_session_callback_factory()` and `SessionCallbackFactory` to accept `SessionRunContext` instead of 10 individual getter parameters. Update the orchestrator call site to construct and pass the context object.\n\n## Context\nThis is the core callback wiring refactor that replaces \"lambda soup\" with a typed Parameter Object. All changes must be made atomically to maintain a compilable codebase.\n\n## Scope\n**In**: Update signature of `build_session_callback_factory()`, update `SessionCallbackFactory.__init__()`, update orchestrator call site in `_init_pipeline_runners()`, update tests\n**Out**: Factory pattern changes (T003)\n\n## Primary Files\nsrc/orchestration/orchestration_wiring.py, src/pipeline/session_callback_factory.py, src/orchestration/orchestrator.py\n\n## Subsystems\norchestration, pipeline\n\n## Changes\n- `src/orchestration/orchestration_wiring.py` — [Exists] — Update `build_session_callback_factory()`:\n  - Remove 10 individual getter params (lines 254-263)\n  - Add `context: SessionRunContext` parameter\n  - Keep `cumulative_review_runner` separate (it's an optional runner, not a getter)\n  - New signature: `(runtime, pipeline, async_gate_runner, review_runner, context, cumulative_review_runner=None)`\n  - Update function body to extract getters from `context`\n\n- `src/pipeline/session_callback_factory.py` — [Exists] — Update `SessionCallbackFactory.__init__()`:\n  - Replace individual getter params with `context: SessionRunContext`\n  - Update docstring examples\n  - Update internal usage to access `context.log_provider_getter` etc.\n\n- `src/orchestration/orchestrator.py` — [Exists] — Update `_init_pipeline_runners()`:\n  - Construct `SessionRunContext` with all 9 getters\n  - Pass context to `build_session_callback_factory()`\n  - Import `SessionRunContext` from types\n\n- `tests/unit/orchestration/test_orchestration_wiring.py` — [Exists] — Update tests:\n  - Create `SessionRunContext` fixtures\n  - Update `build_session_callback_factory()` calls\n\n- `tests/unit/pipeline/test_session_callback_factory.py` — [Exists] — Update tests:\n  - Update constructor calls to use context object\n\n## Wiring Map\n```\nSessionRunContext → build_session_callback_factory() → SessionCallbackFactory.__init__() → stored for callback creation\n```\n\n## Acceptance Criteria\n- `build_session_callback_factory()` takes 6 params instead of 15 (runtime, pipeline, async_gate_runner, review_runner, context, cumulative_review_runner)\n- `SessionCallbackFactory` stores context and extracts getters on demand\n- All existing callback behavior unchanged (getters called at same points)\n- All wiring tests pass with updated signatures\n- No direct orchestrator field reads in callback code (all via context getters)\n\n## Verification\n- `uv run pytest tests/unit/orchestration/test_orchestration_wiring.py -v` passes\n- `uv run pytest tests/unit/pipeline/test_session_callback_factory.py -v` passes\n- `uvx ruff check src/orchestration/orchestration_wiring.py src/pipeline/session_callback_factory.py` passes\n- `uvx ty check src/orchestration/ src/pipeline/session_callback_factory.py` passes\n- Integration: `uv run pytest tests/integration/ -v` passes\n\n## Integration Path Test\nThis task exercises the composition root path: orchestrator creates SessionRunContext → passes to wiring function → wiring creates SessionCallbackFactory → factory used during session runs.\n\n## Notes for Agent\n- This is an atomic refactor: all files must change together\n- Follow existing Callable patterns for getter signatures\n- cumulative_review_runner stays separate because it's an optional runner object, not a getter\n- Test fixtures should use lambda stubs, not mocks (per tests/CLAUDE.md)","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-12T04:11:29.011956771Z","created_by":"cyou","updated_at":"2026-01-12T04:36:12.521070011Z","closed_at":"2026-01-12T04:36:12.521070011Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-smab.2","depends_on_id":"mala-smab","type":"parent-child","created_at":"2026-01-12T04:11:29.01261348Z","created_by":"cyou"},{"issue_id":"mala-smab.2","depends_on_id":"mala-smab.1","type":"blocks","created_at":"2026-01-12T04:11:54.468098308Z","created_by":"cyou"}]}
{"id":"mala-smab.3","title":"[T003] [integration-path-test] Complete factory pattern - move RuntimeDeps construction","description":"## Type: task | Priority: P1 | Phase: Feature | Parallel: No\n\n## Goal\nMove `CommandRunner`, `EnvConfig`, `LockManager` construction from `MalaOrchestrator._build_runtime_deps()` to `create_orchestrator()` factory. Eliminate the God Object anti-pattern by completing the existing factory pattern.\n\n## Context\nThe orchestrator currently constructs its own runtime dependencies internally via `_build_runtime_deps()`, violating dependency injection principles. The factory already handles most initialization - this completes that pattern.\n\n## Scope\n**In**: Move dep construction to factory, update orchestrator to receive complete RuntimeDeps, delete `_build_runtime_deps()`, add factory tests\n**Out**: Callback wiring (T002)\n\n## Primary Files\nsrc/orchestration/factory.py, src/orchestration/orchestrator.py, tests/unit/orchestration/test_factory.py\n\n## Subsystems\norchestration\n\n## Changes\n- `src/orchestration/factory.py` — [Exists] — Update `create_orchestrator()`:\n  - Construct `CommandRunner(cwd=repo_path)` if not provided in deps\n  - Construct `EnvConfig()` if not provided in deps\n  - Construct `LockManager()` if not provided in deps\n  - Pass complete RuntimeDeps to orchestrator\n  - Logic: `deps.field if deps and deps.field else DefaultClass()`\n\n- `src/orchestration/orchestrator.py` — [Exists] —\n  - Update `_init_from_factory()` to receive complete RuntimeDeps (no internal construction)\n  - Delete `_build_runtime_deps()` method entirely (lines 411-422)\n  - Update any code that called `_build_runtime_deps()` to use the passed RuntimeDeps\n\n- `tests/unit/orchestration/test_factory.py` — [Exists] — Add tests:\n  - Test `create_orchestrator()` constructs RuntimeDeps with defaults when `deps=None`\n  - Test factory fills gaps when `deps` has some fields `None`\n  - Test factory respects provided values when `deps` fields are set\n\n## Wiring Map\n```\ncreate_orchestrator() → constructs RuntimeDeps → MalaOrchestrator.__init__() → _init_from_factory() → uses RuntimeDeps\n```\n\n## Acceptance Criteria\n- `_build_runtime_deps()` method no longer exists in orchestrator.py\n- `create_orchestrator()` constructs CommandRunner, EnvConfig, LockManager when not provided\n- Orchestrator receives fully-constructed RuntimeDeps (doesn't create any internally)\n- Factory handles partial OrchestratorDependencies (fills None fields with defaults)\n- `create_orchestrator()` public signature unchanged (backward compatible)\n- All factory tests pass covering: full defaults, partial deps, all deps provided\n\n## Verification\n- `grep \"_build_runtime_deps\" src/orchestration/orchestrator.py` returns nothing\n- `uv run pytest tests/unit/orchestration/test_factory.py -v` passes\n- `uv run pytest tests/unit/orchestration/ -v` passes (all orchestration tests)\n- `uv run pytest tests/integration/ -v` passes\n- `uvx ruff check src/orchestration/factory.py src/orchestration/orchestrator.py` passes\n- Manual: `uv run mala run --max-issues 1 --dry-run` initializes successfully\n\n## Integration Path Test\nThis task exercises the composition root path: CLI → create_orchestrator() → RuntimeDeps construction → MalaOrchestrator init → session run.\n\n## Notes for Agent\n- This completes an existing factory pattern, not creating a new one\n- CommandRunner needs repo_path: `CommandRunner(cwd=repo_path)`\n- EnvConfig and LockManager are zero-arg constructors\n- The factory already exists at line 686; extend its logic\n- Ensure tests use fakes from tests/fakes/, not mocks","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-12T04:11:48.357524825Z","created_by":"cyou","updated_at":"2026-01-12T04:46:32.610331127Z","closed_at":"2026-01-12T04:46:32.610331127Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-smab.3","depends_on_id":"mala-smab","type":"parent-child","created_at":"2026-01-12T04:11:48.358403543Z","created_by":"cyou"},{"issue_id":"mala-smab.3","depends_on_id":"mala-smab.2","type":"blocks","created_at":"2026-01-12T04:11:55.056746748Z","created_by":"cyou"}]}
{"id":"mala-smaj","title":"Epic: Per-Trigger Validation Configuration","description":"Implement per-trigger validation configuration system that allows users to specify exactly which validation commands run at specific trigger points (epic_completion, session_end, periodic).\n\n**Spec**: /home/cyou/mala/plans/2026-01-07-epic-validation-trigger-spec.md\n**Plan**: /home/cyou/mala/plans/2026-01-07-validation-triggers-plan.md\n**Tasks**: /home/cyou/mala/plans/2026-01-07-validation-triggers-tasks.md\n\n**Key Features**:\n- Three trigger types: epic_completion, session_end, periodic\n- Per-trigger failure modes: abort, remediate (with retry loop), continue\n- Command list structure with base pool + per-trigger overrides\n- BREAKING CHANGES: Repurpose global_validation_commands, deprecate validate_every\n- Blocking validation execution to prevent workspace conflicts\n- 10 new MalaEventSink events for trigger lifecycle\n\n**Success Criteria**:\n- Fast feedback queueing: epic_completion trigger queued within 10s of epic closure\n- Correct trigger execution: periodic fires at interval multiples\n- Migration clarity: hard errors for deprecated configs with guide URL\n- Remediation loop correctness: spawns fixer, retries, aborts on exhaustion\n\n**Acceptance Criteria Coverage**:\n- R1: Hard migration error (T004)\n- R2: Supported triggers (T010-T012)\n- R3: Command list selection (T003, T007)\n- R4: Fail-fast execution (T007)\n- R5: Failure modes (T008)\n- R6: Migration/presets (T004, T005)\n\n14 tasks across 6 phases.","status":"closed","priority":0,"issue_type":"epic","created_at":"2026-01-07T04:32:53.55608913Z","created_by":"cyou","updated_at":"2026-01-09T07:01:21.103014742Z","closed_at":"2026-01-09T07:01:21.103014742Z","close_reason":"Closed"}
{"id":"mala-smaj.1","title":"[T001] Add trigger config enums and dataclasses to config.py","description":"**Type**: task\n**Priority**: P1\n**Story**: Foundation\n**Primary Files**: src/domain/validation/config.py\n**Subsystems**: domain\n**Dependencies**: None\n\n**Goal**:\nAdd all trigger-related enums (TriggerType, FailureMode, EpicDepth, FireOn) and dataclasses (TriggerCommandRef, BaseTriggerConfig, EpicCompletionTriggerConfig, SessionEndTriggerConfig, PeriodicTriggerConfig, ValidationTriggersConfig) to the config module. Also update the existing ValidationConfig dataclass to include the new validation_triggers field.\n\n**Context**:\n- Foundation for all trigger configuration\n- Follows existing frozen dataclass pattern in config.py\n- Enums parse from YAML strings (e.g., \"abort\" → FailureMode.ABORT)\n\n**Scope**:\n- In: Enums, dataclasses, type definitions, ValidationConfig field update\n- Out: Parsing logic (T003), validation logic (T004)\n\n**Changes**:\n- `src/domain/validation/config.py` — Exists — Add:\n  - `TriggerType` enum: EPIC_COMPLETION, SESSION_END, PERIODIC\n  - `FailureMode` enum: ABORT, CONTINUE, REMEDIATE\n  - `EpicDepth` enum: TOP_LEVEL, ALL\n  - `FireOn` enum: SUCCESS, FAILURE, BOTH\n  - `TriggerCommandRef` frozen dataclass: ref, command?, timeout?\n  - `BaseTriggerConfig` frozen dataclass: failure_mode, commands, max_retries?\n  - `EpicCompletionTriggerConfig(BaseTriggerConfig)`: epic_depth, fire_on\n  - `SessionEndTriggerConfig(BaseTriggerConfig)`: (no additional fields)\n  - `PeriodicTriggerConfig(BaseTriggerConfig)`: interval\n  - `ValidationTriggersConfig` frozen dataclass: epic_completion?, session_end?, periodic?, is_empty()\n  - **UPDATE ValidationConfig**: Add `validation_triggers: ValidationTriggersConfig | None = None` field\n\n**Acceptance Criteria**:\n- All 4 enums defined with correct values\n- All 6 new dataclasses are frozen\n- TriggerCommandRef supports optional command/timeout overrides\n- BaseTriggerConfig has failure_mode (FailureMode), commands (tuple), max_retries (int|None)\n- ValidationTriggersConfig.is_empty() returns True when all triggers are None\n- **ValidationConfig has new validation_triggers field with None default**\n\n**Verification**:\n- Type check: `uvx ty check src/domain/validation/config.py`\n- Import test: `python -c \"from src.domain.validation.config import TriggerType, FailureMode, ValidationTriggersConfig, ValidationConfig\"`\n- Verify: `python -c \"from src.domain.validation.config import ValidationConfig; print(ValidationConfig.__dataclass_fields__['validation_triggers'])\"`\n\n**Notes for Agent**:\n- Use `tuple[TriggerCommandRef, ...]` not `list` for frozen dataclass fields\n- Follow existing CommandConfig pattern for TriggerCommandRef\n- All dataclasses must be frozen=True\n- The validation_triggers field on ValidationConfig must default to None for backward compatibility","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-07T04:33:17.530236691Z","created_by":"cyou","updated_at":"2026-01-07T15:11:22.074560436Z","closed_at":"2026-01-07T15:11:22.074560436Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-smaj.1","depends_on_id":"mala-smaj","type":"parent-child","created_at":"2026-01-07T04:33:17.530856997Z","created_by":"cyou"}]}
{"id":"mala-smaj.10","title":"[T010] Implement epic_completion trigger integration","description":"**Type**: task\n**Priority**: P1\n**Story**: Integration Points\n**Parallel**: [P]\n**Primary Files**: src/pipeline/epic_verification_coordinator.py, tests/unit/pipeline/test_trigger_execution.py\n**Subsystems**: pipeline\n**Dependencies**: T009\n\n**Goal**:\nHook epic_completion trigger into EpicVerificationCoordinator.check_epic_closure() to queue validation after epic verification.\n\n**Context**:\n- Fires after epic verification completes (success or failure)\n- Respects epic_depth filter (top_level vs all)\n- Respects fire_on filter (success, failure, both)\n\n**Scope**:\n- In: Epic completion trigger hook, filter logic\n- Out: Other triggers (T011, T012)\n\n**Changes**:\n- `src/pipeline/epic_verification_coordinator.py` — Exists — Update:\n  - After verification result determined, check if epic_completion trigger configured\n  - Apply epic_depth filter: if top_level, check epic has no epic parent\n  - Apply fire_on filter: success, failure, or both\n  - If filters pass, call run_coordinator.queue_trigger_validation(TriggerType.EPIC_COMPLETION, context)\n  - Context: epic_id, depth, verification_result\n- `tests/unit/pipeline/test_trigger_execution.py` — Exists — Add:\n  - Test epic_depth=top_level: nested epic doesn't fire\n  - Test epic_depth=all: nested epic fires\n  - Test fire_on=success: only fires on pass\n  - Test fire_on=failure: only fires on fail\n  - Test fire_on=both: always fires\n  - Test leaf epic with no children fires\n\n**Acceptance Criteria**:\n- epic_completion trigger fires after check_epic_closure()\n- epic_depth=top_level only fires for epics with no epic parent\n- fire_on filter respected correctly\n- Context includes epic_id, depth, verification_result\n\n**Verification**:\n- Unit tests: `uv run pytest tests/unit/pipeline/test_trigger_execution.py -k epic_completion -v`\n- Type check: `uvx ty check src/pipeline/epic_verification_coordinator.py`\n\n**Notes for Agent**:\n- EpicVerificationCoordinator needs access to run_coordinator (may need to inject)\n- Check existing epic parent detection logic in codebase","notes":"Quality gate failed: External review failed: tests/unit/pipeline/test_trigger_execution.py:881: [P1] Missing imports for trigger configuration types in tests\nLog: /home/cyou/.claude/projects/-home-cyou-mala/942fd220-758e-447e-b233-a1ac35e67e71.jsonl","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-07T04:35:25.986663967Z","created_by":"cyou","updated_at":"2026-01-08T22:16:43.246827514Z","closed_at":"2026-01-08T22:16:43.246827514Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-smaj.10","depends_on_id":"mala-smaj","type":"parent-child","created_at":"2026-01-07T04:35:25.987657081Z","created_by":"cyou"},{"issue_id":"mala-smaj.10","depends_on_id":"mala-smaj.9","type":"blocks","created_at":"2026-01-07T04:36:28.626179793Z","created_by":"cyou"}]}
{"id":"mala-smaj.11","title":"[T011] Implement periodic trigger integration","description":"**Type**: task\n**Priority**: P1\n**Story**: Integration Points\n**Parallel**: [P]\n**Primary Files**: src/orchestration/orchestrator.py, tests/unit/pipeline/test_trigger_execution.py\n**Subsystems**: orchestration\n**Dependencies**: T009\n\n**Goal**:\nImplement periodic trigger logic: increment non_epic_completed_count after non-epic issue completion, fire trigger at interval multiples.\n\n**Context**:\n- Counter increments AFTER per-session validation, BEFORE checking periodic condition\n- Only non-epic issues increment counter (epic completions don't)\n- Fires at interval, 2*interval, 3*interval... (continuous counter)\n\n**Scope**:\n- In: Periodic trigger counter and firing logic\n- Out: Other triggers (T010, T012)\n\n**Changes**:\n- `src/orchestration/orchestrator.py` — Exists — Update:\n  - Implement `_check_and_queue_periodic_trigger()`:\n    - Only call for non-epic issues\n    - Increment _non_epic_completed_count\n    - If count % interval == 0 AND periodic trigger configured: queue trigger\n  - Call from main loop after per-session validation completes\n- `tests/unit/pipeline/test_trigger_execution.py` — Exists — Add:\n  - Test counter increments only for non-epic issues\n  - Test trigger fires at exact interval (5, 10, 15...)\n  - Test epic issue doesn't increment counter\n  - Test interval=1 fires every issue\n  - Test fewer issues than interval → never fires\n\n**Acceptance Criteria**:\n- Counter only increments for non-epic issues\n- Trigger fires at interval multiples (continuous counter, no reset)\n- Counter state is in-memory only (resets on restart)\n\n**Verification**:\n- Unit tests: `uv run pytest tests/unit/pipeline/test_trigger_execution.py -k periodic -v`\n- Integration test from T009 now passes\n\n**Notes for Agent**:\n- Find the exact location in main loop after per-session validation\n- Counter is simple int, no persistence needed","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-07T04:35:38.161248508Z","created_by":"cyou","updated_at":"2026-01-09T06:24:32.760946721Z","closed_at":"2026-01-09T06:24:32.760946721Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-smaj.11","depends_on_id":"mala-smaj","type":"parent-child","created_at":"2026-01-07T04:35:38.161790404Z","created_by":"cyou"},{"issue_id":"mala-smaj.11","depends_on_id":"mala-smaj.9","type":"blocks","created_at":"2026-01-07T04:36:28.724995824Z","created_by":"cyou"},{"issue_id":"mala-smaj.11","depends_on_id":"mala-smaj.10","type":"blocks","created_at":"2026-01-07T04:38:14.984706241Z","created_by":"cyou"}]}
{"id":"mala-smaj.12","title":"[T012] Implement session_end trigger integration with blocking","description":"**Type**: task\n**Priority**: P1\n**Story**: Integration Points\n**Parallel**: [P]\n**Primary Files**: src/orchestration/orchestrator.py, tests/unit/pipeline/test_trigger_execution.py\n**Subsystems**: orchestration\n**Dependencies**: T009\n\n**Goal**:\nImplement session_end trigger with skip conditions and blocking execution that prevents next issue assignment until validation completes.\n\n**Context**:\n- Skip conditions: abort_run=True OR success_count==0\n- Blocking: main loop waits for run_trigger_validation() before assigning next issue\n- session_end fires once at end of run\n\n**Scope**:\n- In: Session end trigger, skip conditions, blocking wait\n- Out: Other triggers (T010, T011)\n\n**Changes**:\n- `src/orchestration/orchestrator.py` — Exists — Update:\n  - Implement `_fire_session_end_trigger()`:\n    - Check skip: if abort_run=True, skip (abort takes precedence)\n    - Check skip: if success_count==0, skip (nothing to validate)\n    - If session_end configured: queue trigger, call run_trigger_validation()\n  - Add blocking wait after queueing ANY trigger (not just session_end):\n    - Call run_coordinator.run_trigger_validation() synchronously\n    - Check result: if aborted, set abort_run=True\n  - Call _fire_session_end_trigger() at existing session end location (~line 1135)\n- `tests/unit/pipeline/test_trigger_execution.py` — Exists — Add:\n  - Test session_end fires when success_count \u003e 0 and !abort_run\n  - Test session_end skipped when abort_run=True\n  - Test session_end skipped when success_count==0\n  - Test blocking: no new issue assigned during validation\n  - Test abort from trigger sets abort_run=True\n\n**Acceptance Criteria**:\n- session_end fires at correct location (existing session end)\n- Skip conditions respected (abort_run, success_count)\n- Blocking wait prevents next issue assignment\n- Abort result sets orchestrator abort_run flag\n\n**Verification**:\n- Unit tests: `uv run pytest tests/unit/pipeline/test_trigger_execution.py -k session_end -v`\n- Type check: `uvx ty check src/orchestration/orchestrator.py`\n\n**Notes for Agent**:\n- success_count includes both epics and non-epic issues\n- Blocking is synchronous call to run_trigger_validation()\n- Find exact line for session_end trigger (~1135 per plan)","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-07T04:35:50.69247148Z","created_by":"cyou","updated_at":"2026-01-09T06:40:30.258809319Z","closed_at":"2026-01-09T06:40:30.258809319Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-smaj.12","depends_on_id":"mala-smaj","type":"parent-child","created_at":"2026-01-07T04:35:50.693047817Z","created_by":"cyou"},{"issue_id":"mala-smaj.12","depends_on_id":"mala-smaj.9","type":"blocks","created_at":"2026-01-07T04:36:28.82305856Z","created_by":"cyou"},{"issue_id":"mala-smaj.12","depends_on_id":"mala-smaj.11","type":"blocks","created_at":"2026-01-07T04:38:15.084193778Z","created_by":"cyou"}]}
{"id":"mala-smaj.13","title":"[T013] Add MalaEventSink trigger events and console handlers","description":"**Type**: task\n**Priority**: P2\n**Story**: Instrumentation\n**Primary Files**: src/core/protocols.py, src/infra/io/log_output/console.py, src/pipeline/run_coordinator.py\n**Subsystems**: core, infra, pipeline\n**Dependencies**: T006\n\n**Goal**:\nAdd 10 new MalaEventSink methods for trigger lifecycle and implement console handlers to log trigger events.\n\n**Context**:\n- Events defined in spec: queued, started, command_started, command_completed, passed, failed, skipped, remediation_started, remediation_succeeded, remediation_exhausted\n- Console handlers provide user visibility into trigger execution\n\n**Scope**:\n- In: Protocol methods, console handlers, event emission in RunCoordinator\n- Out: Metrics/telemetry (future work)\n\n**Changes**:\n- `src/core/protocols.py` — Exists — Add to MalaEventSink:\n  - `on_trigger_validation_queued(trigger_type: str, trigger_context: str) -\u003e None`\n  - `on_trigger_validation_started(trigger_type: str, commands: list[str]) -\u003e None`\n  - `on_trigger_command_started(trigger_type: str, command_ref: str, index: int) -\u003e None`\n  - `on_trigger_command_completed(trigger_type: str, command_ref: str, index: int, passed: bool, duration_seconds: float) -\u003e None`\n  - `on_trigger_validation_passed(trigger_type: str, duration_seconds: float) -\u003e None`\n  - `on_trigger_validation_failed(trigger_type: str, failed_command: str, failure_mode: str) -\u003e None`\n  - `on_trigger_validation_skipped(trigger_type: str, reason: str) -\u003e None`\n  - `on_trigger_remediation_started(trigger_type: str, attempt: int, max_retries: int) -\u003e None`\n  - `on_trigger_remediation_succeeded(trigger_type: str, attempt: int) -\u003e None`\n  - `on_trigger_remediation_exhausted(trigger_type: str, attempts: int) -\u003e None`\n- `src/infra/io/log_output/console.py` — Exists — Add handlers:\n  - Implement all 10 methods with console logging\n  - Format: \"[trigger_type] event_name: details\"\n- `src/pipeline/run_coordinator.py` — Exists — Add event emission:\n  - Emit events at appropriate points in run_trigger_validation()\n  - Emit events in queue/clear methods\n\n**Acceptance Criteria**:\n- All 10 event methods added to MalaEventSink protocol\n- Console handlers log all events with trigger_type and details\n- Events emitted at correct lifecycle points in RunCoordinator\n- Existing MalaEventSink implementations updated (if any besides console)\n\n**Verification**:\n- Type check: `uvx ty check src/core/protocols.py`\n- Manual: Run with triggers configured, verify console output shows trigger events\n- All MalaEventSink implementers compile without errors\n\n**Notes for Agent**:\n- Check for other MalaEventSink implementers that need updating\n- Events should be non-blocking (synchronous but fast)\n- Follow existing event method patterns in protocols.py","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-07T04:36:06.942052894Z","created_by":"cyou","updated_at":"2026-01-08T18:06:55.068927979Z","closed_at":"2026-01-08T18:06:55.068927979Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-smaj.13","depends_on_id":"mala-smaj","type":"parent-child","created_at":"2026-01-07T04:36:06.94262003Z","created_by":"cyou"},{"issue_id":"mala-smaj.13","depends_on_id":"mala-smaj.6","type":"blocks","created_at":"2026-01-07T04:36:28.921090197Z","created_by":"cyou"},{"issue_id":"mala-smaj.13","depends_on_id":"mala-smaj.8","type":"blocks","created_at":"2026-01-07T04:38:15.183953213Z","created_by":"cyou"}]}
{"id":"mala-smaj.14","title":"[T014] Create validation-triggers.md user documentation","description":"**Type**: chore\n**Priority**: P3\n**Story**: Documentation\n**Primary Files**: docs/validation-triggers.md\n**Subsystems**: docs\n**Dependencies**: T009, T010, T011, T012\n\n**Goal**:\nCreate comprehensive user documentation for validation trigger configuration.\n\n**Context**:\n- Users need to understand the new trigger system\n- Must cover migration from old config format\n- Include practical examples for common use cases\n\n**Scope**:\n- In: User documentation, examples, migration guide\n- Out: API docs (auto-generated), CLI help (separate task if needed)\n\n**Changes**:\n- `docs/validation-triggers.md` — New — Create:\n  - Overview of trigger system\n  - Configuration reference (all fields, defaults, valid values)\n  - Trigger types: epic_completion, session_end, periodic\n  - Failure modes: abort, continue, remediate\n  - Command list structure with base pool references\n  - Migration guide from old config format\n  - Example configurations for common use cases:\n    - Fast feedback at epic completion\n    - Comprehensive validation at session end\n    - Periodic lint checks during long sessions\n  - Troubleshooting common errors\n\n**Acceptance Criteria**:\n- All trigger types documented with examples\n- All failure modes explained with behavior\n- Migration guide covers both breaking changes\n- Example configs are copy-pasteable and valid\n\n**Verification**:\n- Manual: Review docs for completeness\n- Example configs can be parsed without errors\n\n**Notes for Agent**:\n- Reference the spec for exact error messages\n- Include the migration URL in appropriate places\n- Link to this doc from project-config.md","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-07T04:36:18.276512784Z","created_by":"cyou","updated_at":"2026-01-09T06:43:41.875961013Z","closed_at":"2026-01-09T06:43:41.875961013Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-smaj.14","depends_on_id":"mala-smaj","type":"parent-child","created_at":"2026-01-07T04:36:18.27719545Z","created_by":"cyou"},{"issue_id":"mala-smaj.14","depends_on_id":"mala-smaj.9","type":"blocks","created_at":"2026-01-07T04:36:29.019015904Z","created_by":"cyou"},{"issue_id":"mala-smaj.14","depends_on_id":"mala-smaj.10","type":"blocks","created_at":"2026-01-07T04:36:29.120712537Z","created_by":"cyou"},{"issue_id":"mala-smaj.14","depends_on_id":"mala-smaj.11","type":"blocks","created_at":"2026-01-07T04:36:29.217909249Z","created_by":"cyou"},{"issue_id":"mala-smaj.14","depends_on_id":"mala-smaj.12","type":"blocks","created_at":"2026-01-07T04:36:29.317959072Z","created_by":"cyou"}]}
{"id":"mala-smaj.15","title":"[Review] 2 non-blocking findings from mala-smaj.1","description":"## Review Findings\n\nThis issue consolidates 2 non-blocking findings from code review.\n\n**Source issue:** mala-smaj.1\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Normalize `BaseTriggerConfig.commands` to a tuple at runtime\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/domain/validation/config.py:207-221\n\n`BaseTriggerConfig.commands` is annotated as `tuple[...]`, but nothing prevents callers (or future YAML parsing) from passing a `list` (YAML sequences deserialize to lists). Because the dataclass is frozen, a mutable list here breaks the immutability guarantee of this module: the object is “frozen” but its `commands` contents can still be mutated.\n\nFix: add a small `__post_init__` on `BaseTriggerConfig` to coerce `list` → `tuple` (similar to `ValidationConfig.__post_init__`), e.g. `if isinstance(self.commands, list): object.__setattr__(self, \"commands\", tuple(self.commands))`.\n\n---\n\n### Finding 2: [P2] Add tests for new trigger config types and `is_empty()`\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/domain/validation/config.py:263-285\n\nThis change introduces new public config types and logic (`ValidationTriggersConfig.is_empty()`), but there are no unit tests to lock in the expected behavior (e.g., `is_empty()` true when all fields are `None`, false when any trigger is set).\n\nA small unit test file exercising construction and `is_empty()` would prevent regressions as parsing/validation work lands in later tasks.\n\n---\n\n\u003c!-- fp:62194498182fe6a7 --\u003e\n\u003c!-- fp:3002592c131b69a6 --\u003e\n\u003c!-- review_finding:7de1b7e1aa79 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-07T15:11:22.359171198Z","created_by":"cyou","updated_at":"2026-01-07T16:42:40.664105609Z","closed_at":"2026-01-07T16:42:40.664105609Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-smaj.1"],"dependencies":[{"issue_id":"mala-smaj.15","depends_on_id":"mala-smaj","type":"parent-child","created_at":"2026-01-07T15:11:22.359699795Z","created_by":"cyou"}]}
{"id":"mala-smaj.16","title":"[Review] 1 non-blocking finding from mala-smaj.3","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-smaj.3\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Avoid TypeError when sorting unknown YAML keys\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/domain/validation/config_loader.py:518-522\n\nSeveral unknown-field checks do `first = sorted(unknown)[0]`. If a user supplies mixed-type YAML keys (e.g. int + str), Python can raise `TypeError` during sorting, which bypasses your intended `ConfigError` path.\n\nExample that can crash instead of raising `ConfigError`:\n`validation_triggers: {1: {}, oops: {}}`\n\nYou already handle this at the top-level schema check by stringifying keys; consider applying the same pattern anywhere you sort unknown keys (e.g. `sorted(str(k) for k in unknown)`).\n\n---\n\n\u003c!-- fp:e0df85669d4c681f --\u003e\n\u003c!-- review_finding:88ed5c74ae9f --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-07T17:03:51.930490284Z","created_by":"cyou","updated_at":"2026-01-07T17:06:59.734948452Z","closed_at":"2026-01-07T17:06:59.734948452Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-smaj.3"],"dependencies":[{"issue_id":"mala-smaj.16","depends_on_id":"mala-smaj","type":"parent-child","created_at":"2026-01-07T17:03:51.93111501Z","created_by":"cyou"}]}
{"id":"mala-smaj.17","title":"[Review] 3 non-blocking findings from mala-tfv2","description":"## Review Findings\n\nThis issue consolidates 3 non-blocking findings from code review.\n\n**Source issue:** mala-tfv2\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Test regex doesn't match actual error message\n\n**Priority:** P2\n**Reviewer:** claude\n**Location:** tests/unit/infra/test_config_merger.py:1753-1754\n\nThe test uses `match=r\"require.*preset.*inherit\"` but the error message raised at config_merger.py:628-632 is `\"Cannot specify partial override for custom command '{name}' in global_validation_commands: no preset command to inherit from...\"`. The word \"require\" doesn't appear in the error message, so the regex won't match and the test will fail. Change the regex to match the actual message, e.g., `r\"partial override.*preset.*inherit\"` or `r\"no preset.*inherit\"`.\n\n---\n\n### Finding 2: [P2] Fix programmatic merge semantics for `allow_fail=False`\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/domain/validation/config_merger.py:511-589\n\nIn `_merge_custom_command_deep`, programmatic configs are detected by `not user_cmd._fields_set`, then `allow_fail` is treated as explicit only when truthy:\n\n```py\nallow_fail_is_explicit = ... or (is_programmatic and user_cmd.allow_fail)\n```\n\nConcrete impact: if a preset has `allow_fail=True` and a programmatic user override intends to force `allow_fail=False` (while leaving `_fields_set` empty), the merge will inherit `True` from the preset instead of applying the user’s `False`. This breaks “user overrides specific fields” semantics for boolean false values.\n\nA consistent approach is to treat `allow_fail` as explicit for programmatic configs whenever it differs from the default or whenever the caller supplied it (e.g., add a separate signal, or compare against `False` similarly to how timeout compares against `None`).\n\n---\n\n### Finding 3: [P3] Confirm `CustomCommandConfig` runtime import is needed\n\n**Priority:** P3\n**Reviewer:** codex\n**Location:** src/domain/validation/config_merger.py:27-41\n\n`CustomCommandConfig` is moved from a `TYPE_CHECKING` import into the runtime import list. If `config_merger.py` only needs it for annotations (and the module uses postponed evaluation of annotations), this becomes an unnecessary runtime dependency. If it is required for constructing instances (it is used in `_merge_custom_command_deep`), then it’s correct; otherwise, consider keeping it under `TYPE_CHECKING`.\n\nThis is only actionable if the project relies on minimal runtime imports or has circular-import sensitivities.\n\n---\n\n\u003c!-- fp:b058776f4dba0588 --\u003e\n\u003c!-- fp:8ad2fe7fc8954d9f --\u003e\n\u003c!-- fp:46b02a0e4d2972ce --\u003e\n\u003c!-- review_finding:36a1b12c3041 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-07T22:01:20.122391552Z","created_by":"cyou","updated_at":"2026-01-08T15:42:49.667237123Z","closed_at":"2026-01-08T15:42:49.667237123Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-tfv2"],"dependencies":[{"issue_id":"mala-smaj.17","depends_on_id":"mala-smaj","type":"parent-child","created_at":"2026-01-07T22:01:20.12293755Z","created_by":"cyou"}]}
{"id":"mala-smaj.18","title":"[Review] 1 non-blocking finding from mala-smaj.17","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-smaj.17\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Use flexible regex for ConfigError match\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** tests/unit/infra/test_config_merger.py:1753\n\nThe change to `match=r\"require a preset to inherit\"` does not address the reviewer's finding that 'require' is missing from some variants of this error message (e.g., at `config_merger.py:430, 630`). To properly address the finding and ensure the test is robust against both error variants, use the suggested regex `r\"partial override.*preset.*inherit\"` which avoids the problematic 'require' keyword.\n\n---\n\n\u003c!-- fp:e377015cd82ee078 --\u003e\n\u003c!-- review_finding:110e865a61c4 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-08T15:42:49.996362106Z","created_by":"cyou","updated_at":"2026-01-08T15:47:26.68971523Z","closed_at":"2026-01-08T15:47:26.68971523Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-smaj.17"],"dependencies":[{"issue_id":"mala-smaj.18","depends_on_id":"mala-smaj","type":"parent-child","created_at":"2026-01-08T15:42:49.996945416Z","created_by":"cyou"}]}
{"id":"mala-smaj.19","title":"Log validation trigger configs at startup (CLI + debug log)","description":"Add startup logging for ValidationConfig and trigger configurations to aid debugging.\n\n**CLI output** (in console_sink._log_config):\n- Log which triggers are enabled (epic_completion, session_end, periodic)\n- Log failure modes per trigger\n- Log command counts per trigger\n\n**Debug log**:\n- Full ValidationConfig dump (commands, triggers, failure modes)\n- Resolved trigger configs after merge\n\n**Context**: With the new per-trigger validation system, users need visibility into what's actually configured.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-08T17:23:40.084281793Z","created_by":"cyou","updated_at":"2026-01-08T18:11:35.007387139Z","closed_at":"2026-01-08T18:11:35.007387139Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-smaj.19","depends_on_id":"mala-smaj","type":"parent-child","created_at":"2026-01-08T17:23:40.08481134Z","created_by":"cyou"}]}
{"id":"mala-smaj.2","title":"[T002] [integration-path-test] Config parsing skeleton + failing integration test","description":"**Type**: task\n**Priority**: P1\n**Story**: Config \u0026 Migration\n**Primary Files**: src/domain/validation/config_loader.py, tests/integration/test_validation_triggers.py\n**Subsystems**: domain, tests\n**Dependencies**: T001\n\n**Goal**:\nAdd skeleton parsing methods in config_loader.py (stubs that raise NotImplementedError) and create a failing integration test that exercises the config loading path with validation_triggers.\n\n**Context**:\n- TDD: Create the test first, then implement\n- Integration test should load config via the normal path (config_loader.load_config)\n- Skeleton ensures imports/wiring work before implementation\n\n**Scope**:\n- In: Skeleton parsing, failing integration test\n- Out: Actual parsing implementation (T003)\n\n**Changes**:\n- `src/domain/validation/config_loader.py` — Exists — Add:\n  - Update `_ALLOWED_TOP_LEVEL_FIELDS` to include `validation_triggers`\n  - Stub `_parse_validation_triggers(data: dict) -\u003e ValidationTriggersConfig | None` that raises NotImplementedError\n  - Call stub from main loading path\n- `tests/integration/test_validation_triggers.py` — New — Add:\n  - `test_config_loads_validation_triggers_via_normal_path()` - loads mala.yaml with validation_triggers via load_config(), asserts ValidationTriggersConfig populated\n\n**Acceptance Criteria**:\n- Integration test exists and FAILS for the right reason (NotImplementedError)\n- Skeleton method is called from main config loading path\n- Test uses real config loading path (not direct dataclass construction)\n\n**Verification**:\n- Run: `uv run pytest tests/integration/test_validation_triggers.py::test_config_loads_validation_triggers_via_normal_path -v`\n- Expected: FAILS with NotImplementedError (skeleton)\n- After T003: Test passes\n\n**Integration Path Test**:\nThis test exercises the config loading path through load_config() → ValidationConfig construction.\n\n**Notes for Agent**:\n- Create tests/integration/test_validation_triggers.py as new file\n- Test should create a temp mala.yaml with validation_triggers section\n- Skeleton should be minimal (just enough to wire up and fail)","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-07T04:33:31.2614026Z","created_by":"cyou","updated_at":"2026-01-07T16:43:28.886489543Z","closed_at":"2026-01-07T16:43:28.886489543Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-smaj.2","depends_on_id":"mala-smaj","type":"parent-child","created_at":"2026-01-07T04:33:31.262047856Z","created_by":"cyou"},{"issue_id":"mala-smaj.2","depends_on_id":"mala-smaj.1","type":"blocks","created_at":"2026-01-07T04:36:27.361356009Z","created_by":"cyou"}]}
{"id":"mala-smaj.20","title":"[Review] 2 non-blocking findings from mala-smaj.13","description":"## Review Findings\n\nThis issue consolidates 2 non-blocking findings from code review.\n\n**Source issue:** mala-smaj.13\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Emit remediation_exhausted with actual attempts count\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/pipeline/run_coordinator.py:951-962\n\n`on_trigger_remediation_exhausted(trigger_type, attempts)` is documented as \"Total number of attempts made\" and `ConsoleEventSink` prints it as such, but `RunCoordinator` emits `max_retries` instead. This produces incorrect console output and recorded events whenever remediation does not run all `max_retries` attempts (e.g., if the loop exits early for any reason) and is semantically wrong even in the normal path (attempt count should be the last `attempt` value / number of tries).\n\nExample problematic call:\n```py\nself.event_sink.on_trigger_remediation_exhausted(trigger_type.value, max_retries)\n```\nIt should pass the actual attempts made (e.g., `attempt` after the loop, or a dedicated counter).\n\n---\n\n### Finding 2: [P2] Missing queue clearance on SIGINT during remediation\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/pipeline/run_coordinator.py:923-926\n\nIn `_run_trigger_remediation`, when a SIGINT is detected during command execution, the method returns an aborted result but fails to call `self.clear_trigger_queue('sigint')`. This is inconsistent with the main `run_trigger_validation` loop (which correctly clears the queue on interrupt) and prevents the remaining triggers from emitting their required `on_trigger_validation_skipped` events.\n\n---\n\n\u003c!-- fp:7be390b7fb5c4408 --\u003e\n\u003c!-- fp:064d7a85f9054947 --\u003e\n\u003c!-- review_finding:2c5fc2542c5d --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-08T18:06:55.363128467Z","created_by":"cyou","updated_at":"2026-01-08T18:20:27.988845572Z","closed_at":"2026-01-08T18:20:27.988845572Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-smaj.13"],"dependencies":[{"issue_id":"mala-smaj.20","depends_on_id":"mala-smaj","type":"parent-child","created_at":"2026-01-08T18:06:55.363784195Z","created_by":"cyou"}]}
{"id":"mala-smaj.21","title":"[Review] 3 non-blocking findings from mala-smaj.19","description":"## Review Findings\n\nThis issue consolidates 3 non-blocking findings from code review.\n\n**Source issue:** mala-smaj.19\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Avoid printing \"None\" failure_mode in trigger CLI summary\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/infra/io/console_sink.py:135-174\n\nIn `ConsoleEventSink._log_triggers`, the formatted strings interpolate `t.failure_mode` directly, so an enabled trigger with no configured failure mode prints e.g. `session_end(None,2cmd)`. This is user-facing CLI output intended to clarify configuration, and showing the literal `None` is confusing/misleading.\n\nConcrete path from diff: `TriggerSummary.failure_mode` defaults to `None` and `_build_trigger_summary` sets it to `None` when missing; `_log_triggers` then does `f\"...({t.failure_mode},{t.command_count}cmd)\"`.\n\nSuggested fix: render a clearer placeholder (e.g. `mode=unset` or omit the mode segment when `failure_mode is None`).\n\n---\n\n### Finding 2: [P2] Missing 'Full ValidationConfig dump' in debug logs\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/infra/io/console_sink.py:182-211\n\nThe task context specifies that the debug log should contain a 'Full ValidationConfig dump (commands, triggers, failure modes)'. The current implementation only logs a summary with command counts via `_log_triggers_verbose`. It does not log the actual command definitions or even the command names associated with each trigger, which significantly limits the visibility needed for debugging 'what's actually configured'.\n\n---\n\n### Finding 3: [P2] Inconsistent and fragile attribute access in logging helpers\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/orchestration/run_config.py:47\n\nIn `_log_triggers_verbose`, `getattr` is used defensively for `epic_completion`, but then direct access is used for `epic.failure_mode` and `epic.command_count`. This is inconsistent given the type is known. Furthermore, in `run_config.py`, `failure_mode.value` is accessed assuming it is always an Enum. If the domain object contains a string or other type, this will raise an `AttributeError`. A safer pattern would be `getattr(failure_mode, 'value', failure_mode)`.\n\n---\n\n\u003c!-- fp:216cab6c25059687 --\u003e\n\u003c!-- fp:28cca4ef3be39bac --\u003e\n\u003c!-- fp:5f14928b5ccc9422 --\u003e\n\u003c!-- review_finding:4678523a20cb --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-08T18:11:35.301941794Z","created_by":"cyou","updated_at":"2026-01-08T18:17:16.903009544Z","closed_at":"2026-01-08T18:17:16.903009544Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-smaj.19"],"dependencies":[{"issue_id":"mala-smaj.21","depends_on_id":"mala-smaj","type":"parent-child","created_at":"2026-01-08T18:11:35.302603975Z","created_by":"cyou"}]}
{"id":"mala-smaj.22","title":"[Remediation] Global trigger validation execution MUST block next issue fr...","description":"## Context\nThis issue was auto-created by epic verification for epic `mala-smaj`.\n\n## Epic Description / Acceptance Criteria\nTitle: Epic: Per-Trigger Validation Configuration\n\nImplement per-trigger validation configuration system that allows users to specify exactly which validation commands run at specific trigger points (epic_completion, session_end, periodic).\n\n**Spec**: /home/cyou/mala/plans/2026-01-07-epic-validation-trigger-spec.md\n**Plan**: /home/cyou/mala/plans/2026-01-07-validation-triggers-plan.md\n**Tasks**: /home/cyou/mala/plans/2026-01-07-validation-triggers-tasks.md\n\n**Key Features**:\n- Three trigger types: epic_completion, session_end, periodic\n- Per-trigger failure modes: abort, remediate (with retry loop), continue\n- Command list structure with base pool + per-trigger overrides\n- BREAKING CHANGES: Repurpose global_validation_commands, deprecate validate_every\n- Blocking validation execution to prevent workspace conflicts\n- 10 new MalaEventSink events for trigger lifecycle\n\n**Success Criteria**:\n- Fast feedback queueing: epic_completion trigger queued within 10s of epic closure\n- Correct trigger execution: periodic fires at interval multiples\n- Migration clarity: hard errors for deprecated configs with guide URL\n- Remediation loop correctness: spawns fixer, retries, aborts on exhaustion\n\n**Acceptance Criteria Coverage**:\n- R1: Hard migration error (T004)\n- R2: Supported triggers (T010-T012)\n- R3: Command list selection (T003, T007)\n- R4: Fail-fast execution (T007)\n- R5: Failure modes (T008)\n- R6: Migration/presets (T004, T005)\n\n14 tasks across 6 phases.\n\n## Commit Scope\n- Range hint: 8e9e0be2cd594e87f37dffdb8e7b9a0d42874e00^..33d4638449d870bc0edb385c862d3b6cec78f25a\n- Commits:\n- 33d4638449d870bc0edb385c862d3b6cec78f25a bd-mala-smaj.14: Create validation-triggers.md user documentation\n- bcd12eeae05e429bb0126b27c4b80c144458897a bd-mala-smaj.12: Add clarifying comment about abort_run property delegation\n- 21a6460fd89678b892efe249287dd685077bc31a bd-mala-smaj.12: Add blocking behavior test with asyncio.Event\n- 1e05310e01d5b76cb4701906db37aa4d1f754b10 bd-mala-smaj.12: Implement session_end trigger integration with blocking\n- a09684ca3bcf55691631dbfe2a45097921b8505f bd-mala-smaj.11: Add explicit is_epic guard in periodic trigger method\n- 9d301aa828ba19a804496b5839335a6acb2ddc3c bd-mala-smaj.11: Fix review issues - test real method, rename unused param\n- 2740312f512e1d89c4b671d709ae599cefef357c bd-mala-smaj.11: Implement periodic trigger integration with unit tests\n- c2820d173217506ac7c6c2117bab8fdbecb3e2b5 bd-mala-smaj.10: Consolidate fire_on filter comment\n- 7bcb7b621961d4b8a0aa491d770bea04753b6a44 bd-mala-smaj.10: Fix trigger firing and async test pattern\n- 37dd84a24ddee4b638fc71497831b5c209452b9c bd-mala-smaj.10: Fix double parent lookup and test mapping\n- 4b3c1b95192c7ed00315dca701ff21a42bd6dc6b bd-mala-smaj.10: Implement epic_completion trigger integration\n- b74a7c0c2528ff5ed94fc35f169bec4eb9a7be96 bd-mala-smaj.9: Rewrite test to exercise full orchestrator loop\n- 8184193bf6cd83229f269208caaca90bc65326c9 bd-mala-smaj.9: Update test docstring - T011 implemented\n- 3c39c8e6135267a9102721b09c8378ad6479ab5f bd-mala-smaj.9: Fix trigger logging display and add command names\n- 85d7244fefd94cb222096a2ad35c7e4d0fd940c7 bd-mala-smaj.20: Fix remediation attempts count and pre-existing issues\n- c9d5f96f70f4d9b069f47123edfc1068bec6283b bd-mala-smaj.21: Fix trigger logging display and add command names\n- 7e5ba54241ffc292b76447d91c8a60bac3f9d36d bd-mala-smaj.9: Add hook invocations and fix stubs to be no-ops\n- 1a51ca8871e09c90427017fbca636d1bf8cf21ca bd-mala-smaj.9: Add orchestrator trigger hooks skeleton + failing integration test\n- ed307a033e10f6961aa1c1ab484e8d0ae0deb612 bd-mala-smaj.20: Pass actual attempts count to remediation_exhausted event\n- 466545271e1d47c7c23dc83aa604bf43e6cf0e22 bd-mala-smaj.19: Log validation trigger configs at startup\n- 89c32620e51346066693a27c152f02979c36e3f4 bd-mala-smaj.13: Add MalaEventSink trigger events and console handlers\n- cd92ec89a9c53d1d29158b136f1ee2cf97bd69eb bd-mala-smaj.8: Add early interrupt check and BaseException cleanup\n- b5021c232ebfedf207e262191f618cef3f28f383 bd-mala-smaj.8: Fix interrupt handling issues from code review\n- a125c80353be3991f88486ea518c40822858ed76 bd-mala-smaj.8: Implement failure mode handling (abort/continue/remediate) and SIGINT\n- 1279eb5b53c708ae739f6ed1c50f91fac44edef4 bd-mala-smaj.7: Fix override truthiness checks and integration test\n- cf4e36dba3d30b0c9e116890382f2797077dca9e bd-mala-smaj.7: Implement trigger command resolution and execution\n- 3f68351adcc56c485e57e42b55a87e7b09c8a089 bd-mala-smaj.18: Use flexible case-insensitive regex for ConfigError match\n- e4b96a59607d85868720d752ad853e15a7632211 bd-mala-smaj.6: Use minimal fakes instead of None in trigger queue test\n- ff953b2582f601ba38ab2142efaeebdda8a7d23f bd-mala-smaj.17: Fix test regex to match actual error message\n- 9cfa2e3c73aeabe3a03d5cb140a3f229ced28d7f bd-mala-smaj.6: Add trigger queue integration test + fix test type errors\n- 6d034ce6dc79ead459c97102d0a6600f37b7f7bc bd-mala-smaj.4: Fix lint errors for ValidationConfig type annotations\n- e3599c92660350b4a478884350ad67a07d947451 bd-mala-tfv2: Add field-level deep merge for custom commands in global_validation_commands\n- 0ab38f57e3effb412be2b2b14bf6e98f9c25a50a bd-mala-812j: Validate custom_commands for empty command strings in _validate_no_partial_commands\n- 036a986f8c8fa278fb2fb0468874354e3f4969a3 bd-mala-smaj.4: Add migration guide URL to deprecated config error messages\n- 20f603210d9dcd3d34c9b0d2a7e10b7a92d7d7d0 bd-mala-smaj.4: Fix validation_triggers merge and _fields_set tracking\n- fbf440d1f6a938034c01a8d6f4e0e3b64fbae957 bd-mala-smaj.4: Run migration validation on merged config (after preset merge)\n- 6fb3d4b61b1a111aea8061e6bdbd94dfc2342105 bd-mala-smaj.4: Implement migration validation for deprecated configs\n- 8ec91c5dcca763bb4bb3362797782982fedf61c9 bd-mala-smaj.5: Address P1 review issues\n- 507a883bf1d0093fcf0383fbd17d898ab3141eea bd-mala-smaj.5: Fix review issues for field-level deep merge\n- 03d61a6129e9474a53486d3eb779de44f65cfe22 bd-mala-smaj.5: Implement field-level deep merge for global_validation_commands\n- ac526005545af05a2f3a5d59f1b49d14d62ea4ef bd-mala-smaj.16: Stringify keys before sorting to avoid TypeError\n- a281ab1cf54c47c0af2e88a7e994ddc4b8b6c7ba bd-mala-smaj.3: Add strict validation to trigger config parsing\n- c863e5ea70936bc9a406540906f66e4c91bbf8ec bd-mala-smaj.3: Implement config parsing for validation_triggers\n- 7ad954209ad183e875101d5bd6e06fa05b87f3e9 bd-mala-smaj.2: Add validation_triggers skeleton + failing integration test\n- 1821b410791d2077c87b1b802eed8cb420039265 bd-mala-smaj.15: Add tuple normalization and tests for trigger configs\n- 8e9e0be2cd594e87f37dffdb8e7b9a0d42874e00 bd-mala-smaj.1: Add trigger config enums and dataclasses\n\n## Child Issues\n- mala-812j\n- mala-smaj.1\n- mala-smaj.10\n- mala-smaj.11\n- mala-smaj.12\n- mala-smaj.13\n- mala-smaj.14\n- mala-smaj.15\n- mala-smaj.16\n- mala-smaj.17\n- mala-smaj.18\n- mala-smaj.19\n- mala-smaj.2\n- mala-smaj.20\n- mala-smaj.21\n- mala-smaj.3\n- mala-smaj.4\n- mala-smaj.5\n- mala-smaj.6\n- mala-smaj.7\n- mala-smaj.8\n- mala-smaj.9\n- mala-tfv2\n\n## Unmet Criterion\nGlobal trigger validation execution MUST block next issue from starting to prevent workspace conflicts\n\n## Evidence\nIn src/orchestration/orchestrator.py:949, the blocking call 'await self.run_coordinator.run_trigger_validation()' is commented out for periodic and epic_completion triggers. Only session_end trigger properly blocks at line 1093. This violates the workspace safety requirement from the spec.\n\n## Priority\nP1 (blocking)\n\n## Resolution\nAddress this criterion to unblock epic closure. When complete, close this issue.\n","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-09T06:48:29.684741539Z","created_by":"cyou","updated_at":"2026-01-09T06:55:59.30314932Z","closed_at":"2026-01-09T06:55:59.30314932Z","close_reason":"Closed","labels":["auto_generated","epic_remediation:mala-smaj:ba5e710bf774c9d5c140f986ba8954fd8351db90a8bb58a09cc553888a142932"],"dependencies":[{"issue_id":"mala-smaj.22","depends_on_id":"mala-smaj","type":"parent-child","created_at":"2026-01-09T06:48:29.685395237Z","created_by":"cyou"}]}
{"id":"mala-smaj.23","title":"[Review] 1 non-blocking finding from mala-642f","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-642f\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Include base `commands.custom_commands` in trigger base pool fallback\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/pipeline/run_coordinator.py:1064-1111\n\n`_build_base_pool` now only adds custom commands from `global_validation_commands.custom_commands`. Any custom commands previously defined under `commands.custom_commands` will no longer be resolvable by triggers unless duplicated into `global_validation_commands`, which is a behavioral break from the earlier implementation and from the stated “commands as fallback” idea.\n\nConcrete failure: a repo with `commands.custom_commands = {\"ci\": ...}` and triggers referencing `ref=\"ci\"` will now raise “unknown command ref” (or equivalent) because the pool never includes `ci` unless it is also present in `global_validation_commands.custom_commands`.\n\nFix: when building the pool, merge custom commands from both sources (e.g., start with base `commands.custom_commands`, then overlay/override with `global_validation_commands.custom_commands`), or document/enforce migration so old configs are upgraded automatically before trigger execution.\n\n---\n\n\u003c!-- fp:c59ee592f8697919 --\u003e\n\u003c!-- review_finding:e614cebc3ae9 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-09T21:23:05.632138127Z","created_by":"cyou","updated_at":"2026-01-09T21:34:26.969162967Z","closed_at":"2026-01-09T21:34:26.969162967Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-642f"],"dependencies":[{"issue_id":"mala-smaj.23","depends_on_id":"mala-smaj","type":"parent-child","created_at":"2026-01-09T21:23:05.632770543Z","created_by":"cyou"}]}
{"id":"mala-smaj.24","title":"[Review] 2 non-blocking findings from mala-7wp2","description":"## Review Findings\n\nThis issue consolidates 2 non-blocking findings from code review.\n\n**Source issue:** mala-7wp2\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Fix `baseline` requirement wording vs trigger examples\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** docs/validation-triggers.md:303-317\n\nThe docs say `baseline` is required only “When cumulative”, but the examples and trigger list imply cumulative review is tied to trigger type (`epic_completion`, `run_end`) rather than an explicit mode flag. As written, `session_end` could also be configured with `baseline`, and `run_end` could be configured without it, yet the doc requirement is ambiguous and not enforceable based on the fields shown.\n\nThis is likely to confuse users and drift from the actual parser rules (which accept `code_review` for `run_end` without showing any “cumulative” discriminator here). Tighten the docs to define when `baseline` is required (e.g., “required when the trigger reviews accumulated changes rather than a single issue”) and explicitly state which triggers support/need it.\n\n\n---\n\n### Finding 2: [P3] Clarify `run_end` ordering description\n\n**Priority:** P3\n**Reviewer:** codex\n**Location:** docs/validation-triggers.md:145-147\n\n`run_end` is documented as firing “after all issues and session_end”. That implies `session_end` always runs before `run_end`, but `session_end` is per-issue while `run_end` is global; depending on orchestration semantics, “after all issues” already includes the final issue’s `session_end`, making the extra clause potentially redundant or misleading.\n\nConsider rephrasing to something unambiguous like “after all issue work completes; runs once at the end of the run” (and, if true, explicitly state that all per-issue `session_end` validations must have finished before `run_end` starts).\n\n\n---\n\n\u003c!-- fp:18f6743b017476e9 --\u003e\n\u003c!-- fp:52df832977fe8995 --\u003e\n\u003c!-- review_finding:43ec5352eef5 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-09T21:42:19.619138787Z","created_by":"cyou","updated_at":"2026-01-09T21:43:38.799798959Z","closed_at":"2026-01-09T21:43:38.799798959Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-7wp2"],"dependencies":[{"issue_id":"mala-smaj.24","depends_on_id":"mala-smaj","type":"parent-child","created_at":"2026-01-09T21:42:19.619700385Z","created_by":"cyou"}]}
{"id":"mala-smaj.25","title":"[BUG] run_end trigger queued but never executed","description":"run_end is queued in MalaOrchestrator._fire_run_end_trigger() but there is no run_trigger_validation() call after queueing at run completion. This means run_end commands and code_review never execute. Fix by invoking trigger validation after queueing, similar to session_end, and ensure it blocks before global validation.\n\nFiles: src/orchestration/orchestrator.py","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-10T00:42:49.942541Z","created_by":"cyou","updated_at":"2026-01-10T00:49:14.815149048Z","closed_at":"2026-01-10T00:49:14.815149048Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-smaj.25","depends_on_id":"mala-smaj","type":"parent-child","created_at":"2026-01-10T00:42:49.942541Z","created_by":"cyou"}]}
{"id":"mala-smaj.26","title":"[BUG] run_end trigger queue can leak into next run","description":"Because run_end is only queued at run completion and run_trigger_validation() is not called, the queued trigger may execute during the next run's first issue completion. This can run with incorrect run_metadata. Ensure run_end is executed (and queue cleared) within the same run.\n\nFiles: src/orchestration/orchestrator.py","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-10T00:42:49.942541Z","created_by":"cyou","updated_at":"2026-01-10T00:56:16.236149964Z","closed_at":"2026-01-10T00:56:16.236149964Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-smaj.26","depends_on_id":"mala-smaj","type":"parent-child","created_at":"2026-01-10T00:42:49.942541Z","created_by":"cyou"},{"issue_id":"mala-smaj.26","depends_on_id":"mala-smaj.25","type":"blocks","created_at":"2026-01-10T00:42:49.942541Z","created_by":"cyou"}]}
{"id":"mala-smaj.27","title":"run_end missing from validation_triggers summary","description":"run_end is not included in ValidationTriggersSummary, so run start logs omit it. Add run_end to _build_trigger_summary().\n\nFiles: src/orchestration/run_config.py","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-10T00:42:49.942541Z","created_by":"cyou","updated_at":"2026-01-10T00:47:42.569189118Z","closed_at":"2026-01-10T00:47:42.569189118Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-smaj.27","depends_on_id":"mala-smaj","type":"parent-child","created_at":"2026-01-10T00:42:49.942541Z","created_by":"cyou"}]}
{"id":"mala-smaj.3","title":"[T003] Implement config parsing for validation_triggers","description":"**Type**: task\n**Priority**: P1\n**Story**: Config \u0026 Migration\n**Primary Files**: src/domain/validation/config_loader.py, tests/unit/domain/test_validation_config.py\n**Subsystems**: domain\n**Dependencies**: T002\n\n**Goal**:\nImplement full parsing of validation_triggers from mala.yaml, including nested trigger configs and command refs.\n\n**Context**:\n- Replace NotImplementedError stub from T002\n- Parse YAML dict into ValidationTriggersConfig and nested dataclasses\n- Validate required fields, enum values, ref existence deferred to T004\n\n**Scope**:\n- In: YAML → dataclass parsing, unit tests\n- Out: Migration validation (T004), merger logic (T005)\n\n**Changes**:\n- `src/domain/validation/config_loader.py` — Exists — Update:\n  - Implement `_parse_validation_triggers()` with full parsing\n  - Implement `_parse_trigger_command_ref(data: dict) -\u003e TriggerCommandRef`\n  - Implement `_parse_epic_completion_trigger(data: dict) -\u003e EpicCompletionTriggerConfig`\n  - Implement `_parse_session_end_trigger(data: dict) -\u003e SessionEndTriggerConfig`\n  - Implement `_parse_periodic_trigger(data: dict) -\u003e PeriodicTriggerConfig`\n  - Validate required fields: failure_mode for all triggers, max_retries when failure_mode=remediate, interval for periodic\n- `tests/unit/domain/test_validation_config.py` — Exists — Add:\n  - Test valid parsing with all triggers and fields\n  - Test enum parsing (failure_mode, epic_depth, fire_on)\n  - Test TriggerCommandRef with/without overrides\n  - Test required field validation (failure_mode missing → error)\n  - Test max_retries required when failure_mode=remediate\n  - Test empty commands list is valid\n\n**Wiring Map**:\n- `mala.yaml validation_triggers → _parse_validation_triggers() → ValidationConfig.validation_triggers → ValidationTriggersConfig`\n\n**Acceptance Criteria**:\n- All trigger types parse correctly from YAML\n- Enums parse from string values (\"abort\" → FailureMode.ABORT)\n- Required field validation raises ConfigError with clear message\n- TriggerCommandRef supports ref-only and ref+override forms\n- Empty `commands: []` is valid (parsed as empty tuple)\n\n**Verification**:\n- Unit tests: `uv run pytest tests/unit/domain/test_validation_config.py -k trigger -v`\n- Integration test from T002 now passes\n- Type check: `uvx ty check src/domain/validation/config_loader.py`\n\n**Notes for Agent**:\n- Error message format: \"failure_mode required for trigger epic_completion\"\n- Error message format: \"max_retries required when failure_mode=remediate for trigger X\"\n- Use ConfigError for all validation failures","notes":"Quality gate failed: Command failed with exit code -2 (exit code: -2)\nError output: Check stderr output for details","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-07T04:33:46.231433747Z","created_by":"cyou","updated_at":"2026-01-07T17:03:51.639303642Z","closed_at":"2026-01-07T17:03:51.639303642Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-smaj.3","depends_on_id":"mala-smaj","type":"parent-child","created_at":"2026-01-07T04:33:46.232062543Z","created_by":"cyou"},{"issue_id":"mala-smaj.3","depends_on_id":"mala-smaj.2","type":"blocks","created_at":"2026-01-07T04:36:27.459536344Z","created_by":"cyou"}]}
{"id":"mala-smaj.4","title":"[T004] Implement migration validation for deprecated configs","description":"**Type**: task\n**Priority**: P1\n**Story**: Config \u0026 Migration\n**Parallel**: [P]\n**Primary Files**: src/domain/validation/config_loader.py, tests/unit/domain/test_validation_config.py\n**Subsystems**: domain\n**Dependencies**: T003\n\n**Goal**:\nAdd `_validate_migration()` function that fails fast on deprecated config patterns (validate_every, global_validation_commands without validation_triggers).\n\n**Context**:\n- BREAKING CHANGE: Existing configs with only global_validation_commands now fail\n- Migration validation runs on effective merged config (after preset merge)\n- Error messages must include migration guide URL and example snippet\n\n**Scope**:\n- In: Migration validation logic, unit tests\n- Out: Actually merging configs (T005)\n\n**Changes**:\n- `src/domain/validation/config_loader.py` — Exists — Add:\n  - `_validate_migration(config: ValidationConfig) -\u003e None` function\n  - Check 1: If `validate_every` present → ConfigError with deprecation message\n  - Check 2: If `global_validation_commands` non-empty AND `validation_triggers` is None → ConfigError\n  - Call `_validate_migration()` after config loading but before returning\n- `tests/unit/domain/test_validation_config.py` — Exists — Add:\n  - Test validate_every present → error with correct message\n  - Test global_validation_commands without validation_triggers → error\n  - Test empty global_validation_commands without validation_triggers → OK (empty pool is valid)\n  - Test global_validation_commands WITH validation_triggers → OK\n  - Test empty validation_triggers {} → OK (explicit opt-out)\n  - Error message assertions (contains URL, example snippet)\n\n**Acceptance Criteria**:\n- validate_every triggers hard error at startup\n- Non-empty global_validation_commands without validation_triggers triggers hard error\n- Error messages include migration guide URL: https://docs.mala.ai/migration/validation-triggers\n- Error messages include concrete example snippet\n\n**Verification**:\n- Unit tests: `uv run pytest tests/unit/domain/test_validation_config.py -k migration -v`\n- Manual: Create mala.yaml with only global_validation_commands → startup fails with migration error\n\n**Notes for Agent**:\n- Error format for validate_every: \"validate_every is deprecated. Use validation_triggers.periodic with interval field. See migration guide at https://docs.mala.ai/migration/validation-triggers\"\n- Error format for global_validation_commands: \"validation_triggers required when global_validation_commands is defined. See migration guide at https://docs.mala.ai/migration/validation-triggers\\n\\nExample:\\nvalidation_triggers:\\n  session_end:\\n    failure_mode: remediate\\n    max_retries: 3\\n    commands:\\n      - ref: test\"","notes":"Quality gate failed: External review failed: src/domain/prompts.py:304: [P1] Crash when mala.yaml is missing\nLog: /home/cyou/.claude/projects/-home-cyou-mala/11315f62-66a0-48b6-bd15-223001217478.jsonl","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-07T04:34:01.292028749Z","created_by":"cyou","updated_at":"2026-01-07T22:08:46.199367562Z","closed_at":"2026-01-07T22:08:46.199367562Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-smaj.4","depends_on_id":"mala-smaj","type":"parent-child","created_at":"2026-01-07T04:34:01.292778144Z","created_by":"cyou"},{"issue_id":"mala-smaj.4","depends_on_id":"mala-smaj.3","type":"blocks","created_at":"2026-01-07T04:36:27.554743228Z","created_by":"cyou"},{"issue_id":"mala-smaj.4","depends_on_id":"mala-smaj.5","type":"blocks","created_at":"2026-01-07T04:38:14.887985277Z","created_by":"cyou"}]}
{"id":"mala-smaj.5","title":"[T005] Implement field-level deep merge for global_validation_commands","description":"**Type**: task\n**Priority**: P1\n**Story**: Config \u0026 Migration\n**Parallel**: [P]\n**Primary Files**: src/domain/validation/config_merger.py, tests/unit/domain/test_validation_config.py\n**Subsystems**: domain\n**Dependencies**: T003\n\n**Goal**:\nImplement field-level deep merge for global_validation_commands so project commands override preset commands by name, with unspecified fields inheriting from preset.\n\n**Context**:\n- Preset: `{test: {command: 'pytest', timeout: 300}}`\n- Project: `{test: {timeout: 120}}`\n- Effective: `{test: {command: 'pytest', timeout: 120}}`\n- Per spec: project-specified fields override, unspecified fields inherit\n\n**Scope**:\n- In: Field-level merge logic, unit tests\n- Out: validation_triggers merge (per spec, no inheritance - project-only)\n\n**Changes**:\n- `src/domain/validation/config_merger.py` — Exists — Update:\n  - Modify merge logic for `global_validation_commands` field\n  - For each command name in project: deep merge with preset entry (if exists)\n  - Field-level precedence: project field \u003e preset field \u003e system default (120s for timeout)\n- `tests/unit/domain/test_validation_config.py` — Exists — Add:\n  - Test preset-only command inherited\n  - Test project-only command added\n  - Test field-level merge: project timeout overrides preset timeout, preset command inherited\n  - Test project command field overrides preset command field\n  - Test empty project global_validation_commands: {} → uses preset pool\n  - Test project adds new command to preset pool\n\n**Acceptance Criteria**:\n- Project commands merge field-by-field with preset commands\n- New project commands added to pool\n- Empty project global_validation_commands uses preset pool\n- Triggers can reference commands from either preset or project\n\n**Verification**:\n- Unit tests: `uv run pytest tests/unit/domain/test_validation_config.py -k merger -v`\n- Type check: `uvx ty check src/domain/validation/config_merger.py`\n\n**Notes for Agent**:\n- validation_triggers does NOT merge (per spec: \"presets only provide base pool\")\n- Only global_validation_commands gets field-level deep merge\n- Keep existing merge logic for other fields unchanged","notes":"Quality gate failed: External review failed: src/domain/validation/config_merger.py:344: [P1] Custom commands do not undergo field-level deep merge; src/domain/validation/config_merger.py:341: [P1] _clear_customs: true incorrectly wipes out user-defined custom commands; src/domain/validation/config_merger.py:43: [P1] _validate_no_partial_commands ignores custom commands\nLog: /home/cyou/.claude/projects/-home-cyou-mala/489e45fb-ba4d-42f5-ac59-59ba6deedf6c.jsonl","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-07T04:34:14.915130721Z","created_by":"cyou","updated_at":"2026-01-07T19:08:37.878790501Z","closed_at":"2026-01-07T19:08:37.878790501Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-smaj.5","depends_on_id":"mala-smaj","type":"parent-child","created_at":"2026-01-07T04:34:14.915730927Z","created_by":"cyou"},{"issue_id":"mala-smaj.5","depends_on_id":"mala-smaj.3","type":"blocks","created_at":"2026-01-07T04:36:27.651823081Z","created_by":"cyou"}]}
{"id":"mala-smaj.6","title":"[T006] [integration-path-test] RunCoordinator trigger queue skeleton + failing integration test","description":"**Type**: task\n**Priority**: P1\n**Story**: Trigger Execution\n**Primary Files**: src/pipeline/run_coordinator.py, tests/integration/test_validation_triggers.py\n**Subsystems**: pipeline, tests\n**Dependencies**: T002, T003, T004, T005\n\n**Goal**:\nAdd skeleton methods for trigger queue management in RunCoordinator (queue_trigger_validation, run_trigger_validation, clear_trigger_queue) and create failing integration test that exercises the queue path.\n\n**Context**:\n- TDD: Create test first, implement later\n- RunCoordinator already handles global validation and fixer agents\n- Trigger queue is simple FIFO list\n\n**Scope**:\n- In: Skeleton methods, trigger queue data structure, failing integration test\n- Out: Command execution implementation (T007), failure mode handling (T008)\n\n**Changes**:\n- `src/pipeline/run_coordinator.py` — Exists — Add:\n  - `self._trigger_queue: list[tuple[TriggerType, dict[str, Any]]] = []` in __init__\n  - `queue_trigger_validation(trigger_type: TriggerType, context: dict[str, Any]) -\u003e None` - appends to queue\n  - `run_trigger_validation(dry_run: bool = False) -\u003e TriggerValidationResult` - stub raises NotImplementedError\n  - `clear_trigger_queue(reason: str) -\u003e None` - clears queue, emits skipped events (stub)\n  - `TriggerValidationResult` dataclass: status (passed/failed/aborted), details\n- `tests/integration/test_validation_triggers.py` — Exists — Add:\n  - `test_trigger_queues_and_executes_via_run_coordinator()` - queues trigger, calls run_trigger_validation(), asserts result\n\n**Acceptance Criteria**:\n- Integration test exists and FAILS for the right reason (NotImplementedError)\n- Trigger queue is initialized as empty list\n- queue_trigger_validation appends to queue correctly\n- Test exercises the path through RunCoordinator (not direct method calls)\n\n**Verification**:\n- Run: `uv run pytest tests/integration/test_validation_triggers.py::test_trigger_queues_and_executes_via_run_coordinator -v`\n- Expected: FAILS with NotImplementedError (skeleton)\n- After T007: Test passes\n\n**Integration Path Test**:\nThis test exercises RunCoordinator.queue_trigger_validation() → run_trigger_validation() path.\n\n**Notes for Agent**:\n- TriggerValidationResult should be a frozen dataclass\n- Status can be Literal[\"passed\", \"failed\", \"aborted\"] or an enum","notes":"Quality gate failed: External review failed: src/pipeline/run_coordinator.py:1: [P1] Missing skeleton implementation in src/pipeline/run_coordinator.py; tests/integration/pipeline/test_agent_session_runner.py:4441: [P1] Breaking signature change in CapturingSDKClient helper\nLog: /home/cyou/.claude/projects/-home-cyou-mala/8f324b58-33a4-4624-b402-867f2841c572.jsonl","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-07T04:34:29.366680535Z","created_by":"cyou","updated_at":"2026-01-08T16:02:27.423547568Z","closed_at":"2026-01-08T16:02:27.423547568Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-smaj.6","depends_on_id":"mala-smaj","type":"parent-child","created_at":"2026-01-07T04:34:29.36739888Z","created_by":"cyou"},{"issue_id":"mala-smaj.6","depends_on_id":"mala-smaj.2","type":"blocks","created_at":"2026-01-07T04:36:27.746848866Z","created_by":"cyou"},{"issue_id":"mala-smaj.6","depends_on_id":"mala-smaj.3","type":"blocks","created_at":"2026-01-07T04:36:27.842094241Z","created_by":"cyou"},{"issue_id":"mala-smaj.6","depends_on_id":"mala-smaj.4","type":"blocks","created_at":"2026-01-07T04:36:27.938237229Z","created_by":"cyou"},{"issue_id":"mala-smaj.6","depends_on_id":"mala-smaj.5","type":"blocks","created_at":"2026-01-07T04:36:28.035804968Z","created_by":"cyou"}]}
{"id":"mala-smaj.7","title":"[T007] Implement trigger command resolution and execution","description":"**Type**: task\n**Priority**: P1\n**Story**: Trigger Execution\n**Primary Files**: src/pipeline/run_coordinator.py, tests/unit/pipeline/test_trigger_execution.py\n**Subsystems**: pipeline\n**Dependencies**: T006\n\n**Goal**:\nImplement command resolution from base pool and sequential command execution with fail-fast behavior.\n\n**Context**:\n- Commands reference base pool via `ref` field\n- Overrides (command, timeout) applied on top of base pool entry\n- First failure stops remaining commands (fail-fast)\n- Timeout treated as failure\n\n**Scope**:\n- In: Command resolution, sequential execution, fail-fast, dry-run mode\n- Out: Failure mode handling (T008)\n\n**Changes**:\n- `src/pipeline/run_coordinator.py` — Exists — Update:\n  - Implement `_resolve_trigger_commands(trigger_config, base_pool) -\u003e list[ResolvedCommand]`\n  - ResolvedCommand: ref, effective_command, effective_timeout\n  - Implement `_execute_trigger_commands(commands, dry_run) -\u003e list[CommandResult]`\n  - Implement `run_trigger_validation()` body: resolve commands, execute, return result\n  - Fail-fast: stop on first failure\n  - Dry-run: skip subprocess execution, all commands treated as passed\n- `tests/unit/pipeline/test_trigger_execution.py` — New — Add:\n  - Test command resolution from base pool\n  - Test command override (command string override)\n  - Test timeout override\n  - Test missing ref → ConfigError\n  - Test fail-fast: second command fails → third not executed\n  - Test timeout treated as failure\n  - Test dry-run mode: all commands pass, no subprocess\n  - Test empty command list → passed result\n\n**Wiring Map**:\n- `ValidationTriggersConfig → RunCoordinator.run_trigger_validation() → _resolve_trigger_commands() → _execute_trigger_commands() → CommandResult`\n\n**Acceptance Criteria**:\n- Commands resolve correctly from base pool\n- Overrides applied field-by-field (command, timeout)\n- Missing ref raises ConfigError with message listing available commands\n- Fail-fast stops execution on first failure\n- Dry-run mode skips subprocess, returns passed for all\n- Empty command list returns passed status\n\n**Verification**:\n- Unit tests: `uv run pytest tests/unit/pipeline/test_trigger_execution.py -v`\n- Integration test from T006 now passes\n\n**Notes for Agent**:\n- Error format for missing ref: \"trigger epic_completion references unknown command 'typo'. Available: test, lint, typecheck\"\n- ResolvedCommand can be a dataclass or NamedTuple\n- CommandResult should include: ref, passed, duration_seconds, error_message?","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-07T04:34:44.766606116Z","created_by":"cyou","updated_at":"2026-01-08T16:17:04.947890267Z","closed_at":"2026-01-08T16:17:04.947890267Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-smaj.7","depends_on_id":"mala-smaj","type":"parent-child","created_at":"2026-01-07T04:34:44.767192852Z","created_by":"cyou"},{"issue_id":"mala-smaj.7","depends_on_id":"mala-smaj.6","type":"blocks","created_at":"2026-01-07T04:36:28.132147995Z","created_by":"cyou"}]}
{"id":"mala-smaj.8","title":"[T008] Implement failure mode handling (abort/continue/remediate)","description":"**Type**: task\n**Priority**: P1\n**Story**: Trigger Execution\n**Primary Files**: src/pipeline/run_coordinator.py, tests/unit/pipeline/test_trigger_execution.py\n**Subsystems**: pipeline\n**Dependencies**: T007\n\n**Goal**:\nImplement failure_mode handling: abort sets result to aborted, continue logs and proceeds, remediate spawns fixer with retry loop. Also implement SIGINT handling for graceful interrupt during validation.\n\n**Context**:\n- Uses existing `_run_fixer_agent()` mechanism for remediation\n- Remediation exhaustion → abort (terminal behavior per spec)\n- Abort clears queue and emits skipped events\n- SIGINT during validation must be responsive (no indefinite blocking)\n\n**Scope**:\n- In: Failure mode switch, remediation retry loop, abort handling, SIGINT handling\n- Out: Integration points (T009-T012)\n\n**Changes**:\n- `src/pipeline/run_coordinator.py` — Exists — Update:\n  - Add failure_mode handling in `run_trigger_validation()`:\n    - ABORT: set result.status = \"aborted\", call clear_trigger_queue(\"run_aborted\")\n    - CONTINUE: log failure, set result.status = \"failed\", return (don't abort)\n    - REMEDIATE: for attempt in range(max_retries): call _run_fixer_agent(), re-run failed command\n  - Implement `clear_trigger_queue(reason)`: emit on_trigger_validation_skipped for each queued item, clear list\n  - Remediation exhaustion → abort\n  - **SIGINT handling**:\n    - Register signal handler when validation starts\n    - On SIGINT: send SIGTERM to subprocess, set internal `_interrupted` flag\n    - Check `_interrupted` after each command; if set, abort remaining commands and clear queue\n    - Restore original signal handler when validation completes\n- `tests/unit/pipeline/test_trigger_execution.py` — Exists — Add:\n  - Test abort mode: failure sets aborted status, clears queue\n  - Test continue mode: failure logs, returns failed status, doesn't abort\n  - Test remediate mode: spawns fixer, re-runs command\n  - Test remediate exhaustion: aborts after max_retries\n  - Test remediate success: fixer fixes, command passes, continues\n  - Test max_retries=0: no fixer spawned, immediate abort\n  - Test SIGINT: validation interrupted, queue cleared, result is aborted\n\n**Acceptance Criteria**:\n- ABORT mode sets aborted status and clears queue\n- CONTINUE mode logs failure but doesn't abort\n- REMEDIATE mode spawns fixer up to max_retries times\n- Remediation exhaustion aborts the run\n- max_retries=0 means no fixer spawned\n- SIGINT immediately releases blocking wait and aborts\n\n**Verification**:\n- Unit tests: `uv run pytest tests/unit/pipeline/test_trigger_execution.py -k failure_mode -v`\n- Type check: `uvx ty check src/pipeline/run_coordinator.py`\n\n**Notes for Agent**:\n- Reuse existing _run_fixer_agent() - don't reimplement\n- Event emission for remediation handled in T013\n- Keep failure_mode switch clean and readable\n- Per spec: SIGINT must be responsive (user can always cancel)\n- Signal handling: register handler at start of validation, restore original after","notes":"Quality gate failed: External review failed: src/pipeline/run_coordinator.py:630: [P1] Ensure SIGINT terminates the in-flight subprocess immediately; tests/unit/pipeline/test_trigger_execution.py:26: [P1] Missing imports in unit tests\nLog: /home/cyou/.claude/projects/-home-cyou-mala/5fe4a630-46f3-448d-8488-443e014856e7.jsonl","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-07T04:35:00.311101981Z","created_by":"cyou","updated_at":"2026-01-08T17:20:25.022094233Z","closed_at":"2026-01-08T17:20:25.022094233Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-smaj.8","depends_on_id":"mala-smaj","type":"parent-child","created_at":"2026-01-07T04:35:00.311677128Z","created_by":"cyou"},{"issue_id":"mala-smaj.8","depends_on_id":"mala-smaj.7","type":"blocks","created_at":"2026-01-07T04:36:28.231382354Z","created_by":"cyou"}]}
{"id":"mala-smaj.9","title":"[T009] [integration-path-test] Orchestrator trigger hooks skeleton + failing integration test","description":"**Type**: task\n**Priority**: P1\n**Story**: Integration Points\n**Primary Files**: src/orchestration/orchestrator.py, tests/integration/test_validation_triggers.py\n**Subsystems**: orchestration, tests\n**Dependencies**: T006, T007, T008\n\n**Goal**:\nAdd skeleton hooks in orchestrator for trigger firing (periodic counter, session_end location, blocking wait) and create failing integration test.\n\n**Context**:\n- Orchestrator main loop needs hooks for periodic and session_end triggers\n- Epic completion hooked in EpicVerificationCoordinator (T010)\n- Blocking: main loop waits for run_trigger_validation() before next issue\n\n**Scope**:\n- In: Orchestrator skeleton hooks, non_epic_completed_count, failing integration test\n- Out: Actual trigger integration (T010-T012)\n\n**Changes**:\n- `src/orchestration/orchestrator.py` — Exists — Add:\n  - `self._non_epic_completed_count: int = 0` state variable\n  - Stub `_check_and_queue_periodic_trigger()` - increments counter, checks interval, queues if match\n  - Stub `_fire_session_end_trigger()` - checks skip conditions, queues trigger\n  - Identify blocking wait location (after queueing, before next issue assignment)\n- `tests/integration/test_validation_triggers.py` — Exists — Add:\n  - `test_orchestrator_fires_periodic_trigger_at_interval()` - configures interval=2, runs 2 issues, asserts trigger queued\n\n**Acceptance Criteria**:\n- Integration test exists and FAILS (skeleton raises NotImplementedError or doesn't queue)\n- non_epic_completed_count initialized to 0\n- Stub methods exist at correct locations in orchestrator\n\n**Verification**:\n- Run: `uv run pytest tests/integration/test_validation_triggers.py::test_orchestrator_fires_periodic_trigger_at_interval -v`\n- Expected: FAILS (skeleton doesn't implement logic yet)\n- After T011: Test passes\n\n**Integration Path Test**:\nThis test exercises orchestrator main loop → periodic trigger queueing path.\n\n**Notes for Agent**:\n- Identify the exact line (~1135 per plan) for session_end trigger\n- Don't implement full logic yet - just stubs and test","notes":"Quality gate failed: Interrupted: Interrupted by user\nLog: /home/cyou/.claude/projects/-home-cyou-mala/61eb66a2-e489-4ef9-9e56-72a0a8b7c122.jsonl","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-07T04:35:13.671498389Z","created_by":"cyou","updated_at":"2026-01-08T20:02:19.246016506Z","closed_at":"2026-01-08T20:02:19.246016506Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-smaj.9","depends_on_id":"mala-smaj","type":"parent-child","created_at":"2026-01-07T04:35:13.672176704Z","created_by":"cyou"},{"issue_id":"mala-smaj.9","depends_on_id":"mala-smaj.6","type":"blocks","created_at":"2026-01-07T04:36:28.328052289Z","created_by":"cyou"},{"issue_id":"mala-smaj.9","depends_on_id":"mala-smaj.7","type":"blocks","created_at":"2026-01-07T04:36:28.42695292Z","created_by":"cyou"},{"issue_id":"mala-smaj.9","depends_on_id":"mala-smaj.8","type":"blocks","created_at":"2026-01-07T04:36:28.52442163Z","created_by":"cyou"}]}
{"id":"mala-sp2s","title":"[Advisory] Legacy init removed from MalaOrchestrator","description":"## Context\nThis issue was auto-created by epic verification for epic `mala-cavk`.\n\n## Epic Description / Acceptance Criteria\nTitle: Epic: Architecture refactor - break up god classes\n\n# Architecture Refactor: High-Priority God Class Breakup\n\n**Spec**: `docs/2026-01-01-architecture-review.md`\n**Plan**: `docs/2026-01-01-architecture-refactor-plan.md`\n\n## Prerequisites\n\n**Depends on mala-iy6l** (Import-linter package restructure). The iy6l epic restructures the codebase into layer-based packages, and this refactor assumes those paths:\n- `src/orchestrator.py` → `src/orchestration/orchestrator.py`\n- `src/cli.py` → `src/cli/cli.py`\n\n## Goals\n- Reduce complexity and duplication in 3 high-priority areas identified in architecture review\n- Pure refactoring - no behavioral changes\n- Deliver as 3 incremental PRs that can be reviewed and merged independently\n\n## PRs (in order)\n1. **EventSink** - Create BaseEventSink to eliminate NullEventSink duplication (NullEventSink \u003c20 lines)\n2. **AgentSessionRunner** - Extract run_session into focused helpers (675 → \u003c100 lines)  \n3. **MalaOrchestrator** - Extract IssueExecutionCoordinator + remove legacy init (1565 → \u003c1000 lines)\n\n## Acceptance Criteria\n- [ ] NullEventSink reduced to \u003c20 lines\n- [ ] run_session reduced to \u003c100 lines\n- [ ] Legacy init removed from MalaOrchestrator\n- [ ] All existing tests pass\n- [ ] New unit tests for extracted helpers\n\n## Commit Scope\n- Range hint: b826537c90ea28c402742c605757be17820e0fc0^..4301154dfb4c127999d225d639f708c977ab399c\n- Commits:\n- 6817b912b7e448c22748fb6778e5a7c3d5582992 bd-mala-cavk.3: Fix ruff TC003 type-checking import violations\n- 99e411ab4383d9e7d840a4678d98b9d1cfb2205c bd-mala-cavk.3: Fix type annotations and linting issues in test files\n- 977be4d0c6064476546a918b59f5ad49f0c5f63c bd-mala-cavk.3: Extract IssueExecutionCoordinator from MalaOrchestrator\n- 4301154dfb4c127999d225d639f708c977ab399c bd-mala-jnah: Complete orphans_only propagation and add tests\n- ed70ba0d99d0c6bcfe02e781714e7d9fc63ba429 bd-mala-jnah: Add --orphans-only mode to filter non-epic issues\n- 942c6b2f8efd65b778f7f9c3036fbb05091d115e bd-mala-cavk.1: Fix protocol coverage tests to exclude ABCMeta helpers\n- b826537c90ea28c402742c605757be17820e0fc0 bd-mala-cavk.1: Create BaseEventSink, simplify NullEventSink\n- 9e7a3432c10286156f76215298af1c3d6edf1ad5 bd-mala-cavk.5: Add unit tests for extracted orchestration helpers\n- bed8d43b45cc1a6134b0b3cc7d3cd519d7bbaf2f bd-mala-cavk.5: Fix type narrowing in orchestrator factory method\n- ed05c003dcbfe8bdb629d950d5799ec1a831769e bd-mala-cavk.5: Reduce MalaOrchestrator to \u003c1000 lines\n- 46c0f46abccd2af19b1d68380e7957114133f76c bd-mala-cavk.2: Extract helpers from AgentSessionRunner.run_session\n- efb74872758e0863c8d0dd5b72ed0ff2f3a6fd0a bd-mala-cavk.2: Extract helpers from AgentSessionRunner.run_session\n- 6ed49e97da07d72095614485f055986285134b38 bd-mala-cavk.4: Extract helpers from AgentSessionRunner.run_session\n- 39923dfdf0bef6eb9e45ebf86a75a0ec52eb810a bd-mala-dhry: Make debug logging best-effort with opt-out\n- 64f762395351b6c5bfc81832766a84175c573abf bd-mala-dhry: Remove --debug-log CLI arg, always enable debug logging\n\n## Child Issues\n- mala-cavk.1\n- mala-cavk.2\n- mala-cavk.3\n- mala-cavk.4\n- mala-cavk.5\n- mala-dhry\n- mala-jnah\n\n## Unmet Criterion\nLegacy init removed from MalaOrchestrator\n\n## Evidence\nThe legacy __init__ method still exists at lines 121-220 with ~100 lines of compatibility logic that delegates to the factory method. While @overload decorators were removed, the main legacy initialization method remains.\n\n## Priority\nP3 (informational)\n\n## Resolution\nThis is advisory (P2/P3) and does not block epic closure. When complete, close this issue.\n","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-01T23:27:21.238176894Z","created_by":"cyou","updated_at":"2026-01-02T01:44:04.710375705Z","closed_at":"2026-01-02T01:44:04.710375705Z","close_reason":"Closed","labels":["auto_generated","epic_remediation:mala-cavk:7ba335f7ea3d3138e49d297d66af317c13dfd7604328c09b3ec29dbeb85e7684"],"dependencies":[{"issue_id":"mala-sp2s","depends_on_id":"mala-i8ke","type":"blocks","created_at":"2026-01-01T23:32:33.38477104Z","created_by":"daemon"}]}
{"id":"mala-sui6","title":"[Review] 3 non-blocking findings from mala-au9g.4","description":"## Review Findings\n\nThis issue consolidates 3 non-blocking findings from code review.\n\n**Source issue:** mala-au9g.4\n**Highest priority:** P3\n\n---\n\n### Finding 1: [P3] PipelineComponents class is defined but never used\n\n**Priority:** P3\n**Reviewer:** claude\n**Location:** src/orchestration/orchestration_wiring.py:102-118\n\nThe `PipelineComponents` dataclass is defined in orchestration_wiring.py but is never instantiated or referenced anywhere in the codebase. If it's intended for future use, consider adding a TODO comment; otherwise, it should be removed to avoid dead code.\n\n---\n\n### Finding 2: [P3] Redundant test patching of identical gate_runner references\n\n**Priority:** P3\n**Reviewer:** claude\n**Location:** tests/test_orchestrator.py:4176-4181\n\nThe test patches both `orchestrator.gate_runner.gate_checker` and `orchestrator.async_gate_runner.gate_runner.gate_checker`, but these refer to the same object since `build_gate_runner` passes the same `gate_runner` instance to `AsyncGateRunner`. The second patch is redundant.\n\n---\n\n### Finding 3: [P3] Unused PipelineComponents dataclass\n\n**Priority:** P3\n**Reviewer:** gemini\n**Location:** src/orchestration/orchestration_wiring.py:91-105\n\nThe `PipelineComponents` dataclass in `orchestration_wiring.py` is currently unused, and its docstring refers to a non-existent `build_pipeline_components` function. Since `MalaOrchestrator` assigns components individually in `_init_pipeline_runners`, this dataclass and its misleading docstring should be removed to avoid confusion.\n\n---\n","notes":"Quality gate failed: Quality gate failed: No commit with bd-mala-sui6 found after run baseline (stale commits from previous runs are rejected)\nLog: /home/cyou/.claude/projects/-home-cyou-mala/037678c0-682c-4867-867a-6c6718f55b16.jsonl","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-03T08:09:42.214206963Z","created_by":"cyou","updated_at":"2026-01-03T15:03:09.525713959Z","closed_at":"2026-01-03T15:03:09.525713959Z","close_reason":"Closed","labels":["auto_generated","needs-followup","review_finding","review_findings:39c9d5c973f0","source:mala-au9g.4"]}
{"id":"mala-sw4q","title":"use cerberus exclude flag to exclude .beads/* from its review","description":"## Context\n\nCerberus is the multi-model code review system used by mala. It reviews commits and generates review findings.\n\n## Problem\n\nCerberus is currently reviewing files in the .beads/ directory (the beads issue tracker database). These are auto-generated JSONL files that should not be reviewed.\n\n## Solution\n\nUse Cerberus's exclude flag to skip .beads/* files from review.\n\n## Files to Modify\n\n- Check how cerberus is invoked (likely in src/pipeline/ or src/orchestration/)\n- Add exclude pattern for .beads/*\n\n## Acceptance Criteria\n\n- [ ] .beads/ directory excluded from cerberus review\n- [ ] Existing review functionality unchanged","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-01T18:02:11.039651332Z","created_by":"cyou","updated_at":"2026-01-01T23:40:23.351324548Z","closed_at":"2026-01-01T23:40:23.351324548Z","close_reason":"Closed"}
{"id":"mala-t1t8","title":"[Review] 4 non-blocking findings from mala-et9i","description":"## Review Findings\n\nThis issue consolidates 4 non-blocking findings from code review.\n\n**Source issue:** mala-et9i\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Error message suggests disabling lock enforcement but that won't prevent the error\n\n**Priority:** P2\n**Reviewer:** claude\n**Location:** src/infra/agent_runtime.py:271-275\n\nThe updated error message suggests `with_hooks(include_lock_enforcement_hook=False)` as an alternative, but this won't prevent the error. In `build()` at line 368, MCP servers are always built when `_mcp_servers is None`, regardless of the lock enforcement setting. The user will still hit this error even after disabling lock enforcement. Either the error message should remove this misleading suggestion, or `build()` should skip MCP server creation when lock enforcement is disabled.\n\n---\n\n### Finding 2: [P2] Fix misleading guidance in missing MCP factory ValueError\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/infra/agent_runtime.py:270-276\n\nWhen `self._mcp_server_factory` is `None` and no servers were provided, `build()` calls `with_mcp(...)` which calls `_build_mcp_servers(...)` and raises this `ValueError`. The message implies that `with_hooks(include_lock_enforcement_hook=False)` is an alternative to providing a factory/servers, but disabling lock enforcement doesn’t avoid needing MCP server config, so the same error still occurs. Suggest rewording to require `mcp_server_factory` or `with_mcp(servers=...)`, and mention disabling lock enforcement only as an additional step when using custom servers that don’t provide locking tools (not as a standalone alternative).\n\n---\n\n### Finding 3: [P3] Align infra subpackage isolation doc with directional contracts\n\n**Priority:** P3\n**Reviewer:** codex\n**Location:** docs/architecture.md:535\n\nThis bullet now states that `src.infra.clients`, `src.infra.tools`, and `src.infra.telemetry` “must not import each other,” but the enforced import-linter rules are directional (e.g. tools can’t import clients; telemetry can’t import other infra subpackages; clients can’t import IO/hooks/telemetry). As written under “Expected to pass,” it overstates what’s enforced today and can mislead readers about what imports are actually allowed.\n\n---\n\n### Finding 4: [P3] Update with_hooks() arg docs to match _UNSET semantics\n\n**Priority:** P3\n**Reviewer:** codex\n**Location:** src/infra/agent_runtime.py:145-156\n\n`with_hooks()` now uses `_UNSET` to mean “preserve current state,” but the Args section still says parameters like `include_stop_hook` and `include_lock_enforcement_hook` default to True. After this change, the effective default is “no change (initially True),” so the docstring should reflect that to avoid confusion when chaining multiple `with_hooks()` calls.\n\n---\n\n\u003c!-- fp:3dda7d05d34522d3 --\u003e\n\u003c!-- fp:289a816a9fd47f38 --\u003e\n\u003c!-- fp:0a454755a1e4e2b4 --\u003e\n\u003c!-- fp:3118504b54ee666c --\u003e\n\u003c!-- review_finding:174fa1765118 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-05T05:01:39.578017954Z","created_by":"cyou","updated_at":"2026-01-05T05:05:37.474735733Z","closed_at":"2026-01-05T05:05:37.474735733Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-et9i"]}
{"id":"mala-t2ee","title":"use codex sdk instead claude for agent loop","status":"tombstone","priority":2,"issue_type":"feature","created_at":"2026-01-03T19:30:12.77673783Z","created_by":"cyou","updated_at":"2026-01-06T22:52:10.761723406Z","deleted_at":"2026-01-06T22:52:10.761723406Z","deleted_by":"batch delete","delete_reason":"batch delete","original_type":"feature"}
{"id":"mala-t5m","title":"Cache file reads to avoid repeated full-file rereads","description":"## Problem\nAgents repeatedly read the same files multiple times within a single session, wasting tokens on content already in context.\n\n## Evidence from log analysis\n- `quality_gate.py`: read 27 times in one session\n- `beads_client.py`: read 12 times in one session\n- `spec_runner.py`: read 12 times\n- `test_quality_gate.py`: read 9 times\n- `test_validation.py`: read 7 times\n\nEach read sends 80-100k chars back to the model.\n\n## Solution\nOptions to consider:\n1. Track recently-read files by content hash; skip re-reading if unchanged\n2. Use targeted reads (specific line ranges/functions) instead of entire files\n3. Add guidance to agent prompts about avoiding redundant reads\n\n## Expected Impact\n~30% token reduction per session","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-29T00:43:41.156223108Z","created_by":"cyou","updated_at":"2025-12-29T01:59:27.876113447Z","closed_at":"2025-12-29T01:59:27.876113447Z","close_reason":"Closed"}
{"id":"mala-t84","title":"Orchestrator: implement run-level validation (Gate 4) after all issues complete","description":"Context:\n- Gate 4 from docs/quality-hardening-plan.md is designed but not implemented\n- E2E tests never run because orchestrator only uses ValidationScope.PER_ISSUE\n- run_validation field in run metadata is always null\n- E2ERunner exists and works, just not wired up\n\nCurrent state:\n- spec_runner.py:258 only runs E2E when scope == ValidationScope.RUN_LEVEL\n- orchestrator.py:884 always calls validation with scope=ValidationScope.PER_ISSUE\n- run_metadata.record_run_validation() exists but is never called\n- CLI supports --disable-validations=run-level-validate but there's nothing to disable\n\nRequired changes:\n1. After all issues complete in orchestrator.run(), run validation with RUN_LEVEL scope\n2. Build spec with ValidationScope.RUN_LEVEL (includes E2E by default)\n3. Run in clean worktree at current HEAD\n4. Record results via run_metadata.record_run_validation()\n5. On failure: spawn a fixer agent in the same run to address the issues (not a follow-up issue)\n   - The fixer agent gets the validation failure output as context\n   - Re-run Gate 4 after fixer completes\n   - Retry loop with max attempts (e.g., max_gate_retries)\n   - Only fail the run after retries exhausted\n\nWhy spawn in same run (not follow-up issue):\n- Follow-up issues can be missed due to --epic-id, --only-ids, --max-issues filters\n- Guarantees the validation failure is addressed before run completes\n- Matches user expectation of \"spawns another agent to fix any issues\"\n\nAcceptance criteria:\n- E2E tests run after all issues complete (when not disabled)\n- run_validation populated in run metadata JSON\n- --disable-validations=run-level-validate skips Gate 4\n- --disable-validations=e2e skips just E2E within Gate 4\n- On Gate 4 failure: fixer agent spawned with failure context\n- Fixer agent attempts to fix issues, then Gate 4 re-runs\n- Retry loop respects max attempts before failing run\n- Run exits non-zero only after retries exhausted\n\nTest plan:\n- Unit test: orchestrator calls run-level validation after issues complete\n- Unit test: run_validation recorded in metadata\n- Unit test: disable flags respected\n- Unit test: fixer agent spawned on Gate 4 failure\n- Unit test: retry loop respects max attempts\n- Integration: full run with E2E passing\n- Integration: E2E failure triggers fixer and retry\n","notes":"Quality gate failed: Quality gate failed: Missing validation commands: ruff format, ruff check, pytest, ty check, uv sync; No progress: commit unchanged and no new validation evidence\nLog: /home/cyou/.claude/projects/-home-cyou-mala/eeea6f46-b9c3-46c1-a617-3b63df0a48d1.jsonl","status":"closed","priority":0,"issue_type":"task","created_at":"2025-12-27T19:37:38.163794008Z","updated_at":"2025-12-27T23:17:19.540957371Z","closed_at":"2025-12-27T23:17:19.540960361Z","labels":["needs-followup"]}
{"id":"mala-tci","title":"Update README for new CLI defaults and flags","description":"README still shows max-agents default as 3 and doesn't document --epic, --only, or --no-truncate. Update usage + CLI options table to reflect current behavior (unlimited max-agents by default).","status":"closed","priority":3,"issue_type":"task","created_at":"2025-12-26T15:34:30.211575764Z","updated_at":"2025-12-26T18:40:12.900416446Z","closed_at":"2025-12-26T18:40:12.900416446Z","close_reason":"Closed"}
{"id":"mala-tdt1","title":"fix agents commiting others changes","status":"tombstone","priority":2,"issue_type":"feature","created_at":"2026-01-05T02:19:28.963088618Z","created_by":"cyou","updated_at":"2026-01-06T22:52:10.761723406Z","deleted_at":"2026-01-06T22:52:10.761723406Z","deleted_by":"batch delete","delete_reason":"batch delete","original_type":"feature"}
{"id":"mala-tf9s","title":"add review comments into the same epic as the issue they came from","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-02T15:54:09.02624556Z","created_by":"cyou","updated_at":"2026-01-03T20:10:38.01175604Z","closed_at":"2026-01-03T20:10:38.01175604Z","close_reason":"Closed"}
{"id":"mala-tfv2","title":"[BUG] Custom commands do not undergo field-level deep merge","description":"**Source**: Review of mala-smaj.5\n\n**Problem**: In config_merger.py line 351, custom commands use dict-level merge only:\n  merged_custom_commands = {**preset.custom_commands, **user.custom_commands}\n\nBuilt-in commands use _merge_command_field_deep for proper field-level merge, but custom commands do not.\n\n**Impact**: If user specifies only timeout for a custom command expecting to inherit command from preset, the entire preset config is replaced instead of merged. User loses inherited fields.\n\n**Example**:\n- Preset: {custom_commands: {mytest: {command: 'pytest', timeout: 300}}}\n- User: {custom_commands: {mytest: {timeout: 120}}}\n- Expected: {custom_commands: {mytest: {command: 'pytest', timeout: 120}}}\n- Actual: {custom_commands: {mytest: {timeout: 120, command: ''}}}\n\n**Location**: src/domain/validation/config_merger.py:344-351\n\n**Fix**: Iterate over merged custom command keys and apply _merge_command_field_deep to each overlapping command.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-07T19:15:27.872708392Z","created_by":"cyou","updated_at":"2026-01-07T22:01:19.832252083Z","closed_at":"2026-01-07T22:01:19.832252083Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-tfv2","depends_on_id":"mala-smaj","type":"parent-child","created_at":"2026-01-07T19:15:58.060819234Z","created_by":"cyou"}]}
{"id":"mala-tkz","title":"Truncate pytest output to reduce token usage","description":"## Problem\nFull pytest output with verbose mode (-v) generates 10k+ chars per run. Sessions have 9-15 pytest invocations, feeding massive amounts of test output back to the model.\n\n## Evidence\n- mala-51q.2: 15 pytest runs × ~10k chars = ~150k chars of pytest output\n- mala-51q.4: 12 pytest runs\n- mala-869.5: 9 pytest runs\n\nMost of this is PASSED test names that don't help the agent.\n\n## Solution\nDefault to minimal pytest output:\n```\npytest -q --maxfail=1 --disable-warnings --tb=short\n```\n\nOr:\n- Capture full output to file\n- Only return summary + failures to the model\n- Tail small chunk if verbose output needed\n\n## Expected Impact\n~50% reduction in pytest-related tokens","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-29T00:43:41.201104836Z","created_by":"cyou","updated_at":"2025-12-29T01:44:01.732004998Z","closed_at":"2025-12-29T01:44:01.732004998Z","close_reason":"Closed"}
{"id":"mala-tlb3","title":"smarter handling of blocked agents - timeout should not include blocked time, or exit early and try later?","description":"shorter wait time for locks? clean exit path that bypasses checks","status":"tombstone","priority":2,"issue_type":"feature","created_at":"2026-01-02T03:09:07.782510435Z","created_by":"cyou","updated_at":"2026-01-06T22:52:10.761723406Z","deleted_at":"2026-01-06T22:52:10.761723406Z","deleted_by":"batch delete","delete_reason":"batch delete","original_type":"feature"}
{"id":"mala-tqi4","title":"[Review] src/infra/hooks/lint_cache.py:106: [P2] Case-sensitive extraction prevents recognition of uppercase compound commands","description":"## Review Finding\n\nThis issue was auto-created from a P2 review finding.\n\n**Source issue:** mala-4313\n**Reviewer:** gemini\n**Location:** src/infra/hooks/lint_cache.py:106\n\n## Details\n\nThe fix is incomplete because `extract_tool_name` is case-sensitive. Uppercase compound commands (e.g., `CARGO clippy`) or wrappers (e.g., `NPX eslint`) will not be correctly extracted as compound tools because `extract_tool_name` performs case-sensitive matching against its internal token sets. This results in inconsistent `lint_type` identification compared to lowercase versions of the same commands.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-02T16:58:51.14420971Z","created_by":"cyou","updated_at":"2026-01-02T17:05:50.040516823Z","closed_at":"2026-01-02T17:05:50.040516823Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_finding:d6ea5828a999","source:mala-4313"]}
{"id":"mala-tqiu","title":"[BUG] ValidationConfig.from_dict fails to pass validation_triggers to constructor","description":"**Source**: Review of mala-99l1.2\n\n**Problem**: ValidationConfig.from_dict at line 1039-1054 parses every field except validation_triggers. The field is never parsed nor passed to the constructor.\n\n**Impact**: Silent data loss - if a user specifies validation_triggers in mala.yaml, it is completely ignored. The config will always have validation_triggers=None regardless of YAML content.\n\n**Location**: src/domain/validation/config.py:1044\n\n**Fix**: Add parsing logic for validation_triggers (likely calling ValidationTriggersConfig.from_dict()) and pass it to the constructor.","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-07T19:15:27.756125293Z","created_by":"cyou","updated_at":"2026-01-08T14:33:28.110604461Z","closed_at":"2026-01-08T14:33:28.110604461Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-tqiu","depends_on_id":"mala-99l1","type":"parent-child","created_at":"2026-01-07T19:15:58.175159238Z","created_by":"cyou"}]}
{"id":"mala-tsog","title":"[Review] 1 non-blocking finding from mala-idx6.8","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-idx6.8\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] workspace_context lacks env_config parameter\n\n**Priority:** P2\n**Reviewer:** claude\n**Location:** src/domain/validation/spec_workspace.py:236\n\nThe `workspace_context` context manager calls `setup_workspace` without passing `env_config`. While `setup_workspace` was updated to accept an optional `env_config` parameter, the context manager wrapper was not updated to accept and forward this parameter. Callers using `workspace_context` will always get fallback behavior (direct infra imports) regardless of whether they intended to inject `env_config`.\n\n```python\nworkspace = setup_workspace(spec, context, log_dir, step_timeout_seconds)\n```\n\nShould accept and pass `env_config` as a parameter to maintain consistency with the direct `setup_workspace` API.\n\n---\n","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T07:24:13.417873495Z","created_by":"cyou","updated_at":"2026-01-03T07:26:13.599523614Z","closed_at":"2026-01-03T07:26:13.599523614Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_findings:f58c71f97092","source:mala-idx6.8"]}
{"id":"mala-twoa","title":"resume should try to find the last session that worked on the issue and re-enter it","description":"# Session Resume for --resume Flag\n\nWhen `--resume` flag is used, the orchestrator should attempt to resume prior Claude SDK sessions for WIP issues.\n\n## Goals\n- Enhance `--resume` to also resume Claude SDK sessions for WIP issues\n- Add `--strict` flag that fails issues when no prior session found or session is stale\n- Improve context efficiency for users iteratively working on issues\n\n## Acceptance Criteria\n1. `--resume` attempts session resume for WIP issues\n2. Most recent session_id is used (sorted by `started_at` timestamp)\n3. Default lenient behavior: create new session if not found or resumption fails\n4. `--strict` fails that issue only if no prior session found OR session resumption fails\n5. Session resume logging only shown with `--verbose`\n6. `--strict` requires `--resume` (CLI validation)\n\n## Technical Approach\n- Add `lookup_prior_session(repo_path, issue_id)` helper to scan RunMetadata files\n- Add `resume_session_id` field to `AgentSessionInput`\n- Add `strict_resume` field to `OrchestratorConfig`\n- Orchestrator calls lookup, passes to runner, handles stale session errors\n\n## Plan Reference\nSee `plans/2026-01-06-session-resume-plan.md` for full design.","status":"closed","priority":2,"issue_type":"epic","created_at":"2026-01-05T02:41:32.26899585Z","created_by":"cyou","updated_at":"2026-01-06T22:28:19.897203503Z","closed_at":"2026-01-06T22:28:19.897203503Z","close_reason":"Closed"}
{"id":"mala-twoa.1","title":"[integration-path-test] Skeleton: Add resume_session_id to AgentSessionInput + stub lookup + failing test","description":"## Goal\nCreate the skeleton wiring for session resumption: add the `resume_session_id` field to `AgentSessionInput`, stub `lookup_prior_session()`, and write a failing integration test that exercises the full path.\n\n## Context\nThis establishes the compile-ready wiring surface following TDD \"red\" phase. The integration test will fail because `_initialize_session()` doesn't yet call `with_resume()` - that implementation happens in T003, which will turn this test green.\n\n## Scope\n- **In**: Add field, stub function, failing test\n- **Out**: Actual implementation of lookup (T002) or resume logic (T003)\n\n## Changes\n- `src/pipeline/agent_session_runner.py` — Exists — Add `resume_session_id: str | None = None` to `AgentSessionInput` dataclass\n- `src/infra/io/log_output/run_metadata.py` — Exists — Add stub `lookup_prior_session(repo_path: Path, issue_id: str) -\u003e str | None` that returns None with docstring noting \"Stub - implementation in T002\"\n- `tests/integration/pipeline/test_agent_session_runner.py` — Exists — Add test `test_resume_session_id_wired_to_sdk`:\n  - Create `AgentSessionInput` with `resume_session_id=\"test-session-123\"`\n  - Mock `sdk_client_factory.with_resume()` \n  - Run session\n  - Assert `with_resume()` was called with the session_id\n  - **This test should FAIL** - that's correct for TDD \"red\" phase\n\n## Wiring Map\n- `resume_session_id` → `AgentSessionInput` → `AgentSessionRunner._initialize_session()` → `sdk_client_factory.with_resume(options, session_id)`\n\n## Acceptance Criteria\n- [ ] `AgentSessionInput` has `resume_session_id: str | None = None` field\n- [ ] `lookup_prior_session()` function exists with stub implementation (returns None)\n- [ ] Integration test exists and **FAILS** (expected - proves we need implementation)\n- [ ] Project compiles/type-checks (`uvx ty check` passes)\n- [ ] Test failure message indicates `with_resume()` was not called (not import/syntax error)\n\n## Verification\n```bash\nuvx ty check  # Should pass - code compiles\npytest tests/integration/pipeline/test_agent_session_runner.py -k resume -v\n# Test should FAIL with assertion error (with_resume not called)\n# This is CORRECT - T003 will make it pass\n```\n\n## Notes for Agent\n- The test MUST fail for the right reason: `with_resume()` not being called\n- Do NOT implement `with_resume()` call in `_initialize_session()` - that's T003's job\n- Do NOT implement actual lookup logic - that's T002's job\n- The stub `lookup_prior_session()` returning None is intentional\n- Use `MagicMock` or appropriate fake for `sdk_client_factory.with_resume()`","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-06T15:31:59.692329113Z","created_by":"cyou","updated_at":"2026-01-06T15:41:55.227019818Z","closed_at":"2026-01-06T15:41:55.227019818Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-twoa.1","depends_on_id":"mala-twoa","type":"parent-child","created_at":"2026-01-06T15:31:59.701667773Z","created_by":"cyou"}]}
{"id":"mala-twoa.10","title":"[Remediation] --strict fails that issue only if no prior session found OR ...","description":"## Context\nThis issue was auto-created by epic verification for epic `mala-twoa`.\n\n## Epic Description / Acceptance Criteria\nTitle: resume should try to find the last session that worked on the issue and re-enter it\n\n# Session Resume for --resume Flag\n\nWhen `--resume` flag is used, the orchestrator should attempt to resume prior Claude SDK sessions for WIP issues.\n\n## Goals\n- Enhance `--resume` to also resume Claude SDK sessions for WIP issues\n- Add `--strict` flag that fails issues when no prior session found or session is stale\n- Improve context efficiency for users iteratively working on issues\n\n## Acceptance Criteria\n1. `--resume` attempts session resume for WIP issues\n2. Most recent session_id is used (sorted by `started_at` timestamp)\n3. Default lenient behavior: create new session if not found or resumption fails\n4. `--strict` fails that issue only if no prior session found OR session resumption fails\n5. Session resume logging only shown with `--verbose`\n6. `--strict` requires `--resume` (CLI validation)\n\n## Technical Approach\n- Add `lookup_prior_session(repo_path, issue_id)` helper to scan RunMetadata files\n- Add `resume_session_id` field to `AgentSessionInput`\n- Add `strict_resume` field to `OrchestratorConfig`\n- Orchestrator calls lookup, passes to runner, handles stale session errors\n\n## Plan Reference\nSee `plans/2026-01-06-session-resume-plan.md` for full design.\n\n## Commit Scope\n- Range hint: daeff2a4d773036a0b039e28c53d68fbf30da9eb^..239214a88029a0e30657d90d81a9e48f62f20e25\n- Commits:\n- 239214a88029a0e30657d90d81a9e48f62f20e25 bd-mala-twoa.5: Fix type errors across codebase\n- dcef66c465d3bd3ba2c94f2d7904ea75f0f8cfec bd-mala-twoa.5: Address review feedback\n- 1b5db0af74d8597c02ec837b04b0f53bf6e70fd3 bd-mala-twoa.5: Fix lint errors in issue_manager and beads_client\n- 754fea8f976615ccf78d62f691d8b4e5f1a07e7b bd-mala-twoa.5: Refactor session discovery to shared infra\n- d6f7e76f981c52cc6b8814cee6865ec2517ec456 bd-mala-twoa.4: Add --strict CLI flag with validation\n- 358fc518fc9f65a8176d07ad0d2590156f33f173 bd-mala-twoa.3: Move stale session handling inside run_session\n- 8f24454e830fbae86cf1274aa79a50350f3fb2a8 bd-mala-twoa.3: Fix docstring for stale session heuristic\n- 62e4011c156aba23f3e0752a5df5902a4724397e bd-mala-twoa.9: Add UTC timezone to parsed filename timestamps in early-exit optimization\n- 6c73eadfdb1ffdfb31941e868fa3ea72d18f3101 bd-mala-twoa.8: Fix early-exit filename timestamp parsing in lookup_prior_session\n- a983cee566db0e2b97c95603446f08003790334f bd-mala-twoa.3: Fix review issues - stale session handling + deadlock registration\n- 59518ccb366b84ba2d0dc79e9832e82597110ecc bd-mala-twoa.7: Fix TypeError risk and add early-exit optimization in lookup_prior_session\n- fb1752adecd5b41a6b8c2e2e0d612610e8fadec7 bd-mala-twoa.3: Wire session lookup in orchestrator + strict mode + config\n- ff3d59e70864ae0c098a401297997c7d91c6ae48 bd-mala-twoa.6: Consolidate timestamp parsing and improve lookup_prior_session\n- 35818fe80839b4f01a243eab76016db62aed2ea0 bd-mala-twoa.2: Implement lookup_prior_session() with unit tests\n- daeff2a4d773036a0b039e28c53d68fbf30da9eb bd-mala-twoa.1: Add resume_session_id skeleton + failing integration test\n\n## Child Issues\n- mala-twoa.1\n- mala-twoa.2\n- mala-twoa.3\n- mala-twoa.4\n- mala-twoa.5\n- mala-twoa.6\n- mala-twoa.7\n- mala-twoa.8\n- mala-twoa.9\n\n## Unmet Criterion\n--strict fails that issue only if no prior session found OR session resumption fails\n\n## Evidence\nThe strict mode implementation in orchestrator.py:697-714 only handles the 'no prior session found' case. When session resumption fails due to stale sessions (handled in agent_session_runner.py:914-929), the system always retries with a fresh session regardless of strict_resume mode. The AgentSessionRunner doesn't receive or check the strict_resume flag, so stale session failures are handled leniently even in strict mode.\n\n## Priority\nP1 (blocking)\n\n## Resolution\nAddress this criterion to unblock epic closure. When complete, close this issue.\n","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-06T22:08:22.816295587Z","created_by":"cyou","updated_at":"2026-01-06T22:26:03.568248297Z","closed_at":"2026-01-06T22:26:03.568248297Z","close_reason":"Closed","labels":["auto_generated","epic_remediation:mala-twoa:a0c913b963b2a4589ff44876b9ccd0ac7eec9fdd9076daa1d548ce25e430bd04"],"dependencies":[{"issue_id":"mala-twoa.10","depends_on_id":"mala-twoa","type":"parent-child","created_at":"2026-01-06T22:08:22.816776793Z","created_by":"cyou"}]}
{"id":"mala-twoa.11","title":"[Review] 7 non-blocking findings from mala-twoa.5","description":"## Review Findings\n\nThis issue consolidates 7 non-blocking findings from code review.\n\n**Source issue:** mala-twoa.5\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] CLI corrupt-file warnings inconsistent between commands\n\n**Priority:** P2\n**Reviewer:** claude\n**Location:** src/cli/logs.py:124-140\n\nThe `list` and `sessions` commands now use infra's `load_runs()` which logs corrupt file warnings via `_logger.warning()`, while the `show` command uses `_parse_run_file_cli()` which prints to stderr. This inconsistency means CLI users see corrupt file warnings for `mala logs show` but not for `mala logs list` or `mala logs sessions` (unless they configure Python logging to show warnings). Consider having `_collect_runs_with_paths` use `_parse_run_file_cli` for consistent user-facing behavior, or document this as intentional.\n\n---\n\n### Finding 2: [P2] OrderPreferenceLike duplicated across modules\n\n**Priority:** P2\n**Reviewer:** claude\n**Location:** src/infra/issue_manager.py:16-31\n\nThe `OrderPreferenceLike` type alias and `SupportsOrderPreference` protocol are defined identically in both `src/infra/issue_manager.py` (lines 16-31) and `src/core/protocols.py` (lines 47-62). While this avoids import cycles (issue_manager is infra, protocols is core), it creates maintenance burden and drift risk. The types are currently identical but could diverge. Consider either: (1) having issue_manager import from protocols (if layering allows), or (2) adding a comment in both locations referencing each other to ensure sync.\n\n---\n\n### Finding 3: [P2] Preserve CLI-visible corrupt-metadata warnings when using load_runs\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/cli/logs.py:189-195\n\n`list_runs()` goes through `load_runs()` via `_collect_runs_with_paths()`, which relies on infra logging (`_logger.warning`) for corrupt JSON. That changes prior CLI behavior where corrupt files produced stderr warnings, and it also makes `mala logs list`/`mala logs sessions --all` silently skip corrupt files unless logging is configured. Consider adding an opt-in warning callback/collector to `load_runs()` (or a CLI wrapper that preserves stderr warnings) so this remains a pure refactor from a CLI user perspective.\n\n---\n\n### Finding 4: [P2] Deduplicate OrderPreferenceLike type definitions to avoid drift\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/infra/issue_manager.py:14-33\n\n`OrderPreferenceLike` (and related `OrderPreferenceValue`/`SupportsOrderPreference`) is defined both in `src/core/protocols.py` and again in `src/infra/issue_manager.py`. This is easy to accidentally diverge over time and can cause subtle type-check/runtime normalization inconsistencies across callers (some already import from `src.core.protocols`). Prefer a single canonical definition (likely `src.core.protocols`) and import it in `IssueManager`, keeping only the normalization logic local if needed.\n\n---\n\n### Finding 5: [P2] Avoid duplicating OrderPreferenceLike across modules\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/infra/issue_manager.py:14-30\n\nThe `OrderPreferenceValue`, `SupportsOrderPreference`, `OrderPreferenceLike`, and `DEFAULT_ORDER_PREFERENCE` types are duplicated between `src/core/protocols.py` and `src/infra/issue_manager.py`. Since `protocols.py` is intended to be a centralized leaf module for orchestrator-facing types, `issue_manager.py` should import these definitions from there to prevent type drift and simplify maintenance.\n\n---\n\n### Finding 6: [P2] Consistently preserve CLI warnings for corrupt metadata\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/cli/logs.py:84-113\n\nThe `list_runs` and `sessions` commands now use shared infra functions (`load_runs`, `find_sessions_for_issue`) which log corrupt metadata via `_logger.warning`. In the CLI, these warnings may be invisible or have an inconsistent format compared to the `Warning: skipping corrupt file...` messages preserved in `_find_matching_runs` via the duplicated `_parse_run_file_cli`. Shared infra should ideally accept an error callback or the CLI should configure logging to ensure these user-facing warnings remain visible and consistent.\n\n---\n\n### Finding 7: [P2] Deduplicate run metadata validation logic in logs.py\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/cli/logs.py:84-113\n\n`_parse_run_file_cli` in `logs.py` re-implements the validation logic and `_REQUIRED_KEYS` already present in `src/infra/io/log_output/run_metadata.py`'s `_validate_run_data`. This duplication was introduced to preserve custom warning output but undermines the refactor's goal of centralizing discovery logic. `logs.py` should instead reuse the validation logic from `run_metadata.py`.\n\n---\n\n\u003c!-- fp:c6a44f5ad5e6fc97 --\u003e\n\u003c!-- fp:39ea4e2c263bb1fa --\u003e\n\u003c!-- fp:a63dd9d3a84c84d2 --\u003e\n\u003c!-- fp:d2acd57f3647d1dd --\u003e\n\u003c!-- fp:2e9076720c673d23 --\u003e\n\u003c!-- fp:b49d2a32c4705098 --\u003e\n\u003c!-- fp:5227e109b62281eb --\u003e\n\u003c!-- review_finding:1ed647d9e30e --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-06T22:28:19.929001824Z","created_by":"cyou","updated_at":"2026-01-06T22:36:25.64343553Z","closed_at":"2026-01-06T22:36:25.64343553Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-twoa.5"],"dependencies":[{"issue_id":"mala-twoa.11","depends_on_id":"mala-twoa","type":"parent-child","created_at":"2026-01-06T22:28:19.929428554Z","created_by":"cyou"}]}
{"id":"mala-twoa.12","title":"[Review] 2 non-blocking findings from mala-twoa.11","description":"## Review Findings\n\nThis issue consolidates 2 non-blocking findings from code review.\n\n**Source issue:** mala-twoa.11\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Propagate on_corrupt through find_sessions_for_issue\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/infra/io/log_output/run_metadata.py:960-966\n\nAfter this change, some CLI paths pass `on_corrupt=_warn_corrupt_file` into `load_runs()` (so corrupt/invalid metadata emits stderr warnings), but `find_sessions_for_issue()` still calls `_parse_run_file(path)` without the new callback, and `src/cli/logs.py` uses `find_sessions_for_issue(None, issue)` for `mala logs sessions` without `--all`. This means corrupt files are silently skipped unless Python logging is configured, making warnings inconsistent across CLI commands. Consider adding an optional `on_corrupt` param to `find_sessions_for_issue()` (default `None`) and threading it through:\n\n```python\ndata = _parse_run_file(path)\n```\n\n---\n\n### Finding 2: [P2] Replace redundant _parse_run_file_cli with shared _parse_run_file\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/cli/logs.py:84-117\n\n`_parse_run_file_cli` in `logs.py` is redundant and duplicates logic from `_parse_run_file` in `run_metadata.py` (e.g., the `issues` normalization block). Furthermore, it remains inconsistent with the newly added `_warn_corrupt_file` used elsewhere in the CLI because it fails to print a warning when `_validate_run_data` returns `False`. Replacing the function body with a call to `_parse_run_file(path, on_corrupt=_warn_corrupt_file)` would eliminate duplication and ensure consistent warning behavior across all `mala logs` commands.\n\n---\n\n\u003c!-- fp:cf9e3b781ece29c7 --\u003e\n\u003c!-- fp:93b6c17a58a84903 --\u003e\n\u003c!-- review_finding:e33a16eee513 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-06T22:36:25.675347785Z","created_by":"cyou","updated_at":"2026-01-06T22:41:17.932516952Z","closed_at":"2026-01-06T22:41:17.932516952Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-twoa.11"],"dependencies":[{"issue_id":"mala-twoa.12","depends_on_id":"mala-twoa","type":"parent-child","created_at":"2026-01-06T22:36:25.675810273Z","created_by":"cyou"}]}
{"id":"mala-twoa.2","title":"Implement lookup_prior_session() helper with unit tests","description":"## Goal\nImplement the `lookup_prior_session()` helper function that finds the most recent session_id for a given issue, reusing existing infrastructure from `logs.py`.\n\n## Context\nThe `mala logs sessions --issue \u003cid\u003e` command already implements session discovery. We should extract/reuse that logic rather than duplicating it.\n\n## Scope\n- **In**: Implement lookup function reusing existing `_discover_run_files`, `_collect_runs`, `_extract_sessions` patterns\n- **Out**: Orchestrator integration (T003), CLI flags (T004)\n\n## Changes\n- `src/infra/io/log_output/run_metadata.py` — Exists — Add `lookup_prior_session(repo_path: Path, issue_id: str) -\u003e str | None`:\n  - Reuse `get_repo_runs_dir()` for directory\n  - Follow same pattern as `logs.py:_extract_sessions()` for session extraction\n  - Sort by `started_at` timestamp (same as `_sort_sessions`)\n  - Return first non-None session_id for the issue, or None if not found\n  - Handle missing dir, corrupt JSON gracefully\n\n- `src/cli/logs.py` — Exists — **Optional refactor**: Consider extracting `_extract_sessions` and `_sort_sessions` to `run_metadata.py` as public functions so both `logs sessions` command and orchestrator can share them. If time-boxed, can defer this refactor.\n\n- `tests/unit/pipeline/test_run_metadata.py` — Exists — Add unit tests:\n  - `test_lookup_finds_recent_session`\n  - `test_lookup_returns_none_if_missing`\n  - `test_lookup_ignores_runs_without_session_id`\n  - `test_lookup_handles_corrupt_json`\n  - `test_lookup_sorts_by_started_at_timestamp`\n\n## Implementation Note\nCheck `src/cli/logs.py` for existing patterns:\n- `_discover_run_files()` - finds JSON files\n- `_collect_runs()` - loads and validates\n- `_extract_sessions()` - extracts session info per issue\n- `_sort_sessions()` - sorts by `run_started_at` desc\n\nEither:\n1. **Minimal**: Implement `lookup_prior_session()` following same patterns inline\n2. **Better**: Extract shared logic to `run_metadata.py`, update `logs.py` to use it\n\n## Acceptance Criteria\n- [ ] `lookup_prior_session()` returns most recent session_id for issue\n- [ ] Returns None if no matching session found\n- [ ] Gracefully handles corrupt JSON files (skip, continue)\n- [ ] Gracefully handles missing runs directory\n- [ ] Sorts by `started_at` timestamp (same logic as `logs sessions`)\n- [ ] All unit tests pass\n\n## Verification\n```bash\nuvx ty check\npytest tests/unit/pipeline/test_run_metadata.py -v -k lookup\n# Also verify logs sessions still works:\nmala logs sessions --issue \u003csome-issue-id\u003e\n```\n\n## Notes for Agent\n- Use `get_repo_runs_dir(repo_path)` to get the runs directory\n- The sorting logic in `logs.py:_sort_sessions` uses `started_at` field - use same approach\n- Stop scanning after first match (performance optimization)\n- Log warnings for corrupt files but don't fail","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-06T15:32:14.464045357Z","created_by":"cyou","updated_at":"2026-01-06T15:47:17.785577956Z","closed_at":"2026-01-06T15:47:17.785577956Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-twoa.2","depends_on_id":"mala-twoa","type":"parent-child","created_at":"2026-01-06T15:32:14.480732661Z","created_by":"cyou"},{"issue_id":"mala-twoa.2","depends_on_id":"mala-twoa.1","type":"blocks","created_at":"2026-01-06T15:33:01.309919626Z","created_by":"cyou"}]}
{"id":"mala-twoa.3","title":"Wire session lookup in orchestrator + handle strict mode + add strict_resume config","description":"## Goal\nWire the session lookup into the orchestrator: call `lookup_prior_session()` before running WIP issues, pass `resume_session_id` to the runner, and handle strict mode failures.\n\n## Context\nThis connects the lookup (T002) to the runner (T001). When `prioritize_wip=True` (--resume flag), the orchestrator looks up prior sessions and passes them to the runner. Stale session errors are caught at the orchestrator level.\n\n## Scope\n- **In**: Config field, orchestrator lookup call, strict mode handling, verbose logging\n- **Out**: CLI flag (T004)\n\n## Changes\n- `src/orchestration/types.py` — Exists — Add `strict_resume: bool = False` to `OrchestratorConfig`\n- `src/orchestration/orchestrator.py` — Exists —\n  - Import `lookup_prior_session`\n  - In issue processing (before `run_session`): if `prioritize_wip`, call `lookup_prior_session(repo_path, issue_id)`\n  - Pass `resume_session_id` in `AgentSessionInput`\n  - Wrap session run in try/catch for stale session errors\n  - Lenient mode: catch error, log warning, retry without resume_session_id\n  - Strict mode: catch error, mark issue failed, continue to next\n  - Log \"Resuming session {session_id} for issue {issue_id}\" only when verbose mode enabled\n- `src/pipeline/agent_session_runner.py` — Exists — In `_initialize_session()`:\n  - If `input.resume_session_id` is set, call `sdk_client_factory.with_resume(options, resume_session_id)`\n- `tests/unit/orchestration/test_orchestrator.py` — Exists — Add tests:\n  - `test_resume_calls_lookup_for_wip_issues`\n  - `test_strict_mode_fails_issue_when_no_session`\n  - `test_lenient_mode_fallback_on_stale_session`\n\n## Wiring Map\n- `prioritize_wip (--resume)` → `OrchestratorConfig` → `MalaOrchestrator._process_issue()` → `lookup_prior_session()` → `AgentSessionInput.resume_session_id` → `AgentSessionRunner._initialize_session()` → `with_resume()`\n\n## Acceptance Criteria\n- [ ] `strict_resume` field added to `OrchestratorConfig`\n- [ ] Orchestrator calls `lookup_prior_session()` when `prioritize_wip=True`\n- [ ] `resume_session_id` passed to `AgentSessionInput`\n- [ ] `_initialize_session()` calls `with_resume()` when `resume_session_id` is set\n- [ ] Stale session errors caught at orchestrator level\n- [ ] Lenient mode: fallback to new session on error\n- [ ] Strict mode: fail issue on error, continue run\n- [ ] Session resumption logged only when verbose mode is enabled (AC #5)\n- [ ] Integration test from T001 now PASSES\n- [ ] All new unit tests pass\n\n## Verification\n```bash\nuvx ty check\npytest tests/unit/orchestration/test_orchestrator.py -v -k resume\npytest tests/integration/pipeline/test_agent_session_runner.py -k resume\n# Integration test should now PASS\n```\n\n## Stale Session Error Handling\nCatch SDK errors that indicate unresumable sessions:\n- `SessionNotFoundError` or `InvalidSessionError` (if SDK defines these)\n- HTTP 404/410 response errors wrapped in SDK exceptions\n- Any error with \"session\" + \"not found\" / \"invalid\" / \"expired\" in message\n\nDo NOT catch as stale:\n- Rate limit errors (429)\n- Network timeouts\n- Authentication failures\n- Generic server errors (500)\n\n## Notes for Agent\n- The `with_resume()` call should be in `_initialize_session()` where options are built\n- Check existing error handling in `idle_retry_policy.py` for patterns\n- Verbose logging: check how other verbose-only logs are gated in orchestrator","notes":"Quality gate failed: External review failed: src/pipeline/agent_session_runner.py:912: [P1] Enforce `strict_resume` for stale/invalid session IDs; src/pipeline/agent_session_runner.py:855: [P1] Clear `resume_session_id` after first loop iteration\nLog: /home/cyou/.claude/projects/-home-cyou-mala/285ff065-e402-4de6-bf26-8d625fa556da.jsonl","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-06T15:32:32.81476922Z","created_by":"cyou","updated_at":"2026-01-06T21:22:00.129759709Z","closed_at":"2026-01-06T21:22:00.129759709Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-twoa.3","depends_on_id":"mala-twoa","type":"parent-child","created_at":"2026-01-06T15:32:32.82540453Z","created_by":"cyou"},{"issue_id":"mala-twoa.3","depends_on_id":"mala-twoa.1","type":"blocks","created_at":"2026-01-06T15:33:01.368899133Z","created_by":"cyou"},{"issue_id":"mala-twoa.3","depends_on_id":"mala-twoa.2","type":"blocks","created_at":"2026-01-06T15:33:01.419756591Z","created_by":"cyou"}]}
{"id":"mala-twoa.4","title":"Add CLI --strict flag + validation + update --resume help text","description":"## Goal\nAdd the `--strict` CLI flag with validation that it requires `--resume`, and update the `--resume` help text to reflect the new session resumption behavior.\n\n## Context\nThis is the user-facing CLI surface. The `--strict` flag controls whether missing/stale sessions cause issue failure. It only makes sense with `--resume`.\n\n## Scope\n- **In**: CLI flag, validation, help text update\n- **Out**: Core implementation (already done in T001-T003)\n\n## Changes\n- `src/cli/cli.py` — Exists —\n  - Add `--strict` flag (bool, default False, help panel \"Scope \u0026 Ordering\")\n  - Add validation: if `strict and not resume`, raise `typer.BadParameter(\"--strict requires --resume flag\")`\n  - Update `--resume` help text to: \"Prioritize in_progress issues AND attempt to resume their prior Claude sessions\"\n  - Pass `strict_resume=strict` to `OrchestratorConfig`\n- `tests/unit/cli/test_cli.py` — Exists (or create if needed) — Add tests:\n  - `test_strict_without_resume_raises_error`\n  - `test_strict_with_resume_passes_to_config`\n\n## Acceptance Criteria\n- [ ] `--strict` flag added to CLI\n- [ ] `--strict` without `--resume` raises `BadParameter` error\n- [ ] `--resume` help text updated\n- [ ] `strict_resume` passed to `OrchestratorConfig`\n- [ ] All tests pass\n- [ ] CLI `--help` shows updated text\n\n## Verification\n```bash\nuvx ty check\nmala run --strict --help  # Should show error\nmala run --resume --strict --help  # Should show help\npytest tests/unit/cli/ -v -k strict\npytest tests/  # Full test suite passes\n```\n\n## Notes for Agent\n- Validation should happen early in the `run()` function, before any async work\n- Use `typer.BadParameter` for the validation error\n- The help text change is a documentation/behavior change - note in any release notes if applicable","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-06T15:32:55.418043505Z","created_by":"cyou","updated_at":"2026-01-06T21:26:12.191609835Z","closed_at":"2026-01-06T21:26:12.191609835Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-twoa.4","depends_on_id":"mala-twoa","type":"parent-child","created_at":"2026-01-06T15:32:55.435714112Z","created_by":"cyou"},{"issue_id":"mala-twoa.4","depends_on_id":"mala-twoa.3","type":"blocks","created_at":"2026-01-06T15:33:01.47314414Z","created_by":"cyou"}]}
{"id":"mala-twoa.5","title":"Refactor: Extract session discovery to shared infra, deduplicate logs.py","description":"## Goal\nRefactor session discovery logic to proper architectural layer. Extract shared functions from `logs.py` to `run_metadata.py` so both CLI and orchestrator use the same implementation.\n\n## Context\nAfter the initial session resume implementation (T001-T004), we'll have two implementations:\n1. `cli/logs.py` - private `_discover_run_files`, `_extract_sessions`, `_sort_sessions`\n2. `run_metadata.py` - new `lookup_prior_session()`\n\nThis creates duplication and a layering issue (session discovery should be infra, not CLI).\n\n## Scope\n- **In**: Extract shared logic, update both consumers\n- **Out**: Changing behavior (pure refactor)\n\n## Changes\n- `src/infra/io/log_output/run_metadata.py` — Exists — Add public functions:\n  - `discover_run_files(repo_path: Path | None = None) -\u003e list[Path]`\n  - `load_runs(files: list[Path], limit: int | None = None) -\u003e list[RunMetadata]`\n  - `find_sessions_for_issue(repo_path: Path, issue_id: str) -\u003e list[tuple[RunMetadata, IssueRun]]`\n  - Refactor `lookup_prior_session()` to use `find_sessions_for_issue()`\n\n- `src/cli/logs.py` — Exists — Replace private helpers with imports from `run_metadata.py`:\n  - Delete `_discover_run_files`, `_collect_runs`, `_extract_sessions`, `_sort_sessions`\n  - Import and use shared functions\n  - Keep CLI-specific formatting/output logic\n\n- `tests/unit/pipeline/test_run_metadata.py` — Exists — Add tests for new public functions\n\n## Acceptance Criteria\n- [ ] `logs sessions --issue X` produces identical output before/after\n- [ ] `logs runs` produces identical output before/after\n- [ ] No duplicate session discovery logic between cli/ and infra/\n- [ ] Proper layering: cli → infra (not infra → cli)\n- [ ] All existing tests pass\n\n## Verification\n```bash\n# Before refactor, capture baseline:\nmala logs sessions --issue mala-twoa --json \u003e /tmp/before.json\nmala logs runs --json \u003e /tmp/before-runs.json\n\n# After refactor:\nmala logs sessions --issue mala-twoa --json \u003e /tmp/after.json\nmala logs runs --json \u003e /tmp/after-runs.json\n\ndiff /tmp/before.json /tmp/after.json  # Should be identical\ndiff /tmp/before-runs.json /tmp/after-runs.json\n\npytest tests/unit/pipeline/test_run_metadata.py -v\npytest tests/unit/cli/ -v\n```\n\n## Notes for Agent\n- This is a pure refactor - behavior must not change\n- The key insight: session discovery is infrastructure, not CLI\n- `logs.py` should become thin (CLI concerns only), delegating to infra\n- Consider using `RunMetadata` objects instead of dicts for type safety","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-06T15:44:54.994110594Z","created_by":"cyou","updated_at":"2026-01-06T22:03:29.865828685Z","closed_at":"2026-01-06T22:03:29.865828685Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-twoa.5","depends_on_id":"mala-twoa","type":"parent-child","created_at":"2026-01-06T15:44:54.994650272Z","created_by":"cyou"},{"issue_id":"mala-twoa.5","depends_on_id":"mala-twoa.4","type":"blocks","created_at":"2026-01-06T15:44:59.923485255Z","created_by":"cyou"}]}
{"id":"mala-twoa.6","title":"[Review] 6 non-blocking findings from mala-twoa.2","description":"## Review Findings\n\nThis issue consolidates 6 non-blocking findings from code review.\n\n**Source issue:** mala-twoa.2\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Consider reusing _parse_timestamp from logs.py to avoid duplication\n\n**Priority:** P2\n**Reviewer:** claude\n**Location:** src/infra/io/log_output/run_metadata.py:759-774\n\nThe new `_parse_timestamp_for_sort` function in `run_metadata.py` is nearly identical to `_parse_timestamp` in `logs.py` (lines 143-151). Both parse ISO timestamps to epoch floats with the same logic. The author's context mentions potentially extracting shared logic to `run_metadata.py`. While this works correctly, consolidating these into a single shared function would improve maintainability and follow the author's \"Better\" approach from the implementation notes.\n\n---\n\n### Finding 2: [P2] Make lookup_prior_session deterministic on timestamp ties\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/infra/io/log_output/run_metadata.py:754-756\n\n`lookup_prior_session()` sorts candidates only by `started_at` epoch seconds. If two run metadata files have identical `started_at` (not uncommon if timestamps are second-resolution), the winner depends on filesystem/glob iteration order, so results can vary across machines/runs. `src/cli/logs.py` avoids this by adding secondary keys (e.g., `run_id`) for ties; consider collecting `run_id` (or filename) and sorting by `(-ts, run_id)` (or equivalent) to make the returned session stable.\n\n---\n\n### Finding 3: [P3] Don’t silently swallow corrupt run files (match logs.py warnings)\n\n**Priority:** P3\n**Reviewer:** codex\n**Location:** src/infra/io/log_output/run_metadata.py:724-729\n\nCorrupt/undecodable JSON files are skipped without any signal. This differs from `src/cli/logs.py` which prints a warning to stderr for corrupt files, and makes it harder to diagnose why prior sessions aren’t found in real repos. Consider emitting a warning (or logger call) when skipping a file due to parse errors, while still continuing.\n\n---\n\n### Finding 4: [P3] Avoid duplicating timestamp parsing logic from logs.py\n\n**Priority:** P3\n**Reviewer:** codex\n**Location:** src/infra/io/log_output/run_metadata.py:769-772\n\n`_parse_timestamp_for_sort()` duplicates `src/cli/logs.py:_parse_timestamp()` almost verbatim. This increases drift risk (e.g., one side adds new timestamp formats or tie-breaking behavior). A small refactor to centralize timestamp parsing (and ideally sorting) in `run_metadata.py`, with `logs.py` importing it, would better match the stated goal of reusing the existing session discovery logic.\n\n---\n\n### Finding 5: [P2] Inefficient O(N) scan of all run files\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/infra/io/log_output/run_metadata.py:724-745\n\nThe current implementation of `lookup_prior_session` scans and parses every single JSON file in the runs directory before sorting them. As the number of runs grows, this will cause significant performance degradation. Reusing the pattern from `logs.py` (sorting filenames descending and leveraging the timestamp prefix) would allow for a much faster search, especially since the most recent session is typically found in one of the latest files.\n\n---\n\n### Finding 6: [P3] Duplicated timestamp parsing logic\n\n**Priority:** P3\n**Reviewer:** gemini\n**Location:** src/infra/io/log_output/run_metadata.py:756-771\n\n`_parse_timestamp_for_sort` is an exact duplicate of `_parse_timestamp` in `src/cli/logs.py`. This logic should be unified in `run_metadata.py` and shared between the CLI and the infra layer to prevent maintenance drift and ensure consistent sorting behavior across the application.\n\n---\n\n\u003c!-- fp:c3a11ebf6d9f1e54 --\u003e\n\u003c!-- fp:7eb516e81361e7bd --\u003e\n\u003c!-- fp:4b4056deed54dd9f --\u003e\n\u003c!-- fp:e000f674d502fadc --\u003e\n\u003c!-- fp:574cb845eba894c8 --\u003e\n\u003c!-- fp:a96bb227420da005 --\u003e\n\u003c!-- review_finding:459aec2eb232 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-06T15:47:17.82880616Z","created_by":"cyou","updated_at":"2026-01-06T15:58:07.95687553Z","closed_at":"2026-01-06T15:58:07.95687553Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-twoa.2"],"dependencies":[{"issue_id":"mala-twoa.6","depends_on_id":"mala-twoa","type":"parent-child","created_at":"2026-01-06T15:47:17.829356598Z","created_by":"cyou"}]}
{"id":"mala-twoa.7","title":"[Review] 4 non-blocking findings from mala-twoa.6","description":"## Review Findings\n\nThis issue consolidates 4 non-blocking findings from code review.\n\n**Source issue:** mala-twoa.6\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Validate/cast run_id before using it as deterministic sort key\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/infra/io/log_output/run_metadata.py:756-766\n\n`run_id` comes from `json.load()` (i.e., `Any`) and is used as a secondary sort key. If any matching candidates share the same timestamp and have mixed/invalid `run_id` types (e.g., `None`, int), `candidates.sort(key=lambda x: (-x[0], x[1]))` can raise `TypeError` when comparing non-strings, and it also violates the annotated `list[tuple[float, str, str]]`.\n\nExample:\n`run_id = data.get(\"run_id\", \"\")`\n\nFix: coerce/validate `run_id` to a `str` (and ideally decide how to order missing/invalid run_ids) before appending/sorting.\n\n---\n\n### Finding 2: [P3] Remove filename sort or add early-exit to actually improve efficiency\n\n**Priority:** P3\n**Reviewer:** codex\n**Location:** src/infra/io/log_output/run_metadata.py:727-734\n\n`json_files` is sorted by filename descending with a comment about leveraging timestamp prefixes for efficiency, but the function still parses *every* JSON file and then sorts candidates, so the filename ordering provides no benefit and adds extra `O(N log N)` overhead and a misleading comment.\n\nIf you want the efficiency win, keep the newest-first order and short-circuit once you find a valid match (with whatever tie-handling you need). Otherwise, drop the sort/comment to avoid extra work.\n\n---\n\n### Finding 3: [P2] lookup_prior_session still performs O(N) scan of all metadata files\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/infra/io/log_output/run_metadata.py:727-759\n\nThe implementation sorts `json_files` by filename descending but still iterates through every file in the directory to collect candidates. To address the performance concern in Finding 5 (which this change claims to address in the commit message), the loop should break early once a match is found and the current file's timestamp prefix is older than the match's timestamp.\n\n---\n\n### Finding 4: [P2] Potential TypeError crash in lookup_prior_session if run_id is null\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/infra/io/log_output/run_metadata.py:757\n\n`run_id = data.get(\"run_id\", \"\")` will result in `run_id` being `None` if the key exists in JSON with a null value. This will cause `candidates.sort()` to raise `TypeError` when comparing `None` to a string. Using `data.get(\"run_id\") or \"\"` (consistent with `logs.py`) would be safer.\n\n---\n\n\u003c!-- fp:7da4b7d834758229 --\u003e\n\u003c!-- fp:fd8926e65d16fcad --\u003e\n\u003c!-- fp:893965ce81042a7d --\u003e\n\u003c!-- fp:662705e2c261a2e9 --\u003e\n\u003c!-- review_finding:79a73626c676 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-06T15:58:08.001199082Z","created_by":"cyou","updated_at":"2026-01-06T16:04:16.065131878Z","closed_at":"2026-01-06T16:04:16.065131878Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-twoa.6"],"dependencies":[{"issue_id":"mala-twoa.7","depends_on_id":"mala-twoa","type":"parent-child","created_at":"2026-01-06T15:58:08.001647559Z","created_by":"cyou"}]}
{"id":"mala-twoa.8","title":"[Review] 2 non-blocking findings from mala-twoa.7","description":"## Review Findings\n\nThis issue consolidates 2 non-blocking findings from code review.\n\n**Source issue:** mala-twoa.7\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Early-exit optimization never triggers due to filename format mismatch\n\n**Priority:** P2\n**Reviewer:** claude\n**Location:** src/infra/io/log_output/run_metadata.py:741-747\n\nThe early-exit logic parses filenames with format `%Y%m%d_%H%M%S` (e.g., `20260106_160025`) but `save()` at line 492 creates files with format `%Y-%m-%dT%H-%M-%S` (e.g., `2026-01-06T16-00-25`). The `strptime` will always raise `ValueError`, fall through to `pass`, and continue scanning all files. The optimization claimed in the commit message does not actually work.\n\n---\n\n### Finding 2: [P2] Fix early-exit filename timestamp parsing\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/infra/io/log_output/run_metadata.py:736-745\n\n`lookup_prior_session` assumes filenames start with `YYYYMMDD_HHMMSS` and parses `name[:15]` with `%Y%m%d_%H%M%S`, but `RunMetadata.save()` writes metadata files with a prefix like `%Y-%m-%dT%H-%M-%S` (e.g. `2026-01-06T16-00-25_...json`). That means the `strptime()` almost always raises `ValueError`, so the loop never early-exits and the comments/commit message are misleading.\n\nAlso, if you update the parsing to match the real format, the break condition should compare at second granularity (filename prefix is seconds, `started_at` can include microseconds) to avoid incorrectly breaking when multiple runs start within the same second.\n\n```py\nfile_ts = datetime.strptime(name[:19], \"%Y-%m-%dT%H-%M-%S\").replace(tzinfo=UTC)\nif file_ts.timestamp() \u003c int(best[0]):\n```\n\n---\n\n\u003c!-- fp:512e56bb626c1158 --\u003e\n\u003c!-- fp:6e922a04d36fe56b --\u003e\n\u003c!-- review_finding:2783998c3d51 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-06T16:04:16.106682576Z","created_by":"cyou","updated_at":"2026-01-06T16:07:24.975256949Z","closed_at":"2026-01-06T16:07:24.975256949Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-twoa.7"],"dependencies":[{"issue_id":"mala-twoa.8","depends_on_id":"mala-twoa","type":"parent-child","created_at":"2026-01-06T16:04:16.107136254Z","created_by":"cyou"}]}
{"id":"mala-twoa.9","title":"[Review] 2 non-blocking findings from mala-twoa.8","description":"## Review Findings\n\nThis issue consolidates 2 non-blocking findings from code review.\n\n**Source issue:** mala-twoa.8\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Treat parsed filename timestamps as UTC\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/infra/io/log_output/run_metadata.py:743-747\n\n`datetime.strptime(...)` returns a naive datetime, and calling `.timestamp()` on it interprets it in the *local* timezone. Since `RunMetadata.save()` formats `started_at` (set via `datetime.now(UTC)`) into the filename without an offset, this comparison becomes timezone-dependent. In UTC+ locales, the early-exit can incorrectly `break` even while scanning files with the same second prefix, which can skip a newer run whose `started_at` differs only by microseconds.\n\nSuggested fix:\n```py\nfile_ts = datetime.strptime(name[:19], \"%Y-%m-%dT%H-%M-%S\").replace(tzinfo=UTC)\n```\nThen compare `file_ts.timestamp()` against `int(best[0])` as you do now.\n\n---\n\n### Finding 2: [P2] Missing UTC timezone in early-exit timestamp parsing\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/infra/io/log_output/run_metadata.py:744-747\n\nThe `file_ts` is parsed as a naive datetime object, which means `file_ts.timestamp()` uses the local system timezone. Since `best[0]` is a UTC timestamp, this mismatch makes the comparison incorrect on non-UTC systems. This can cause the early-exit optimization to either trigger too early (potentially missing the best session) or fail to trigger for recent files, which partially defeats the fix intended by this commit and previously identified in review.\n\n---\n\n\u003c!-- fp:f9e1e2bd9cb4b406 --\u003e\n\u003c!-- fp:fb487b917c8fa41d --\u003e\n\u003c!-- review_finding:09917a78720e --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-06T16:07:25.018315926Z","created_by":"cyou","updated_at":"2026-01-06T16:16:54.700700899Z","closed_at":"2026-01-06T16:16:54.700700899Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-twoa.8"],"dependencies":[{"issue_id":"mala-twoa.9","depends_on_id":"mala-twoa","type":"parent-child","created_at":"2026-01-06T16:07:25.018749274Z","created_by":"cyou"}]}
{"id":"mala-txpf","title":"[P1] _disable_fixture_e2e_config duplicates `e2e:` key lines","description":"## Review Finding\n\n**Priority:** P1\n**Trigger:** session_end\n**Baseline:** 14fb49c..3438772\n**Location:** src/domain/validation/e2e.py:391-409\n**Source Issue:** mala-ptio.1\n\n---\n\nWhen inside a top-level `commands:` block and encountering an existing `e2e:` line, the function appends a new `e2e: null` line but then also appends the original line after the `if in_commands` block (because it doesn’t suppress it unless it `continue`s). The code only `continue`s after writing `e2e: null`, but it already appended the replacement line and then still appends the original later in the loop when control reaches `output.append(line)`.\n\nConcrete outcome: the resulting `mala.yaml` will contain two `e2e:` keys at the same indentation, which is ambiguous/invalid in YAML (last one wins in most parsers) and defeats the purpose of deterministically forcing `null`.\n\nRelevant snippet:\n```py\nelif stripped.startswith(\"e2e:\"):\n    output.append(... + \"e2e: null\")\n    e2e_set = True\n    continue\n```\nThe `continue` avoids appending the original only for this branch, but the earlier `output.append(line)` at the end still happens for other paths; verify the control flow carefully—currently it appends both in practice for `e2e:` because the original `line` is still appended outside the `if in_commands` when the branch doesn’t `continue` early enough.","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-10T21:26:09.913089504Z","created_by":"cyou","updated_at":"2026-01-10T22:00:04.307235815Z","closed_at":"2026-01-10T22:00:04.307235815Z","close_reason":"Closed","labels":["auto_generated","cumulative-review","fp:58856819694b767d","review_finding","trigger:session_end"]}
{"id":"mala-u1w","title":"Test: tiny README change","status":"tombstone","priority":2,"issue_type":"task","created_at":"2025-12-25T19:05:18.089001881Z","updated_at":"2025-12-25T19:08:44.858511785Z","close_reason":"Closed","deleted_at":"2025-12-25T19:08:44.858511785Z","deleted_by":"daemon","delete_reason":"delete","original_type":"task"}
{"id":"mala-u8rm","title":"[Review] 6 non-blocking findings from mala-idx6.4","description":"## Review Findings\n\nThis issue consolidates 6 non-blocking findings from code review.\n\n**Source issue:** mala-idx6.4\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] is_baseline_stale doesn't pass cwd to injected runner\n\n**Priority:** P2\n**Reviewer:** claude\n**Location:** src/domain/validation/coverage.py:352-361\n\nWhen a `command_runner` is injected into `is_baseline_stale`, the git commands are run without an explicit `cwd` override, relying on the runner's construction-time cwd. If the injected runner was constructed with a different cwd (e.g., the `BaselineCoverageService.command_runner`), git commands like `git status --porcelain` will execute in the wrong directory. The helper functions in `helpers.py` consistently pass `cwd=repo_path` to `runner.run()`, but `is_baseline_stale` does not.\n\n---\n\n### Finding 2: [P2] Preserve repo_path when using injected runner\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/domain/validation/coverage.py:351-362\n\n`is_baseline_stale` still receives `repo_path`, but when a runner is injected it calls `run` without `cwd=repo_path`, so git runs in the runner’s default working directory. If callers pass a shared runner (or one configured for a different cwd), the baseline freshness check is evaluated against the wrong repo. Passing `cwd=repo_path` to these calls preserves the previous behavior.\n\n`dirty_result = runner.run([\"git\", \"status\", \"--porcelain\"])`\n`commit_time_result = runner.run([\"git\", \"log\", \"-1\", \"--format=%ct\"])`\n\n---\n\n### Finding 3: [P2] Injected runner ignores repo_path in is_baseline_stale\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/domain/validation/coverage.py:351-361\n\nIn `is_baseline_stale`, when a `command_runner` is injected, the code calls `runner.run(cmd)` without passing `cwd=repo_path`. This assumes the injected runner is already configured for the correct directory. If a shared runner (e.g., initialized with the main repo root) is passed, these git commands will run in the wrong location. The original code explicitly used `cwd=repo_path` with `run_command`.\n\n---\n\n### Finding 4: [P2] Injected runner ignores repo_path in helper functions\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/domain/validation/helpers.py:234-316\n\nIn `init_fixture_repo`, `get_ready_issue_id`, and `annotate_issue`, the code calls `runner.run(cmd)` without passing `cwd=repo_path` when a runner is injected. This is a regression from the original `run_command(cmd, cwd=repo_path)` calls and can lead to commands (like `git init` or `bd update`) executing in the wrong directory if a shared runner is provided.\n\n---\n\n### Finding 5: [P2] Injected runner ignored in BaselineCoverageService._capture_baseline_sync\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/domain/validation/coverage.py:622-624\n\nThe method creates a new `CommandRunner` instance instead of using `self.command_runner` (if provided). This defeats the purpose of dependency injection for this service and prevents testing with injected mock runners without resorting to global patching of the `CommandRunner` class. It should use `self.command_runner or CommandRunner(...)` and pass `cwd=worktree_path` to the `run()` call.\n\n---\n\n### Finding 6: [P3] Test mock runner functions are too restrictive\n\n**Priority:** P3\n**Reviewer:** gemini\n**Location:** tests/test_coverage.py:27-31\n\nThe `make_mock_runner` helper and `mock_run` functions in `tests/test_coverage.py` have signatures that only accept `args`, lacking `**kwargs`. This makes them incompatible with the `CommandRunnerPort.run` protocol and will cause tests to break if callers are updated to pass `cwd` or other optional parameters.\n\n---\n","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T08:22:23.567520008Z","created_by":"cyou","updated_at":"2026-01-03T08:29:47.255654494Z","closed_at":"2026-01-03T08:29:47.255654494Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_findings:00420e64299f","source:mala-idx6.4"]}
{"id":"mala-uaxr","title":"focus mode should order by epic priority","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-07T15:16:21.008298949Z","created_by":"cyou","updated_at":"2026-01-07T16:26:55.755367453Z","closed_at":"2026-01-07T16:26:55.755367453Z","close_reason":"Closed"}
{"id":"mala-ub6v","title":"[Review] src/pipeline/agent_session_runner.py:1005-1014: [P2] Remove redundant logic and duplication in `agent_session_runner.py`","description":"## Review Finding\n\nThis issue was auto-created from a P2 review finding.\n\n**Source issue:** mala-fhyl\n**Reviewer:** gemini\n**Location:** src/pipeline/agent_session_runner.py:1005-1014\n\n## Details\n\nThe `if cmds else` logic is redundant because `cmds` is guaranteed to be a non-None `PromptValidationCommands` instance (it is initialized with `or _get_default_validation_commands()` just above). Additionally, this change hardcodes default command strings that are already centralized in `src/domain/prompts.py`, which increases maintenance burden and risks future inconsistency if the defaults are changed again.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T00:42:21.566458804Z","created_by":"cyou","updated_at":"2026-01-03T00:48:02.942997666Z","closed_at":"2026-01-03T00:48:02.942997666Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_finding:4d3cd1ffb511","source:mala-fhyl"]}
{"id":"mala-uco3","title":"remove braintrust, absolutely everything, no trace left","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-05T01:14:18.098843209Z","created_by":"cyou","updated_at":"2026-01-06T00:18:08.544431679Z","closed_at":"2026-01-06T00:18:08.544431679Z","close_reason":"Closed"}
{"id":"mala-ui4o","title":"[Review] 6 non-blocking findings from mala-ifkl","description":"## Review Findings\n\nThis issue consolidates 6 non-blocking findings from code review.\n\n**Source issue:** mala-ifkl\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Avoid private typing API for protocol member enumeration\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** tests/contracts/__init__.py:11-34\n\n`get_protocol_members()` falls back to `typing._get_protocol_attrs`, which is a private API and can disappear/change across Python versions, breaking the contract suite for non-functional reasons. Prefer a stable public API (e.g., `typing.get_protocol_members` when available, otherwise `typing_extensions.get_protocol_members`) so the tests don’t depend on CPython internals.\n\n---\n\n### Finding 2: [P2] Don’t pass `spec=None` to `parse_validation_evidence_with_spec`\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** tests/contracts/test_gate_checker_contract.py:137-146\n\nThis contract test calls `parse_validation_evidence_with_spec(..., spec=None)` (with a type ignore), but the protocol requires a `ValidationSpecProtocol` and the fake’s signature also expects a non-optional spec. Passing `None` sidesteps the contract and can hide real breakages once the fake (or real impl) starts using spec-driven behavior. Build a minimal real `ValidationSpec` (e.g., `ValidationSpec(commands=[], scope=ValidationScope.PER_ISSUE)`) or a tiny stub that satisfies `ValidationSpecProtocol`, and pass that instead.\n\n---\n\n### Finding 3: [P2] Add call tracking to all FakeGateChecker methods\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** tests/fakes/gate_checker.py:98-105\n\nMethods `check_commit_exists` and `parse_validation_evidence_with_spec` in `FakeGateChecker` currently do not record their call arguments, unlike `check_with_resolution` and `check_no_progress`. This inconsistency makes it impossible for tests to verify that the orchestrator is passing the correct parameters to these gate methods.\n\n---\n\n### Finding 4: [P2] Record all input arguments in FakeEpicVerificationModel attempts\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** tests/fakes/epic_model.py:13-22\n\nThe `VerificationAttempt` in `FakeEpicVerificationModel` only records the `epic_id` and `verdict`, but loses other critical input arguments such as `commit_range`, `commit_list`, and `spec_content`. Tests verifying the verification logic should be able to assert that the correct commit range and list were provided to the model.\n\n---\n\n### Finding 5: [P2] Avoid private typing._get_protocol_attrs in contract tests\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** tests/contracts/test_command_runner_contract.py:18\n\nReliance on `typing._get_protocol_attrs` is fragile as it is a private internal API that may change or be removed in future Python versions, and it requires blanket type-check ignores. Consider using `isinstance(instance, ProtocolClass)` for `@runtime_checkable` protocols, or filtering `dir(ProtocolClass)` against known Protocol/ABC internal attributes to identify members safely.\n\n---\n\n### Finding 6: [P2] Track wait_for_lock parameters in FakeLockManager\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** tests/fakes/lock_manager.py:81-93\n\n`FakeLockManager.wait_for_lock` currently delegates to `try_lock`, losing its specific parameters like `timeout_seconds` and `poll_interval_ms`. To allow tests to verify that agents are using appropriate timeouts, these arguments should be recorded in the `acquire_calls` or a similar tracking list.\n\n---\n\n\u003c!-- fp:3f6a51d4071fc980 --\u003e\n\u003c!-- fp:188f41d8c0557c71 --\u003e\n\u003c!-- fp:a5ac0d31f404dc0b --\u003e\n\u003c!-- fp:86cb7e0564effb64 --\u003e\n\u003c!-- fp:ffde7cdd864302eb --\u003e\n\u003c!-- fp:4d43ac1e9e79b1f7 --\u003e\n\u003c!-- review_finding:905cdbb55f64 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-05T16:28:25.095652122Z","created_by":"cyou","updated_at":"2026-01-05T16:33:02.996081127Z","closed_at":"2026-01-05T16:33:02.996081127Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-ifkl"]}
{"id":"mala-up10","title":"Align context pressure ratio with spec or update spec","description":"Plan/spec uses pressure_ratio = input_tokens / limit, but code uses (input+output)/limit and tests enforce that. Decide intended behavior, then update code/tests/docs for consistency. Impact: restart timing.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T22:29:12.232870801Z","created_by":"cyou","updated_at":"2026-01-03T22:39:59.506447464Z","closed_at":"2026-01-03T22:39:59.506447464Z","close_reason":"Closed"}
{"id":"mala-uysj","title":"Split WiringDependencies into focused dataclasses","description":"Split the 27-field `WiringDependencies` dataclass into 3 focused, cohesive dataclasses:\n\n- **RuntimeDeps** (8 fields): Protocol implementations\n- **PipelineConfig** (11 fields): Pipeline execution configuration\n- **IssueFilterConfig** (8 fields): Issue selection criteria\n\n**Source**: Architecture review finding (Medium severity)\n\n**Plan**: `plans/2026-01-04-wiring-dependencies-split-plan.md`\n\n**Acceptance Criteria**:\n- [ ] All 27 fields distributed across 3 dataclasses\n- [ ] `repo_path` belongs in `PipelineConfig`\n- [ ] New dataclasses defined in `types.py`\n- [ ] No runtime behavior changes\n- [ ] 85% test coverage maintained","status":"closed","priority":2,"issue_type":"epic","created_at":"2026-01-04T21:14:17.971230215Z","created_by":"cyou","updated_at":"2026-01-05T00:02:26.334570889Z","closed_at":"2026-01-05T00:02:26.334570889Z","close_reason":"Closed"}
{"id":"mala-uysj.1","title":"[T001] Define RuntimeDeps, PipelineConfig, IssueFilterConfig in types.py","description":"## Goal\nAdd 3 new frozen dataclasses to `src/orchestration/types.py` that will replace the monolithic `WiringDependencies`.\n\n## Context\nArchitecture review identified `WiringDependencies` (27 fields) as a \"large parameter bag\" mixing runtime deps, config values, and filter flags. Splitting improves discoverability and makes build function signatures clearer.\n\n## Scope\n**In**: Define dataclasses in types.py\n**Out**: Wiring module changes (T002), test updates (T003)\n\n## Changes\n- `src/orchestration/types.py` — Exists — Add:\n  - `RuntimeDeps` (8 fields): quality_gate, code_reviewer, beads, event_sink, command_runner, env_config, lock_manager, mala_config\n  - `PipelineConfig` (11 fields): repo_path, timeout_seconds, max_gate_retries, max_review_retries, coverage_threshold, disabled_validations, context_restart_threshold, context_limit, prompts, prompt_validation_commands, deadlock_monitor\n  - `IssueFilterConfig` (8 fields): max_agents, max_issues, epic_id, only_ids, prioritize_wip, focus, orphans_only, epic_override_ids\n\n## Acceptance Criteria\n- [ ] All 3 dataclasses defined with `@dataclass(frozen=True)`\n- [ ] All 27 fields from WiringDependencies accounted for (8+11+8=27)\n- [ ] `repo_path` is in `PipelineConfig` (user decision)\n- [ ] Uses TYPE_CHECKING imports for protocol types to prevent import cycles\n\n## Verification\n```bash\nuvx ty check src/orchestration/types.py\n```\n\n## Notes for Agent\n- Follow existing `OrchestratorConfig`/`OrchestratorDependencies` pattern in same file\n- Use TYPE_CHECKING for: GateChecker, CodeReviewer, IssueProvider, MalaEventSink, CommandRunnerPort, EnvConfigPort, LockManagerPort, MalaConfig, PromptProvider, PromptValidationCommands, DeadlockMonitor\n- Fields with defaults should use `field(default_factory=...)` for mutable defaults","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-04T21:14:34.501681452Z","created_by":"cyou","updated_at":"2026-01-04T23:39:43.11203578Z","closed_at":"2026-01-04T23:39:43.11203578Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-uysj.1","depends_on_id":"mala-uysj","type":"parent-child","created_at":"2026-01-04T21:14:34.512565794Z","created_by":"daemon"}]}
{"id":"mala-uysj.2","title":"[T002] Update wiring module and orchestrator to use new dataclasses","description":"## Goal\nUpdate `orchestration_wiring.py` and `orchestrator.py` to use the new `RuntimeDeps`, `PipelineConfig`, `IssueFilterConfig` dataclasses instead of `WiringDependencies`.\n\n## Context\nT001 defined the new dataclasses. This task wires them into the build functions and orchestrator construction.\n\n## Scope\n**In**: Update function signatures, remove WiringDependencies, update orchestrator\n**Out**: Test updates (T003)\n\n## Changes\n- `src/orchestration/orchestration_wiring.py` — Exists —\n  - Remove `WiringDependencies` class definition\n  - Update imports to use types from `types.py`\n  - Update function signatures:\n    - `build_gate_runner(runtime: RuntimeDeps, pipeline: PipelineConfig)`\n    - `build_review_runner(runtime: RuntimeDeps, pipeline: PipelineConfig)`\n    - `build_run_coordinator(runtime: RuntimeDeps, pipeline: PipelineConfig, ...)`\n    - `build_issue_coordinator(filters: IssueFilterConfig, runtime: RuntimeDeps)`\n    - `build_session_callback_factory(runtime: RuntimeDeps, pipeline: PipelineConfig, ...)`\n    - `build_session_config(pipeline: PipelineConfig, ...)`\n  - Update function bodies to access fields from correct dataclass\n\n- `src/orchestration/orchestrator.py` — Exists —\n  - Update `_build_wiring_dependencies()` → `_build_wiring_configs()` returning tuple of 3 objects\n  - Or split into 3 methods: `_build_runtime_deps()`, `_build_pipeline_config()`, `_build_issue_filter_config()`\n  - Update `_init_pipeline_runners()` to pass correct objects to build functions\n\n## Acceptance Criteria\n- [ ] `WiringDependencies` class removed from orchestration_wiring.py\n- [ ] All 6 build functions use new signatures\n- [ ] Orchestrator constructs 3 separate config objects\n- [ ] No references to `WiringDependencies` remain in orchestration module\n- [ ] Type checker passes: `uvx ty check src/orchestration/`\n\n## Verification\n```bash\nuvx ty check src/orchestration/\nuv run pytest tests/unit/orchestration/test_orchestrator.py -x\n```\n\n## Notes for Agent\n- Field access changes: `deps.quality_gate` → `runtime.quality_gate`, `deps.timeout_seconds` → `pipeline.timeout_seconds`, etc.\n- For `build_run_coordinator`, also needs `sdk_client_factory` param (unchanged)\n- For `build_session_callback_factory`, keep existing callback params (unchanged)","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-04T21:14:49.965182607Z","created_by":"cyou","updated_at":"2026-01-04T23:48:19.090767748Z","closed_at":"2026-01-04T23:48:19.090767748Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-uysj.2","depends_on_id":"mala-uysj","type":"parent-child","created_at":"2026-01-04T21:14:49.977758595Z","created_by":"daemon"},{"issue_id":"mala-uysj.2","depends_on_id":"mala-uysj.1","type":"blocks","created_at":"2026-01-04T21:15:09.957540374Z","created_by":"daemon"}]}
{"id":"mala-uysj.3","title":"[T003] Update test fixtures and add wiring unit tests","description":"## Goal\nUpdate existing test fixtures that reference `WiringDependencies` and add unit tests for the new dataclasses and wiring functions.\n\n## Context\nT002 removed `WiringDependencies`. Test fixtures may construct it directly and need updates. New dataclasses need unit test coverage.\n\n## Scope\n**In**: Test fixture updates, new wiring tests\n**Out**: Implementation changes (done in T001, T002)\n\n## Changes\n- `tests/unit/orchestration/test_orchestrator.py` — Exists —\n  - Update any fixtures that construct `WiringDependencies` directly\n  - Replace with construction of `RuntimeDeps`, `PipelineConfig`, `IssueFilterConfig`\n\n- `tests/unit/orchestration/test_orchestration_wiring.py` — **New** —\n  - Test `RuntimeDeps` instantiation with valid values\n  - Test `PipelineConfig` instantiation with valid values\n  - Test `IssueFilterConfig` instantiation with valid values\n  - Test immutability (frozen=True) - attempting to modify raises FrozenInstanceError\n  - Test each build function with new signatures produces correct component\n\n## Acceptance Criteria\n- [ ] All existing tests in test_orchestrator.py pass\n- [ ] New test_orchestration_wiring.py created with dataclass tests\n- [ ] 85% coverage on src/orchestration/types.py (new dataclasses)\n- [ ] 85% coverage on src/orchestration/orchestration_wiring.py\n- [ ] Integration tests pass unchanged\n\n## Verification\n```bash\nuv run pytest tests/unit/orchestration/ -v --cov=src/orchestration --cov-report=term-missing\nuv run pytest -m integration -k orchestrat\n```\n\n## Notes for Agent\n- [integration-path-test] This task verifies the full construction path works\n- Search for `WiringDependencies` in test files to find fixtures needing updates\n- Frozen dataclass raises `dataclasses.FrozenInstanceError` on mutation\n- Use `pytest.raises(FrozenInstanceError)` to test immutability","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-04T21:15:04.806635277Z","created_by":"cyou","updated_at":"2026-01-05T00:00:47.746040323Z","closed_at":"2026-01-05T00:00:47.746040323Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-uysj.3","depends_on_id":"mala-uysj","type":"parent-child","created_at":"2026-01-04T21:15:04.818863598Z","created_by":"daemon"},{"issue_id":"mala-uysj.3","depends_on_id":"mala-uysj.2","type":"blocks","created_at":"2026-01-04T21:15:09.973618441Z","created_by":"daemon"}]}
{"id":"mala-uysj.4","title":"[Review] 2 non-blocking findings from mala-uysj.1","description":"## Review Findings\n\nThis issue consolidates 2 non-blocking findings from code review.\n\n**Source issue:** mala-uysj.1\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Make epic_override_ids non-optional (default empty set)\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/orchestration/types.py:210-214\n\n`IssueFilterConfig.epic_override_ids` is currently `set[str] | None = None`, but the existing `WiringDependencies.epic_override_ids` is a required `set[str]` (`src/orchestration/orchestration_wiring.py:103`). When the wiring split happens, allowing `None` here will force extra `None` checks or can break any code that assumes set operations/iteration.\n\nSuggested shape: `epic_override_ids: set[str] = field(default_factory=set)` (and add `field` import).\n\n---\n\n### Finding 2: [P3] Update module docstring design principles to include new dataclasses\n\n**Priority:** P3\n**Reviewer:** codex\n**Location:** src/orchestration/types.py:6-10\n\nThe top-of-file docstring still claims the module’s design principles are only `OrchestratorConfig`, `OrchestratorDependencies`, and `_DerivedConfig`, but this commit adds `RuntimeDeps`, `PipelineConfig`, and `IssueFilterConfig`. This can mislead future readers about what belongs in this module and why.\n\n---\n\n\u003c!-- fp:ddd126d7916e2f53 --\u003e\n\u003c!-- fp:9985e1de4eeddaba --\u003e\n\u003c!-- review_finding:e17760fa17ee --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-04T23:39:43.190419489Z","created_by":"cyou","updated_at":"2026-01-04T23:50:53.526361169Z","closed_at":"2026-01-04T23:50:53.526361169Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-uysj.1"],"dependencies":[{"issue_id":"mala-uysj.4","depends_on_id":"mala-uysj","type":"parent-child","created_at":"2026-01-04T23:39:43.190875696Z","created_by":"daemon"}]}
{"id":"mala-uysj.5","title":"[Review] 1 non-blocking finding from mala-uysj.4","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-uysj.4\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Don’t pass None to non-optional epic_override_ids\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/cli/cli.py:749-766\n\n`OrchestratorConfig.epic_override_ids` is now `set[str]` with a default empty set, but the CLI still passes `epic_override_ids` directly, which can be `None` when `--epic-override` isn’t provided. This reintroduces the very `None` state the change is trying to eliminate, and any future code that assumes set operations/iteration on `orch_config.epic_override_ids` could break. Coerce at construction time (e.g., `epic_override_ids=epic_override_ids or set()`) or make `ValidatedRunArgs.epic_override_ids` always a `set[str]`.\n\n---\n\n\u003c!-- fp:652146db1669349a --\u003e\n\u003c!-- review_finding:9b7c6564d648 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-04T23:50:53.599295574Z","created_by":"cyou","updated_at":"2026-01-04T23:55:04.464044003Z","closed_at":"2026-01-04T23:55:04.464044003Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-uysj.4"],"dependencies":[{"issue_id":"mala-uysj.5","depends_on_id":"mala-uysj","type":"parent-child","created_at":"2026-01-04T23:50:53.5997488Z","created_by":"daemon"}]}
{"id":"mala-v250","title":"refactor prioritization into discrete modes","status":"closed","priority":2,"issue_type":"feature","created_at":"2026-01-04T03:04:17.823078189Z","created_by":"cyou","updated_at":"2026-01-05T02:37:46.753746037Z","closed_at":"2026-01-05T02:37:46.753746037Z","close_reason":"Closed"}
{"id":"mala-v2w5","title":"Eliminate remaining patch/MagicMock usage in unit tests","description":"## Problem\\nUnit tests still rely on patch() and MagicMock in several files, contrary to the testing philosophy and the T013 acceptance criteria. This keeps tests coupled to implementation details and undermines the mocks-to-fakes migration. Current hotspots include test_validation.py, test_idle_retry_policy.py, and test_locking_mcp.py, plus scattered MagicMock usage.\\n\\n## Scope\\n- Remove remaining patch() usage in tests/unit where feasible by injecting fakes or refactoring tests to assert on behavior.\\n- Replace MagicMock/AsyncMock with fakes where a protocol fake exists (FakeCommandRunner, FakeGateChecker, FakeIssueProvider, FakeSDKClient, FakeLockManager).\\n- If MagicMock must remain (e.g., SDK internals with no fake), document the exception in tests/README.md with rationale.\\n\\n## Acceptance Criteria\\n- rg -n 'with patch|@patch' tests/unit returns empty or only documented exceptions.\\n- Remaining MagicMock/AsyncMock usage in tests/unit is minimized and justified in tests/README.md.\\n- No tests rely on call-count assertions (prefer observable state).\\n\\n## Files (likely)\\n- tests/unit/domain/test_validation.py\\n- tests/unit/pipeline/test_idle_retry_policy.py\\n- tests/unit/infra/tools/test_locking_mcp.py\\n- tests/unit/infra/test_cerberus_* (if applicable)\\n- tests/README.md (exceptions list)\\n\\n## Verification\\n- rg -n 'with patch|@patch' tests/unit\\n- rg -n 'MagicMock|AsyncMock' tests/unit\\n- uv run pytest -m unit","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-05T14:17:59.536711426Z","created_by":"cyou","updated_at":"2026-01-05T16:25:30.80037629Z","closed_at":"2026-01-05T16:25:30.80037629Z","close_reason":"Closed"}
{"id":"mala-v77e","title":"[Review] 2 non-blocking findings from mala-kyq1.1","description":"## Review Findings\n\nThis issue consolidates 2 non-blocking findings from code review.\n\n**Source issue:** mala-kyq1.1\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Critical verdict mismatch warning is swallowed when event_sink is None\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/infra/clients/cerberus_review.py:586-597\n\nIn `map_exit_code_to_result`, the warning about exit code and JSON verdict disagreement is only emitted if `event_sink` is provided. This is a critical diagnostic for the \"fail-closed\" security logic. If the client is used without a sink (e.g., in standalone tools or certain tests), this visibility is lost. Consider falling back to the original direct `log` call if `event_sink` is `None` to ensure this mismatch is always reported.\n\n---\n\n### Finding 2: [P3] MockEventSink should inherit from BaseEventSink\n\n**Priority:** P3\n**Reviewer:** gemini\n**Location:** tests/test_cerberus_review.py:33-46\n\nIn `tests/test_cerberus_review.py`, `MockEventSink` is defined from scratch and cast to `MalaEventSink`. It is safer and more idiomatic in this codebase to inherit from `BaseEventSink`. This provides no-op implementations for the rest of the 40+ methods in the `MalaEventSink` protocol, preventing potential `AttributeError` if the code being tested is updated to emit additional events.\n\n---\n","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T07:08:12.93755031Z","created_by":"cyou","updated_at":"2026-01-03T07:12:13.207741567Z","closed_at":"2026-01-03T07:12:13.207741567Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_findings:380cef83ccde","source:mala-kyq1.1"]}
{"id":"mala-vdf","title":"Offload bd/git calls from async loop","description":"Context:\n- Orchestrator uses blocking subprocess calls via BeadsClient inside the async loop, which can stall other agent tasks.\n- Needs verification: confirm bd/git commands can hang long enough to impact agent receive loops.\n\nScope:\n- In: move bd/git calls used in the orchestrator loop to async-friendly execution (asyncio.to_thread or async subprocess) with timeouts; update orchestrator to await these calls.\n- Out: replacing the bd CLI or changing issue selection logic.\n\nAcceptance Criteria:\n- Slow or hanging bd commands do not block other agent tasks.\n- Timeouts are handled with clear warnings and safe fallback behavior.\n\nTest Plan:\n- TDD: add a test that simulates a slow bd command, implement async/offload behavior, then run orchestrator tests.\n\nNotes for Agent:\n- Keep the public BeadsClient API consistent unless updating call sites in orchestrator.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-26T17:35:56.818359351Z","updated_at":"2025-12-26T18:58:44.392606409Z","closed_at":"2025-12-26T18:58:44.392606409Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-vdf","depends_on_id":"mala-jnq","type":"parent-child","created_at":"2025-12-26T17:36:15.723438514Z","created_by":"daemon"},{"issue_id":"mala-vdf","depends_on_id":"mala-dqp","type":"blocks","created_at":"2025-12-26T17:36:21.612596465Z","created_by":"daemon"}]}
{"id":"mala-ved","title":"Ensure logs are written incrementally during execution","description":"Context:\n- Logs should be flushed/written as events happen, not buffered until exit.\n- Currently if mala is interrupted or crashes, log data may be lost.\n\nScope:\n- In: review log writing code paths; ensure flush after each log entry or batch.\n- Out: log format changes; new log destinations.\n\nAcceptance Criteria:\n- JSONL logs are written incrementally (fsync or flush after writes).\n- Interrupting mala mid-run preserves all logs up to that point.\n- No significant performance regression from flush frequency.\n\nTest Plan:\n- Manual: start a run, kill -9 mid-execution, verify logs are complete up to kill point.\n- Unit: mock file ops to verify flush calls.","status":"closed","priority":1,"issue_type":"bug","created_at":"2025-12-27T01:09:55.08491113Z","updated_at":"2025-12-31T18:02:19.511745246Z","closed_at":"2025-12-31T18:02:19.511745246Z","close_reason":"Closed"}
{"id":"mala-vin5","title":"Propagate context_restart_threshold and context_limit to AgentSessionConfig","description":"OrchestratorConfig defines context_restart_threshold/context_limit but build_session_config never passes them into AgentSessionConfig, so overrides are ignored and defaults always used. Wire deps/context values into AgentSessionConfig so run_session honors configured limits.","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-03T22:19:14.29235722Z","created_by":"cyou","updated_at":"2026-01-03T22:37:59.265498495Z","closed_at":"2026-01-03T22:37:59.265498495Z","close_reason":"Closed"}
{"id":"mala-vjb9","title":"in the status command, show all locks, and all filenames, group by agent","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T19:51:09.541994062Z","created_by":"cyou","updated_at":"2026-01-03T21:11:18.818304541Z","closed_at":"2026-01-03T21:11:18.818304541Z","close_reason":"Closed"}
{"id":"mala-vjoh","title":"run level validations after epic close","status":"tombstone","priority":2,"issue_type":"feature","created_at":"2026-01-06T05:20:46.369242629Z","created_by":"cyou","updated_at":"2026-01-06T22:52:10.761723406Z","deleted_at":"2026-01-06T22:52:10.761723406Z","deleted_by":"batch delete","delete_reason":"batch delete","original_type":"feature"}
{"id":"mala-vk19","title":"Epic: Run Lifecycle Restructure","description":"Move session_end trigger from once-at-run-end to per-issue (after gate, before review). Merge global_validation and run_end into unified run-level stage. Remove RunCoordinator.run_validation() and reject legacy global_validation_commands key.\n\n**Spec:** `/home/cyou/mala/docs/2026-01-10-run-lifecycle-restructure-spec.md`\n**Plan:** `/home/cyou/mala/plans/2026-01-10-run-lifecycle-restructure-plan.md`\n\n## Requirements (R1-R12)\n- R1: Per-issue session_end placement (after gate pass, before review)\n- R2: Skip session_end on gate failure\n- R3: Unified run-level stage (merge global_validation into run_end)\n- R4: Reject legacy global_validation_commands key with migration error\n- R5: SessionEndResult evidence for review\n- R6: Allow review-only run_end (empty commands OK)\n- R7: run_end fire_on behavior\n- R8: Remove RunCoordinator.run_validation\n- R9: Session_end failure_mode and remediation\n- R10: Session_end timeout/interrupt behavior\n- R11: Session_end code review baseline (issue.base_sha)\n- R12: run_end failure_mode gating for code review\n\n## Phases\n1. Run-level consolidation (remove global_validation, reject legacy config)\n2. Core infrastructure (SessionEndResult, SessionEndRetryState, FixerInterface, IssueResult fields)\n3. Per-issue session_end execution (lifecycle states, callbacks, effect handling)\n4. Review integration (pass SessionEndResult evidence to review)\n5. Testing \u0026 validation (unit, integration, scenario tests)","status":"closed","priority":1,"issue_type":"epic","created_at":"2026-01-10T05:49:23.134241036Z","created_by":"cyou","updated_at":"2026-01-10T19:52:43.603043148Z","closed_at":"2026-01-10T19:52:43.603043148Z","close_reason":"Closed"}
{"id":"mala-vk19.1","title":"[T001] Reject global_validation_commands config key","description":"**Type**: task\n**Priority**: P1\n**Phase**: 1 (Run-level consolidation)\n**Primary Files**: src/domain/validation/config_loader.py, tests/unit/domain/validation/test_config_loader.py\n**Subsystems**: config\n\n## Goal\nAdd validation that hard-rejects the deprecated `global_validation_commands` config key with a migration error message showing example YAML for `run_end.commands`.\n\n## Context\n- Per spec R4: Config must fail validation with example migration if `global_validation_commands` key is present\n- Error must be surfaced in CLI output AND logged\n- Migration snippet must be included in error message\n\n## Scope\n- **In**: Add validation check in config_loader.py; add unit test for error format\n- **Out**: Removing existing global_validation logic (separate task)\n\n## Changes\n- `src/domain/validation/config_loader.py` — [Exists] — Add check for `global_validation_commands` key; raise ConfigValidationError with migration example\n- `tests/unit/domain/validation/test_config_loader.py` — [Exists] — Add test for rejection with correct error message format\n\n## Acceptance Criteria\n- Given a config that includes `global_validation_commands`, when config validation runs, validation fails\n- Error message includes example YAML snippet showing `validation_triggers.run_end.commands`\n- Error message is returned to CLI and logged\n\n## Verification\n- Unit test: config with `global_validation_commands` key raises ConfigValidationError\n- Unit test: error message contains \"validation_triggers\", \"run_end\", \"commands\"\n- Unit test: error message contains example YAML structure\n\n## Notes for Agent\n- Check existing ConfigValidationError patterns in config_loader.py\n- The example migration YAML snippet is in the plan:\n```yaml\nvalidation_triggers:\n  run_end:\n    failure_mode: continue\n    commands:\n      - ref: test\n      - ref: lint\n```","notes":"Quality gate failed: External review failed: internal_error\nLog: /home/cyou/.claude/projects/-home-cyou-mala/7c119ab8-6d06-4836-bc5f-d2cf346591ba.jsonl","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-10T05:49:58.789250773Z","created_by":"cyou","updated_at":"2026-01-10T06:55:04.021159632Z","closed_at":"2026-01-10T06:55:04.021159632Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-vk19.1","depends_on_id":"mala-vk19","type":"parent-child","created_at":"2026-01-10T05:49:58.789904008Z","created_by":"cyou"}]}
{"id":"mala-vk19.10","title":"[T010] [integration-path-test] Add Effect.RUN_SESSION_END handling + integration test","description":"**Type**: task\n**Priority**: P1\n**Phase**: 3 (Per-issue session_end execution)\n**Primary Files**: src/pipeline/agent_session_runner.py, src/pipeline/lifecycle_effect_handler.py, tests/integration/test_run_lifecycle_restructure.py (New)\n**Subsystems**: pipeline\n\n## Goal\nAdd Effect.RUN_SESSION_END handling to agent_session_runner._run_lifecycle_loop() and create a failing integration test that verifies the end-to-end flow.\n\n## Context\n- Per plan: session_end executes in agent_session_runner after gate passes\n- This is the skeleton + integration test task for the session_end feature\n- Integration test should fail initially (session_end not yet implemented)\n\n## Scope\n- **In**: Add effect handling skeleton in lifecycle loop; add lifecycle effect handler; create failing integration test\n- **Out**: Full callback implementation (T011), remediation (T012)\n\n## Changes\n- `src/pipeline/agent_session_runner.py` — [Exists] — Add Effect.RUN_SESSION_END case in _run_lifecycle_loop()\n- `src/pipeline/lifecycle_effect_handler.py` — [Exists] — Add session_end effect handling\n- `tests/integration/test_run_lifecycle_restructure.py` — [New] — Integration test for gate → session_end → review flow\n\n## Acceptance Criteria\n- Effect.RUN_SESSION_END is handled in lifecycle loop\n- Lifecycle calls on_session_end_check callback when effect fires\n- Integration test exercises: issue creation → agent work → gate pass → session_end → review\n- Integration test verifies log ordering: session_end started after gate_passed, before review_start\n\n## Verification\n- Integration test: Full lifecycle flow executes through session_end\n- Integration test: Log ordering verified (session_end between gate and review)\n- Unit test: Effect handler produces correct state transition\n\n## Notes for Agent\n- Reference Effect.RUN_GATE handling pattern in agent_session_runner\n- Integration test should use realistic fixtures (not mocks)\n- Test should verify `[trigger] session_end started` log appears in correct position\n- This test will initially fail on the \"session_end completed\" assertion until T011 implements the callback","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-10T05:52:44.922099259Z","created_by":"cyou","updated_at":"2026-01-10T07:40:32.503276431Z","closed_at":"2026-01-10T07:40:32.503276431Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-vk19.10","depends_on_id":"mala-vk19","type":"parent-child","created_at":"2026-01-10T05:52:44.922843954Z","created_by":"cyou"},{"issue_id":"mala-vk19.10","depends_on_id":"mala-vk19.7","type":"blocks","created_at":"2026-01-10T05:55:47.874332729Z","created_by":"cyou"},{"issue_id":"mala-vk19.10","depends_on_id":"mala-vk19.8","type":"blocks","created_at":"2026-01-10T05:55:47.978108951Z","created_by":"cyou"},{"issue_id":"mala-vk19.10","depends_on_id":"mala-vk19.9","type":"blocks","created_at":"2026-01-10T05:55:48.085589737Z","created_by":"cyou"}]}
{"id":"mala-vk19.11","title":"[T011] Implement session_end callback (commands, timeout, code_review)","description":"**Type**: task\n**Priority**: P1\n**Phase**: 3 (Per-issue session_end execution)\n**Primary Files**: src/pipeline/session_callback_factory.py, tests/unit/pipeline/test_session_end_callback.py (New)\n**Subsystems**: pipeline\n\n## Goal\nImplement the full session_end callback: command execution with asyncio.timeout, optional code_review integration, and proper result creation.\n\n## Context\n- Per spec R9: session_end supports failure_mode (abort, continue, remediate)\n- Per spec R10: timeout wraps entire session_end execution\n- Per spec R11: code_review uses base_sha..HEAD range\n- This task covers continue/abort modes; remediate is in T012\n\n## Scope\n- **In**: Command execution; asyncio.timeout wrapper; code_review integration; failure_mode handling (abort, continue)\n- **Out**: Remediation loop (T012)\n\n## Changes\n- `src/pipeline/session_callback_factory.py` — [Exists] — Implement full callback logic\n- `tests/unit/pipeline/test_session_end_callback.py` — [New] — Unit tests for callback behavior\n\n## Wiring Map\n- SessionEndTriggerConfig → LifecycleContext → callback\n- base_sha from IssueResult → CumulativeReviewRunner (for code_review)\n- abort_event signal → run abort handling\n\n## Acceptance Criteria\n- Commands execute sequentially with individual timeouts\n- Overall session_end has asyncio.timeout wrapper\n- On timeout: return SessionEndResult(status=\"timeout\", commands=[], reason=\"session_end_timeout\")\n- On SIGINT: complete current command, return status=\"interrupted\"\n- If all commands pass and code_review enabled, run CumulativeReviewRunner\n- failure_mode=abort sets abort_event and returns immediately\n- failure_mode=continue returns result and proceeds\n\n## Verification\n- Unit test: Commands execute in sequence\n- Unit test: Timeout returns correct result with empty commands\n- Unit test: SIGINT returns result with completed commands\n- Unit test: code_review runs after commands pass\n- Unit test: failure_mode=abort sets abort flag\n- Unit test: failure_mode=continue proceeds regardless of outcome\n- Config override test: non-default timeout value reaches runtime\n\n## Notes for Agent\n- Use asyncio.timeout() for overall timeout\n- Reference CumulativeReviewRunner for code_review integration\n- base_sha comes from IssueResult, passed through context\n- Per spec R10: partial results discarded on timeout (available in logs only)","notes":"Quality gate failed: Quality gate failed: Missing validation evidence for: lint, test, typecheck, format\nLog: /home/cyou/.claude/projects/-home-cyou-mala/72695750-3449-49e1-b59a-b990b5c1b603.jsonl","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-10T05:53:03.051515379Z","created_by":"cyou","updated_at":"2026-01-10T18:03:24.248192981Z","closed_at":"2026-01-10T18:03:24.248192981Z","close_reason":"Implementation complete. SIGINT handling issue from review moved to follow-up task vk19.20.","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-vk19.11","depends_on_id":"mala-vk19","type":"parent-child","created_at":"2026-01-10T05:53:03.052282563Z","created_by":"cyou"},{"issue_id":"mala-vk19.11","depends_on_id":"mala-vk19.10","type":"blocks","created_at":"2026-01-10T05:55:49.412888662Z","created_by":"cyou"},{"issue_id":"mala-vk19.11","depends_on_id":"mala-vk19.4","type":"blocks","created_at":"2026-01-10T05:55:49.523501405Z","created_by":"cyou"},{"issue_id":"mala-vk19.11","depends_on_id":"mala-vk19.5","type":"blocks","created_at":"2026-01-10T05:55:49.628524369Z","created_by":"cyou"}]}
{"id":"mala-vk19.12","title":"[T012] Implement session_end remediation loop","description":"**Type**: task\n**Priority**: P1\n**Phase**: 3 (Per-issue session_end execution)\n**Primary Files**: src/pipeline/session_callback_factory.py, tests/unit/pipeline/test_session_end_remediation.py (New)\n**Subsystems**: pipeline\n\n## Goal\nImplement the remediation loop for session_end when failure_mode=remediate, using FixerInterface.\n\n## Context\n- Per spec R9: remediation runs up to 1 + max_retries validation attempts\n- Per spec: fixer runs before each retry (not before attempt 1)\n- Code_review runs once after final outcome (pass or exhausted)\n- Remediation blocks only current issue's review, not other issues\n\n## Scope\n- **In**: Remediation loop logic; FixerInterface integration; retry accounting\n- **Out**: Basic callback (T011)\n\n## Changes\n- `src/pipeline/session_callback_factory.py` — [Exists] — Add remediation loop to callback\n- `tests/unit/pipeline/test_session_end_remediation.py` — [New] — Unit tests for remediation logic\n\n## Acceptance Criteria\n- failure_mode=remediate triggers remediation loop on failure\n- Fixer runs before each retry (not before initial attempt)\n- Total validation attempts = 1 + max_retries\n- On exhausted retries: return SessionEndResult(status=\"fail\", reason=\"max_retries_exhausted\")\n- On successful retry: return SessionEndResult(status=\"pass\")\n- code_review runs exactly once after final outcome\n- Remediation uses SessionEndRetryState for tracking\n\n## Verification\n- Unit test: With max_retries=2, fixer runs twice on consecutive failures\n- Unit test: Successful retry stops the loop and returns pass\n- Unit test: Exhausted retries returns fail with correct reason\n- Unit test: code_review runs after exhausted retries\n- Unit test: code_review runs after successful retry\n\n## Notes for Agent\n- FixerInterface.run_fixer(failure_output, issue_id) is called for each retry\n- Update SessionEndRetryState.attempt after each fixer run\n- Example with max_retries=2:\n  1. Attempt 1 fails → fixer runs\n  2. Attempt 2 fails → fixer runs\n  3. Attempt 3 fails → exhausted, status=fail\n- Check existing gate remediation pattern for reference","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-10T05:53:18.395986713Z","created_by":"cyou","updated_at":"2026-01-10T18:55:22.698996095Z","closed_at":"2026-01-10T18:55:22.698996095Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-vk19.12","depends_on_id":"mala-vk19","type":"parent-child","created_at":"2026-01-10T05:53:18.396678988Z","created_by":"cyou"},{"issue_id":"mala-vk19.12","depends_on_id":"mala-vk19.11","type":"blocks","created_at":"2026-01-10T05:55:58.137459328Z","created_by":"cyou"}]}
{"id":"mala-vk19.13","title":"[T013] Capture base_sha at workspace checkout","description":"**Type**: task\n**Priority**: P1\n**Phase**: 3 (Per-issue session_end execution)\n**Primary Files**: src/orchestration/orchestrator.py, tests/unit/orchestration/test_orchestrator.py\n**Subsystems**: orchestration\n\n## Goal\nCapture `issue.base_sha` at workspace checkout (before agent work) and store it in IssueResult.\n\n## Context\n- Per spec R11: base_sha is HEAD commit after workspace checkout, before any agent writes\n- Per plan: Each issue runs in its own worktree; base_sha captured per-issue\n- base_sha is immutable across retries/remediation for that issue\n\n## Scope\n- **In**: Capture base_sha at checkout; store in IssueResult\n- **Out**: Using base_sha in code_review (wired in T011)\n\n## Changes\n- `src/orchestration/orchestrator.py` — [Exists] — Capture base_sha after workspace checkout, before run_implementer\n- `tests/unit/orchestration/test_orchestrator.py` — [Exists] — Test base_sha capture\n\n## Wiring Map\n- git.get_head_sha() at checkout → IssueResult.base_sha → session_end callback → CumulativeReviewRunner\n\n## Acceptance Criteria\n- base_sha is captured after workspace checkout, before agent work starts\n- base_sha is stored in IssueResult\n- base_sha is immutable (not updated on retries)\n- base_sha is available to session_end callback\n\n## Verification\n- Unit test: IssueResult.base_sha is populated after checkout\n- Unit test: base_sha matches HEAD at checkout time\n- Unit test: base_sha does not change during remediation\n- Integration test: session_end code_review uses correct base_sha\n\n## Notes for Agent\n- Look for workspace checkout in orchestrator.run_implementer()\n- Use git client to get HEAD sha (check existing git utilities)\n- IssueResult is created before agent work; add base_sha at that point\n- Ensure base_sha is passed through to LifecycleContext","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-10T05:53:32.960375524Z","created_by":"cyou","updated_at":"2026-01-10T15:50:16.506464388Z","closed_at":"2026-01-10T15:50:16.506464388Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-vk19.13","depends_on_id":"mala-vk19","type":"parent-child","created_at":"2026-01-10T05:53:32.961086108Z","created_by":"cyou"},{"issue_id":"mala-vk19.13","depends_on_id":"mala-vk19.6","type":"blocks","created_at":"2026-01-10T05:55:58.244447478Z","created_by":"cyou"},{"issue_id":"mala-vk19.13","depends_on_id":"mala-vk19.3","type":"blocks","created_at":"2026-01-10T06:01:39.089365437Z","created_by":"cyou"}]}
{"id":"mala-vk19.14","title":"[T014] Wire abort_event for run abort handling","description":"**Type**: task\n**Priority**: P1\n**Phase**: 3 (Per-issue session_end execution)\n**Primary Files**: src/pipeline/run_coordinator.py, src/pipeline/session_callback_factory.py, src/pipeline/agent_session_runner.py\n**Subsystems**: pipeline\n\n## Goal\nWire the `abort_event: asyncio.Event` for run abort handling per the Run Abort Contract.\n\n## Context\n- Per plan: When session_end failure_mode=abort triggers, callback sets shared abort_event\n- In-flight issues check abort_event between stages\n- Pending issues are skipped; run_end is skipped with reason=run_aborted\n\n## Scope\n- **In**: Create abort_event in coordinator; pass to callbacks; check in lifecycle loop\n- **Out**: Testing abort scenarios (Phase 5)\n\n## Changes\n- `src/pipeline/run_coordinator.py` — [Exists] — Add abort_event field; expose to callbacks\n- `src/pipeline/session_callback_factory.py` — [Exists] — Accept abort_event; set on abort\n- `src/pipeline/agent_session_runner.py` — [Exists] — Check abort_event between stages\n\n## Wiring Map\n- RunCoordinator.abort_event → SessionCallbackFactory → session_end callback\n- abort_event checked in lifecycle loop between stages\n\n## Acceptance Criteria\n- abort_event is created in RunCoordinator\n- session_end callback can set abort_event when failure_mode=abort\n- Lifecycle loop checks abort_event between stages\n- In-flight issues complete current command, then finalize as interrupted\n- run_end skipped when abort_event is set\n\n## Verification\n- Unit test: abort_event is set when session_end aborts\n- Unit test: Lifecycle loop detects abort_event\n- Unit test: run_end is skipped when abort_event is set\n- Integration test: Run abort scenario (in Phase 5)\n\n## Notes for Agent\n- abort_event = asyncio.Event() in RunCoordinator.__init__\n- Pass abort_event to SessionCallbackFactory\n- In lifecycle loop, check abort_event.is_set() before each major stage\n- On abort detection: complete current command, finalize as interrupted","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-10T05:53:50.348408524Z","created_by":"cyou","updated_at":"2026-01-10T19:12:54.223571584Z","closed_at":"2026-01-10T19:12:54.223571584Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-vk19.14","depends_on_id":"mala-vk19","type":"parent-child","created_at":"2026-01-10T05:53:50.349121409Z","created_by":"cyou"},{"issue_id":"mala-vk19.14","depends_on_id":"mala-vk19.11","type":"blocks","created_at":"2026-01-10T05:55:58.35386714Z","created_by":"cyou"},{"issue_id":"mala-vk19.14","depends_on_id":"mala-vk19.10","type":"blocks","created_at":"2026-01-10T06:01:39.199303675Z","created_by":"cyou"},{"issue_id":"mala-vk19.14","depends_on_id":"mala-vk19.12","type":"blocks","created_at":"2026-01-10T06:01:39.3063014Z","created_by":"cyou"},{"issue_id":"mala-vk19.14","depends_on_id":"mala-vk19.2","type":"blocks","created_at":"2026-01-10T06:01:39.412474232Z","created_by":"cyou"}]}
{"id":"mala-vk19.15","title":"[T015] Pass SessionEndResult evidence to review context","description":"**Type**: task\n**Priority**: P1\n**Phase**: 4 (Review integration)\n**Primary Files**: src/pipeline/review_runner.py, tests/unit/pipeline/test_review_runner.py\n**Subsystems**: pipeline\n\n## Goal\nPass `SessionEndResult` to review context as informational evidence. Review does NOT auto-fail on session_end failure.\n\n## Context\n- Per spec R5: SessionEndResult is informational for review\n- Review surfaces results in evidence bundle for reviewer prompt\n- Review proceeds regardless of session_end outcome (does not auto-reject)\n\n## Scope\n- **In**: Add session_end_result to review input; update review prompt; verify no auto-fail\n- **Out**: SessionEndResult creation (already in T011)\n\n## Changes\n- `src/pipeline/review_runner.py` — [Exists] — Accept session_end_result in review context; include in prompt\n- `tests/unit/pipeline/test_review_runner.py` — [Exists] — Test evidence passing\n\n## Wiring Map\n- IssueResult.session_end_result → review input context → review prompt\n\n## Acceptance Criteria\n- Review context accepts session_end_result parameter\n- Review prompt includes session_end evidence when available\n- Review does NOT auto-fail when session_end.status=fail\n- Review proceeds normally with session_end evidence\n- Review works correctly when session_end_result is None (skipped case)\n\n## R5 Verification\n- SessionEndResult with status=fail → review receives it, does not auto-reject\n- SessionEndResult with status=pass → review receives it as evidence\n- SessionEndResult with status=skipped → review proceeds without evidence (no error)\n\n## Verification\n- Unit test: Review receives SessionEndResult in context\n- Unit test: Review prompt contains session_end evidence when present\n- Unit test: Review with session_end.status=fail does not auto-reject\n- Unit test: Review with session_end_result=None proceeds normally\n- Integration test: End-to-end session_end → review evidence passing\n\n## Notes for Agent\n- SessionEndResult is already in IssueResult (from T006, T011)\n- Review prompt should format session_end evidence clearly\n- Check existing evidence passing patterns in review_runner\n- Per spec: \"review MUST NOT auto-fail solely due to session_end.status=fail\"","notes":"Quality gate failed: Killed as deadlock victim (will retry after blocker completes)","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-10T05:54:16.281026205Z","created_by":"cyou","updated_at":"2026-01-10T19:05:12.29931325Z","closed_at":"2026-01-10T19:05:12.29931325Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-vk19.15","depends_on_id":"mala-vk19","type":"parent-child","created_at":"2026-01-10T05:54:16.281684911Z","created_by":"cyou"},{"issue_id":"mala-vk19.15","depends_on_id":"mala-vk19.6","type":"blocks","created_at":"2026-01-10T05:55:59.660390774Z","created_by":"cyou"},{"issue_id":"mala-vk19.15","depends_on_id":"mala-vk19.11","type":"blocks","created_at":"2026-01-10T05:55:59.772293598Z","created_by":"cyou"},{"issue_id":"mala-vk19.15","depends_on_id":"mala-vk19.20","type":"blocks","created_at":"2026-01-10T18:27:49.45785615Z","created_by":"cyou"}]}
{"id":"mala-vk19.16","title":"[T016] Scenario tests: timeout and interrupt behavior","description":"**Type**: task\n**Priority**: P2\n**Phase**: 5 (Testing \u0026 validation)\n**Primary Files**: tests/integration/test_run_lifecycle_restructure.py\n**Subsystems**: pipeline\n\n## Goal\nCreate scenario tests for session_end timeout and interrupt behavior per spec R10.\n\n## Context\n- Per spec R10: On timeout/interrupt, proceed to review with explicit status\n- Partial results discarded on timeout (empty commands array)\n- Completed commands preserved on interrupt\n\n## Scope\n- **In**: Timeout scenario test; interrupt scenario test\n- **Out**: Basic flow tests (already in T010)\n\n## Changes\n- `tests/integration/test_run_lifecycle_restructure.py` — [Exists from T010] — Add timeout and interrupt scenarios\n\n## Acceptance Criteria\n- Timeout scenario: session_end times out mid-command → status=timeout, commands=[]\n- Interrupt scenario: SIGINT during session_end → status=interrupted, completed commands preserved\n- Both scenarios: issue proceeds to review with correct SessionEndResult\n\n## Verification\n- Scenario test: Mock slow command that exceeds timeout; verify result\n- Scenario test: Send SIGINT during session_end; verify partial results\n- Verify log ordering in both scenarios\n\n## Notes for Agent\n- Use asyncio.timeout() to simulate timeout\n- For SIGINT test, signal the abort_event\n- Verify SessionEndResult.commands is empty on timeout, populated on interrupt","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-10T05:54:39.072703654Z","created_by":"cyou","updated_at":"2026-01-10T18:39:08.441104972Z","closed_at":"2026-01-10T18:39:08.441104972Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-vk19.16","depends_on_id":"mala-vk19","type":"parent-child","created_at":"2026-01-10T05:54:39.073399659Z","created_by":"cyou"},{"issue_id":"mala-vk19.16","depends_on_id":"mala-vk19.11","type":"blocks","created_at":"2026-01-10T05:56:09.03204479Z","created_by":"cyou"},{"issue_id":"mala-vk19.16","depends_on_id":"mala-vk19.10","type":"blocks","created_at":"2026-01-10T06:01:47.196209552Z","created_by":"cyou"}]}
{"id":"mala-vk19.17","title":"[T017] Scenario tests: run abort and mixed outcomes","description":"**Type**: task\n**Priority**: P2\n**Phase**: 5 (Testing \u0026 validation)\n**Primary Files**: tests/integration/test_run_lifecycle_restructure.py\n**Subsystems**: pipeline, orchestration\n\n## Goal\nCreate scenario tests for run abort, mixed outcome handling, and R7 fire_on truth table verification.\n\n## Context\n- Per plan Run Abort Contract: failure_mode=abort sets abort_event, in-flight issues complete and finalize\n- Mixed outcomes: some issues pass, some fail; all proceed to review\n- Per spec R7: run_end fire_on must follow truth table (success/failure/both)\n\n## Scope\n- **In**: Run abort scenario; mixed outcomes scenario; fire_on truth table tests\n- **Out**: Basic flow tests (T010)\n\n## Changes\n- `tests/integration/test_run_lifecycle_restructure.py` — [Exists] — Add run abort, mixed outcome, and fire_on scenarios\n\n## R7 fire_on Truth Table Tests (REQUIRED)\n\nPer spec, must verify all combinations:\n\n| success_count | failure_count | fire_on: success | fire_on: failure | fire_on: both |\n|---------------|---------------|------------------|------------------|---------------|\n| 0 | N (\u003e0) | skip | fire | fire |\n| N (\u003e0) | 0 | fire | skip | fire |\n| N (\u003e0) | M (\u003e0) | fire | fire | fire |\n\nEach cell needs a test case.\n\n## Acceptance Criteria\n- Run abort: session_end abort sets abort_event; in-flight issues finalize as interrupted; run_end skipped\n- Mixed outcomes: issues with pass/fail session_end all proceed to review\n- **R7 Coverage**: All 6 fire_on truth table combinations tested\n- run_end logs `skipped: reason=fire_on_not_met` when condition not met\n- run_end logs `skipped: reason=run_aborted` when abort triggered\n\n## Verification\n- Scenario test: Run abort during session_end → run_end logs skipped with reason=run_aborted\n- Scenario test: 2 issues, one passes session_end, one fails → both proceed to review\n- Scenario test: fire_on=success with all failures → run_end skipped\n- Scenario test: fire_on=success with 1 success + 1 failure → run_end fires\n- Scenario test: fire_on=failure with all successes → run_end skipped\n- Scenario test: fire_on=both with any outcome → run_end fires\n\n## Notes for Agent\n- Run abort test needs multiple concurrent issues\n- fire_on tests must cover the full truth table from spec R7\n- Verify log messages match spec format exactly","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-10T05:54:52.708313355Z","created_by":"cyou","updated_at":"2026-01-10T19:39:56.935493755Z","closed_at":"2026-01-10T19:39:56.935493755Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-vk19.17","depends_on_id":"mala-vk19","type":"parent-child","created_at":"2026-01-10T05:54:52.70899425Z","created_by":"cyou"},{"issue_id":"mala-vk19.17","depends_on_id":"mala-vk19.14","type":"blocks","created_at":"2026-01-10T05:56:09.142733253Z","created_by":"cyou"},{"issue_id":"mala-vk19.17","depends_on_id":"mala-vk19.10","type":"blocks","created_at":"2026-01-10T06:01:47.30277911Z","created_by":"cyou"}]}
{"id":"mala-vk19.18","title":"[T018] Final verification: all tests pass + manual log ordering check","description":"**Type**: task\n**Priority**: P2\n**Phase**: 5 (Testing \u0026 validation)\n**Primary Files**: tests/\n**Subsystems**: all\n\n## Goal\nFinal verification that all tests pass, all old code is fully removed, and all spec requirements R1-R12 are satisfied.\n\n## Context\n- Per spec: All existing tests must pass after restructure\n- Must verify complete removal of deprecated code paths\n- Must verify all 12 requirements are implemented\n- This is the final gate before feature is complete\n\n## Scope\n- **In**: Run full test suite; verify old code removal; verify spec coverage; manual verification\n- **Out**: N/A - this is the final task\n\n## Changes\n- No code changes - verification only\n\n## Test Suite Verification\n```bash\nuv run pytest -m unit\nuv run pytest -m integration\nuvx ty check\nuvx ruff check .\n```\n\n## Old Code Removal Verification (REQUIRED)\n\n```bash\n# Must return NO matches (old code fully removed):\ngrep -r \"run_validation\" src/ --include=\"*.py\" | grep -v \"test_\"\ngrep -r \"global_validation\" src/ --include=\"*.py\" | grep -v \"config_loader\"\ngrep -r \"_fire_session_end_trigger\" src/orchestration/ --include=\"*.py\"\ngrep -r \"\\[run\\] GATE\" src/ --include=\"*.py\"\n```\n\n## Spec Requirements Verification Checklist\n\n### R1-R5 (Per-issue session_end)\n- [ ] R1: session_end runs after gate pass, before review (log ordering)\n- [ ] R2: session_end skipped when gate fails (log shows reason=gate_failed)\n- [ ] R3: No separate global_validation stage in logs\n- [ ] R4: Config with global_validation_commands raises error with migration example\n- [ ] R5: SessionEndResult appears in review context\n\n### R6-R8 (Run-level unification)\n- [ ] R6: run_end with empty commands + code_review.enabled=true works (no validation error)\n- [ ] R7: fire_on truth table verified (see T017 tests)\n- [ ] R8: No run_validation method in RunCoordinator\n\n### R9-R12 (Behavior)\n- [ ] R9: session_end failure_mode (abort/continue/remediate) works\n- [ ] R10: session_end timeout returns status=timeout with empty commands\n- [ ] R11: base_sha is captured and used for code_review range\n- [ ] R12: run_end failure_mode gates code_review (abort skips, continue proceeds)\n\n## R6 Specific Verification\nTest that run_end accepts empty commands when code_review is enabled:\n```yaml\nvalidation_triggers:\n  run_end:\n    commands: []  # empty\n    code_review:\n      enabled: true\n```\nThis config must not raise validation error and must execute code_review.\n\n## R12 Specific Verification\nTest that run_end failure_mode gates code_review:\n- failure_mode=abort + command fails → code_review skipped\n- failure_mode=continue + command fails → code_review runs\n- failure_mode=remediate + exhausted → code_review runs\n\n## Manual Verification Checklist\n- [ ] Run logs show `session_end started` after `gate_passed`, before `review_start`\n- [ ] Config with `global_validation_commands` produces error with example migration\n- [ ] No `[run] GATE` or `global_validation` entries in run logs\n- [ ] `[trigger] run_end skipped: reason=fire_on_not_met` when condition not met\n- [ ] `[trigger] run_end skipped: reason=run_aborted` on run abort\n- [ ] `RunCoordinator` class has no `run_validation` method\n- [ ] Orchestrator has no `_fire_session_end_trigger` call at run-end level\n\n## Notes for Agent\n- This task depends on ALL other tasks completing\n- Document any test failures and their fixes\n- Capture evidence of old code removal (grep outputs)\n- Capture evidence of spec verification (test outputs or screenshots)\n- If any requirement is not satisfied, create follow-up issue","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-10T05:55:07.741621615Z","created_by":"cyou","updated_at":"2026-01-10T19:48:00.872366969Z","closed_at":"2026-01-10T19:48:00.872366969Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-vk19.18","depends_on_id":"mala-vk19","type":"parent-child","created_at":"2026-01-10T05:55:07.742293111Z","created_by":"cyou"},{"issue_id":"mala-vk19.18","depends_on_id":"mala-vk19.12","type":"blocks","created_at":"2026-01-10T05:56:10.510935955Z","created_by":"cyou"},{"issue_id":"mala-vk19.18","depends_on_id":"mala-vk19.13","type":"blocks","created_at":"2026-01-10T05:56:10.616654394Z","created_by":"cyou"},{"issue_id":"mala-vk19.18","depends_on_id":"mala-vk19.14","type":"blocks","created_at":"2026-01-10T05:56:10.727622315Z","created_by":"cyou"},{"issue_id":"mala-vk19.18","depends_on_id":"mala-vk19.15","type":"blocks","created_at":"2026-01-10T05:56:10.835503128Z","created_by":"cyou"},{"issue_id":"mala-vk19.18","depends_on_id":"mala-vk19.16","type":"blocks","created_at":"2026-01-10T05:56:10.942127931Z","created_by":"cyou"},{"issue_id":"mala-vk19.18","depends_on_id":"mala-vk19.17","type":"blocks","created_at":"2026-01-10T05:56:11.050458711Z","created_by":"cyou"},{"issue_id":"mala-vk19.18","depends_on_id":"mala-vk19.2","type":"blocks","created_at":"2026-01-10T05:56:17.422412716Z","created_by":"cyou"},{"issue_id":"mala-vk19.18","depends_on_id":"mala-vk19.3","type":"blocks","created_at":"2026-01-10T05:56:17.529524035Z","created_by":"cyou"}]}
{"id":"mala-vk19.19","title":"[Review] 1 non-blocking finding from mala-vk19.9","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-vk19.9\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Test uses @pytest.mark.anyio instead of codebase standard @pytest.mark.asyncio\n\n**Priority:** P2\n**Reviewer:** claude\n**Location:** tests/unit/pipeline/test_session_callback_factory.py:38-55\n\nThe test file uses `@pytest.mark.anyio` for async tests (lines 38, 55), but the codebase consistently uses `@pytest.mark.asyncio` (e.g., `tests/unit/infra/test_sdk_transport.py` uses `@pytest.mark.asyncio`). The project has `pytest-asyncio` configured with `asyncio_mode = \"auto\"`, so these markers are actually unnecessary. With `--strict-markers` in addopts, using an unregistered marker like `anyio` could cause test collection failures if `pytest-anyio` isn't transitively installed. Either remove the markers entirely (since `asyncio_mode=auto` handles async tests automatically) or use `@pytest.mark.asyncio` for consistency.\n\n---\n\n\u003c!-- fp:c549f3d4c676418b --\u003e\n\u003c!-- review_finding:76caa66c997d --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-10T07:11:14.819951628Z","created_by":"cyou","updated_at":"2026-01-10T07:12:54.908843194Z","closed_at":"2026-01-10T07:12:54.908843194Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-vk19.9"],"dependencies":[{"issue_id":"mala-vk19.19","depends_on_id":"mala-vk19","type":"parent-child","created_at":"2026-01-10T07:11:14.820661512Z","created_by":"cyou"}]}
{"id":"mala-vk19.2","title":"[T002] Remove RunCoordinator.run_validation() and global_validation calls","description":"**Type**: task\n**Priority**: P1\n**Phase**: 1 (Run-level consolidation)\n**Primary Files**: src/pipeline/run_coordinator.py, src/orchestration/orchestrator.py, tests/\n**Subsystems**: pipeline, orchestration\n\n## Goal\nRemove the separate run_validation stage by deleting `RunCoordinator.run_validation()` and all calls to global_validation in orchestrator. Run-level validation is now handled exclusively by `run_end` trigger.\n\n## Context\n- Per spec R8: No run-level validation path should exist outside `run_end`\n- Per spec R3: Merge global_validation into run_end (this is the \"delete old\" side)\n- Removes code that will never be called after T001 rejects the config key\n\n## Scope\n- **In**: Delete run_validation method; delete global_validation call site in orchestrator; update tests\n- **Out**: session_end removal from run end (separate task T003)\n\n## Changes\n- `src/pipeline/run_coordinator.py` — [Exists] — DELETE `run_validation()` method entirely\n- `src/orchestration/orchestrator.py` — [Exists] — DELETE global_validation call (L1504-1552)\n- `tests/` — Update any tests that expect global_validation behavior\n\n## What Must Be Deleted\n1. **RunCoordinator.run_validation()** method - delete the entire method\n2. **Any helper methods** only used by run_validation - delete them too\n3. **orchestrator global_validation call** - delete the call site and any setup code\n4. **Test mocks/expectations** for run_validation - update or delete\n\n## Acceptance Criteria\n- `RunCoordinator` class no longer has a `run_validation()` method\n- No code path in orchestrator calls global_validation or run_validation\n- Run logs show no `[run] GATE` or `global_validation` entries\n- No dead code left behind (helper methods, unused imports)\n\n## Verification\n```bash\n# These must return NO matches:\ngrep -n \"def run_validation\" src/pipeline/run_coordinator.py\ngrep -n \"run_validation\" src/orchestration/orchestrator.py\ngrep -n \"global_validation\" src/orchestration/orchestrator.py | grep -v \"#\"\n\n# Existing tests pass:\nuv run pytest -m unit\n```\n\n## Notes for Agent\n- run_validation is around L674-1033 in run_coordinator.py\n- global_validation call in orchestrator is around L1504-1552\n- Some tests may mock run_validation - those need updating to use run_end patterns\n- Delete cleanly - no commented-out code, no \"# removed\" markers\n- Check for any imports that become unused after deletion","notes":"Quality gate failed: External review failed: internal_error\nLog: /home/cyou/.claude/projects/-home-cyou-mala/034ccbbd-7993-4e81-8853-c4fa76de3b04.jsonl","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-10T05:50:14.842386222Z","created_by":"cyou","updated_at":"2026-01-10T15:14:45.060934964Z","closed_at":"2026-01-10T15:14:45.060934964Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-vk19.2","depends_on_id":"mala-vk19","type":"parent-child","created_at":"2026-01-10T05:50:14.843067287Z","created_by":"cyou"},{"issue_id":"mala-vk19.2","depends_on_id":"mala-vk19.1","type":"blocks","created_at":"2026-01-10T05:55:37.849790022Z","created_by":"cyou"}]}
{"id":"mala-vk19.20","title":"Fix SIGINT handling in session_end to complete current command before returning","description":"## Background\nExternal review on vk19.11 found:\n- P1: SIGINT can cancel mid-command, violating 'complete current command'\n- Acceptance criteria says: 'On SIGINT: complete current command, return status=\"interrupted\"'\n- In _execute_session_end, each command await can be cancelled mid-execution\n\n## Fix Required\nProtect command execution from cancellation (asyncio.shield or similar) while allowing the interrupt_event to be checked between commands.\n\n## Related\n- Discovered during vk19.11 review\n- File: src/pipeline/session_callback_factory.py L385-487","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-10T18:02:18.913137399Z","created_by":"cyou","updated_at":"2026-01-10T18:53:37.962022978Z","closed_at":"2026-01-10T18:53:37.962022978Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-vk19.20","depends_on_id":"mala-vk19","type":"parent-child","created_at":"2026-01-10T18:02:18.91405844Z","created_by":"cyou"},{"issue_id":"mala-vk19.20","depends_on_id":"mala-vk19.11","type":"blocks","created_at":"2026-01-10T18:02:23.176030059Z","created_by":"cyou"}]}
{"id":"mala-vk19.21","title":"[Review] 1 non-blocking finding from mala-vk19.20","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-vk19.20\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] session_end_result not wired to ReviewInput, making format function dead code\n\n**Priority:** P2\n**Reviewer:** claude\n**Location:** src/pipeline/session_callback_factory.py:221-229\n\nThe `on_review_check` callback receives `_session_end_result` (L214) but doesn't pass it to `ReviewInput` (L221-229). The new `_format_session_end_evidence` function and the code using `input.session_end_result` in review_runner.py L302-304 will never execute since ReviewInput.session_end_result defaults to None. To activate this feature, add `session_end_result=_session_end_result` to the ReviewInput constructor at L229.\n\n---\n\n\u003c!-- fp:50e04e48ff2c31a3 --\u003e\n\u003c!-- review_finding:0442d7b91966 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-10T18:53:38.268239076Z","created_by":"cyou","updated_at":"2026-01-10T18:59:09.018487007Z","closed_at":"2026-01-10T18:59:09.018487007Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-vk19.20"],"dependencies":[{"issue_id":"mala-vk19.21","depends_on_id":"mala-vk19","type":"parent-child","created_at":"2026-01-10T18:53:38.268818183Z","created_by":"cyou"}]}
{"id":"mala-vk19.22","title":"Fix run_end code_review gating for failure_mode continue/remediate","description":"R12 gap: run_end code_review currently only runs when all commands pass. Must run code_review after command failure when failure_mode is continue/remediate; abort should skip. See src/pipeline/run_coordinator.py:_run_trigger_validation_loop.","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-10T20:02:25.697444865Z","created_by":"cyou","updated_at":"2026-01-10T20:50:36.943836332Z","closed_at":"2026-01-10T20:50:36.943836332Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-vk19.22","depends_on_id":"mala-vk19","type":"parent-child","created_at":"2026-01-10T20:02:25.69810907Z","created_by":"cyou"}]}
{"id":"mala-vk19.23","title":"Persist SessionEndResult into IssueResult","description":"R5 gap: SessionEndResult exists in lifecycle ctx but is never stored in IssueResult output. AgentSessionOutput lacks session_end_result; run_implementer doesn't pass it into IssueResult. Ensure session_end evidence is persisted for review context and run metadata.","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-10T20:02:34.836536426Z","created_by":"cyou","updated_at":"2026-01-10T20:51:18.879594664Z","closed_at":"2026-01-10T20:51:18.879594664Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-vk19.23","depends_on_id":"mala-vk19","type":"parent-child","created_at":"2026-01-10T20:02:34.83731503Z","created_by":"cyou"},{"issue_id":"mala-vk19.23","depends_on_id":"mala-vk19.25","type":"related","created_at":"2026-01-10T20:04:14.629952066Z","created_by":"cyou"},{"issue_id":"mala-vk19.23","depends_on_id":"mala-vk19.25","type":"blocks","created_at":"2026-01-10T20:17:50.0530719Z","created_by":"cyou"}]}
{"id":"mala-vk19.24","title":"Discard session_end partial command results on timeout/SIGINT","description":"R10 gap: session_end returns commands=list(command_results) on SIGINT paths; spec requires empty commands for timeout/interrupt. Update session_end callback to discard partial results on timeout/interrupt.","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-10T20:02:43.112723976Z","created_by":"cyou","updated_at":"2026-01-10T21:00:12.315605015Z","closed_at":"2026-01-10T21:00:12.315605015Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-vk19.24","depends_on_id":"mala-vk19","type":"parent-child","created_at":"2026-01-10T20:02:43.113399791Z","created_by":"cyou"},{"issue_id":"mala-vk19.24","depends_on_id":"mala-vk19.25","type":"related","created_at":"2026-01-10T20:04:22.073227941Z","created_by":"cyou"},{"issue_id":"mala-vk19.24","depends_on_id":"mala-vk19.23","type":"related","created_at":"2026-01-10T20:04:29.532998854Z","created_by":"cyou"},{"issue_id":"mala-vk19.24","depends_on_id":"mala-vk19.25","type":"blocks","created_at":"2026-01-10T20:17:50.159349457Z","created_by":"cyou"},{"issue_id":"mala-vk19.24","depends_on_id":"mala-vk19.23","type":"blocks","created_at":"2026-01-10T20:17:50.261624874Z","created_by":"cyou"}]}
{"id":"mala-vk19.25","title":"Align SessionEndResult command schema with spec","description":"Spec R5 expects commands entries {ref, passed, duration_seconds, error_message} but SessionEndResult.to_dict serializes command/returncode/stdout/stderr/timed_out. Decide and align schema or update spec/docs/tests accordingly.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-10T20:02:51.37275032Z","created_by":"cyou","updated_at":"2026-01-10T20:46:20.900184966Z","closed_at":"2026-01-10T20:46:20.900184966Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-vk19.25","depends_on_id":"mala-vk19","type":"parent-child","created_at":"2026-01-10T20:02:51.373477844Z","created_by":"cyou"}]}
{"id":"mala-vk19.26","title":"Resolve doc inconsistency: plans/run-lifecycle-spec.md vs final spec","description":"Draft plan contradicts final spec (session_end ordering, logging, compatibility). Update or retire plans/run-lifecycle-spec.md to match docs/2026-01-10-run-lifecycle-restructure-spec.md.","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-10T20:03:01.111185577Z","created_by":"cyou","updated_at":"2026-01-10T20:18:12.503526404Z","closed_at":"2026-01-10T20:18:12.503526404Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-vk19.26","depends_on_id":"mala-vk19","type":"parent-child","created_at":"2026-01-10T20:03:01.111767603Z","created_by":"cyou"}]}
{"id":"mala-vk19.27","title":"Reconcile beads tracking vs commits (missing vk19 tasks)","description":"Commits reference vk19.2/.16/.17/.19/.20 but .beads/issues.jsonl lacks these entries. Either add missing beads tasks or update tracking to reflect actual work.","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-10T20:03:09.76215142Z","created_by":"cyou","updated_at":"2026-01-10T20:18:30.000416169Z","closed_at":"2026-01-10T20:18:30.000416169Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-vk19.27","depends_on_id":"mala-vk19","type":"parent-child","created_at":"2026-01-10T20:03:09.762705426Z","created_by":"cyou"}]}
{"id":"mala-vk19.28","title":"[Review] 1 non-blocking finding from mala-vk19.24","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-vk19.24\n**Highest priority:** P3\n\n---\n\n### Finding 1: [P3] Fix contradictory docstring about preserved commands on SIGINT\n\n**Priority:** P3\n**Reviewer:** codex\n**Location:** tests/integration/test_run_lifecycle_restructure.py:465-478\n\n`test_session_end_interrupt_scenario`’s docstring still claims “Completed commands preserved in result”, but the test now simulates `SessionEndResult(status=\"interrupted\", commands=[])` and the task context/spec requires empty commands on interrupt/timeout. This is inconsistent documentation within the test itself.\n\n```py\n- Completed commands preserved in result\n...\ncommands=[]\n```\n\n---\n\n\u003c!-- fp:2b206e86216ced44 --\u003e\n\u003c!-- review_finding:bb46226dc14d --\u003e","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-10T21:00:12.642367675Z","created_by":"cyou","updated_at":"2026-01-10T21:02:47.99090629Z","closed_at":"2026-01-10T21:02:47.99090629Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-vk19.24"],"dependencies":[{"issue_id":"mala-vk19.28","depends_on_id":"mala-vk19","type":"parent-child","created_at":"2026-01-10T21:00:12.643060111Z","created_by":"cyou"}]}
{"id":"mala-vk19.3","title":"[T003] Remove _fire_session_end_trigger() from run end","description":"**Type**: task\n**Priority**: P1\n**Phase**: 1 (Run-level consolidation)\n**Primary Files**: src/orchestration/orchestrator.py, tests/\n**Subsystems**: orchestration\n\n## Goal\nRemove the current run-end session_end trigger invocation. session_end will be repurposed as a per-issue trigger in Phase 3.\n\n## Context\n- session_end currently fires once at run end (after all issues complete)\n- Per spec, session_end should fire per-issue (after gate, before review)\n- This task removes the old invocation; Phase 3 adds the new per-issue logic\n\n## Scope\n- **In**: Remove/delete `_fire_session_end_trigger()` call from run end sequence (L1216-1257)\n- **Out**: Adding per-issue session_end (Phase 3)\n\n## Changes\n- `src/orchestration/orchestrator.py` — [Exists] — DELETE call to `_fire_session_end_trigger()` from run end sequence\n- `tests/` — Update tests that expect run-end session_end trigger\n\n## What Must Be Deleted/Changed\n1. **Call to _fire_session_end_trigger()** at run end - DELETE the call site\n2. **If _fire_session_end_trigger() is now unused** - DELETE the method entirely\n3. **If method is reused for per-issue** - KEEP method but DELETE run-end call\n4. **Test expectations** for run-end session_end - UPDATE or DELETE\n\n## Acceptance Criteria\n- session_end trigger no longer fires at run end\n- No `[trigger] session_end` logs appear at run end level\n- The method may still exist if it's reused for per-issue (Phase 3)\n- No dead code, no commented-out code\n\n## Verification\n```bash\n# Verify no run-end session_end call:\n# Look for session_end calls in the run-end section of orchestrator\ngrep -n \"session_end\" src/orchestration/orchestrator.py | grep -v \"per.issue\\|#\"\n\n# Check the run-end sequence doesn't call session_end\n# (manual review of run() method structure)\n\n# Tests pass:\nuv run pytest -m unit\n```\n\n## Notes for Agent\n- The method is around L1216-1257 in orchestrator.py\n- Determine if _fire_session_end_trigger() is needed for Phase 3 per-issue use\n- If yes, keep the method but remove the run-end call\n- If no (Phase 3 uses different approach), delete the method entirely\n- Update any tests that assert on run-end session_end behavior","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-10T05:50:31.811321475Z","created_by":"cyou","updated_at":"2026-01-10T15:29:40.576912826Z","closed_at":"2026-01-10T15:29:40.576912826Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-vk19.3","depends_on_id":"mala-vk19","type":"parent-child","created_at":"2026-01-10T05:50:31.81200664Z","created_by":"cyou"},{"issue_id":"mala-vk19.3","depends_on_id":"mala-vk19.1","type":"blocks","created_at":"2026-01-10T05:55:37.960366615Z","created_by":"cyou"},{"issue_id":"mala-vk19.3","depends_on_id":"mala-vk19.2","type":"blocks","created_at":"2026-01-10T06:01:38.981603438Z","created_by":"cyou"}]}
{"id":"mala-vk19.4","title":"[T004] Create SessionEndResult and SessionEndRetryState dataclasses","description":"**Type**: task\n**Priority**: P1\n**Phase**: 2 (Core infrastructure)\n**Primary Files**: src/pipeline/session_end_result.py (New), tests/unit/pipeline/test_session_end_result.py (New)\n**Subsystems**: pipeline\n\n## Goal\nCreate the `SessionEndResult` and `SessionEndRetryState` dataclasses that will hold session_end execution state and results.\n\n## Context\n- Per spec R5: SessionEndResult data contract is defined\n- These types are consumed by lifecycle, callbacks, and review\n- Must match the exact contract in the spec\n\n## Scope\n- **In**: Create new file with dataclasses; create unit tests for types\n- **Out**: Integration with lifecycle (separate tasks)\n\n## Changes\n- `src/pipeline/session_end_result.py` — [New] — Create SessionEndResult and SessionEndRetryState dataclasses\n- `tests/unit/pipeline/test_session_end_result.py` — [New] — Unit tests for type creation and validation\n\n## Acceptance Criteria\n- `SessionEndResult` has fields: status, started_at, finished_at, commands, code_review_result, reason\n- `status` is Literal[\"pass\", \"fail\", \"timeout\", \"interrupted\", \"skipped\"]\n- `SessionEndRetryState` has fields: attempt, max_retries, log_offset, previous_commit_hash\n- Both types serialize cleanly (for persistence)\n\n## Verification\n- Unit test: Create SessionEndResult with each status value\n- Unit test: Create SessionEndRetryState with default and custom values\n- Unit test: Verify nullable fields work correctly (started_at null when skipped)\n- Type check passes (uvx ty check)\n\n## Notes for Agent\n- Reference the data contract in spec section R5:\n```python\n@dataclass\nclass SessionEndResult:\n    status: Literal[\"pass\", \"fail\", \"timeout\", \"interrupted\", \"skipped\"]\n    started_at: datetime | None      # null only when status=skipped\n    finished_at: datetime | None     # null only when status=skipped\n    commands: list[CommandResult]    # empty if skipped or partial results discarded\n    code_review_result: CodeReviewResult | None\n    reason: str | None               # e.g., \"gate_failed\", \"not_configured\", \"max_retries_exhausted\"\n```\n- Check if CommandResult exists; if not, define or import it","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-10T05:51:04.852524669Z","created_by":"cyou","updated_at":"2026-01-10T06:57:14.822983055Z","closed_at":"2026-01-10T06:57:14.822983055Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-vk19.4","depends_on_id":"mala-vk19","type":"parent-child","created_at":"2026-01-10T05:51:04.853300854Z","created_by":"cyou"}]}
{"id":"mala-vk19.5","title":"[T005] Create FixerInterface protocol for session_end remediation","description":"**Type**: task\n**Priority**: P1\n**Phase**: 2 (Core infrastructure)\n**Primary Files**: src/pipeline/fixer_interface.py (New), src/pipeline/run_coordinator.py, tests/unit/pipeline/test_fixer_interface.py (New)\n**Subsystems**: pipeline\n\n## Goal\nCreate a `FixerInterface` protocol that abstracts fixer agent invocation for session_end remediation, avoiding tight coupling with RunCoordinator.\n\n## Context\n- Per plan: Session_end callback needs fixer access without direct coordinator reference\n- FixerInterface allows injection of a narrow interface rather than full coordinator\n- RunCoordinator._run_fixer_agent() will implement this interface\n\n## Scope\n- **In**: Define protocol; create adapter over run_coordinator; add unit tests\n- **Out**: Wiring into session_end callback (Phase 3)\n\n## Changes\n- `src/pipeline/fixer_interface.py` — [New] — Define FixerInterface protocol\n- `src/pipeline/run_coordinator.py` — [Exists] — Expose method compatible with FixerInterface\n- `tests/unit/pipeline/test_fixer_interface.py` — [New] — Contract tests for protocol\n\n## Acceptance Criteria\n- `FixerInterface` is a Protocol with `run_fixer(failure_output: str, issue_id: str) -\u003e Awaitable[FixerResult]`\n- RunCoordinator can be adapted to satisfy FixerInterface\n- Session_end callback receives FixerInterface, not RunCoordinator\n\n## Verification\n- Unit test: Mock implementing FixerInterface works correctly\n- Unit test: Adapter over run_coordinator satisfies protocol\n- Type check passes with protocol usage\n\n## Notes for Agent\n```python\nclass FixerInterface(Protocol):\n    async def run_fixer(self, failure_output: str, issue_id: str) -\u003e FixerResult: ...\n```\n- FixerResult may already exist or need definition (check codebase)\n- The adapter can be a simple class wrapping run_coordinator._run_fixer_agent()","notes":"Quality gate failed: Timeout after 60 minutes","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-10T05:51:20.30710775Z","created_by":"cyou","updated_at":"2026-01-10T15:18:06.769441665Z","closed_at":"2026-01-10T15:18:06.769441665Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-vk19.5","depends_on_id":"mala-vk19","type":"parent-child","created_at":"2026-01-10T05:51:20.308171112Z","created_by":"cyou"}]}
{"id":"mala-vk19.6","title":"[T006] Add base_sha and session_end_result fields to IssueResult","description":"**Type**: task\n**Priority**: P1\n**Phase**: 2 (Core infrastructure)\n**Primary Files**: src/pipeline/issue_result.py, tests/unit/pipeline/test_issue_result.py\n**Subsystems**: pipeline\n\n## Goal\nAdd `base_sha: str | None` and `session_end_result: SessionEndResult | None` fields to IssueResult dataclass.\n\n## Context\n- Per spec R11: base_sha is captured at issue session start (before any agent writes)\n- Per spec R5: SessionEndResult is stored in issue record for review evidence\n- These fields are the \"single source of truth\" for session_end evidence\n\n## Scope\n- **In**: Add fields to IssueResult; update tests\n- **Out**: Capturing base_sha at runtime (Phase 3); populating session_end_result (Phase 3)\n\n## Changes\n- `src/pipeline/issue_result.py` — [Exists] — Add base_sha and session_end_result fields\n- `tests/unit/pipeline/test_issue_result.py` — [Exists or New] — Test new fields\n\n## Wiring Map\n- base_sha: orchestrator checkout → IssueResult.base_sha → session_end callback → CumulativeReviewRunner\n- session_end_result: callback result → IssueResult.session_end_result → review context\n\n## Acceptance Criteria\n- IssueResult has `base_sha: str | None = None` field\n- IssueResult has `session_end_result: SessionEndResult | None = None` field\n- Both fields default to None (backward compatible)\n- Existing IssueResult creation patterns still work\n\n## Verification\n- Unit test: Create IssueResult with and without new fields\n- Unit test: Fields serialize correctly (for persistence)\n- Type check passes\n- Existing tests pass without modification (backward compatible defaults)\n\n## Notes for Agent\n- IssueResult is a dataclass in src/pipeline/issue_result.py\n- Import SessionEndResult from the new session_end_result.py (T004 must complete first)\n- Ensure no circular imports","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-10T05:51:34.098073993Z","created_by":"cyou","updated_at":"2026-01-10T07:03:58.676544731Z","closed_at":"2026-01-10T07:03:58.676544731Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-vk19.6","depends_on_id":"mala-vk19","type":"parent-child","created_at":"2026-01-10T05:51:34.098729198Z","created_by":"cyou"},{"issue_id":"mala-vk19.6","depends_on_id":"mala-vk19.4","type":"blocks","created_at":"2026-01-10T05:55:39.180808177Z","created_by":"cyou"}]}
{"id":"mala-vk19.7","title":"[T007] Add RUNNING_SESSION_END lifecycle state and Effect.RUN_SESSION_END","description":"**Type**: task\n**Priority**: P1\n**Phase**: 2 (Core infrastructure)\n**Primary Files**: src/domain/lifecycle.py, tests/unit/domain/test_lifecycle.py\n**Subsystems**: domain\n\n## Goal\nAdd new lifecycle state `RUNNING_SESSION_END` and effect `Effect.RUN_SESSION_END` to the state machine, with proper transitions.\n\n## Context\n- Per plan: New state machine flow is agent → gate → session_end → review → finalize\n- gate passed → RUNNING_SESSION_END\n- session_end completed → RUNNING_REVIEW (or SUCCESS if no review)\n- Optionally add SESSION_END_REMEDIATING for remediation state\n\n## Scope\n- **In**: Add state and effect enums; add state transitions; add unit tests\n- **Out**: Effect handler implementation (Phase 3)\n\n## Changes\n- `src/domain/lifecycle.py` — [Exists] — Add RUNNING_SESSION_END state, Effect.RUN_SESSION_END, transitions\n- `tests/unit/domain/test_lifecycle.py` — [Exists or New] — Test new state transitions\n\n## Acceptance Criteria\n- ImplementerLifecycle state enum has RUNNING_SESSION_END\n- Effect enum has RUN_SESSION_END\n- State transition: gate passed → RUNNING_SESSION_END is valid\n- State transition: session_end any status → RUNNING_REVIEW is valid\n- Invalid transitions raise appropriate errors\n\n## Verification\n- Unit test: State machine accepts gate_passed → RUNNING_SESSION_END transition\n- Unit test: State machine accepts RUNNING_SESSION_END → RUNNING_REVIEW transition\n- Unit test: State machine rejects invalid transitions (e.g., IMPLEMENTING → RUNNING_SESSION_END)\n- Type check passes\n\n## Notes for Agent\n- Check existing state machine patterns in lifecycle.py\n- The transitions table needs updating\n- May need SESSION_END_REMEDIATING state for remediation loops (check plan)\n- Effect.RUN_SESSION_END will be handled in agent_session_runner (Phase 3)","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-10T05:51:49.792889545Z","created_by":"cyou","updated_at":"2026-01-10T06:43:56.998569801Z","closed_at":"2026-01-10T06:43:56.998569801Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-vk19.7","depends_on_id":"mala-vk19","type":"parent-child","created_at":"2026-01-10T05:51:49.79357353Z","created_by":"cyou"}]}
{"id":"mala-vk19.8","title":"[T008] Add session_end event sink methods","description":"**Type**: task\n**Priority**: P1\n**Phase**: 2 (Core infrastructure)\n**Primary Files**: src/infra/io/event_sink.py, tests/unit/infra/io/test_event_sink.py\n**Subsystems**: infra\n\n## Goal\nAdd event sink methods for session_end lifecycle events following the existing trigger event pattern.\n\n## Context\n- Per spec: Log events as `[trigger] session_end started: issue_id=X`\n- Events: started, completed, skipped\n- Must follow existing trigger event pattern for consistency\n\n## Scope\n- **In**: Add on_session_end_started, on_session_end_completed, on_session_end_skipped methods\n- **Out**: Calling these methods (Phase 3)\n\n## Changes\n- `src/infra/io/event_sink.py` — [Exists] — Add session_end event methods\n- `tests/unit/infra/io/test_event_sink.py` — [Exists or New] — Test event formatting\n\n## Acceptance Criteria\n- `on_session_end_started(issue_id: str)` logs `[trigger] session_end started: issue_id=X`\n- `on_session_end_completed(issue_id: str, result: str)` logs `[trigger] session_end completed: issue_id=X, result=Y`\n- `on_session_end_skipped(issue_id: str, reason: str)` logs `[trigger] session_end skipped: issue_id=X, reason=Y`\n- Events follow existing trigger pattern format\n\n## Verification\n- Unit test: on_session_end_started produces correct log format\n- Unit test: on_session_end_completed includes result in log\n- Unit test: on_session_end_skipped includes reason in log\n- Integration: events appear in correct order in run logs\n\n## Notes for Agent\n- Check existing trigger event patterns in event_sink.py (e.g., run_end events)\n- result values: pass, fail, timeout, interrupted\n- reason values: gate_failed, not_configured","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-10T05:52:02.320306447Z","created_by":"cyou","updated_at":"2026-01-10T06:16:41.532592729Z","closed_at":"2026-01-10T06:16:41.532592729Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-vk19.8","depends_on_id":"mala-vk19","type":"parent-child","created_at":"2026-01-10T05:52:02.321000892Z","created_by":"cyou"}]}
{"id":"mala-vk19.9","title":"[T009] Add on_session_end_check callback to SessionCallbacks","description":"**Type**: task\n**Priority**: P1\n**Phase**: 2 (Core infrastructure)\n**Primary Files**: src/pipeline/session_callback_factory.py, tests/unit/pipeline/test_session_callback_factory.py\n**Subsystems**: pipeline\n\n## Goal\nAdd the `on_session_end_check` callback signature to SessionCallbacks, following the pattern of `on_gate_check`.\n\n## Context\n- Per plan: session_end callback follows same pattern as gate callback\n- Signature: `Callable[[str, Path, SessionEndRetryState], Awaitable[SessionEndResult]]`\n- Callback factory will build this callback (implementation in Phase 3)\n\n## Scope\n- **In**: Add callback type to SessionCallbacks; add stub factory method\n- **Out**: Full callback implementation (Phase 3)\n\n## Changes\n- `src/pipeline/session_callback_factory.py` — [Exists] — Add on_session_end_check to SessionCallbacks; add stub factory method\n- `tests/unit/pipeline/test_session_callback_factory.py` — [Exists or New] — Test callback signature\n\n## Acceptance Criteria\n- SessionCallbacks has `on_session_end_check` field with correct signature\n- SessionCallbackFactory has method to create session_end callback (stub returning skipped for now)\n- Callback accepts (issue_id, log_path, retry_state) and returns SessionEndResult\n\n## Verification\n- Unit test: Callback signature type-checks correctly\n- Unit test: Stub factory method returns SessionEndResult with status=skipped\n- Type check passes\n\n## Notes for Agent\n- Reference on_gate_check pattern for callback structure\n- Stub implementation should return:\n  `SessionEndResult(status=\"skipped\", reason=\"not_implemented\", ...)`\n- FixerInterface will be injected into the factory in Phase 3","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-10T05:52:15.188534168Z","created_by":"cyou","updated_at":"2026-01-10T07:11:14.473637473Z","closed_at":"2026-01-10T07:11:14.473637473Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-vk19.9","depends_on_id":"mala-vk19","type":"parent-child","created_at":"2026-01-10T05:52:15.189130734Z","created_by":"cyou"},{"issue_id":"mala-vk19.9","depends_on_id":"mala-vk19.4","type":"blocks","created_at":"2026-01-10T05:55:39.286743444Z","created_by":"cyou"}]}
{"id":"mala-vl8t","title":"[Review] tests/test_event_sink.py:5: [P2] Test file import path changed from src.event_sink to src.infra.io.event_sink","description":"## Review Finding\n\nThis issue was auto-created from a P2 review finding.\n\n**Source issue:** mala-40pg.2\n**Reviewer:** claude\n**Location:** tests/test_event_sink.py:5\n\n## Details\n\nThe test file import was changed from `src.event_sink` to `src.infra.io.event_sink`. While this may be correct after the package restructure, this change was not mentioned in the task scope and could indicate either an unrelated fix bundled into this commit or a necessary update for tests to pass. The change should be documented if intentional.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-01T19:33:10.85436426Z","created_by":"cyou","updated_at":"2026-01-01T20:08:55.95114542Z","closed_at":"2026-01-01T20:08:55.95114542Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_finding:1922633bfa88","source:mala-40pg.2"]}
{"id":"mala-vnzl","title":"modularize implementer prompt for different toolchains","notes":"## Testing Strategy\n\n### 1. Unit Tests\n\n**ValidationCommands dataclass (config.py):**\n- Test loading go.yaml → ValidationCommands has correct lint/test/format\n- Test loading python.yaml → ValidationCommands has correct commands\n- Test missing command field → defaults to None\n\n**build_prompt_validation_commands() helper (prompts.py):**\n- Test with full preset → all commands populated\n- Test with missing lint → fallback to \"echo 'no lint configured'\"\n- Test with missing test → fallback message\n- Test with missing format → fallback message\n\n### 2. Integration Tests\n\n**Prompt rendering:**\n- Load implementer_prompt.md as template\n- Call .format() with Go commands → verify \"golangci-lint run\" appears, no \"uvx ruff\"\n- Call .format() with Python commands → verify \"uvx ruff check\" appears, no \"golangci-lint\"\n- Verify no unsubstituted {placeholders} remain in output\n\n**gate_followup.md:**\n- Same template substitution tests as above\n\n### 3. End-to-End Validation\n\n**Manual smoke test:**\n```bash\n# Run mala on ai-meter (Go project) with --max-issues 1\ncd ~/ai-meter\nmala run --max-issues 1 .\n\n# Verify in session log:\n# 1. Agent was told to run \"golangci-lint run\" (not \"uvx ruff\")\n# 2. Agent actually ran golangci-lint\n# 3. Quality gate passes with lint evidence\n```\n\n**Check session logs for correct commands:**\n```bash\n# After run, grep the session log\ngrep -E \"golangci-lint|ruff\" ~/.claude/projects/-home-cyou-ai-meter/*.jsonl | tail -20\n# Should see golangci-lint, NOT ruff\n```\n\n### 4. Regression Tests\n\n**Existing Python projects still work:**\n- Run mala on a Python repo (e.g., ~/mala itself)\n- Verify agents see \"uvx ruff check\", \"uv run pytest\"\n- Verify quality gate passes\n\n### 5. Edge Cases\n\n- Preset with empty string command → should use fallback\n- Preset with command containing special chars (quotes, $vars) → should escape properly in template\n- No preset configured → graceful fallback, no crash","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-02T23:51:40.856175149Z","created_by":"cyou","updated_at":"2026-01-03T00:31:02.13286291Z","closed_at":"2026-01-03T00:31:02.13286291Z","close_reason":"Closed"}
{"id":"mala-vrm","title":"Refactor: move implementer prompt to src/prompts/","description":"## Context\nThe implementer prompt lives at `src/implementer_prompt.md`. The proposed structure puts prompts under `src/prompts/`.\n\n## Scope\nMove the prompt file only. Do **not** update loaders or tests in this issue; integration will wire it up later.\n\n### Files\n- Move: `src/implementer_prompt.md` → `src/prompts/implementer_prompt.md`\n\n## Requirements\n- Prompt content must be unchanged after move.\n\n## Acceptance Criteria\n- File exists at `src/prompts/implementer_prompt.md` with identical content.\n- No other files are modified in this issue.\n\n## Notes\n- Integration issue will update imports/tests referencing `IMPLEMENTER_PROMPT_TEMPLATE`.\n","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-25T19:54:51.200086139Z","updated_at":"2025-12-25T20:08:18.586126987Z","closed_at":"2025-12-25T20:08:18.586126987Z","close_reason":"Closed"}
{"id":"mala-vw5w","title":"Epic: Agent SDK Reviewer Integration","description":"Implement AgentSDKReviewer as the default in-process code reviewer for mala, replacing external Cerberus process.\n\n**Goal**: Reduce installation friction (no Cerberus plugin required) and enable faster, more integrated code reviews using Claude Agent SDK directly.\n\n**Spec**: /home/cyou/mala/plans/2026-01-07-agent-sdk-reviewer-spec.md\n**Plan**: /home/cyou/mala/plans/2026-01-07-agent-sdk-reviewer-plan.md\n\n**Phases**:\n- Phase 1: Infrastructure \u0026 Config (prompts, config fields)\n- Phase 2: AgentSDKReviewer Implementation (TDD: skeleton→integration→implementation)\n- Phase 3: Factory Integration (reviewer selection logic)\n- Phase 4: Testing (unit, integration, E2E)\n\n**Success Criteria**:\n- Agent SDK reviewer works by default without Cerberus installation\n- Cerberus remains available as optional override via config\n- All 7 spec requirements (R1-R7) verified with tests","status":"tombstone","priority":1,"issue_type":"epic","created_at":"2026-01-07T03:15:07.97137038Z","created_by":"cyou","updated_at":"2026-01-07T03:33:06.314899406Z","deleted_at":"2026-01-07T03:33:06.314899406Z","deleted_by":"batch delete","delete_reason":"batch delete","original_type":"epic"}
{"id":"mala-vw5w.1","title":"[T001] Create review system prompt","description":"**Type**: task\n**Phase**: Setup/Foundation\n**Primary Files**: src/prompts/review.md (New)\n**Subsystems**: config\n\n**Goal**: Create review system prompt for Agent SDK reviewer based on Cerberus prompt\n\n**Changes**:\n- src/prompts/review.md — New — Copy from Cerberus review-code.md, adapt to use int priorities (0-3)\n\n**Acceptance Criteria**:\n- File exists with review instructions, JSON schema, P0-P3 definitions\n\n**Verification**:\ntest -f src/prompts/review.md \u0026\u0026 grep -q 'verdict' src/prompts/review.md","status":"tombstone","priority":2,"issue_type":"task","created_at":"2026-01-07T03:15:43.084480009Z","created_by":"cyou","updated_at":"2026-01-07T03:18:35.437924984Z","deleted_at":"2026-01-07T03:18:35.437924984Z","deleted_by":"cyou","delete_reason":"delete","original_type":"task"}
{"id":"mala-vw5w.2","title":"[T001] Create review system prompt","description":"**Phase**: Setup/Foundation\n**Files**: src/prompts/review.md (New)\n**Subsystems**: prompts\n\n**Goal**: Create review system prompt for Agent SDK reviewer\n\n**Changes**:\n- Copy from /home/cyou/.claude/plugins/cache/cerberus/cerberus/*/prompts/generators/review-code.md\n- Adapt to use int priorities (0-3) instead of strings\n- Include JSON schema, P0-P3 definitions, focus areas\n\n**Verification**:\ntest -f src/prompts/review.md \u0026\u0026 grep -q 'verdict' src/prompts/review.md\n\n**Notes**: Priority format critical - use int (0-3) not string (P0-P3)","status":"tombstone","priority":2,"issue_type":"task","created_at":"2026-01-07T03:16:06.835845815Z","created_by":"cyou","updated_at":"2026-01-07T03:33:06.314899406Z","deleted_at":"2026-01-07T03:33:06.314899406Z","deleted_by":"batch delete","delete_reason":"batch delete","original_type":"task"}
{"id":"mala-vw5w.3","title":"[T002] Add config fields for reviewer selection","description":"**Phase**: Setup/Foundation\n**Files**: src/domain/validation/config.py (Modify), src/domain/prompts.py (Modify)\n**Subsystems**: config\n\n**Goal**: Add configuration fields for reviewer type and Agent SDK settings\n\n**Changes**:\n- ValidationConfig (line 592): Add reviewer_type, agent_sdk_review_timeout, agent_sdk_reviewer_model\n- PromptProvider (line 87): Add review_prompt with default empty string\n- load_prompts() (line 110): Add review_prompt loading\n\n**Verification**:\ngrep -q 'reviewer_type' src/domain/validation/config.py \u0026\u0026 grep -q 'review_prompt' src/domain/prompts.py\n\n**Config Override Test**: Add test proving reviewer_type config reaches factory","status":"tombstone","priority":2,"issue_type":"task","created_at":"2026-01-07T03:16:21.763973576Z","created_by":"cyou","updated_at":"2026-01-07T03:33:06.314899406Z","deleted_at":"2026-01-07T03:33:06.314899406Z","deleted_by":"batch delete","delete_reason":"batch delete","original_type":"task"}
{"id":"mala-vw5w.4","title":"[T003] [integration-path-test] AgentSDKReviewer skeleton + integration test","description":"**Phase**: Implementation (TDD: Skeleton + Integration Test)\n**Files**: src/infra/clients/agent_sdk_review.py (New), tests/integration/test_factory_wiring.py (New)\n**Subsystems**: infra/clients\n\n**Goal**: Create skeleton AgentSDKReviewer class with stub methods and failing integration test\n\n**Changes**:\n- Create AgentSDKReviewer dataclass with protocol signature\n- Stub methods return NotImplementedError\n- Integration test via factory.create_orchestrator() - should fail on NotImplementedError\n- Test covers: reviewer_type=agent_sdk instantiates AgentSDKReviewer\n\n**Acceptance Criteria**:\n- Project builds/compiles\n- Integration test fails for intended reason (NotImplementedError)\n- Test traverses composition root (factory → reviewer)\n\n**Verification**:\npytest tests/integration/test_factory_wiring.py -k agent_sdk (should fail with NotImplementedError)\n\n**Integration Path Test**: This task includes factory→reviewer path test","status":"tombstone","priority":2,"issue_type":"task","created_at":"2026-01-07T03:16:38.050455232Z","created_by":"cyou","updated_at":"2026-01-07T03:33:06.314899406Z","deleted_at":"2026-01-07T03:33:06.314899406Z","deleted_by":"batch delete","delete_reason":"batch delete","original_type":"task"}
{"id":"mala-vw5w.5","title":"[T004] Implement AgentSDKReviewer core logic (unit tests + code)","description":"**Phase**: Implementation\n**Files**: src/infra/clients/agent_sdk_review.py (Modify), tests/unit/infra/test_agent_sdk_review.py (New)\n**Subsystems**: infra/clients\n\n**Goal**: Implement AgentSDKReviewer.__call__ with unit tests\n\n**TDD Steps**:\n1. Write failing unit tests (empty diff, valid response, timeout, errors)\n2. Implement __call__, _check_diff_empty, _get_diff_content, _parse_response\n3. Verify integration test passes\n\n**Changes**:\n- AsyncAnthropic client, await messages.create()\n- Empty diff short-circuit (git diff --stat check)\n- JSON parsing with robust extraction (code blocks + fallback)\n- Error handling (timeout→parse_error, auth→fatal_error)\n- Event sink telemetry (on_review_started, on_review_passed, on_review_retry)\n\n**Unit Tests** (10 cases):\n- test_empty_diff_returns_pass\n- test_successful_review_pass/fail\n- test_malformed_json, test_missing_verdict, test_invalid_verdict\n- test_api_timeout, test_auth_error\n- test_context_file_included\n- test_overrides_disabled_setting\n\n**Final Verification**: All tests pass (unit + integration from T003)\n\n**Acceptance Criteria**:\n- All unit tests pass\n- Integration test from T003 now passes\n- Empty diff short-circuits correctly\n- JSON parsing handles nested objects","status":"tombstone","priority":2,"issue_type":"task","created_at":"2026-01-07T03:16:57.110684405Z","created_by":"cyou","updated_at":"2026-01-07T03:33:06.314899406Z","deleted_at":"2026-01-07T03:33:06.314899406Z","deleted_by":"batch delete","delete_reason":"batch delete","original_type":"task"}
{"id":"mala-vw5w.6","title":"[T005] Factory integration - reviewer selection logic","description":"**Phase**: Factory Integration\n**Files**: src/orchestration/factory.py (Modify)\n**Subsystems**: orchestration\n\n**Goal**: Update factory to instantiate correct reviewer based on config\n\n**Changes**:\n- Import AgentSDKReviewer\n- Read reviewer_type from config (default: agent_sdk)\n- Conditional logic: if cerberus → DefaultReviewer, else → AgentSDKReviewer\n- Pass review_prompt from prompts.review_prompt\n- Pass configurable model and timeout from config\n\n**Wiring Map**:\n- reviewer_type config → factory.create_orchestrator() → AgentSDKReviewer instantiation\n- review_prompt file → PromptProvider.review_prompt → AgentSDKReviewer.review_prompt\n- agent_sdk_reviewer_model config → AgentSDKReviewer.model\n\n**Verification**:\npytest tests/integration/test_factory_wiring.py (all tests pass)\n\n**Acceptance Criteria**:\n- reviewer_type=agent_sdk creates AgentSDKReviewer\n- reviewer_type=cerberus creates DefaultReviewer\n- Config values reach reviewer constructor","status":"tombstone","priority":2,"issue_type":"task","created_at":"2026-01-07T03:17:12.996583861Z","created_by":"cyou","updated_at":"2026-01-07T03:33:06.314899406Z","deleted_at":"2026-01-07T03:33:06.314899406Z","deleted_by":"batch delete","delete_reason":"batch delete","original_type":"task"}
{"id":"mala-vw5w.7","title":"[T006] Add E2E test with real Agent SDK","description":"**Phase**: Testing (E2E)\n**Files**: tests/e2e/test_agent_sdk_e2e.py (New)\n**Subsystems**: tests\n\n**Goal**: Create E2E test that makes real API call to verify end-to-end functionality\n\n**Changes**:\n- Mark with @pytest.mark.e2e\n- Requires ANTHROPIC_API_KEY environment variable\n- Create small test diff (add dummy file)\n- Run AgentSDKReviewer, verify ReviewResult structure\n- Assert verdict exists, issues is list\n\n**Verification**:\nANTHROPIC_API_KEY=$KEY pytest tests/e2e/test_agent_sdk_e2e.py -m e2e\n\n**Acceptance Criteria**:\n- E2E test makes real API call\n- Returns valid ReviewResult\n- Test skipped if ANTHROPIC_API_KEY not set\n\n**Notes**: Optional - can be skipped if API costs are concern","status":"tombstone","priority":3,"issue_type":"task","created_at":"2026-01-07T03:17:30.058649547Z","created_by":"cyou","updated_at":"2026-01-07T03:33:06.314899406Z","deleted_at":"2026-01-07T03:33:06.314899406Z","deleted_by":"batch delete","delete_reason":"batch delete","original_type":"task"}
{"id":"mala-w1js","title":"Add higher-level test for idle timeout resume in AgentSessionRunner","description":"Current coverage for idle retry is unit-only (IdleTimeoutRetryPolicy). Add an integration-style test that drives AgentSessionRunner with a fake SDK stream and verifies idle timeout retry/backoff + session resume wiring end-to-end.\\n\\nFiles: tests/unit/pipeline/test_agent_session_runner.py (or new integration test).\\n\\nAcceptance: test simulates idle timeout then resume and asserts state reset + query retry + session_id use.","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-05T01:27:21.163367261Z","created_by":"cyou","updated_at":"2026-01-05T01:51:19.108282282Z","closed_at":"2026-01-05T01:51:19.108282282Z","close_reason":"Closed"}
{"id":"mala-w1pu","title":"Fix OrchestratorState lifecycle (state reset vs per-instance)","description":"Medium finding: OrchestratorState is documented as per-run but instantiated once in _init_runtime_state and never reset. Risk: state leakage across runs if a MalaOrchestrator instance is reused. Decide on contract (per-run vs per-instance) and align implementation/docs accordingly.\\n\\nFiles: src/orchestration/orchestrator.py (state initialization in __init__/_init_runtime_state), src/orchestration/orchestrator_state.py (docstring says created at start of run()).\\n\\nAcceptance: Either reinitialize _state at start of run() or update docs/tests to clearly state per-instance and ensure no reuse expectations.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-05T00:55:07.436187204Z","created_by":"cyou","updated_at":"2026-01-05T01:28:39.386780393Z","closed_at":"2026-01-05T01:28:39.386780393Z","close_reason":"Closed"}
{"id":"mala-w3zd","title":"[Review] 1 non-blocking finding from mala-iniy","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-iniy\n**Highest priority:** P3\n\n---\n\n### Finding 1: [P3] Make git-log FakeCommandRunner fail-closed by default\n\n**Priority:** P3\n**Reviewer:** codex\n**Location:** tests/unit/domain/test_quality_gate.py:55-66\n\n`make_git_log_response_runner(...)` builds `FakeCommandRunner(..., allow_unregistered=True)`, which means unexpected/changed `git log` args can silently return a “success with empty stdout” instead of failing fast. That can reduce test signal (e.g., a wrong `git log` invocation may look like “no commit found” rather than an assertion failure). Prefer `allow_unregistered=False` here and register any extra expected commands explicitly (or add an opt-in flag for the rare cases that truly need unregistered calls).\n\n---\n\n\u003c!-- fp:f5b8b382fff8541f --\u003e\n\u003c!-- review_finding:348cc4dca957 --\u003e","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-05T17:58:28.758147093Z","created_by":"cyou","updated_at":"2026-01-05T18:03:56.410315957Z","closed_at":"2026-01-05T18:03:56.410315957Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-iniy"]}
{"id":"mala-w85r","title":"[Review] src/domain/validation/code_pattern_matcher.py:39-46: [P2] Prevent **/ from matching within a filename","description":"## Review Finding\n\nThis issue was auto-created from a P2 review finding.\n\n**Source issue:** mala-fdp1.6\n**Reviewer:** codex\n**Location:** src/domain/validation/code_pattern_matcher.py:39-46\n\n## Details\n\n`**/` is translated as `.*` plus an optional slash, so patterns like `**/test_*.py` will incorrectly match `mytest_main.py` (no slash) and `src/abctest.py`. In glob semantics, `**/` should only match whole directory segments (including zero), not partial prefixes. Consider translating `**/` to something like `(?:.*/)?` or `(?:[^/]+/)*` so a slash is required if `**` consumes characters.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-02T03:15:34.530811916Z","created_by":"cyou","updated_at":"2026-01-02T03:24:09.994891692Z","closed_at":"2026-01-02T03:24:09.994891692Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_finding:866693220841","source:mala-fdp1.6"]}
{"id":"mala-w8w","title":"Epic: Code health fixes from 2025-12-26 review","description":"Context:\n- Umbrella epic for code health findings from the 2025-12-26 review.\n- Groups small fixes in orchestrator startup, validation runner robustness, and repo hygiene.\n\nScope:\n- In: child issues addressing specific review findings.\n- Out: larger validation pipeline refactors and new feature work.\n\nAcceptance Criteria:\n- All child issues are closed.\n\nTest Plan:\n- Covered by each child issue.","status":"closed","priority":1,"issue_type":"epic","created_at":"2025-12-26T23:06:47.086956645Z","updated_at":"2025-12-27T09:03:57.370866039Z","closed_at":"2025-12-27T09:03:57.370866039Z","close_reason":"All children completed"}
{"id":"mala-w8w.1","title":"Orchestrator startup: preserve env + create nested lock dir","description":"Context:\n- Needs verification: ClaudeAgentOptions may treat env as a full replacement, which can drop required vars when tools run.\n- agent_env is built in src/orchestrator.py:281 without inheriting os.environ.\n- LOCK_DIR is created in src/orchestrator.py:669 without parents=True, which fails for nested MALA_LOCK_DIR paths.\n\nScope:\n- In: merge os.environ into agent_env and ensure LOCK_DIR mkdir uses parents=True.\n- Out: changes to permission modes, hook behavior, or tool restrictions.\n\nAcceptance Criteria:\n- Agent tool calls see inherited env vars plus lock overrides (PATH/LOCK_DIR/AGENT_ID/REPO_NAMESPACE).\n- Orchestrator startup succeeds when MALA_LOCK_DIR points to a nested, non-existent directory.\n- Behavior of lock scripts and PATH resolution remains unchanged.\n\nTest Plan:\n- TDD: add failing test(s) in tests/test_orchestrator.py for env inheritance and nested lock dir creation.\n- Implement the fix, then run uv run pytest tests/test_orchestrator.py.\n\nNotes for Agent:\n- If SDK already merges envs, document/verify and keep behavior explicit.","status":"closed","priority":1,"issue_type":"bug","created_at":"2025-12-26T23:06:57.435746036Z","updated_at":"2025-12-27T07:06:35.564633929Z","closed_at":"2025-12-27T07:06:35.564633929Z","close_reason":"Closed","labels":["needs-verification"],"dependencies":[{"issue_id":"mala-w8w.1","depends_on_id":"mala-w8w","type":"parent-child","created_at":"2025-12-26T23:06:57.442345813Z","created_by":"daemon"}]}
{"id":"mala-w8w.2","title":"ValidationRunner robustness: handle timeout text + valid E2E flags","description":"Context:\n- _run_command decodes TimeoutExpired stdout/stderr as bytes in src/validation.py:194, which can raise if they are already strings.\n- _run_e2e builds a mala run command with --no-post-validate/--no-e2e in src/validation.py:235, but these flags are not in the CLI.\n\nScope:\n- In: make timeout handling robust for str/bytes outputs; remove or replace unsupported flags in the E2E command.\n- Out: implementing the broader validation pipeline plan or new CLI flags beyond what’s needed for compatibility.\n\nAcceptance Criteria:\n- Timeout handling never raises when TimeoutExpired outputs are strings.\n- E2E command uses only supported CLI flags.\n- Existing validation behavior (exit codes, stderr/stdout tailing) is preserved.\n\nTest Plan:\n- TDD: add failing tests in tests/test_validation.py for string TimeoutExpired outputs and E2E command construction.\n- Implement fixes, then run uv run pytest tests/test_validation.py.\n\nNotes for Agent:\n- Keep changes localized to validation.py and its tests.","status":"closed","priority":2,"issue_type":"bug","created_at":"2025-12-26T23:07:13.48342038Z","updated_at":"2025-12-27T06:19:26.498789919Z","closed_at":"2025-12-27T06:19:26.498789919Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-w8w.2","depends_on_id":"mala-w8w","type":"parent-child","created_at":"2025-12-26T23:07:13.489971047Z","created_by":"daemon"}]}
{"id":"mala-w8w.3","title":"Remove dist artifacts and ignore dist/ outputs","description":"Context:\n- dist/ contains built artifacts (wheel/tarball) that can go stale and confuse releases.\n- Repo currently lacks a .gitignore entry for dist/ (file does not exist in tree).\n\nScope:\n- In: remove tracked dist artifacts and add dist/ to .gitignore.\n- Out: build system changes or release automation updates.\n\nAcceptance Criteria:\n- dist/ artifacts are removed from git history (tracked files deleted).\n- .gitignore includes dist/ to prevent reintroduction.\n\nTest Plan:\n- None (file cleanup only).\n\nNotes for Agent:\n- Ensure no other generated paths are unintentionally removed.","status":"closed","priority":4,"issue_type":"chore","created_at":"2025-12-26T23:07:20.726896981Z","updated_at":"2025-12-27T06:39:09.897140907Z","closed_at":"2025-12-27T06:39:09.897140907Z","close_reason":"Already resolved: dist/ is in .gitignore and no artifacts are tracked","dependencies":[{"issue_id":"mala-w8w.3","depends_on_id":"mala-w8w","type":"parent-child","created_at":"2025-12-26T23:07:20.735168885Z","created_by":"daemon"}]}
{"id":"mala-wd1","title":"Refactor: extract Claude SDK subagent loop","description":"## Context\nThe Claude SDK receive loop is currently embedded in `MalaOrchestrator.run_implementer` in `src/main.py`. It should be extracted to a reusable `src/agent_loop.py` module.\n\n## Scope\nCreate `src/agent_loop.py` only. Do **not** modify `src/main.py` in this issue; integration will wire it up later.\n\n### Files\n- Add: `src/agent_loop.py`\n\n## Requirements\n- The extracted loop should be the same logic as today:\n  - `async with ClaudeSDKClient(options=options) as client:`\n  - `await client.query(prompt)`\n  - `async for message in client.receive_response(): ...`\n- It must still:\n  - Handle `AssistantMessage` / `ResultMessage` logging exactly as today.\n  - Support JSONL logging and Braintrust logging (existing behavior).\n- Return the final result text and any needed success/summary data in a way `run_implementer` can use.\n\n## Acceptance Criteria\n- `src/agent_loop.py` exists with the extracted loop behavior.\n- No other files are modified in this issue.\n\n## Notes\n- Integration issue will delegate to this module from `src/main.py`.\n","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-25T19:54:51.165716848Z","updated_at":"2025-12-25T20:11:59.449563429Z","closed_at":"2025-12-25T20:11:59.449563429Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-wd1","depends_on_id":"mala-1by","type":"blocks","created_at":"2025-12-25T20:06:47.467342748Z","created_by":"daemon"}]}
{"id":"mala-wg1","title":"Add baseline coverage functions to coverage.py","description":"Context:\n- `coverage.py` currently only parses and checks against fixed thresholds\n- Need new functions: `get_baseline_coverage()` and `is_baseline_stale()`\n- Must distinguish \"missing\" (returns None) from \"parse error\" (raises/fails)\n- Staleness detection: compare mtime vs last commit time + check dirty working tree\n\nScope:\n- In: Add `get_baseline_coverage(report_path: Path) -\u003e float | None`\n- In: Add `is_baseline_stale(report_path: Path, repo_path: Path) -\u003e bool`\n- In: Update `check_coverage_threshold` to accept `min_percent: float | None`\n- In: Update `parse_and_check_coverage` signature similarly\n- Out: Auto-refresh logic (that's in spec_runner.py)\n\nAcceptance Criteria:\n- `get_baseline_coverage()` returns percentage if file exists and valid, None if missing, raises on parse errors\n- `is_baseline_stale()` returns True if mtime \u003c last commit time OR repo is dirty\n- `is_baseline_stale()` handles non-git repos gracefully (returns True if git commands fail)\n- `check_coverage_threshold(result, None)` returns PASSED\n\nTest Plan:\n- TDD: Write failing tests for each new function\n- Test missing file → returns None\n- Test valid file → returns percentage\n- Test malformed XML → raises error\n- Test stale mtime → returns True\n- Test dirty repo → returns True\n- Test non-git repo → handles gracefully\n\nNotes for Agent:\n- Use `git log -1 --format=%ct` for last commit time\n- Use `git status --porcelain` to check dirty status\n- Wrap git commands in try/except for non-git fallback\n- Primary files: src/validation/coverage.py, tests/test_coverage.py","status":"tombstone","priority":1,"issue_type":"task","created_at":"2025-12-27T18:13:44.092339695Z","updated_at":"2025-12-27T18:20:40.62834641Z","labels":["coverage"],"deleted_at":"2025-12-27T18:20:40.62834641Z","deleted_by":"daemon","delete_reason":"delete","original_type":"task"}
{"id":"mala-wjoc","title":"[Review] src/domain/validation/tool_name_extractor.py:195: [P3] Redundant truthy check in _is_meaningful_tool","description":"## Review Finding\n\nThis issue was auto-created from a P3 review finding.\n\n**Source issue:** mala-fdp1.5\n**Reviewer:** claude\n**Location:** src/domain/validation/tool_name_extractor.py:195\n\n## Details\n\nLine 195 has `if tool_name` as a guard on `tool_name.split()`, but this is already guaranteed to be truthy due to the `if not tool_name: return False` check on line 192.","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-02T03:17:47.845864972Z","created_by":"cyou","updated_at":"2026-01-02T03:21:30.891492058Z","closed_at":"2026-01-02T03:21:30.891492058Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_finding:f26991de2d0e","source:mala-fdp1.5"]}
{"id":"mala-wnke","title":"Epic: mala logs CLI Command","description":"## Context \u0026 Goals\nAdd `mala logs` CLI subcommand group for searching run metadata and session logs. Enables debugging and historical tracking without manual JSON inspection.\n\n**Spec**: `docs/2026-01-05-mala-logs-cli-spec.md`\n**Plan**: `docs/2026-01-05-mala-logs-cli-plan.md`\n\n## Subcommands\n- `mala logs list [--json] [--all]` - Show 20 most recent runs with summary stats\n- `mala logs sessions --issue \u003cid\u003e [--json] [--all]` - Find sessions by issue ID\n- `mala logs show \u003crun-id\u003e [--json]` - Show detailed run metadata\n\n## Key Decisions\n- Separate module `src/cli/logs.py` registered via `app.add_typer()`\n- Table output with `tabulate` (tablefmt=\"simple\"), JSON via `--json` flag\n- CWD-based repo scoping (same as `mala run`), `--all` for all repos\n- Lightweight JSON parsing for `list`/`sessions`, full load for `show`\n\n## Files\n- `src/cli/cli.py` — Add logs subcommand registration\n- `src/cli/logs.py` — **New** — Logs subcommand implementation\n- `pyproject.toml` — Add `tabulate` dependency\n- `tests/unit/cli/test_logs.py` — **New** — Unit tests\n- `tests/integration/cli/test_logs_integration.py` — **New** — Integration tests\n\n## Acceptance Criteria (from spec)\n- AC1: `list` shows 20 most recent runs with run_id, started_at, issue_count, success/fail/timeout counts, metadata_path\n- AC2: `sessions --issue` finds all sessions for issue ID (exact match, case-sensitive)\n- AC3: `show \u003crun-id\u003e` displays detailed metadata including all sessions with JSONL paths\n- AC4: Run-id accepts full UUID or 8-char prefix; ambiguous prefix errors with match list\n- AC5: Default human-readable table; `--json` for JSON-only output\n- AC6: Default current repo; `--all` searches all repos (adds repo_path column)\n- AC7: Empty state exits 0 with \"No runs found\" / `[]` JSON\n- AC8: Corrupt files skipped with stderr warning, continue scanning\n- AC9: Null values show `-` in table, `null` in JSON\n- AC10: Exit codes: 0=success, 1=not-found/ambiguous, 2=corrupt (show only)","status":"closed","priority":1,"issue_type":"epic","created_at":"2026-01-05T18:02:19.396356754Z","created_by":"cyou","updated_at":"2026-01-06T03:15:27.37674952Z","closed_at":"2026-01-06T03:15:27.37674952Z","close_reason":"Closed"}
{"id":"mala-wnke.1","title":"[T001] Add tabulate dependency to pyproject.toml","description":"**Type**: task (setup)\n**Priority**: P1\n**Primary Files**: pyproject.toml\n**Subsystems**: config\n**Dependencies**: None\n\n## Goal\nAdd `tabulate` library as a project dependency to enable table formatting in the logs CLI commands.\n\n## Context\n- The `mala logs` CLI needs human-readable table output\n- `tabulate` provides consistent table formatting with minimal code\n- Will use `tablefmt=\"simple\"` style to match existing mala CLI aesthetics\n\n## Scope\n- In: Add `tabulate` to dependencies in pyproject.toml\n- Out: Any code changes (handled in subsequent tasks)\n\n## Changes\n- `pyproject.toml` — [Exists] — Add `tabulate\u003e=0.9.0` to project dependencies\n\n## Acceptance Criteria\n- `tabulate` appears in `[project.dependencies]` section of pyproject.toml\n- `uv sync` completes successfully after the change\n- `uv run python -c \"import tabulate; print(tabulate.__version__)\"` works\n\n## Verification\n```bash\nuv sync\nuv run python -c \"import tabulate; print(tabulate.__version__)\"\n```\n\n## Notes for Agent\n- Add to the main dependencies list, not dev-dependencies\n- Version constraint `\u003e=0.9.0` is sufficient (stable API)\n- No need to run tests - this is just a dependency addition","notes":"Quality gate failed: Quality gate failed: Missing validation evidence for: test, format, lint, typecheck\nLog: /home/cyou/.claude/projects/-home-cyou-mala/00d17322-b07b-406b-b2e1-4bdaa4e7852e.jsonl","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-05T18:02:38.89370075Z","created_by":"cyou","updated_at":"2026-01-06T01:00:16.498266549Z","closed_at":"2026-01-06T01:00:16.498266549Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-wnke.1","depends_on_id":"mala-wnke","type":"parent-child","created_at":"2026-01-05T18:02:38.894217678Z","created_by":"cyou"}]}
{"id":"mala-wnke.10","title":"[Review] 1 non-blocking finding from mala-wnke.9","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-wnke.9\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Don’t re-raise successful Typer/Click Exit(0) in xfail guards\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** tests/integration/cli/test_logs_integration.py:93-100\n\n`typer.testing.CliRunner.invoke()` can produce `result.exception` for a normal/intentional early-exit (e.g. `raise typer.Exit(code=0)` / Click `Exit`) while still having `result.exit_code == 0`. With the new logic, that scenario hits `raise result.exception` and turns a successful implementation into an error traceback, bypassing the intended \"Implementation landed\" prompt.\n\nConsider treating `exception != None and exit_code == 0` as the “implementation landed” path, e.g.:\n\n```python\nif result.exception is not None and result.exit_code == 0:\n    pytest.fail(\"Implementation landed; ...\")\n```\n\n(Then keep re-raising for non-zero / truly unexpected exceptions.)\n\n---\n\n\u003c!-- fp:de449ffade0b7c6f --\u003e\n\u003c!-- review_finding:a4b2b33b1b2b --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-06T01:25:45.499909536Z","created_by":"cyou","updated_at":"2026-01-06T01:27:40.075004928Z","closed_at":"2026-01-06T01:27:40.075004928Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-wnke.9"],"dependencies":[{"issue_id":"mala-wnke.10","depends_on_id":"mala-wnke","type":"parent-child","created_at":"2026-01-06T01:25:45.500327693Z","created_by":"cyou"}]}
{"id":"mala-wnke.11","title":"[Review] 2 non-blocking findings from mala-wnke.3","description":"## Review Findings\n\nThis issue consolidates 2 non-blocking findings from code review.\n\n**Source issue:** mala-wnke.3\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Avoid O(N) JSON parsing for `mala logs list`\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/cli/logs.py:221-226\n\n`list_runs()` currently parses *every* run JSON (`runs = _collect_runs(files)`) and only then sorts/slices (`_sort_runs(runs)[:limit]`). This can get noticeably slow when there are many runs, and `--all` amplifies it by `rglob`-ing across repos. Since run metadata filenames are already timestamp-prefixed for sorting (see `RunMetadata.save()`), you can iterate newest-first and stop once you’ve collected `limit` valid runs (then optionally sort that small set for determinism).\n\n---\n\n### Finding 2: [P2] `_parse_run_file` can crash on bad file encoding\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/cli/logs.py:89-100\n\nCorrupt files with invalid text encoding will raise `UnicodeDecodeError` during `path.open()`/read, which isn’t caught (only `json.JSONDecodeError`/`OSError`), so `mala logs list` can abort instead of skipping with a warning. Catch `UnicodeDecodeError` alongside the existing exceptions so corruption is handled consistently.\n\n---\n\n\u003c!-- fp:f4cd9787aa8ecd1f --\u003e\n\u003c!-- fp:d23a6817c997b770 --\u003e\n\u003c!-- review_finding:5b0c5c48b7ea --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-06T01:52:53.932193888Z","created_by":"cyou","updated_at":"2026-01-06T01:57:42.527142899Z","closed_at":"2026-01-06T01:57:42.527142899Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-wnke.3"],"dependencies":[{"issue_id":"mala-wnke.11","depends_on_id":"mala-wnke","type":"parent-child","created_at":"2026-01-06T01:52:53.932647366Z","created_by":"cyou"}]}
{"id":"mala-wnke.12","title":"[Review] 3 non-blocking findings from mala-wnke.11","description":"## Review Findings\n\nThis issue consolidates 3 non-blocking findings from code review.\n\n**Source issue:** mala-wnke.11\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Early termination can return wrong runs when timestamps tie at boundary\n\n**Priority:** P2\n**Reviewer:** claude\n**Location:** src/cli/logs.py:229\n\nThe optimization assumes filename sort order matches `_sort_runs` order, but they differ on ties. Filenames tie-break on UUID prefix while `_sort_runs` uses ascending `run_id`. Example: with limit=2 and files `A(ts=10:00)`, `B(ts=09:59, run_id=aaa)`, `C(ts=09:59, run_id=bbb)`, if C's UUID prefix \u003e B's, filename order is A, C, B. Collecting 2 returns A, C instead of correct A, B. This only affects cases where multiple runs start in the same second *and* land exactly at the limit boundary, so it's unlikely but technically incorrect.\n\n---\n\n### Finding 2: [P2] Preserve top-N correctness when limiting by filename order\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/cli/logs.py:227-233\n\n`list_runs()` now stops parsing once it has `limit` valid runs based on filename-desc order, then sorts that subset. This can return a different set than the prior behavior (parse all → sort by `started_at` → slice), because filenames only include second-resolution timestamps (`%Y-%m-%dT%H-%M-%S`) while `started_at` is `isoformat()` (often includes microseconds).\n\nScenario: if multiple runs start within the same second, their filenames share the same timestamp prefix and differ only by the `_shortid`; filename order within that second is not guaranteed to match `started_at` (microseconds) or the tie-breaker (`run_id`). With `--limit N`, you may exclude a run that should be in the top-N by `started_at`.\n\nA common fix is: after reaching `limit`, continue scanning/parsing any additional files that share the same timestamp prefix as the last-included file, then `_sort_runs(...)` and slice to `limit` (restoring prior semantics while keeping work bounded).\n\n---\n\n### Finding 3: [P2] Potential loss of correctness in `list_runs` due to early termination\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/cli/logs.py:224-228\n\nThe change to `_collect_runs` assumes that filename-based sorting is a perfect proxy for the `started_at` sorting used in `_sort_runs`. If a file with a lexically 'older' filename contains a run with a 'newer' `started_at` timestamp (e.g., due to clock skew or delayed metadata write), it will be incorrectly skipped when `limit` is reached. While the optimization is valuable, it sacrifices the strict correctness previously guaranteed by parsing all files before sorting.\n\n---\n\n\u003c!-- fp:55ffa56ed90fb16a --\u003e\n\u003c!-- fp:b580a3984eb1189a --\u003e\n\u003c!-- fp:732c69e0de4294df --\u003e\n\u003c!-- review_finding:06bef5abc0b8 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-06T01:57:42.566726904Z","created_by":"cyou","updated_at":"2026-01-06T02:04:23.548912351Z","closed_at":"2026-01-06T02:04:23.548912351Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-wnke.11"],"dependencies":[{"issue_id":"mala-wnke.12","depends_on_id":"mala-wnke","type":"parent-child","created_at":"2026-01-06T01:57:42.567190025Z","created_by":"cyou"}]}
{"id":"mala-wnke.13","title":"[Review] 6 non-blocking findings from mala-wnke.4","description":"## Review Findings\n\nThis issue consolidates 6 non-blocking findings from code review.\n\n**Source issue:** mala-wnke.4\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Make `--issue` required for `logs sessions`\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/cli/logs.py:395-403\n\nThe implementation makes `--issue` optional (`issue: str | None = None`), which allows `mala logs sessions` to list *all* issues’ sessions. The task/ACs describe `sessions --issue \u003cid\u003e` as the intended entrypoint and explicitly call `--issue` required; leaving it optional can produce unexpectedly large output and deviates from the stated contract. Make the option required (change type to `str` and use Typer’s required default), and update integration tests that invoke `logs sessions` without `--issue`.\n\n---\n\n### Finding 2: [P3] Avoid O(n*m) repo_path lookup in JSON `--all` output\n\n**Priority:** P3\n**Reviewer:** codex\n**Location:** src/cli/logs.py:432-449\n\nWhen `--json --all` is used, repo_path is derived by scanning `runs` for each session row (nested loop). This becomes quadratic with many runs/sessions. You already build `run_repo_map` for table output; reuse the same mapping for JSON output too (e.g., `entry[\"repo_path\"] = run_repo_map.get(s[\"run_id\"])`).\n\n---\n\n### Finding 3: [P3] Tighten empty-output assertions for sessions integration tests\n\n**Priority:** P3\n**Reviewer:** codex\n**Location:** tests/integration/cli/test_logs_integration.py:91-97\n\nThe integration tests allow blank output for the non-JSON empty case (`\"No sessions found\" in output or output.strip() == \"\"`). Since the command prints `\"No sessions found\"` on empty results (and AC7 expects that), allowing empty output can hide regressions where the command accidentally prints nothing. Prefer asserting the expected message (or `[]` for `--json`) to make the tests meaningful.\n\n---\n\n### Finding 4: [P2] Avoid O(N*M) loop for repo_path in JSON output\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/cli/logs.py:394-399\n\nThe JSON output loop performs a nested search through all runs for every session to find the matching `repo_path`. This results in O(N*M) complexity which will cause significant performance degradation as the number of runs and sessions grows. Use a lookup map (similar to the table output) or attach the `repo_path` to the session row during extraction.\n\n---\n\n### Finding 5: [P2] Risk of incorrect repo_path due to non-unique run_id\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/cli/logs.py:417\n\nUsing `run_id` as a key for `repo_path` lookup is risky because `run_id` collisions may occur across different repositories. Additionally, the JSON output (which takes the first match in a newest-first list) and the Table output (where the last match in the map comprehension wins) could show inconsistent results for colliding IDs. Consider passing `repo_path` through `_extract_sessions` to ensure accuracy.\n\n---\n\n### Finding 6: [P3] Unnecessary map construction when all_sessions is False\n\n**Priority:** P3\n**Reviewer:** gemini\n**Location:** src/cli/logs.py:417\n\nThe `run_repo_map` is constructed even when `all_sessions` is `False`, incurring unnecessary overhead for `_get_repo_path` calls. This should be moved inside an `if all_sessions:` block to optimize the common case.\n\n---\n\n\u003c!-- fp:611084f7a6f6d55e --\u003e\n\u003c!-- fp:1b8e14572edb4ba6 --\u003e\n\u003c!-- fp:3eaf84b0fe362757 --\u003e\n\u003c!-- fp:77a4919d3178a314 --\u003e\n\u003c!-- fp:0564f540e9db7d66 --\u003e\n\u003c!-- fp:09a185393fa7d05f --\u003e\n\u003c!-- review_finding:341153722fc4 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-06T02:00:35.085749704Z","created_by":"cyou","updated_at":"2026-01-06T02:08:58.056804393Z","closed_at":"2026-01-06T02:08:58.056804393Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-wnke.4"],"dependencies":[{"issue_id":"mala-wnke.13","depends_on_id":"mala-wnke","type":"parent-child","created_at":"2026-01-06T02:00:35.086228755Z","created_by":"cyou"}]}
{"id":"mala-wnke.14","title":"[Review] 5 non-blocking findings from mala-wnke.12","description":"## Review Findings\n\nThis issue consolidates 5 non-blocking findings from code review.\n\n**Source issue:** mala-wnke.12\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P3] Redundant assignment of boundary_prefix\n\n**Priority:** P3\n**Reviewer:** claude\n**Location:** src/cli/logs.py:199-204\n\nLines 199-204 assign `boundary_prefix` twice. The first assignment on line 200 is immediately overwritten by the second on lines 202-204. This is dead code - the second assignment extracts from `Path(runs[-1][\"metadata_path\"]).name` which is the correct approach. The first assignment should be removed for clarity.\n\n---\n\n### Finding 2: [P2] Avoid IndexError when `_collect_runs(..., limit=0)`\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/cli/logs.py:197-204\n\n`_collect_runs` now checks `len(runs) \u003e= limit` before any runs are collected. If `limit=0` is ever passed (programmatic call or future CLI change), the first loop iteration enters the boundary logic and raises on `runs[-1]`.\n\nRepro:\n```python\n_collect_runs(files, limit=0)\n```\nA simple guard like “if `limit` is not None and `limit \u003c= 0`: return []” avoids the crash and makes the API robust.\n\n---\n\n### Finding 3: [P3] Fix invalid ISO timestamp in updated test data\n\n**Priority:** P3\n**Reviewer:** codex\n**Location:** tests/unit/cli/test_logs.py:305-313\n\n`test_collect_with_limit_stops_early` writes `started_at` as `T{i:02d}:00:00`; for `i=24` this becomes `T24:00:00+00:00`, which is not a valid `datetime.fromisoformat` time and would sort as `0.0` in `_sort_runs`. Even though this test doesn’t sort today, the “realistic filename format” change makes this test data misleading and could hide failures if the code later validates/parses `started_at`.\n\nUsing minutes/seconds (or `i % 24` for hours) keeps the timestamps valid.\n\n---\n\n### Finding 4: [P3] Remove redundant/incorrect boundary_prefix assignment\n\n**Priority:** P3\n**Reviewer:** codex\n**Location:** src/cli/logs.py:199-204\n\nInside the boundary logic, `boundary_prefix` is assigned from the full `metadata_path` string and then immediately overwritten with the filename-derived value. The first assignment violates `_extract_timestamp_prefix`’s contract (“filename not full path”) and is dead code, which makes the control flow harder to reason about.\n\n---\n\n### Finding 5: [P3] Remove redundant assignment to boundary_prefix\n\n**Priority:** P3\n**Reviewer:** gemini\n**Location:** src/cli/logs.py:195-196\n\nThe assignment to `boundary_prefix` at line 196 is redundant because it is immediately overwritten by the more robust version at line 198 (which correctly extracts the filename before getting the prefix). Removing lines 195-196 improves clarity.\n\n---\n\n\u003c!-- fp:864bc32c12ab5dbd --\u003e\n\u003c!-- fp:63e2e76bce366d5b --\u003e\n\u003c!-- fp:e500be90a67a5687 --\u003e\n\u003c!-- fp:39f3b3d63dda6f59 --\u003e\n\u003c!-- fp:29246b117c231aa6 --\u003e\n\u003c!-- review_finding:6e7230a011ad --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-06T02:04:23.592050418Z","created_by":"cyou","updated_at":"2026-01-06T02:16:02.301921905Z","closed_at":"2026-01-06T02:16:02.301921905Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-wnke.12"],"dependencies":[{"issue_id":"mala-wnke.14","depends_on_id":"mala-wnke","type":"parent-child","created_at":"2026-01-06T02:04:23.592621828Z","created_by":"cyou"}]}
{"id":"mala-wnke.2","title":"[T002] [integration-path-test] Skeleton + Integration: logs subcommand stubs and failing integration tests","description":"**Type**: task (skeleton + integration test)\n**Priority**: P1\n**Primary Files**: src/cli/logs.py, src/cli/cli.py, tests/integration/cli/test_logs_integration.py, tests/integration/cli/__init__.py\n**Subsystems**: cli\n**Dependencies**: T001 (tabulate must be available)\n\n## Goal\nCreate the `mala logs` subcommand skeleton with compile-ready stubs and register it in the main CLI. Add failing integration tests that exercise the end-to-end CLI path.\n\n## Context\n- This task establishes the wiring between the new logs module and the main CLI app\n- Integration tests exercise the real Typer CLI invocation path\n- Implementation tasks (T003-T005) will make these tests pass\n\n## Scope\n- In: Create `src/cli/logs.py` with Typer sub-app and stub commands (`list`, `sessions`, `show`)\n- In: Register `logs_app` in `src/cli/cli.py` via `app.add_typer()`\n- In: Create integration test file with failing tests for each command\n- Out: Full implementation (stubs only - return empty/raise NotImplementedError)\n\n## Changes\n- `src/cli/logs.py` — [New] — Create Typer sub-app with:\n  - `logs_app = typer.Typer(name=\"logs\", help=\"Search and inspect mala run logs\")`\n  - `list()` command stub with `--json` and `--all` flags (raises NotImplementedError)\n  - `sessions()` command stub with `--issue`, `--json`, `--all` flags (raises NotImplementedError)\n  - `show()` command stub with `run_id` arg and `--json` flag (raises NotImplementedError)\n- `src/cli/cli.py` — [Exists] — Add `from src.cli.logs import logs_app` and `app.add_typer(logs_app, name=\"logs\")`\n- `tests/integration/cli/__init__.py` — [New] — Create empty init file\n- `tests/integration/cli/test_logs_integration.py` — [New] — Create failing integration tests:\n  - Test `mala logs list` command invocation (expects exit 0 when implemented)\n  - Test `mala logs sessions --issue test-123` command invocation\n  - Test `mala logs show abc12345` command invocation\n  - Test `--json` flag produces valid JSON output\n  - Test `--all` flag works\n  - Use `tmp_path` for synthetic run metadata files\n  - Use `CliRunner` from typer.testing or subprocess\n\n## Wiring Map\n- `src/cli/logs.py` (new module) → `src/cli/cli.py` (app.add_typer registration) → `mala logs` CLI invocation\n\n## Acceptance Criteria\n- `uv run mala logs --help` shows available subcommands (list, sessions, show)\n- `uv run mala logs list --help` shows `--json` and `--all` options\n- `uv run mala logs sessions --help` shows `--issue`, `--json`, `--all` options\n- `uv run mala logs show --help` shows `run_id` argument and `--json` option\n- Integration tests exist and FAIL (because stubs raise NotImplementedError or return empty)\n- Project builds/imports without errors\n\n## Verification\n```bash\nuv run mala logs --help\nuv run mala logs list --help\nuv run pytest tests/integration/cli/test_logs_integration.py -v --tb=short 2\u003e\u00261 | head -50\n# Tests should FAIL (expected - stubs not implemented)\n```\n\n## Integration Path Test\nThis task includes the `[integration-path-test]` marker. The integration tests must:\n1. Use actual CLI invocation (CliRunner or subprocess) - NOT direct function calls\n2. Exercise the `app.add_typer()` registration path\n3. Verify end-to-end from CLI entrypoint to command execution\n\n## Notes for Agent\n- Use `typer.testing.CliRunner` for integration tests\n- Stub implementations should raise `NotImplementedError(\"Not implemented yet\")` or return empty results\n- Follow existing patterns from `src/cli/cli.py` for command structure\n- Tests should fail for the RIGHT reason (not implemented) not WRONG reason (import error, syntax error)\n- Create proper fixtures in integration tests for `tmp_path` based run metadata","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-05T18:02:59.754394661Z","created_by":"cyou","updated_at":"2026-01-06T01:14:44.447185475Z","closed_at":"2026-01-06T01:14:44.447185475Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-wnke.2","depends_on_id":"mala-wnke","type":"parent-child","created_at":"2026-01-05T18:02:59.765260957Z","created_by":"cyou"},{"issue_id":"mala-wnke.2","depends_on_id":"mala-wnke.1","type":"blocks","created_at":"2026-01-05T18:04:17.413554879Z","created_by":"cyou"}]}
{"id":"mala-wnke.3","title":"[T003] Implement logs list command with unit tests","description":"**Type**: task (implementation)\n**Priority**: P2\n**Primary Files**: src/cli/logs.py, tests/unit/cli/test_logs.py\n**Subsystems**: cli\n**Dependencies**: T002 (skeleton must exist)\n\n## Goal\nImplement the `mala logs list` command that shows the 20 most recent runs with summary stats. Includes unit tests for core logic.\n\n## Context\n- `list` scans run metadata JSON files and displays summary information\n- Uses lightweight JSON parsing (not full `RunMetadata.load()`)\n- Sorts by `started_at` desc, then `run_id` asc for determinism\n- Table output by default, JSON with `--json` flag\n\n## Scope\n- In: Implement `list` command logic in `src/cli/logs.py`\n- In: Unit tests for: sorting logic, repo scoping, output formatting, null handling\n- In: Helper functions for file discovery and JSON parsing\n- Out: `sessions` and `show` commands (separate tasks)\n\n## Changes\n- `src/cli/logs.py` — [Exists] — Implement `list` command:\n  - File discovery via `get_repo_runs_dir()` (default) or `get_runs_dir().rglob()` (with `--all`)\n  - Performance: sort filenames descending, parse until 20 valid runs collected\n  - JSON validation: skip files missing `run_id`, `started_at`, `issues` keys\n  - Corrupt JSON: warn to stderr, continue\n  - Count aggregation: issue_count, success_count, fail_count, timeout_count\n  - Table output via `tabulate(tablefmt=\"simple\")`\n  - JSON output via `json.dumps()` with indent=2\n  - Null handling: `-` in table, `null` in JSON\n- `tests/unit/cli/test_logs.py` — [New] — Unit tests for:\n  - `test_sort_runs_by_started_at_desc` - verify sorting logic\n  - `test_sort_runs_tiebreaker_by_run_id_asc` - verify deterministic ordering\n  - `test_count_status_aggregation` - verify success/fail/timeout counts\n  - `test_repo_scoping_filters_by_cwd` - verify CWD-based filtering\n  - `test_null_values_in_table_output` - verify `-` for nulls\n  - `test_null_values_in_json_output` - verify `null` literal\n  - `test_validate_run_metadata_keys` - verify required key checking\n  - `test_limit_to_20_runs` - verify only 20 results returned\n\n## Acceptance Criteria\n- AC1: `mala logs list` shows 20 most recent runs with: run_id, started_at, issue_count, success, fail, timeout, metadata_path\n- AC5: Default human-readable table; `--json` for JSON-only output\n- AC6: Default current repo only\n- AC7: Empty state exits 0 with \"No runs found\" / `[]` JSON\n- AC8: Corrupt files skipped with stderr warning\n- AC9: Null values show `-` in table\n\n## Verification\n```bash\n# Unit tests pass\nuv run pytest tests/unit/cli/test_logs.py -v -k \"list or sort or count or scoping or validate\"\n\n# Integration tests for list now pass\nuv run pytest tests/integration/cli/test_logs_integration.py -v -k \"list\"\n\n# Manual verification (if runs exist)\nuv run mala logs list\nuv run mala logs list --json\n```\n\n## Notes for Agent\n- Import `get_runs_dir`, `get_repo_runs_dir`, `encode_repo_path` from `src.infra.tools.env`\n- Use `json.load()` + dict access, NOT `RunMetadata.load()` for performance\n- Table columns: run_id, started_at, issues, success, fail, timeout, path\n- For `--all` mode, add `repo_path` column (extract from parent directory name)\n- Handle missing runs directory gracefully (empty results, not error)","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-05T18:03:18.284391135Z","created_by":"cyou","updated_at":"2026-01-06T01:52:53.892255201Z","closed_at":"2026-01-06T01:52:53.892255201Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-wnke.3","depends_on_id":"mala-wnke","type":"parent-child","created_at":"2026-01-05T18:03:18.291430733Z","created_by":"cyou"},{"issue_id":"mala-wnke.3","depends_on_id":"mala-wnke.2","type":"blocks","created_at":"2026-01-05T18:04:21.806699083Z","created_by":"cyou"}]}
{"id":"mala-wnke.4","title":"[T004] Implement logs sessions command with unit tests","description":"**Type**: task (implementation)\n**Priority**: P2\n**Primary Files**: src/cli/logs.py, tests/unit/cli/test_logs.py\n**Subsystems**: cli\n**Dependencies**: T002 (skeleton must exist)\n\n## Goal\nImplement the `mala logs sessions --issue \u003cid\u003e` command that finds all sessions matching an issue ID across run metadata files.\n\n## Context\n- `sessions` scans run metadata and filters by exact issue ID match\n- Uses same file discovery and parsing as `list`\n- Returns per-issue-run rows, not per-run summaries\n- Case-sensitive exact match on `issue_id`\n\n## Scope\n- In: Implement `sessions` command logic in `src/cli/logs.py`\n- In: Unit tests for issue matching and session output formatting\n- Out: `list` and `show` commands (separate tasks)\n\n## Changes\n- `src/cli/logs.py` — [Exists] — Implement `sessions` command:\n  - Required `--issue` argument for issue ID filter\n  - File discovery same as `list` (repo-scoped by default, `--all` for all)\n  - For each run, iterate `issues` dict and exact-match `issue_id` (case-sensitive)\n  - Extract: run_id, session_id, issue_id, run's started_at (as run_started_at), status, log_path\n  - Sort by run_started_at desc, then run_id asc, then issue_id asc\n  - Table/JSON output same pattern as `list`\n  - Null handling for session_id and log_path\n- `tests/unit/cli/test_logs.py` — [Exists] — Add unit tests for:\n  - `test_issue_id_exact_match` - verify case-sensitive exact match\n  - `test_issue_id_no_partial_match` - verify no substring matching\n  - `test_sessions_output_format` - verify output fields\n  - `test_sessions_null_session_id` - verify null handling\n  - `test_sessions_null_log_path` - verify null handling\n\n## Acceptance Criteria\n- AC2: `sessions --issue` finds all sessions for issue ID (exact match, case-sensitive)\n- AC5: Default human-readable table; `--json` for JSON-only output\n- AC6: Default current repo; `--all` searches all repos\n- AC7: Empty results exit 0 with \"No sessions found\" / `[]` JSON\n- AC9: Null values show `-` in table, `null` in JSON\n\n## Verification\n```bash\n# Unit tests pass\nuv run pytest tests/unit/cli/test_logs.py -v -k \"issue or sessions\"\n\n# Integration tests for sessions now pass\nuv run pytest tests/integration/cli/test_logs_integration.py -v -k \"sessions\"\n\n# Manual verification (if runs exist with known issue)\nuv run mala logs sessions --issue bd-mala-xyz\nuv run mala logs sessions --issue bd-mala-xyz --json\n```\n\n## Notes for Agent\n- Reuse file discovery and JSON parsing helpers from `list` implementation\n- Issue matching: `issue_data[\"issue_id\"] == target_issue_id` (exact, case-sensitive)\n- Table columns: run_id, session_id, issue_id, run_started_at, status, log_path\n- For `--all` mode, add `repo_path` column\n- `run_started_at` comes from the run's `started_at`, not from IssueRun (which has no timestamp)","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-05T18:03:34.598564313Z","created_by":"cyou","updated_at":"2026-01-06T02:00:35.044979035Z","closed_at":"2026-01-06T02:00:35.044979035Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-wnke.4","depends_on_id":"mala-wnke","type":"parent-child","created_at":"2026-01-05T18:03:34.608125231Z","created_by":"cyou"},{"issue_id":"mala-wnke.4","depends_on_id":"mala-wnke.2","type":"blocks","created_at":"2026-01-05T18:04:21.825944594Z","created_by":"cyou"},{"issue_id":"mala-wnke.4","depends_on_id":"mala-wnke.3","type":"blocks","created_at":"2026-01-05T18:19:33.841802968Z","created_by":"cyou"}]}
{"id":"mala-wnke.5","title":"[T005] Implement logs show command with unit tests","description":"**Type**: task (implementation)\n**Priority**: P2\n**Primary Files**: src/cli/logs.py, tests/unit/cli/test_logs.py\n**Subsystems**: cli\n**Dependencies**: T002 (skeleton must exist), T004 (file overlap serialization)\n\n## Goal\nImplement the `mala logs show \u003crun-id\u003e` command that displays detailed metadata for a specific run, including run-id prefix matching with ambiguity detection.\n\n## Context\n- `show` finds a single run by run-id (full UUID or 8-char prefix)\n- Must detect ambiguous prefixes and error with match list\n- Returns full run metadata including all issues with their JSONL paths\n- Different exit codes: 0=success, 1=not-found/ambiguous, 2=corrupt\n\n## Scope\n- In: Implement `show` command logic in `src/cli/logs.py`\n- In: Run-id prefix matching with ambiguity detection\n- In: Unit tests for prefix matching logic\n- Out: `list` and `sessions` commands (separate tasks)\n\n## Changes\n- `src/cli/logs.py` — [Exists] — Implement `show` command:\n  - `run_id` positional argument (full UUID or 8-char prefix)\n  - **Search scope**: Current repo only via `get_repo_runs_dir(Path.cwd().resolve())` — consistent with `list` and `sessions` default behavior. No `--all` flag for `show`.\n  - Filename-based matching: extract `{timestamp}_{run_id[:8]}.json` pattern\n  - If 8-char input: match directly against filename prefix\n  - If full UUID: match filename prefix first, then verify `run_id` field in JSON\n  - Fallback: if filename parsing fails, scan JSON `run_id` fields\n  - Ambiguity: if multiple matches, error with list of matching run_ids (exit 1)\n  - Not found: error \"no matching run\" (exit 1)\n  - Matched but corrupt/unreadable: error with details (exit 2)\n  - Output: full run metadata (read raw JSON and output, or use `_to_dict()` pattern)\n  - Table: key-value formatted display of all metadata fields\n  - JSON: direct JSON output of full structure\n- `tests/unit/cli/test_logs.py` — [Exists] — Add unit tests for:\n  - `test_run_id_exact_match` - full UUID matches exactly\n  - `test_run_id_prefix_match` - 8-char prefix matches\n  - `test_run_id_ambiguous_prefix` - multiple matches detected\n  - `test_run_id_not_found` - no match returns appropriate error\n  - `test_extract_run_id_from_filename` - filename parsing logic\n  - `test_show_output_includes_all_issues` - verify issues displayed\n\n## Acceptance Criteria\n- AC3: `show \u003crun-id\u003e` displays detailed metadata including all sessions with JSONL paths\n- AC4: Run-id accepts full UUID or 8-char prefix; ambiguous prefix errors with match list\n- AC5: Default human-readable table; `--json` for JSON-only output\n- AC10: Exit codes: 0=success, 1=not-found/ambiguous, 2=corrupt\n\n## Verification\n```bash\n# Unit tests pass\nuv run pytest tests/unit/cli/test_logs.py -v -k \"run_id or show or prefix or ambiguous\"\n\n# Integration tests for show now pass\nuv run pytest tests/integration/cli/test_logs_integration.py -v -k \"show\"\n\n# Manual verification (if runs exist)\nuv run mala logs show \u003cfull-run-id\u003e\nuv run mala logs show \u003c8-char-prefix\u003e\nuv run mala logs show \u003c8-char-prefix\u003e --json\n```\n\n## Notes for Agent\n- **Search scope**: `show` searches current repo only (`get_repo_runs_dir(cwd)`). If run not found, user must `cd` to correct repo.\n- Filename format: `{ISO-timestamp}_{run_id[:8]}.json` (e.g., `2026-01-05T10-30-00_a1b2c3d4.json`)\n- Extract prefix: split on `_`, take last segment before `.json`\n- For table output, format as key-value pairs, one per line\n- Issues section should list all issues with their full details\n- Error JSON schema: `{\"error\": \"\u003ccode\u003e\", \"message\": \"\u003cdetails\u003e\", \"matches\": [...]?}`\n- `matches` array only included for ambiguous prefix errors","notes":"Quality gate failed: External review failed: src/cli/logs.py:561: [P1] Avoid treating unrelated corrupt files as matching candidates; src/cli/logs.py:566: [P1] Ambiguity detection bypassed for non-conforming files\nLog: /home/cyou/.claude/projects/-home-cyou-mala/c84d1a9d-32ec-4920-98dc-4ae044b08088.jsonl","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-05T18:03:54.458619701Z","created_by":"cyou","updated_at":"2026-01-06T03:11:05.573877054Z","closed_at":"2026-01-06T03:11:05.573877054Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-wnke.5","depends_on_id":"mala-wnke","type":"parent-child","created_at":"2026-01-05T18:03:54.466716733Z","created_by":"cyou"},{"issue_id":"mala-wnke.5","depends_on_id":"mala-wnke.2","type":"blocks","created_at":"2026-01-05T18:04:21.838932118Z","created_by":"cyou"},{"issue_id":"mala-wnke.5","depends_on_id":"mala-wnke.4","type":"blocks","created_at":"2026-01-05T18:19:33.862532938Z","created_by":"cyou"}]}
{"id":"mala-wnke.6","title":"[T006] Final verification: all tests pass and acceptance criteria met","description":"**Type**: task (final verification)\n**Priority**: P2\n**Primary Files**: tests/unit/cli/test_logs.py, tests/integration/cli/test_logs_integration.py\n**Subsystems**: cli\n**Dependencies**: T003, T004, T005 (all implementation tasks)\n\n## Goal\nVerify all integration and unit tests pass, and all acceptance criteria from the spec are met. This is the final gate before the feature is complete.\n\n## Context\n- T003-T005 implement individual commands\n- This task ensures everything works together\n- Includes final integration test verification\n- Runs full test suite to catch any regressions\n\n## Scope\n- In: Run all unit and integration tests for logs CLI\n- In: Verify all 10 acceptance criteria from spec\n- In: Manual verification against real run data (if available)\n- Out: New implementation (should be complete from T003-T005)\n\n## Changes\n- `tests/integration/cli/test_logs_integration.py` — [Exists] — May need minor test adjustments if edge cases discovered\n- No new files expected\n\n## Acceptance Criteria Checklist\n- [ ] AC1: `list` shows 20 most recent runs with run_id, started_at, issue_count, success/fail/timeout counts, metadata_path\n- [ ] AC2: `sessions --issue` finds all sessions for issue ID (exact match, case-sensitive)\n- [ ] AC3: `show \u003crun-id\u003e` displays detailed metadata including all sessions with JSONL paths\n- [ ] AC4: Run-id accepts full UUID or 8-char prefix; ambiguous prefix errors with match list\n- [ ] AC5: Default human-readable table; `--json` for JSON-only output\n- [ ] AC6: Default current repo; `--all` searches all repos (adds repo_path column)\n- [ ] AC7: Empty state exits 0 with \"No runs found\" / `[]` JSON\n- [ ] AC8: Corrupt files skipped with stderr warning, continue scanning\n- [ ] AC9: Null values show `-` in table, `null` in JSON\n- [ ] AC10: Exit codes: 0=success, 1=not-found/ambiguous, 2=corrupt (show only)\n\n## Verification\n```bash\n# All unit tests pass\nuv run pytest tests/unit/cli/test_logs.py -v\n\n# All integration tests pass\nuv run pytest tests/integration/cli/test_logs_integration.py -v\n\n# Full test suite (no regressions)\nuv run pytest -m \"unit or integration\" -n auto\n\n# Linting passes\nuvx ruff check src/cli/logs.py tests/unit/cli/test_logs.py tests/integration/cli/test_logs_integration.py\nuvx ruff format --check src/cli/logs.py tests/unit/cli/test_logs.py tests/integration/cli/test_logs_integration.py\n\n# Type checking passes\nuvx ty check src/cli/logs.py\n\n# Manual verification (if ~/.config/mala/runs/ has data)\nuv run mala logs list\nuv run mala logs list --json | jq .\nuv run mala logs show \u003crun-id\u003e\nuv run mala logs sessions --issue \u003cknown-issue-id\u003e\n```\n\n## Notes for Agent\n- This task is verification-focused, not implementation\n- If tests fail, identify which implementation task (T003-T005) needs fixes\n- Minor test adjustments are OK if they improve coverage\n- Do NOT add new features - only verify existing implementation works\n- Mark all AC checkboxes when verified","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-05T18:04:10.359399417Z","created_by":"cyou","updated_at":"2026-01-06T03:14:03.05865015Z","closed_at":"2026-01-06T03:14:03.05865015Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-wnke.6","depends_on_id":"mala-wnke","type":"parent-child","created_at":"2026-01-05T18:04:10.368439915Z","created_by":"cyou"},{"issue_id":"mala-wnke.6","depends_on_id":"mala-wnke.3","type":"blocks","created_at":"2026-01-05T18:04:26.667653761Z","created_by":"cyou"},{"issue_id":"mala-wnke.6","depends_on_id":"mala-wnke.4","type":"blocks","created_at":"2026-01-05T18:04:26.687232801Z","created_by":"cyou"},{"issue_id":"mala-wnke.6","depends_on_id":"mala-wnke.5","type":"blocks","created_at":"2026-01-05T18:04:26.699663167Z","created_by":"cyou"}]}
{"id":"mala-wnke.7","title":"[Review] 1 non-blocking finding from mala-wnke.2","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-wnke.2\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] xfail marker masks NotImplementedError guard assertions\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** tests/integration/cli/test_logs_integration.py:66-72\n\nThe `@pytest.mark.xfail` marker applied to the test functions masks the assertions intended to guard against regressions. Since the marker is applied to the entire test function, any failure—including an `AssertionError` when an unexpected exception (like `ImportError`) is raised—will still be reported as an `XFAIL`. This conceals the fact that the test failed for the 'wrong' reason. To effectively avoid masking, consider using dynamic xfailing with `pytest.xfail()` only when the expected `NotImplementedError` is caught, or allowing the test to fail normally if the exception is not the one expected.\n\n---\n\n\u003c!-- fp:0299df8955d0b796 --\u003e\n\u003c!-- review_finding:957140c42452 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-06T01:14:44.487591259Z","created_by":"cyou","updated_at":"2026-01-06T01:17:15.214227237Z","closed_at":"2026-01-06T01:17:15.214227237Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-wnke.2"],"dependencies":[{"issue_id":"mala-wnke.7","depends_on_id":"mala-wnke","type":"parent-child","created_at":"2026-01-06T01:14:44.488134036Z","created_by":"cyou"}]}
{"id":"mala-wnke.8","title":"[Review] 1 non-blocking finding from mala-wnke.7","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-wnke.7\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Preserve strict XPASS signal for stub-guarded CLI tests\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** tests/integration/cli/test_logs_integration.py:63-70\n\nWith the new pattern, once `mala logs list` is implemented and returns success (`result.exception is None` and `exit_code == 0`), the test will **silently pass** and won’t force anyone to replace the placeholder assertions. Previously, `@pytest.mark.xfail(strict=True)` would have turned that into an **XPASS** (failing CI) and prompted cleanup. The module docstring also claims this “prompts test updates,” but nothing enforces that now.\n\nConsider explicitly failing when the stub no longer raises (e.g., `pytest.fail(\"Implementation landed; remove xfail guard and assert output\")`), or update the docstring/intent to reflect that these are now permanent smoke tests.\n\n---\n\n\u003c!-- fp:1fea4ecf802115eb --\u003e\n\u003c!-- review_finding:63f079bd1b2a --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-06T01:17:15.253516286Z","created_by":"cyou","updated_at":"2026-01-06T01:20:06.818418507Z","closed_at":"2026-01-06T01:20:06.818418507Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-wnke.7"],"dependencies":[{"issue_id":"mala-wnke.8","depends_on_id":"mala-wnke","type":"parent-child","created_at":"2026-01-06T01:17:15.253926004Z","created_by":"cyou"}]}
{"id":"mala-wnke.9","title":"[Review] 2 non-blocking findings from mala-wnke.8","description":"## Review Findings\n\nThis issue consolidates 2 non-blocking findings from code review.\n\n**Source issue:** mala-wnke.8\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Misleading failure message when non-NotImplementedError exceptions occur\n\n**Priority:** P2\n**Reviewer:** claude\n**Location:** tests/integration/cli/test_logs_integration.py:66-68\n\nThe pattern fails to distinguish between \"implementation succeeded\" and \"implementation threw a different exception.\" If the stub is changed to raise `ValueError` or another error, tests fail with the misleading message \"Implementation landed\" rather than indicating an unexpected exception. Consider checking `result.exception is None` before the fail message, or using a more accurate message like:\n```python\nif result.exception is not None:\n    pytest.fail(f\"Unexpected exception: {result.exception}\")\npytest.fail(\"Implementation landed...\")\n```\n\n---\n\n### Finding 2: [P2] Preserve regression signal for non-NotImplementedError failures\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** tests/integration/cli/test_logs_integration.py:63-70\n\nRight now, any outcome other than `NotImplementedError` (including unexpected exceptions like `SystemExit`/`ValueError`, or a non-zero `exit_code`) hits the unconditional `pytest.fail(\"Implementation landed...\")`. This still fails the test, but the failure reason becomes misleading and loses the “different exception occurred” regression detection described in the module docstring.\n\nConsider preserving the old signal by branching before the placeholder failure, e.g.:\n\n`if result.exception is not None: raise result.exception`\n\n`if result.exit_code != 0: pytest.fail(f\"Unexpected exit_code={result.exit_code}\")`\n\n…then only `pytest.fail(\"Implementation landed; remove xfail guard and add real assertions\")` when `exception is None` and `exit_code == 0`.\n\n---\n\n\u003c!-- fp:d94f9cc4646877c6 --\u003e\n\u003c!-- fp:a09b7c6b06defb3f --\u003e\n\u003c!-- review_finding:762be507764b --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-06T01:20:06.862431662Z","created_by":"cyou","updated_at":"2026-01-06T01:25:45.455942209Z","closed_at":"2026-01-06T01:25:45.455942209Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-wnke.8"],"dependencies":[{"issue_id":"mala-wnke.9","depends_on_id":"mala-wnke","type":"parent-child","created_at":"2026-01-06T01:20:06.86292268Z","created_by":"cyou"}]}
{"id":"mala-wp97","title":"[Remediation] Linting passes: `uvx ruff check . \u0026\u0026 uvx ty check`","description":"## Context\nThis issue was auto-created by epic verification for epic `mala-bp3h`.\n\n## Unmet Criterion\nLinting passes: `uvx ruff check . \u0026\u0026 uvx ty check`\n\n## Evidence\nNo evidence in the diff that final linting verification has been completed\n\n## Severity\nmajor\n\n## Resolution\nAddress this criterion to unblock epic closure. When complete, close this issue.\n","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-01T03:35:30.909413035Z","created_by":"cyou","updated_at":"2026-01-01T03:38:46.233189884Z","closed_at":"2026-01-01T03:38:46.233189884Z","close_reason":"Closed","labels":["auto_generated","epic_remediation:mala-bp3h:51fd3d3faa0e69d47d74462ad43e803b6d5a21d31c2d45f38d05984eb9b5a718"],"dependencies":[{"issue_id":"mala-wp97","depends_on_id":"mala-bp3h","type":"parent-child","created_at":"2026-01-01T03:37:58.124774915Z","created_by":"daemon"}]}
{"id":"mala-wqwk","title":"[Review] tests/test_run_metadata.py:1047-1079: [P2] test_cleanup_is_idempotent doesn't enable debug logging","description":"## Review Finding\n\nThis issue was auto-created from a P2 review finding.\n\n**Source issue:** mala-dhry\n**Reviewer:** claude\n**Location:** tests/test_run_metadata.py:1047-1079\n\n## Details\n\nThe test `test_cleanup_is_idempotent` doesn't remove `MALA_DISABLE_DEBUG_LOG` from the environment (unlike other tests in this class that use `os.environ.pop(\"MALA_DISABLE_DEBUG_LOG\", None)`). As a result, `metadata.debug_log_path` will be `None` and the test never actually calls `cleanup_debug_logging()`. The test passes but doesn't verify the intended idempotent behavior when debug logging is active.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-01T22:54:29.94785431Z","created_by":"cyou","updated_at":"2026-01-02T01:08:27.782532679Z","closed_at":"2026-01-02T01:08:27.782532679Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_finding:26967d64b167","source:mala-dhry"],"dependencies":[{"issue_id":"mala-wqwk","depends_on_id":"mala-cw6h","type":"blocks","created_at":"2026-01-01T23:32:33.341210575Z","created_by":"daemon"}]}
{"id":"mala-wr1","title":"Skip uv sync in implementer if dependencies unchanged","description":"## Context\nThe implementer prompt currently runs `uv sync` at the start of every implementation. This is wasteful when dependencies haven't changed.\n\n## Problem\nRunning `uv sync` on every implementation adds unnecessary overhead when pyproject.toml/uv.lock haven't been modified.\n\n## Solution\nUpdate the implementer prompt to only run `uv sync` if:\n1. pyproject.toml has been modified, OR\n2. uv.lock has been modified, OR\n3. .venv doesn't exist\n\n## Acceptance Criteria\n- Implementer prompt conditionally runs `uv sync`\n- Dependencies are still installed when needed\n- Unnecessary syncs are skipped\n\n## Files to Modify\n- src/prompts/implementer_prompt.md\n","status":"closed","priority":3,"issue_type":"task","created_at":"2025-12-27T06:44:39.140232276Z","updated_at":"2025-12-27T09:03:57.289712504Z","closed_at":"2025-12-27T09:03:57.289712504Z","close_reason":"Closed"}
{"id":"mala-wrtg","title":"[Review] tests/test_orchestrator.py:580-591: [P2] Keep max_issues test isolated from real bd calls","description":"## Review Finding\n\nThis issue was auto-created from a P2 review finding.\n\n**Source issue:** mala-rmxf\n**Reviewer:** codex\n**Location:** tests/test_orchestrator.py:580-591\n\n## Details\n\n`mock_spawn` now returns a real task (`asyncio.create_task`) so the coordinator registers it and runs `_finalize_issue_result`, which calls `beads.close_async` (and other side-effectful paths). Because this test does not mock those methods, it can try to execute `bd close` and fail with FileNotFoundError or mutate real state. To keep this a unit test, stub `beads.close_async` / `_finalize_issue_result`, or return a dummy task and bypass registration.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-02T01:52:47.978844785Z","created_by":"cyou","updated_at":"2026-01-02T01:56:14.771577192Z","closed_at":"2026-01-02T01:56:14.771577192Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_finding:203ffdd754e4","source:mala-rmxf"]}
{"id":"mala-wxzn","title":"[Review] src/domain/validation/coverage.py:660-667: [P2] Force --cov-report to match coverage_config.file","description":"## Review Finding\n\nThis issue was auto-created from a P2 review finding.\n\n**Source issue:** mala-fdp1.9\n**Reviewer:** codex\n**Location:** src/domain/validation/coverage.py:660-667\n\n## Details\n\nIf `coverage_config.file` is not `coverage.xml` but the configured `coverage_config.command` already includes `--cov-report=xml` or `--cov-report=xml:\u003cother\u003e`, `has_xml_report` short-circuits and the command is not updated. The coverage run then writes to the old/default path, while the refresh logic looks for `coverage_config.file`, resulting in “no coverage generated” failures or repeated refresh attempts. Example: `coverage.file=reports/coverage.xml`, command contains `--cov-report=xml` → output goes to `coverage.xml` but the service searches for `reports/coverage.xml`. Consider stripping any existing `--cov-report` entries and always adding `--cov-report=xml:\u003ccoverage_config.file\u003e` or validating the mismatch and failing fast with a clear error.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-02T16:10:51.333468372Z","created_by":"cyou","updated_at":"2026-01-02T17:00:06.432077772Z","closed_at":"2026-01-02T17:00:06.432077772Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_finding:38daec660d1e","source:mala-fdp1.9"]}
{"id":"mala-wy7u","title":"add more control over types of beads issues to process (eg. bug)","status":"tombstone","priority":2,"issue_type":"feature","created_at":"2026-01-03T22:25:30.100201378Z","created_by":"cyou","updated_at":"2026-01-06T22:52:10.761723406Z","deleted_at":"2026-01-06T22:52:10.761723406Z","deleted_by":"batch delete","delete_reason":"batch delete","original_type":"feature"}
{"id":"mala-wya","title":"Prompt cleanup: full validation, mutex rule, REPO_NAMESPACE","description":"Context:\n- Track A1 requires removing guidance that allows skipping tests and replacing with a single clear policy (docs/coordination-plan-codex.md:26-28, 81-83).\n- Implementer prompt includes Speed/Scope Guardrails and \"run tests only on touched files\" guidance (src/prompts/implementer_prompt.md:138-150).\n- Track A3 requires setting REPO_NAMESPACE in the prompt to repo root (docs/coordination-plan-codex.md:33-35).\n\nScope:\n- In: remove Speed/Scope Guardrails section and any skip-tests or blame-pre-existing-failures language.\n- In: require the full validation suite (uv sync, pytest, ruff check/format, ty check) and add the test mutex rule for repo-wide commands.\n- In: export REPO_NAMESPACE to the repo root in the lock environment setup.\n- Out: no lock script or orchestrator logic changes.\n\nAcceptance Criteria:\n- Prompt no longer contains Speed/Scope Guardrails or \"run only touched files\" guidance.\n- Prompt explicitly requires full validation suite and a mutex before repo-wide commands.\n- REPO_NAMESPACE export is present and uses the repo root placeholder.\n\nTest Plan:\n- Doc-only: spot-check rendered prompt with sample values.\n\nNotes for Agent:\n- Keep wording aligned with shared-worktree mode.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-26T16:43:25.110736233Z","updated_at":"2025-12-26T17:03:04.73672941Z","closed_at":"2025-12-26T17:03:04.73672941Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-wya","depends_on_id":"mala-4w7","type":"parent-child","created_at":"2025-12-26T16:47:52.95649035Z","created_by":"daemon"}]}
{"id":"mala-x00","title":"Epic: Validation integration (gates + orchestrator)","description":"Context:\n- Integrates validation modules into gates, CLI, orchestrator, and run metadata.\n- Derived from docs/quality-hardening-plan.md (v5).\n\nScope:\n- In: EvidenceGate updates, CLI parsing, orchestrator pipeline, run metadata.\n- Out: validation module internals (handled in validation epic).\n\nAcceptance Criteria:\n- All child issues closed and pipeline wired end-to-end.\n\nTest Plan:\n- Defer to child issue test plans; run full suite at the end.","status":"closed","priority":1,"issue_type":"epic","created_at":"2025-12-26T23:00:01.628605121Z","updated_at":"2025-12-27T05:57:33.781911141Z","closed_at":"2025-12-27T05:57:33.781911141Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-x00","depends_on_id":"mala-ng3","type":"blocks","created_at":"2025-12-26T23:00:26.410106574Z","created_by":"daemon"},{"issue_id":"mala-x00","depends_on_id":"mala-e0i","type":"blocks","created_at":"2025-12-26T23:00:26.420942397Z","created_by":"daemon"},{"issue_id":"mala-x00","depends_on_id":"mala-2eu","type":"blocks","created_at":"2025-12-26T23:00:26.432302435Z","created_by":"daemon"},{"issue_id":"mala-x00","depends_on_id":"mala-9mb","type":"blocks","created_at":"2025-12-26T23:00:26.443299296Z","created_by":"daemon"}]}
{"id":"mala-x0qs","title":"[Review] 1 non-blocking finding from mala-u8rm","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-u8rm\n**Highest priority:** P3\n\n---\n\n### Finding 1: [P3] Inconsistent timeout handling when using injected runner\n\n**Priority:** P3\n**Reviewer:** claude\n**Location:** src/domain/validation/coverage.py:627-640\n\nWhen `self.command_runner` is provided, the code reuses it directly (lines 635-636) but skips creating a `CommandRunner` with the `timeout` parameter (line 640). If the injected runner was constructed with a different timeout, it won't respect `self.coverage_config.timeout` or `self.step_timeout_seconds`. For production use, this is likely acceptable since the caller controls the injected runner, but it creates an asymmetry where the timeout computation on line 627-629 is only used when creating a new runner.\n\n---\n","notes":"Quality gate failed: Quality gate failed: No commit with bd-mala-x0qs found after run baseline (stale commits from previous runs are rejected)\nLog: /home/cyou/.claude/projects/-home-cyou-mala/544c6615-16c8-4db4-bbc6-cb0a4e67442d.jsonl","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-03T08:29:47.277661578Z","created_by":"cyou","updated_at":"2026-01-03T15:03:25.461067408Z","closed_at":"2026-01-03T15:03:25.461067408Z","close_reason":"Closed","labels":["auto_generated","needs-followup","review_finding","review_findings:8d374d18a22f","source:mala-u8rm"]}
{"id":"mala-x5mg","title":"[Review] 1 non-blocking finding from mala-mgfb.3","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-mgfb.3\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Regression in observability: lost 'first_msg' status in timeout logs\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/pipeline/agent_session_runner.py:1624-1626\n\nThe extraction of `_process_message_stream` moved the `first_message_received` variable into a local scope that is inaccessible when an `IdleTimeoutError` is caught by the caller. This resulted in the removal of the `first_msg` indicator from the warning log, which is important for distinguishing between a total lack of response (e.g., connection issues) and a slow generation that stalled during processing. This flag should be moved into the `state` object to preserve this diagnostic information.\n\n---\n","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T15:21:01.023407213Z","created_by":"cyou","updated_at":"2026-01-03T15:34:52.732941181Z","closed_at":"2026-01-03T15:34:52.732941181Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_findings:b94f4dc0c3f8","source:mala-mgfb.3"]}
{"id":"mala-x5x","title":"Skip redundant linting when no files changed","description":"## Problem\nAgents run ruff check/format and ty check repeatedly even when no files have changed since the last run. This wastes both wall time and tokens on identical output.\n\n## Evidence\nSame lint/test commands repeated 3-4x with no evidence of new commits or file changes, particularly in sessions that hit the 'No progress' failure.\n\n## Solution\nBefore running lint commands:\n1. Check if any tracked files have changed since last lint run (git status or file mtimes)\n2. If no changes, skip the command and report 'No changes since last check'\n3. Cache the commit SHA or file hashes when lint passes\n\n## Expected Impact\n~10% token reduction, minor wall time improvement\n\n## Notes\nLower priority than progress gate (mala-9lp) since that addresses the root cause of retry loops.","notes":"Quality gate failed: Timeout after 30 minutes\nLog: /home/cyou/.claude/projects/-home-cyou-mala/6e48ef51-484e-4ce7-954e-c25425c7e1a0.jsonl","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-29T00:43:50.516069141Z","created_by":"cyou","updated_at":"2025-12-29T15:01:02.389423561Z","closed_at":"2025-12-29T15:01:02.389423561Z","close_reason":"SpecValidationRunner now uses lint cache (src/validation/lint_cache.py) to skip lint/type/format commands when git state unchanged.","labels":["needs-followup"]}
{"id":"mala-xdlk","title":"[Review] 1 non-blocking finding from mala-e17r.3","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-e17r.3\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Remove event_sink re-export shim\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/infra/io/event_sink.py:11\n\nProject migration rules explicitly forbid backward-compatibility shims and re-exports. This change turns `event_sink.py` into a shim by re-exporting `ConsoleEventSink`, which violates that rule and encourages continued imports from the old module. Update all call sites to import `ConsoleEventSink` from `src.infra.io.console_sink` and remove the re-export.\n\n`from .console_sink import ConsoleEventSink`\n\n---\n","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T16:43:50.73634913Z","created_by":"cyou","updated_at":"2026-01-03T16:49:26.620680372Z","closed_at":"2026-01-03T16:49:26.620680372Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_findings:624ee95dd933","source:mala-e17r.3"]}
{"id":"mala-xm2","title":"Codex reviewer fails on retry: only sees incremental diff, not full scope","description":"## Problem\n\nAfter an initial Codex review fails and the subagent makes fixes, the subsequent Codex review only sees the incremental diff (fix commit vs previous commit) rather than the cumulative diff from baseline. This causes the reviewer to incorrectly fail with 'scope incomplete' because the fix commit alone doesn't address all scope items.\n\n### Current Flow\n1. Subagent creates commit A with full implementation\n2. Codex reviews 'commit A vs parent' → sees full scope\n3. Review fails (e.g., missing test coverage)\n4. Subagent creates commit B with fix\n5. Codex reviews 'commit B vs commit A' → only sees fix, not full implementation\n6. Codex fails: 'scope incomplete - diff doesn't address all scope items'\n\n### Root Cause\n`codex_review.py:66` uses:\n```\nReview the diff introduced by commit {commit_sha} (vs its parent).\n```\n\nNo baseline commit hash is tracked. `RetryState` has `baseline_timestamp` (for rejecting stale commits) and `previous_commit_hash` (for no-progress detection), but neither provides a diff range for cumulative review.\n\n## Solution\n\nTrack baseline commit hash and review cumulative diff:\n\n1. Add `baseline_commit_hash: str | None = None` to `RetryState`\n2. Capture `git rev-parse HEAD` before starting the subagent\n3. Add `baseline_commit` parameter to `run_codex_review()`\n4. Update `CODEX_REVIEW_PROMPT` to review `baseline..current` diff range\n\n## In:\n- `src/orchestrator.py`: Add `baseline_commit_hash` to `RetryState`, capture before agent starts, pass to review\n- `src/codex_review.py`: Add `baseline_commit` parameter, update prompt to use diff range\n\n## Out:\n- Codex CLI changes\n- Alternative solutions (thread continuation, commit squashing)","acceptance_criteria":"- Codex reviewer sees cumulative diff on retry (baseline to latest commit)\n- Scope verification works correctly across multiple fix commits\n- Tests verify retry scenario with baseline tracking","status":"closed","priority":0,"issue_type":"bug","created_at":"2025-12-27T22:34:06.489052346Z","updated_at":"2025-12-27T23:46:06.961276862Z","closed_at":"2025-12-27T23:46:06.961276862Z","close_reason":"Closed"}
{"id":"mala-xokg","title":"[P2] Inconsistent skipping of shell-script dependent tests","description":"## Review Finding\n\n**Priority:** P2\n**Trigger:** run_end\n**Baseline:** bf7d0ad2..102e65c8\n**Location:** tests/e2e/test_lock_sdk.py:177\n\n---\n\nThe diff skips `test_agent_runs_lock_try_script` due to unreliable shell scripts. However, `test_agent_checks_lock_ownership` (in the same class) and tests in `TestAgentReleasesLocks` also use `lock-try.sh` but aren't skipped. Consistency is needed to avoid CI failures.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-11T06:40:16.480084729Z","created_by":"cyou","updated_at":"2026-01-11T07:07:56.23114655Z","closed_at":"2026-01-11T07:07:56.23114655Z","close_reason":"Closed","labels":["auto_generated","cumulative-review","fp:501d18cac75dc356","review_finding","trigger:run_end"]}
{"id":"mala-xrtj","title":"[Review] src/pipeline/agent_session_runner.py:998-1007: [P2] Dead code: fallback branches after `cmds` assignment can never execute","description":"## Review Finding\n\nThis issue was auto-created from a P2 review finding.\n\n**Source issue:** mala-fhyl\n**Reviewer:** claude\n**Location:** src/pipeline/agent_session_runner.py:998-1007\n\n## Details\n\nThe `cmds` variable is assigned on lines 989-991 using `self.config.prompt_validation_commands or _get_default_validation_commands()`. Since `_get_default_validation_commands()` always returns a `PromptValidationCommands` object, `cmds` will never be `None` or falsy. The conditional fallbacks (`if cmds else \"...\"`) on lines 998-1007 are unreachable dead code. This doesn't cause incorrect behavior, but the conditionals should be removed for clarity - just use `cmds.lint`, `cmds.format`, etc. directly.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T00:42:21.552223908Z","created_by":"cyou","updated_at":"2026-01-03T00:47:06.996610612Z","closed_at":"2026-01-03T00:47:06.996610612Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_finding:2ee5a5e7ce64","source:mala-fhyl"]}
{"id":"mala-xsdn","title":"Fix deadlock detection for batched lock-try.sh commands","description":"## Problem\n\nDeadlock detection fails to detect cycles when agents use batched lock-try.sh commands with \u0026\u0026 chaining.\n\n## Root Cause\n\nWhen agents batch lock commands like:\n```bash\nlock-try.sh file1.py \u0026\u0026 lock-try.sh file2.py\n```\n\nIf exit code is 1 (second lock blocked), the PostToolUse hook in `src/infra/hooks/deadlock.py` emits **nothing**:\n- No ACQUIRED event for the first lock that succeeded\n- No WAITING event for the second lock that's blocked (only emits WAITING for single-command case, line 263-266)\n\nThe WaitForGraph never learns about held locks, so cycle detection can't find the cycle.\n\n## Real-World Failure\n\nFrom run 2026-01-04T01-19-28_d7b75665:\n- Agent 2 held: agent_runtime.py, agent_session_runner.py, run_coordinator.py, test_agent_runtime.py\n- Agent 2 waiting for: test_agent_session_runner.py (held by Agent 13)\n- Agent 13 held: test_agent_session_runner.py\n- Agent 13 waiting for: agent_session_runner.py (held by Agent 2)\n\nClassic cycle: Agent 2 → Agent 13 → Agent 2\n\nTimeline:\n- 02:02:12 - Agent 13 started lock-wait.sh agent_session_runner.py\n- 02:03:15 - Agent 2 started lock-wait.sh test_agent_session_runner.py\n- Both waited 5+ minutes, no detection, no resolution\n\nDebug log showed ZERO deadlock-related entries (no WAITING, ACQUIRED, etc.).\n\n## Fix Required\n\nIn `make_lock_event_hook()`:\n1. For safe `\u0026\u0026` batches with exit code 1, emit ACQUIRED for all lock-try.sh commands BEFORE the last one (they must have succeeded)\n2. Emit WAITING for the last lock-try.sh in the batch (the one that failed with exit code 1)\n\n## Affected Files\n\n- src/infra/hooks/deadlock.py (lines 239-276)\n- tests/unit/infra/hooks/test_deadlock_hooks.py (add test)\n\n## Acceptance Criteria\n\n- [ ] Test reproduces the bug (two agents with batched lock-try, deadlock not detected)\n- [ ] Test fails before fix\n- [ ] Fix makes test pass\n- [ ] Existing tests still pass","notes":"Quality gate failed: Command failed with exit code 1 (exit code: 1)\nError output: Check stderr output for details\nLog: /home/cyou/.claude/projects/-home-cyou-mala/3561becb-ef50-447e-83ff-4031c64450ef.jsonl","status":"closed","priority":0,"issue_type":"task","created_at":"2026-01-04T04:43:36.331649472Z","created_by":"cyou","updated_at":"2026-01-04T05:47:30.707711107Z","closed_at":"2026-01-04T05:47:30.707711107Z","close_reason":"Closed","labels":["needs-followup"]}
{"id":"mala-xtcx","title":"[Review] src/orchestration/review_tracking.py:40-46: [P2] Dedup per source issue, not per exact finding set","description":"## Review Finding\n\nThis issue was auto-created from a P2 review finding.\n\n**Source issue:** mala-8tk1\n**Reviewer:** codex\n**Location:** src/orchestration/review_tracking.py:40-46\n\n## Details\n\nThe new dedup tag is derived from a hash of the full finding set. If the same source issue is reviewed again with any added/removed/edited finding, the hash changes and a second consolidated issue will be created, leaving multiple tracking issues for the same source. This contradicts the stated goal of “single tracking issue per source.” Consider basing the dedup tag on `source_issue_id` alone (or searching by `source:\u003cid\u003e` and updating/appending instead of creating a new issue).","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T05:59:41.877218535Z","created_by":"cyou","updated_at":"2026-01-03T06:33:30.941490909Z","closed_at":"2026-01-03T06:33:30.941490909Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_finding:3c7703263df3","source:mala-8tk1"]}
{"id":"mala-xv8","title":"Epic: A3 Canonical lock keys","description":"Context:\n- Track A3 requires setting REPO_NAMESPACE, normalizing paths before hashing, and updating lock tests (docs/coordination-plan-codex.md:33-36).\n\nScope:\n- In: coordinate child tasks for Python helper, shell scripts, and test updates.\n- Out: does not implement changes directly.\n\nAcceptance Criteria:\n- All child tasks for A3 are completed and locks are canonicalized end-to-end.\n\nTest Plan:\n- N/A (handled by child tasks).\n\nNotes for Agent:\n- Use parent-child relationships for A3 sub-tasks.","status":"closed","priority":1,"issue_type":"epic","created_at":"2025-12-26T16:43:43.874044032Z","updated_at":"2025-12-26T17:12:06.484854779Z","closed_at":"2025-12-26T17:12:06.484854779Z","close_reason":"All children completed","dependencies":[{"issue_id":"mala-xv8","depends_on_id":"mala-4w7","type":"parent-child","created_at":"2025-12-26T16:47:52.985551137Z","created_by":"daemon"}]}
{"id":"mala-xv8.1","title":"Canonicalize Python lock key generation","description":"Context:\n- Track A3 requires path normalization and repo namespace before hashing (docs/coordination-plan-codex.md:33-35).\n- _lock_key currently uses raw filepath without normalization (src/tools/locking.py:22-34).\n\nScope:\n- In: normalize file paths (resolve symlinks, repo-relative to REPO_NAMESPACE) before hashing in _lock_key/_lock_path.\n- In: ensure absolute and relative paths for the same file resolve to the same key.\n- Out: no shell script changes (handled separately).\n\nAcceptance Criteria:\n- _lock_key produces identical keys for equivalent absolute/relative paths within the same repo namespace.\n- Repo namespace prefix remains part of the canonical key.\n- Unit tests cover normalization and namespace behavior.\n\nTest Plan:\n- TDD: add failing tests in a new tests/test_locking_key.py for normalization cases.\n- Implement locking.py changes, update tests to pass.\n- Run uv run pytest tests/test_locking_key.py.\n\nNotes for Agent:\n- Assume REPO_NAMESPACE is set to the repo root in the prompt.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-26T16:43:52.981798811Z","updated_at":"2025-12-26T17:00:55.334823293Z","closed_at":"2025-12-26T17:00:55.334823293Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-xv8.1","depends_on_id":"mala-xv8","type":"parent-child","created_at":"2025-12-26T16:43:52.990309133Z","created_by":"daemon"}]}
{"id":"mala-xv8.2","title":"Canonicalize lock scripts + update script tests","description":"Context:\n- Track A3 requires path normalization before hashing for lock keys (docs/coordination-plan-codex.md:33-35).\n- Lock scripts currently hash raw input paths (src/scripts/lock-try.sh:20-31).\n- Script-level tests exercise lock scripts behavior (tests/test_lock_scripts.py).\n\nScope:\n- In: update lock-try.sh, lock-check.sh, lock-holder.sh, lock-release.sh to normalize paths (resolve symlinks, repo-relative to REPO_NAMESPACE) before hashing.\n- In: update tests/test_lock_scripts.py to cover normalization and namespace behavior for scripts.\n- Out: no Python locking helper changes (handled separately).\n\nAcceptance Criteria:\n- Scripts generate the same lock file for equivalent absolute/relative paths within the same repo namespace.\n- REPO_NAMESPACE is applied after normalization.\n- tests/test_lock_scripts.py includes cases for normalization and passes.\n\nTest Plan:\n- TDD: add failing tests in tests/test_lock_scripts.py for normalization cases.\n- Implement script changes, update tests to pass.\n- Run uv run pytest tests/test_lock_scripts.py.\n\nNotes for Agent:\n- Use portable path normalization (realpath/readlink fallback) to keep Linux/macOS compatibility.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-26T16:44:02.611587643Z","updated_at":"2025-12-26T17:02:29.674397068Z","closed_at":"2025-12-26T17:02:29.674397068Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-xv8.2","depends_on_id":"mala-xv8","type":"parent-child","created_at":"2025-12-26T16:44:02.621218247Z","created_by":"daemon"}]}
{"id":"mala-xv8.3","title":"Update SDK/integration lock tests for canonical keys","description":"Context:\n- Track A3 calls out updating slow SDK lock tests and helpers to match hashed naming (docs/coordination-plan-codex.md:33-36).\n- tests/test_lock_sdk_integration.py uses raw path-to-lock-name mapping (tests/test_lock_sdk_integration.py:82-91).\n- tests/test_lock_integration.py builds lock keys without path normalization (tests/test_lock_integration.py:24-40).\n\nScope:\n- In: update lock helper functions in tests/test_lock_sdk_integration.py and tests/test_lock_integration.py to match canonicalized, hashed lock key behavior.\n- In: add or adjust test cases to include absolute/relative normalization and REPO_NAMESPACE behavior.\n- Out: no script or Python locking helper changes.\n\nAcceptance Criteria:\n- SDK and integration tests align with canonicalized lock key behavior.\n- Tests cover normalization and namespace scenarios and pass.\n\nTest Plan:\n- TDD: update tests to assert new canonical key behavior (fail first).\n- Adjust helper functions and fixtures, then re-run tests.\n- Run uv run pytest tests/test_lock_sdk_integration.py tests/test_lock_integration.py.\n\nNotes for Agent:\n- Keep SDK tests fast; avoid introducing new long-running steps.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-26T16:44:13.175373837Z","updated_at":"2025-12-26T17:07:58.871392452Z","closed_at":"2025-12-26T17:07:58.871392452Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-xv8.3","depends_on_id":"mala-xv8","type":"parent-child","created_at":"2025-12-26T16:44:13.184089977Z","created_by":"daemon"}]}
{"id":"mala-xwkt","title":"[Review] src/pipeline/run_coordinator.py:73-102: [P2] Duplicate _extract_lint_tools_from_spec function in two files","description":"## Review Finding\n\nThis issue was auto-created from a P2 review finding.\n\n**Source issue:** mala-2mwk\n**Reviewer:** claude\n**Location:** src/pipeline/run_coordinator.py:73-102\n\n## Details\n\nThe `_extract_lint_tools_from_spec` function and `_LINT_COMMAND_KINDS` constant are defined identically in both `src/orchestration/orchestrator.py` and `src/pipeline/run_coordinator.py`. This violates DRY and creates maintenance risk. Consider extracting to a shared module (e.g., `src/domain/validation/tool_name_extractor.py` where `extract_tool_name` already lives).","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-02T17:05:07.700765587Z","created_by":"cyou","updated_at":"2026-01-02T17:17:05.040969706Z","closed_at":"2026-01-02T17:17:05.040969706Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_finding:3cc6feffe54c","source:mala-2mwk"]}
{"id":"mala-y34j","title":"Make lock_acquire idempotent for already-held locks","description":"Problem: lock_acquire treats a lock already held by the same agent as blocked, emits WAITING, and can trigger deadlock invariant warnings/self-cycles.\n\nAcceptance Criteria:\n- Detect when get_lock_holder == agent_id and treat the lock as acquired (no WAITING emission).\n- Ensure results correctly reflect acquired=true for re-entrant paths.\n- Add unit/integration coverage for re-entrant lock_acquire by same agent (no WAITING, no deadlock warnings).","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-04T21:51:15.246626487Z","created_by":"cyou","updated_at":"2026-01-04T23:29:37.335127417Z","closed_at":"2026-01-04T23:29:37.335127417Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-y34j","depends_on_id":"mala-qt6d","type":"blocks","created_at":"2026-01-04T21:54:25.72679633Z","created_by":"cyou"}]}
{"id":"mala-y3b","title":"Tmux-like TUI with vertical logs","description":"Context:\n- Current status output is text-based, hard to monitor multiple agents\n- Want tmux-like split-pane TUI for better visibility\n- Related to mala-bdu (status messages) and mala-p6h (send messages)\n\nLocation:\n- src/cli/cli.py (CLI entry point)\n- src/infra/io/event_sink.py (event stream for log data)\n- New: src/tui/ (new package for TUI implementation)\n\nScope:\n- In: Terminal UI with vertical panes showing agent logs\n- In: Agent selection (click/hotkey to focus agent)\n- In: Direct interaction with selected agent\n- Out: Web-based dashboard; remote access\n\nAcceptance Criteria:\n- `mala run --tui` launches TUI mode\n- Each active agent has a vertical log pane\n- Hotkeys to switch focus between agents (1-9, arrow keys)\n- Selected agent can receive typed input\n- Graceful fallback to text mode if terminal doesn't support TUI\n\nTest Plan:\n- Unit: Test TUI rendering components in isolation\n- Integration: Mock agent streams, verify pane updates\n- Manual: Run multi-agent session, interact via TUI\n\nNotes for Agent:\n- Consider using textual or rich library for TUI\n- Agent output comes from JSONL log stream\n- Input forwarding may need IPC mechanism (see mala-p6h)","status":"closed","priority":4,"issue_type":"feature","created_at":"2025-12-28T06:21:39.378928608Z","updated_at":"2026-01-06T03:19:52.804279797Z","closed_at":"2026-01-06T03:19:52.804279797Z","close_reason":"Closed"}
{"id":"mala-y6yw","title":"remove the human verification needed for for epic verify","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T18:02:14.058185907Z","created_by":"cyou","updated_at":"2026-01-03T18:14:06.670367132Z","closed_at":"2026-01-03T18:14:06.670367132Z","close_reason":"Closed"}
{"id":"mala-y8fw","title":"use tool for locks instead of bash scripts","description":"Replace shell script-based file locking (`lock-try.sh`, `lock-wait.sh`, etc.) with named MCP tools exposed via Claude Agent SDK.\n\n## Goals\n- First-class tool visibility for locking operations\n- Typed JSON parameters with schema validation\n- Structured per-file results for deadlock detection\n- Solve batched `\u0026\u0026` command problem where exit=1 gives no file granularity\n\n## Key Changes\n- **2 MCP tools**: `lock_acquire` and `lock_release`\n- WAITING events emitted via closure callback (not PreToolUse hook)\n- PostToolUse hook for ACQUIRED/RELEASED only\n- Remove bash-parsing logic from hooks\n- Update prompts to reference tools only\n\n## Design Doc\n`plans/2026-01-04-locking-tools-design.md`\n\n## Implementation Plan\n`plans/2026-01-04-locking-tools-impl-plan.md`","status":"closed","priority":1,"issue_type":"epic","created_at":"2026-01-03T21:01:56.465859206Z","created_by":"cyou","updated_at":"2026-01-04T16:14:20.228198444Z","closed_at":"2026-01-04T16:14:20.228198444Z","close_reason":"Closed"}
{"id":"mala-y8fw.1","title":"[T001] Add wait_for_lock_async and get_lock_holder to locking.py","description":"## Goal\nAdd async locking functions to support MCP tool handlers without blocking the event loop.\n\n## Context\nThe MCP tool `lock_acquire` needs to wait for locks without blocking. The existing `wait_for_lock()` uses `time.sleep()` which would block the async event loop.\n\n## Scope\n- In: Add `wait_for_lock_async()` and `get_lock_holder()` to `src/infra/tools/locking.py`\n- Out: MCP server creation (T002), hook changes (T003)\n\n## Changes\n- `src/infra/tools/locking.py` — Exists — Add two functions:\n  - `wait_for_lock_async(filepath, agent_id, repo_namespace, timeout_seconds, poll_interval_ms) -\u003e bool`\n  - `get_lock_holder(filepath, repo_namespace) -\u003e str | None`\n\n## Wiring Map\n- N/A (core utilities, wired by T002)\n\n## Acceptance Criteria\n- `wait_for_lock_async()` uses `asyncio.sleep` not `time.sleep`\n- `wait_for_lock_async()` returns `True` when lock acquired, `False` on timeout\n- `get_lock_holder()` returns agent ID from lock metadata file, or `None` if not locked\n- Export both functions in `__all__`\n- Unit tests verify async behavior (mock time to avoid slow tests)\n\n## Verification\n- `uv run pytest tests/integration/infra/test_lock.py -k async -v`\n- `uvx ruff check src/infra/tools/locking.py`\n- `uvx ty check src/infra/tools/locking.py`\n\n## Notes for Agent\n- Use `asyncio.get_event_loop().time()` for deadline comparison\n- Lock metadata file path: `{lock_dir}/{lock_key}/holder` contains agent_id\n- See existing `wait_for_lock()` for synchronous pattern to adapt","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-04T14:39:13.405652852Z","created_by":"cyou","updated_at":"2026-01-04T14:52:35.030749529Z","closed_at":"2026-01-04T14:52:35.030749529Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-y8fw.1","depends_on_id":"mala-y8fw","type":"parent-child","created_at":"2026-01-04T14:39:13.414185201Z","created_by":"daemon"}]}
{"id":"mala-y8fw.10","title":"[Review] 1 non-blocking finding from mala-y8fw.4","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-y8fw.4\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Commit pollution with unrelated files\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/infra/agent_runtime.py:1\n\nCommit `fcf9c9b3` (\"Fix with_mcp() eager evaluation...\") includes 27 unrelated documentation and plan files (e.g., `docs/2025-12-26-coordination-plan.md`, `plans/2026-01-03-architecture-refactor-plan.md`) that are outside the scope of the stated fix. These unrelated changes should be moved to a separate commit to maintain repository hygiene and adhere to the project mandate of not taking significant actions beyond the clear scope of a request.\n\n---\n\n\u003c!-- fp:ade7f52784c8686b --\u003e\n\u003c!-- review_finding:04710895cf46 --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-04T15:49:07.507721494Z","created_by":"cyou","updated_at":"2026-01-04T15:51:33.15734075Z","closed_at":"2026-01-04T15:51:33.15734075Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-y8fw.4"],"dependencies":[{"issue_id":"mala-y8fw.10","depends_on_id":"mala-y8fw","type":"parent-child","created_at":"2026-01-04T15:49:07.508109696Z","created_by":"daemon"}]}
{"id":"mala-y8fw.11","title":"[Review] 4 non-blocking findings from mala-y8fw.6","description":"## Review Findings\n\nThis issue consolidates 4 non-blocking findings from code review.\n\n**Source issue:** mala-y8fw.6\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] TestLockReleaseAll tests use wrong namespace for lock acquisition\n\n**Priority:** P2\n**Reviewer:** claude\n**Location:** tests/integration/infra/test_locking_mcp.py:283-291\n\nIn `test_release_all_locks` (line 283) and `test_release_all_preserves_other_agents` (line 299), locks are acquired with `try_lock(f, agent_id)` (no `repo_namespace`), but then the handlers are created with `repo_namespace=str(lock_dir)`. The `cleanup_agent_locks` function doesn't use namespace at all—it searches all `.lock` files for matching agent_id. However, this inconsistency could cause confusion and makes the test less representative of real usage where all operations should use consistent namespace. This works by accident because `cleanup_agent_locks` ignores namespace entirely.\n\n---\n\n### Finding 2: [P3] Test assertion checks if locks exist rather than specific count\n\n**Priority:** P3\n**Reviewer:** claude\n**Location:** tests/integration/infra/test_locking_mcp.py:295-297\n\nIn `test_release_all_locks` (line 297), the assertion `assert len(locks) == 0` checks there are no locks in the directory at all. If other tests run concurrently or leave orphaned locks, this could be flaky. A more robust check would verify the specific files are released (e.g., check each file in `files` is no longer locked via `get_lock_holder`).\n\n---\n\n### Finding 3: [P3] Unused TYPE_CHECKING import: McpSdkServerConfig\n\n**Priority:** P3\n**Reviewer:** claude\n**Location:** tests/integration/infra/test_locking_mcp.py:29\n\nThe `McpSdkServerConfig` import under `TYPE_CHECKING` is only used in type comments within `TestLockingMCPServerConfig`, which use assignment-style type comments (`config: McpSdkServerConfig = result # type: ignore`). These are runtime casts that don't actually use the import for type checking. This is minor—the import is harmless but unnecessary.\n\n---\n\n### Finding 4: [P3] Path normalization in test prevents verification of canonical resolution\n\n**Priority:** P3\n**Reviewer:** gemini\n**Location:** tests/integration/infra/test_locking_mcp.py:493-495\n\nIn `test_deduplicates_canonical_paths`, `pathlib.Path` normalization ensures that `base` and `str(lock_dir / f\"./{base_file}\")` result in identical strings. Consequently, the test verifies that duplicate strings are handled, but fails to verify the tool's ability to resolve different path representations (e.g., `./file.py` vs `file.py`) to the same canonical path. Providing strings that aren't pre-normalized would make this test more robust.\n\n---\n\n\u003c!-- fp:fcf4b98711c9f290 --\u003e\n\u003c!-- fp:22c76273ea7e245b --\u003e\n\u003c!-- fp:a2451684866d51c1 --\u003e\n\u003c!-- fp:3813b64bf3e84293 --\u003e\n\u003c!-- review_finding:fcbf4aa731ce --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-04T16:02:44.972332168Z","created_by":"cyou","updated_at":"2026-01-04T16:08:06.92489908Z","closed_at":"2026-01-04T16:08:06.92489908Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-y8fw.6"],"dependencies":[{"issue_id":"mala-y8fw.11","depends_on_id":"mala-y8fw","type":"parent-child","created_at":"2026-01-04T16:02:44.972744277Z","created_by":"daemon"}]}
{"id":"mala-y8fw.12","title":"[Review] 1 non-blocking finding from mala-y8fw.5","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-y8fw.5\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Stop hook still uses shell script instead of tool logic\n\n**Priority:** P2\n**Reviewer:** gemini\n**Location:** src/infra/hooks/locking.py:105-115\n\nThe `cleanup_locks_on_stop` hook in `src/infra/hooks/locking.py` still references `lock-release-all.sh` via `SCRIPTS_DIR`. Since the PR removes `scripts_dir` from the prompt and orchestrator, and the goal is to migrate to MCP tools, this hook should ideally use the internal locking logic directly (similar to how the `lock_release` tool is implemented) to ensure consistency and allow for the eventual removal of the shell scripts.\n\n---\n\n\u003c!-- fp:db5b5c8bda5e6502 --\u003e\n\u003c!-- review_finding:f92623bf89d4 --\u003e","notes":"Quality gate failed: External review failed: spawn failed: Error: No changes found for diff mode: --commit e528b316c948a2a47d040891a17dfeca95411585\nLog: /home/cyou/.claude/projects/-home-cyou-mala/d8ed9aed-dc90-4738-9938-3fa1d0f2f4d6.jsonl","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-04T16:14:20.24455492Z","created_by":"cyou","updated_at":"2026-01-04T16:34:15.732524688Z","closed_at":"2026-01-04T16:34:15.732524688Z","close_reason":"Closed","labels":["auto_generated","needs-followup","review_finding","source:mala-y8fw.5"],"dependencies":[{"issue_id":"mala-y8fw.12","depends_on_id":"mala-y8fw","type":"parent-child","created_at":"2026-01-04T16:14:20.244918838Z","created_by":"daemon"}]}
{"id":"mala-y8fw.2","title":"[T002] Create locking_mcp.py with tool handlers","description":"## Goal\nCreate MCP server with 2 locking tools (`lock_acquire`, `lock_release`) bound to agent context with closure-captured callback for WAITING events.\n\n## Context\nThis is the core implementation - MCP tools that replace shell scripts. Tool handlers emit WAITING events directly via closure-captured callback when `try_lock()` fails.\n\n## Scope\n- In: Create `src/infra/tools/locking_mcp.py` with `create_locking_mcp_server()` factory\n- Out: Wiring to agent_runtime (T004), hook simplification (T003)\n\n## Changes\n- `src/infra/tools/locking_mcp.py` — New — Create with:\n  - `create_locking_mcp_server(agent_id, repo_namespace, emit_lock_event) -\u003e McpSdkServerConfig`\n  - `lock_acquire` tool handler (try locks, emit WAITING via callback, wait until ANY progress)\n  - `lock_release` tool handler (release specific files or all via `all=true`)\n\n## Wiring Map\n- `emit_lock_event` callback → captured in tool handler closures → emits WAITING during blocking\n\n## Acceptance Criteria\n- `lock_acquire` accepts `filepaths: list[str]` and `timeout_seconds: float` (optional, default 30)\n- `lock_acquire` returns per-file results: `{filepath, acquired, holder}`\n- `lock_acquire` with `timeout_seconds=0` is non-blocking\n- `lock_acquire` returns when ANY blocked file becomes available (uses `asyncio.wait(return_when=FIRST_COMPLETED)`)\n- `lock_acquire` emits WAITING events via closure callback when files are blocked\n- `lock_release` uses `oneOf` schema for mutually exclusive `filepaths` vs `all`\n- `lock_release` is idempotent (returns success even if not held)\n- Empty `filepaths` rejected with error\n- JSON schema validation for all tool inputs\n- Return format: `{\"content\": [{\"type\": \"text\", \"text\": json_string}]}`\n\n## Verification\n- `uvx ruff check src/infra/tools/locking_mcp.py`\n- `uvx ty check src/infra/tools/locking_mcp.py`\n- Manual: Import and instantiate `create_locking_mcp_server()` in Python REPL\n\n## Notes for Agent\n- Tool handler signature: `async def handler(args: dict) -\u003e dict`\n- Use `@tool` decorator from Claude Agent SDK\n- Sort filepaths by canonical path before processing\n- For wait-until-ANY-progress: spawn `wait_for_lock_async()` task per blocked file, use `asyncio.wait(..., return_when=FIRST_COMPLETED)`, cancel remaining tasks\n- WAITING emission: once per blocked filepath per call (not per poll)","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-04T14:39:34.358167046Z","created_by":"cyou","updated_at":"2026-01-04T15:09:21.134166943Z","closed_at":"2026-01-04T15:09:21.134166943Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-y8fw.2","depends_on_id":"mala-y8fw","type":"parent-child","created_at":"2026-01-04T14:39:34.367384001Z","created_by":"daemon"},{"issue_id":"mala-y8fw.2","depends_on_id":"mala-y8fw.1","type":"blocks","created_at":"2026-01-04T14:39:37.452635012Z","created_by":"daemon"}]}
{"id":"mala-y8fw.3","title":"[T003] Simplify deadlock hooks to PostToolUse only","description":"## Goal\nSimplify `src/infra/hooks/deadlock.py` to only handle PostToolUse for ACQUIRED/RELEASED events from MCP tools. Remove bash-parsing logic.\n\n## Context\nWith MCP tools, WAITING events are emitted directly by the tool handler via closure-captured callback. Hooks only need to capture ACQUIRED/RELEASED from tool results. Bash command parsing is no longer needed.\n\n## Scope\n- In: Rewrite `src/infra/hooks/deadlock.py` for MCP tool hooks only\n- Out: Runtime wiring (T004), lock enforcement hook (T005)\n\n## Changes\n- `src/infra/hooks/deadlock.py` — Exists — Replace/simplify:\n  - Remove `_LOCK_TRY_PATTERN`, `_LOCK_WAIT_PATTERN`, `_LOCK_RELEASE_PATTERN`\n  - Remove `_extract_lock_path()`, `_extract_all_lock_paths()`, `_is_safe_batch_command()`\n  - Remove `_get_exit_code()`, `_strip_quotes()`\n  - Remove `make_lock_wait_hook()` (PreToolUse not needed)\n  - Rename/rewrite `make_lock_event_hook()` to handle MCP tool results only\n  - Parse `tool_response[\"content\"][0][\"text\"]` as JSON for structured results\n  - Match on tool names: `mcp__mala-locking__lock_acquire`, `mcp__mala-locking__lock_release`\n- `src/infra/hooks/__init__.py` — Exists — Remove `make_lock_wait_hook` from exports (if present)\n\n## Wiring Map\n- N/A (hook simplification, wired by T004)\n\n## Acceptance Criteria\n- PostToolUse hook matches `mcp__mala-locking__lock_acquire|mcp__mala-locking__lock_release`\n- Hook parses JSON from `content[0].text` to get structured results\n- ACQUIRED emitted for each file with `acquired: true` in results\n- RELEASED emitted for each file in `lock_release` results\n- No WAITING events emitted by hooks (tool handler does this)\n- All bash-parsing code removed\n- `make_lock_wait_hook` removed (no PreToolUse hook needed)\n- `hooks/__init__.py` updated to remove `make_lock_wait_hook` export\n\n## Verification\n- `uv run pytest tests/unit/infra/hooks/test_deadlock_hook.py -v`\n- `uvx ruff check src/infra/hooks/deadlock.py`\n- `grep -n \"make_lock_wait_hook\" src/infra/hooks/__init__.py` returns no matches\n- Ensure no bash-related patterns remain in deadlock.py\n\n## Notes for Agent\n- JSON parse with try/except and clear error logging\n- Use `canonicalize_path()` on filepaths from results\n- Hook signature: `async def hook(hook_input, stderr, context) -\u003e dict`\n- Tool response format: `{\"content\": [{\"type\": \"text\", \"text\": \"{\\\"results\\\": [...]}\"}]}`","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-04T14:39:56.337588346Z","created_by":"cyou","updated_at":"2026-01-04T15:35:01.863469799Z","closed_at":"2026-01-04T15:35:01.863469799Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-y8fw.3","depends_on_id":"mala-y8fw","type":"parent-child","created_at":"2026-01-04T14:39:56.345331464Z","created_by":"daemon"},{"issue_id":"mala-y8fw.3","depends_on_id":"mala-y8fw.2","type":"blocks","created_at":"2026-01-04T14:40:00.605935374Z","created_by":"daemon"}]}
{"id":"mala-y8fw.4","title":"[T004] Wire locking MCP server through AgentRuntimeBuilder","description":"## Goal\nExtend `get_mcp_servers()` and wire the locking MCP server through `AgentRuntimeBuilder.build()` with agent_id and emit_lock_event callback.\n\n## Context\nThe locking MCP server needs `agent_id` and `emit_lock_event` callback to function. These are available in `AgentRuntimeBuilder` but need to be passed through to `get_mcp_servers()`.\n\n## Scope\n- In: Modify `src/infra/mcp.py` and `src/infra/agent_runtime.py`\n- Out: Hook logic (T003), prompt updates (T005)\n\n## Changes\n- `src/infra/mcp.py` — Exists — Extend `get_mcp_servers()` signature:\n  - Add `agent_id: str | None = None`\n  - Add `emit_lock_event: Callable[[LockEvent], None] | None = None`\n  - When both provided, include `mala-locking` server from `create_locking_mcp_server()`\n  - Use `repo_namespace=str(repo_path)` for consistency with existing locking.py usage\n- `src/infra/agent_runtime.py` — Exists — Update `with_mcp()` and `build()`:\n  - In `build()`, call `with_mcp()` with `emit_lock_event=monitor.handle_event` when deadlock monitor configured\n  - Pass `agent_id` and `emit_lock_event` to `get_mcp_servers()`\n\n## Wiring Map\n- `AgentRuntimeBuilder.build()` → `get_mcp_servers(repo_path, agent_id, emit_lock_event)` → `create_locking_mcp_server(agent_id, str(repo_path), emit_lock_event)`\n\n## Acceptance Criteria\n- [integration-path-test] `get_mcp_servers()` returns `mala-locking` server when `agent_id` and `emit_lock_event` provided\n- `AgentRuntimeBuilder.build()` correctly wires deadlock monitor callback to MCP server\n- Existing callers of `get_mcp_servers(repo_path)` continue to work (optional params)\n- Lock event callback is thread-safe/async-safe (uses existing `monitor.handle_event`)\n\n## Verification\n- `uv run pytest tests/unit/infra/test_agent_runtime.py -v`\n- `uv run pytest tests/integration/infra/test_locking_mcp.py -v` (created in T006)\n- Verify MCP server appears in agent tools list\n\n## Config Override Test\n- Test that `get_mcp_servers()` without optional params returns empty dict (existing behavior)\n- Test that with `agent_id` and `emit_lock_event`, `mala-locking` server is included\n\n## Notes for Agent\n- Import `create_locking_mcp_server` inside `get_mcp_servers()` to avoid circular imports\n- The callback signature is `Callable[[LockEvent], None]` where LockEvent is from `src.core.models`\n- Use `str(repo_path)` for `repo_namespace` (consistent with existing `with_env()` which sets `REPO_NAMESPACE`)","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-04T14:40:22.456837943Z","created_by":"cyou","updated_at":"2026-01-04T15:49:07.473610306Z","closed_at":"2026-01-04T15:49:07.473610306Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-y8fw.4","depends_on_id":"mala-y8fw","type":"parent-child","created_at":"2026-01-04T14:40:22.464924828Z","created_by":"daemon"},{"issue_id":"mala-y8fw.4","depends_on_id":"mala-y8fw.2","type":"blocks","created_at":"2026-01-04T14:40:26.1419692Z","created_by":"daemon"},{"issue_id":"mala-y8fw.4","depends_on_id":"mala-y8fw.3","type":"blocks","created_at":"2026-01-04T14:40:26.158243439Z","created_by":"daemon"}]}
{"id":"mala-y8fw.5","title":"[T005] Update prompts and lock enforcement hook for tools","description":"## Goal\nUpdate prompt documentation and lock enforcement error messages to reference MCP tools instead of shell scripts.\n\n## Context\nWith MCP tools replacing shell scripts, prompts and error messages need to guide agents to use the new tools.\n\n## Scope\n- In: Update `src/prompts/implementer_prompt.md` and `src/infra/hooks/locking.py`\n- Out: Core locking logic (T001-T004)\n\n## Changes\n- `src/prompts/implementer_prompt.md` — Exists — Replace File Locking Protocol section:\n  - Remove lock script table (lock-try.sh, lock-wait.sh, etc.)\n  - Add MCP tool documentation for `lock_acquire` and `lock_release`\n  - Update workflow examples to show tool usage\n  - Update \"Lock commands\" section with tool-based examples\n  - Remove shell script references from \"Lock handling\" and \"Hard rules\" sections\n- `src/infra/hooks/locking.py` — Exists — Update error message:\n  - Change from `\"Acquire lock with: lock-try.sh {file_path}\"` \n  - To `\"Use lock_acquire tool with filepaths: [\\\"{file_path}\\\"]\"`\n\n## Acceptance Criteria\n- No shell script references remain in `implementer_prompt.md` File Locking sections\n- Error message in `locking.py` references `lock_acquire` tool\n- Prompt examples show proper tool input/output format\n- Workflow section updated for tool-based acquisition strategy\n\n## Verification\n- `grep -n \"lock-try\\|lock-wait\\|lock-release\" src/prompts/implementer_prompt.md` returns no matches\n- `grep -n \"lock-try\\|lock-wait\" src/infra/hooks/locking.py` returns no matches\n- Read updated sections to verify clarity\n\n## Notes for Agent\n- Keep the \"Hard rules\" concept (no retries, timeout cap) but adapt for tool semantics\n- `lock_acquire` with `timeout_seconds=0` is equivalent to old `lock-try.sh`\n- `lock_acquire` with timeout is equivalent to old `lock-wait.sh`\n- Idempotent release behavior matches tool design","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-04T14:40:43.292130736Z","created_by":"cyou","updated_at":"2026-01-04T16:10:50.463215319Z","closed_at":"2026-01-04T16:10:50.463215319Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-y8fw.5","depends_on_id":"mala-y8fw","type":"parent-child","created_at":"2026-01-04T14:40:43.300136208Z","created_by":"daemon"},{"issue_id":"mala-y8fw.5","depends_on_id":"mala-y8fw.4","type":"blocks","created_at":"2026-01-04T14:40:46.463555115Z","created_by":"daemon"}]}
{"id":"mala-y8fw.6","title":"[T006] Integration tests for MCP locking tools","description":"## Goal\nCreate integration tests that verify the full tool→hook→monitor flow with real filesystem locking.\n\n## Context\nIntegration tests are prioritized per plan to validate end-to-end behavior before unit tests fill in coverage.\n\n## Scope\n- In: Create `tests/integration/infra/test_locking_mcp.py`\n- Out: Unit tests (T007)\n\n## Changes\n- `tests/integration/infra/test_locking_mcp.py` — New — Create comprehensive integration tests:\n  - Real filesystem lock directory (temp dir)\n  - Test `lock_acquire` with immediate success\n  - Test `lock_acquire` with contention (background task holds lock)\n  - Test `lock_acquire` returns on ANY progress (one of N blocked files freed)\n  - Test `lock_acquire` timeout behavior (returns partial results)\n  - Test `lock_release` with specific files\n  - Test `lock_release` with `all=true`\n  - Test WAITING events emitted via closure callback (mock callback captures events)\n  - Test PostToolUse hook emits ACQUIRED/RELEASED from tool results\n  - Test multi-file batches process in sorted canonical order\n  - Test empty filepaths rejection\n\n## [integration-path-test]\n- Test `AgentRuntimeBuilder.build()` → full runtime with locking MCP server\n- Verify locking tools appear in MCP server configuration\n\n## Acceptance Criteria\n- All tests use real filesystem (temp lock dir)\n- Concurrency tests spawn background tasks to hold locks\n- Callback mock captures WAITING events emitted by tool handler\n- PostToolUse hook integration verified with mock deadlock monitor\n- ≥90% coverage of `locking_mcp.py`\n\n## Verification\n- `uv run pytest tests/integration/infra/test_locking_mcp.py -v`\n- `uv run pytest tests/integration/infra/test_locking_mcp.py --cov=src/infra/tools/locking_mcp --cov-report=term-missing`\n\n## Notes for Agent\n- Use `pytest-asyncio` for async tests\n- Use `asyncio.create_task()` to spawn background lock holders\n- Use `asyncio.wait_for()` with timeout for concurrent operations\n- Mock `emit_lock_event` callback to capture and assert WAITING events\n- Test fixture should set `MALA_LOCK_DIR` to temp directory","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-04T14:41:03.228283974Z","created_by":"cyou","updated_at":"2026-01-04T16:02:44.93983057Z","closed_at":"2026-01-04T16:02:44.93983057Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-y8fw.6","depends_on_id":"mala-y8fw","type":"parent-child","created_at":"2026-01-04T14:41:03.237271907Z","created_by":"daemon"},{"issue_id":"mala-y8fw.6","depends_on_id":"mala-y8fw.4","type":"blocks","created_at":"2026-01-04T14:41:06.752346043Z","created_by":"daemon"}]}
{"id":"mala-y8fw.7","title":"[T007] Unit tests for MCP tool handlers and async locking","description":"## Goal\nCreate unit tests for MCP tool handlers with mocked dependencies to achieve 85% coverage.\n\n## Context\nUnit tests complement integration tests with faster execution and finer-grained assertions. Can run in parallel with T006 (integration tests) since unit tests use mocks.\n\n## Scope\n- In: Create `tests/unit/infra/tools/test_locking_mcp.py` and add to existing lock tests\n- Out: Integration tests (T006 - parallel)\n\n## Changes\n- `tests/unit/infra/tools/__init__.py` — New — Empty init file\n- `tests/unit/infra/tools/test_locking_mcp.py` — New — Unit tests for tool handlers:\n  - JSON schema validation (empty filepaths rejected, mutually exclusive params)\n  - Return format structure matches spec\n  - `lock_acquire` calls `try_lock()` for each file in sorted order\n  - `lock_acquire` emits WAITING via callback when blocked\n  - `lock_release` idempotent behavior\n  - `lock_release` with `all=true` calls `cleanup_agent_locks()`\n  - Mock `locking.py` functions to verify wiring\n- `tests/integration/infra/test_lock.py` — Exists — Add tests for:\n  - `wait_for_lock_async()` returns True on success\n  - `wait_for_lock_async()` returns False on timeout\n  - `wait_for_lock_async()` uses `asyncio.sleep` not `time.sleep`\n  - `get_lock_holder()` returns agent_id from metadata\n  - `get_lock_holder()` returns None when not locked\n\n## Parallel [P]\nCan run concurrently with T006 after T002 completes.\n\n## Acceptance Criteria\n- ≥85% coverage on `locking_mcp.py` combined with integration tests\n- All tool input validation tested (schema errors)\n- Mocked tests run fast (\u003c1s each)\n- `wait_for_lock_async()` async behavior verified (not blocking)\n\n## Verification\n- `uv run pytest tests/unit/infra/tools/test_locking_mcp.py -v`\n- `uv run pytest tests/integration/infra/test_lock.py -k \"async or holder\" -v`\n- `uv run pytest --cov=src/infra/tools/locking_mcp --cov=src/infra/tools/locking --cov-report=term-missing`\n\n## Notes for Agent\n- Use `pytest.mark.asyncio` for async tests\n- Mock `asyncio.sleep` to verify it's called (not `time.sleep`)\n- Test deadline/timeout logic with mocked loop time\n- Use `unittest.mock.AsyncMock` for async function mocks","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-04T14:41:22.341974809Z","created_by":"cyou","updated_at":"2026-01-04T15:34:30.841543444Z","closed_at":"2026-01-04T15:34:30.841543444Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-y8fw.7","depends_on_id":"mala-y8fw","type":"parent-child","created_at":"2026-01-04T14:41:22.350296467Z","created_by":"daemon"},{"issue_id":"mala-y8fw.7","depends_on_id":"mala-y8fw.2","type":"blocks","created_at":"2026-01-04T14:43:56.41371543Z","created_by":"daemon"}]}
{"id":"mala-y8fw.8","title":"[T008] Update deadlock hook unit tests for MCP tools","description":"## Goal\nUpdate `tests/unit/infra/hooks/test_deadlock_hook.py` to test the simplified PostToolUse-only hook for MCP tools.\n\n## Context\nThe deadlock hook tests need to be rewritten to match the simplified hook that only handles MCP tool results.\n\n## Scope\n- In: Rewrite/update `tests/unit/infra/hooks/test_deadlock_hook.py`\n- Out: Hook implementation (T003)\n\n## Changes\n- `tests/unit/infra/hooks/test_deadlock_hook.py` — Exists — Update tests:\n  - Remove all bash command parsing tests\n  - Remove `_extract_lock_path` and `_is_safe_batch_command` tests\n  - Remove PreToolUse/`make_lock_wait_hook` tests\n  - Add PostToolUse hook tests for MCP tool results\n  - Test ACQUIRED emitted for `lock_acquire` results with `acquired: true`\n  - Test RELEASED emitted for `lock_release` results\n  - Test hook ignores non-locking tools\n  - Test JSON parse error handling\n  - Test path canonicalization applied to results\n\n## Acceptance Criteria\n- No bash-related test code remains\n- PostToolUse hook tested with mock MCP tool responses\n- Error handling tested (malformed JSON)\n- All LockEventType cases covered (ACQUIRED, RELEASED, no WAITING)\n\n## Verification\n- `uv run pytest tests/unit/infra/hooks/test_deadlock_hook.py -v`\n- `uv run pytest tests/unit/infra/hooks/test_deadlock_hook.py --cov=src/infra/hooks/deadlock --cov-report=term-missing`\n\n## Notes for Agent\n- Mock tool response format: `{\"content\": [{\"type\": \"text\", \"text\": \"{...}\"}]}`\n- Test both `lock_acquire` and `lock_release` tool results\n- Verify hook matcher regex matches expected tool names","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-04T14:41:39.948482566Z","created_by":"cyou","updated_at":"2026-01-04T15:40:39.712322669Z","closed_at":"2026-01-04T15:40:39.712322669Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-y8fw.8","depends_on_id":"mala-y8fw","type":"parent-child","created_at":"2026-01-04T14:41:39.957395932Z","created_by":"daemon"},{"issue_id":"mala-y8fw.8","depends_on_id":"mala-y8fw.3","type":"blocks","created_at":"2026-01-04T14:41:44.229695001Z","created_by":"daemon"}]}
{"id":"mala-y8fw.9","title":"[Review] 1 non-blocking finding from mala-y8fw.3","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-y8fw.3\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] lock_release with all=true returns empty released array, but hook expects filepaths\n\n**Priority:** P2\n**Reviewer:** claude\n**Location:** src/infra/hooks/deadlock.py:182-184\n\nWhen `lock_release` is called with `all=true`, the MCP tool returns `{\"released\": [], \"count\": N}` (line 349 of locking_mcp.py). The hook's `_process_release_response` iterates over `data.get(\"released\", [])` expecting file paths, but gets an empty list. This means no RELEASED events are emitted for locks cleaned up via `all=true`, which could leave the deadlock graph stale. The fix would be for the MCP tool to return actual released paths, or for the hook to handle this case differently.\n\n---\n\n\u003c!-- fp:de9df564230fa427 --\u003e\n\u003c!-- review_finding:104e26cae13d --\u003e","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-04T15:35:01.894246352Z","created_by":"cyou","updated_at":"2026-01-04T15:45:50.595633668Z","closed_at":"2026-01-04T15:45:50.595633668Z","close_reason":"Closed","labels":["auto_generated","review_finding","source:mala-y8fw.3"],"dependencies":[{"issue_id":"mala-y8fw.9","depends_on_id":"mala-y8fw","type":"parent-child","created_at":"2026-01-04T15:35:01.894579181Z","created_by":"daemon"}]}
{"id":"mala-y9i","title":"Epic: Healthcheck fixes (correctness + validation)","description":"Tracks fixes from the latest code health review: quality gate correctness, coverage reporting, failed-run metadata, and env override behavior. Parent epic only; children contain detailed scopes, AC, and tests.","status":"closed","priority":1,"issue_type":"epic","created_at":"2025-12-27T17:23:51.264003246Z","updated_at":"2025-12-27T19:21:06.374890315Z","closed_at":"2025-12-27T19:21:06.374890315Z","close_reason":"All children completed"}
{"id":"mala-y9i.1","title":"Quality gate should fail on non-zero validation exits","description":"Context:\n- Quality gate only checks for presence of validation commands in JSONL logs, not whether they succeeded (`src/quality_gate.py`).\n- This can mark issues as successful even when pytest/ruff/ty failed, risking broken changes slipping through.\n- Verify Claude JSONL includes tool_result exit status; if not, adjust parsing fallback accordingly.\n\nScope:\n- In: parse Bash tool_result entries to capture exit status per command kind and fail the gate on any non-zero exit.\n- In: include non-zero exit details in failure reasons to aid follow-up.\n- In: add/adjust tests in `tests/test_quality_gate.py` for success vs failure evidence.\n- Out: changing which validations are required or orchestrator retry policy.\n\nAcceptance Criteria:\n- Gate fails when a required validation command ran but exited non-zero.\n- Gate passes only when required validation commands ran and exited 0.\n- Resolution markers (ISSUE_NO_CHANGE/ISSUE_OBSOLETE) still bypass validation evidence checks.\n\nTest Plan:\n- TDD: add a failing test with JSONL fixture where pytest exits non-zero.\n- Implement minimal parser change; update fixtures as needed.\n- Run `uv run pytest tests/test_quality_gate.py`.\n\nNotes for Agent:\n- Ensure command matching stays consistent with ValidationSpec detection patterns.","status":"closed","priority":1,"issue_type":"bug","created_at":"2025-12-27T17:24:24.649685599Z","updated_at":"2025-12-27T18:32:55.300250022Z","closed_at":"2025-12-27T18:32:55.300250022Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-y9i.1","depends_on_id":"mala-y9i","type":"parent-child","created_at":"2025-12-27T17:24:24.657898438Z","created_by":"daemon"}]}
{"id":"mala-y9i.2","title":"Generate coverage.xml in spec validation","description":"Context:\n- Spec validation checks for `coverage.xml`, but the pytest command in `build_validation_spec` does not generate XML reports (`src/validation/spec.py`).\n- This causes coverage validation to fail with \"report not found\" even when tests run.\n- Needs verification: confirm current test command/output behavior in the runtime environment.\n\nScope:\n- In: when coverage is enabled, add `--cov=src` and `--cov-report=xml` (and `--cov-branch` if required by policy) to the pytest command built in `build_validation_spec`.\n- In: update/extend tests (e.g., `tests/test_spec.py` or `tests/test_validation.py`) to assert XML coverage is generated and consumed.\n- Out: changing coverage thresholds or parsing logic.\n\nAcceptance Criteria:\n- A spec-based validation run produces `coverage.xml` without manual steps.\n- Coverage checks pass/fail based on threshold, not missing file errors.\n- Existing coverage parsing behavior remains unchanged.\n\nTest Plan:\n- TDD: add a failing test that expects XML coverage generation in spec pytest command.\n- Implement minimal change in `build_validation_spec`.\n- Run `uv run pytest tests/test_spec.py` (and any updated coverage tests).\n\nNotes for Agent:\n- Keep the change scoped to spec-based validation to avoid touching legacy runner behavior unless necessary.","status":"closed","priority":2,"issue_type":"bug","created_at":"2025-12-27T17:24:35.528673696Z","updated_at":"2025-12-27T18:18:03.600116003Z","closed_at":"2025-12-27T18:18:03.600116003Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-y9i.2","depends_on_id":"mala-y9i","type":"parent-child","created_at":"2025-12-27T17:24:35.536611666Z","created_by":"daemon"}]}
{"id":"mala-y9i.3","title":"Record quality-gate evidence for failed runs","description":"Context:\n- Failed runs never populate `quality_gate` in run metadata because the failure branch is nested under the success path (`src/orchestrator.py`).\n- This loses evidence and reasons needed for troubleshooting and follow-up triage.\n\nScope:\n- In: refactor result handling so failures always record `quality_gate` evidence/reasons when a log path exists.\n- In: add/adjust tests in `tests/test_orchestrator.py` to assert `quality_gate` is present for failed runs.\n- Out: changing how issues are closed/marked in Beads or altering retry logic.\n\nAcceptance Criteria:\n- Run metadata includes `quality_gate` details for failed runs with logs.\n- Successful runs continue to record `quality_gate` as before.\n- No change to success/failure outcomes or retry behavior.\n\nTest Plan:\n- TDD: add a failing test for failed-run metadata population.\n- Implement the minimal control-flow fix.\n- Run `uv run pytest tests/test_orchestrator.py`.\n\nNotes for Agent:\n- Keep evidence parsing consistent with existing QualityGate helpers.","status":"closed","priority":2,"issue_type":"bug","created_at":"2025-12-27T17:24:44.528665141Z","updated_at":"2025-12-27T18:37:24.657045092Z","closed_at":"2025-12-27T18:37:24.657045092Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-y9i.3","depends_on_id":"mala-y9i","type":"parent-child","created_at":"2025-12-27T17:24:44.537115188Z","created_by":"daemon"}]}
{"id":"mala-y9i.4","title":"Respect MALA_RUNS_DIR/MALA_LOCK_DIR from user .env","description":"Context:\n- `RUNS_DIR` and `LOCK_DIR` are computed at import time before `.env` is loaded, so `MALA_RUNS_DIR`/`MALA_LOCK_DIR` in `~/.config/mala/.env` are ignored (`src/tools/env.py`, `src/cli.py`).\n- This makes configuration overrides appear to have no effect.\n- Needs verification: confirm behavior in a clean shell where only `.env` sets these variables.\n\nScope:\n- In: defer directory resolution until after `load_user_env()` (e.g., replace module-level constants with getters or re-evaluate post-load).\n- In: add/adjust tests (likely `tests/test_cli.py` or a new env test) to confirm overrides are respected.\n- Out: changing default directories or env var names.\n\nAcceptance Criteria:\n- Setting `MALA_RUNS_DIR` and `MALA_LOCK_DIR` in `~/.config/mala/.env` changes runtime paths.\n- Defaults remain unchanged when env vars are not set.\n\nTest Plan:\n- TDD: add a failing test that loads env and asserts the resolved dirs use overrides.\n- Implement minimal refactor to delay evaluation.\n- Run `uv run pytest tests/test_cli.py` (or relevant env test file).\n\nNotes for Agent:\n- Ensure any refactor doesn’t break existing imports that expect constants.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-27T17:24:55.184682777Z","updated_at":"2025-12-27T19:21:06.3556664Z","closed_at":"2025-12-27T19:21:06.3556664Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-y9i.4","depends_on_id":"mala-y9i","type":"parent-child","created_at":"2025-12-27T17:24:55.193409852Z","created_by":"daemon"}]}
{"id":"mala-yb5a","title":"remove the coverage threshold from cli flags, should use the one in mala.yaml","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-06T03:27:54.446692017Z","created_by":"cyou","updated_at":"2026-01-06T03:40:45.254619231Z","closed_at":"2026-01-06T03:40:45.254619231Z","close_reason":"Closed"}
{"id":"mala-yc22","title":"Do not inherit validation_triggers from presets","description":"Spec says presets only provide base command pool; trigger config must be project-defined. Current merge inherits preset validation_triggers when user doesn't set them. Change merge logic to avoid inheriting triggers from preset. File: src/domain/validation/config_merger.py.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-09T16:53:26.186182726Z","created_by":"cyou","updated_at":"2026-01-09T17:01:06.648910265Z","closed_at":"2026-01-09T17:01:06.648910265Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-yc22","depends_on_id":"mala-smaj","type":"parent-child","created_at":"2026-01-09T16:55:11.710863685Z","created_by":"cyou"}]}
{"id":"mala-ychu","title":"[Test] epic verifier output check","description":"temp issue to inspect --silent output","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-01T01:53:21.275463227Z","created_by":"cyou","updated_at":"2026-01-01T01:55:09.963716222Z","closed_at":"2026-01-01T01:55:09.963716222Z","close_reason":"Closed","labels":["auto_generated","epic_remediation:test"]}
{"id":"mala-yco","title":"Make code review generic, not specific to Codex","description":"Make mala language-agnostic:\n\n1. **Code review**: Pluggable review backends beyond Codex (different models, approaches)\n2. **Validation specs**: Add ValidationSpec support for non-Python languages:\n   - JavaScript/TypeScript (eslint, prettier, tsc, jest/vitest)\n   - Go (go vet, gofmt, go test)\n   - Rust (clippy, rustfmt, cargo test)\n   - Generic (user-defined commands in config)\n\nThis enables mala to orchestrate agents across polyglot codebases.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-27T06:45:46.864991081Z","updated_at":"2026-01-01T21:40:37.46505543Z","closed_at":"2026-01-01T21:40:37.46505543Z","close_reason":"Closed","labels":["polyglot"]}
{"id":"mala-yco.1","title":"Cleanup: update console + validation spec review naming","description":"Context:\n- Console event sink still references Codex review and thinking mode.\n- Validation spec docs reference the old \"codex-review\" disable value.\n\nScope:\n- In: update src/event_sink_console.py to use review_enabled and remove thinking-mode output; rename strings to \"external review\" or \"review\".\n- In: update src/validation/spec.py to rename \"codex-review\" to \"review\" in docs/comments.\n- Out: other codex references in pipeline/orchestrator/tests (handled elsewhere).\n\nAcceptance Criteria:\n- Console output uses \"review\" phrasing and has no codex_thinking_mode references.\n- Validation spec comment uses \"review\".\n- rg -n \"codex\" src/event_sink_console.py src/validation/spec.py returns no matches.\n\nTest Plan:\n- Run: rg -n \"codex\" src/event_sink_console.py src/validation/spec.py\n- (Optional) uv run pytest tests/test_event_sink.py\n","status":"tombstone","priority":2,"issue_type":"chore","created_at":"2025-12-30T05:23:44.834411601Z","created_by":"cyou","updated_at":"2025-12-30T05:41:25.140647449Z","dependencies":[{"issue_id":"mala-yco.1","depends_on_id":"mala-yco","type":"parent-child","created_at":"2025-12-30T05:23:44.83478146Z","created_by":"daemon"},{"issue_id":"mala-yco.1","depends_on_id":"mala-8kte.6","type":"blocks","created_at":"2025-12-30T05:23:44.855351606Z","created_by":"daemon"},{"issue_id":"mala-yco.1","depends_on_id":"mala-8kte.7","type":"blocks","created_at":"2025-12-30T05:23:44.877129379Z","created_by":"daemon"}],"deleted_at":"2025-12-30T05:41:25.140647449Z","deleted_by":"daemon","delete_reason":"delete","original_type":"chore"}
{"id":"mala-yg9","title":"Epic: Architecture refactor follow-ups (2025-12-27 review)","description":"Scope: Follow-up work from docs/architecture-check-2025-12-27.md. Tracks orchestrator + validation refactors to improve boundary clarity and testability.","status":"closed","priority":1,"issue_type":"epic","created_at":"2025-12-27T06:13:21.897889922Z","updated_at":"2025-12-28T05:29:33.825890471Z","closed_at":"2025-12-28T05:29:33.825890471Z","close_reason":"All children completed"}
{"id":"mala-yg9.1","title":"Epic: Orchestrator lifecycle \u0026 gate flow","description":"Scope: Orchestrator lifecycle policy, gate flow concurrency, and metadata recording improvements from the 2025-12-27 architecture review.","status":"closed","priority":1,"issue_type":"epic","created_at":"2025-12-27T06:13:27.609461567Z","updated_at":"2025-12-27T09:03:57.376513213Z","closed_at":"2025-12-27T09:03:57.376513213Z","close_reason":"All children completed","dependencies":[{"issue_id":"mala-yg9.1","depends_on_id":"mala-yg9","type":"parent-child","created_at":"2025-12-27T06:13:27.618446169Z","created_by":"daemon"}]}
{"id":"mala-yg9.1.1","title":"Record resolution markers in run metadata","description":"Context:\n- Needs verification: _run_quality_gate uses check_with_resolution, so ISSUE_NO_CHANGE/ISSUE_OBSOLETE can pass, but IssueRun.resolution is never set (src/orchestrator.py:884-897, src/logging/run_metadata.py:57-140).\n- Downstream tooling cannot distinguish a normal commit pass from a no-change/obsolete resolution.\n\nScope:\n- In: propagate gate_result.resolution into IssueRun and persist it via RunMetadata.\n- Out: changing marker syntax or commit requirements for normal flows.\n\nAcceptance Criteria:\n- Run metadata records resolution outcome + rationale when markers are used.\n- Non-resolution flows remain unchanged.\n\nTest Plan:\n- TDD: add a failing test for resolution recording, implement fix, refactor.\n- Run: uv run pytest tests/test_quality_gate.py tests/test_orchestrator.py\n\nNotes for Agent:\n- Consider surfacing resolution in logs for visibility (optional).","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-27T06:15:29.583791182Z","updated_at":"2025-12-27T08:18:08.136749502Z","closed_at":"2025-12-27T08:18:08.136749502Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-yg9.1.1","depends_on_id":"mala-yg9.1","type":"parent-child","created_at":"2025-12-27T06:15:29.592643786Z","created_by":"daemon"},{"issue_id":"mala-yg9.1.1","depends_on_id":"mala-yg9.2.4","type":"blocks","created_at":"2025-12-27T06:16:03.215759211Z","created_by":"daemon"}]}
{"id":"mala-yg9.1.2","title":"Make quality gate checks non-blocking in orchestrator loop","description":"Context:\n- Needs verification: _run_quality_gate is called from the async response loop but uses blocking subprocess/file I/O (src/orchestrator.py:217-433).\n- This can stall the event loop when multiple agents run concurrently.\n\nScope:\n- In: move gate checks to asyncio.to_thread (or equivalent) so the event loop stays responsive.\n- Out: changing gate semantics or evidence requirements.\n\nAcceptance Criteria:\n- Gate checks do not block the event loop (structurally enforced via to_thread/async).\n- Orchestrator can continue servicing other agents while a gate runs.\n\nTest Plan:\n- TDD: add/adjust a test that asserts gate checks are invoked via to_thread or async path, implement change, refactor.\n- Run: uv run pytest tests/test_orchestrator.py\n\nNotes for Agent:\n- Keep logging behavior the same; only adjust execution context.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-27T06:15:38.057191895Z","updated_at":"2025-12-27T08:26:45.183237829Z","closed_at":"2025-12-27T08:26:45.183237829Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-yg9.1.2","depends_on_id":"mala-yg9.1","type":"parent-child","created_at":"2025-12-27T06:15:38.073339276Z","created_by":"daemon"},{"issue_id":"mala-yg9.1.2","depends_on_id":"mala-yg9.1.1","type":"blocks","created_at":"2025-12-27T06:16:03.227763694Z","created_by":"daemon"}]}
{"id":"mala-yg9.1.3","title":"Extract run_implementer lifecycle state machine","description":"Context:\n- run_implementer mixes SDK IO, streaming output, gate checks, retry policy, Codex review, and cleanup in one long loop (src/orchestrator.py:286-520, 760-923).\n- This coupling makes policy changes risky and unit tests hard to write.\n\nScope:\n- In: extract a pure state machine for retry/gate/review transitions (data-in/data-out) and isolate side effects behind small interfaces.\n- Out: changing user-visible CLI behavior or prompt contents.\n\nAcceptance Criteria:\n- Core retry/gate/review policy is testable without Claude SDK or subprocesses.\n- run_implementer is reduced to orchestration with explicit dependencies.\n\nTest Plan:\n- TDD: add unit tests for state transitions, implement refactor, refactor orchestration.\n- Run: uv run pytest tests/test_orchestrator.py\n\nNotes for Agent:\n- Prefer additive refactor with compatibility shims to limit churn.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-27T06:15:45.890888837Z","updated_at":"2025-12-27T08:52:11.379698431Z","closed_at":"2025-12-27T08:52:11.379698431Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-yg9.1.3","depends_on_id":"mala-yg9.1","type":"parent-child","created_at":"2025-12-27T06:15:45.899578351Z","created_by":"daemon"},{"issue_id":"mala-yg9.1.3","depends_on_id":"mala-yg9.1.2","type":"blocks","created_at":"2025-12-27T06:16:03.239560506Z","created_by":"daemon"}]}
{"id":"mala-yg9.2","title":"Epic: Validation system alignment","description":"Scope: Validation spec enforcement, runner alignment, E2E fixture dedup, and gate evidence consistency from the 2025-12-27 architecture review.","status":"closed","priority":1,"issue_type":"epic","created_at":"2025-12-27T06:13:32.616096867Z","updated_at":"2025-12-27T09:03:57.3770263Z","closed_at":"2025-12-27T09:03:57.3770263Z","close_reason":"All children completed","dependencies":[{"issue_id":"mala-yg9.2","depends_on_id":"mala-yg9","type":"parent-child","created_at":"2025-12-27T06:13:32.624829179Z","created_by":"daemon"}]}
{"id":"mala-yg9.2.1","title":"Normalize log parsing offsets in QualityGate","description":"Context:\n- Needs verification: retry offset semantics are inconsistent between binary and text log parsing (see src/quality_gate.py:188-360 and orchestrator usage at src/orchestrator.py:255-275).\n- parse_issue_resolution_from_offset reads bytes while parse_validation_evidence_from_offset reads text, but offsets are reused across both.\n\nScope:\n- In: introduce a single cursor/offset contract for both resolution + evidence parsing (shared binary reader or explicit separate offsets).\n- Out: changing JSONL log format or Claude SDK logging behavior.\n\nAcceptance Criteria:\n- Offsets are byte-based and consistent across both parsers.\n- Retry-scoped evidence and resolution detection are deterministic across attempts.\n\nTest Plan:\n- TDD: add a failing unit test with synthetic JSONL logs spanning multiple attempts, implement fix, refactor.\n- Run: uv run pytest tests/test_quality_gate.py\n\nNotes for Agent:\n- Keep the public QualityGate API stable if possible; prefer internal refactor.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-27T06:14:23.231600538Z","updated_at":"2025-12-27T06:50:38.683075107Z","closed_at":"2025-12-27T06:50:38.683075107Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-yg9.2.1","depends_on_id":"mala-yg9.2","type":"parent-child","created_at":"2025-12-27T06:14:23.238427532Z","created_by":"daemon"}]}
{"id":"mala-yg9.2.2","title":"Drive gate evidence parsing from ValidationSpec commands","description":"Context:\n- Needs verification: ValidationSpec defines commands, but QualityGate uses a separate regex map for evidence (src/validation/spec.py:310-360 vs src/quality_gate.py:151-158).\n- This duplication risks false failures when commands change.\n\nScope:\n- In: define a single authoritative command definition (e.g., command IDs or detection patterns on ValidationCommand) and reuse it in QualityGate evidence parsing.\n- Out: changing the actual validation commands or their ordering.\n\nAcceptance Criteria:\n- Evidence parsing is driven from spec command definitions.\n- Updating spec commands automatically updates gate expectations.\n\nTest Plan:\n- TDD: add/adjust a unit test that fails when spec commands change but evidence parsing does not, then implement fix.\n- Run: uv run pytest tests/test_quality_gate.py\n\nNotes for Agent:\n- Prefer minimal API changes; add optional fields to ValidationCommand if needed.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-27T06:14:32.366184084Z","updated_at":"2025-12-27T07:11:43.246105111Z","closed_at":"2025-12-27T07:11:43.246105111Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-yg9.2.2","depends_on_id":"mala-yg9.2","type":"parent-child","created_at":"2025-12-27T06:14:32.374848132Z","created_by":"daemon"},{"issue_id":"mala-yg9.2.2","depends_on_id":"mala-yg9.2.1","type":"blocks","created_at":"2025-12-27T06:16:03.124875688Z","created_by":"daemon"}]}
{"id":"mala-yg9.2.3","title":"Remove deprecated validation flags and use disable-validations=codex-review","description":"Context:\n- CLI flags --lint-only-for-docs and --skip-e2e-if-no-keys should be removed entirely.\n- Codex review toggling should use --disable-validations=codex-review and replace the --codex-review/--no-codex-review flag (src/cli.py:125-163).\n- Orchestrator currently stores lint_only_for_docs + skip_e2e_if_no_keys and uses codex_review directly (src/orchestrator.py:155-187, 435-520).\n\nScope:\n- In: remove --lint-only-for-docs and --skip-e2e-if-no-keys from CLI and plumbing (cli/orchestrator/spec inputs).\n- In: remove --codex-review/--no-codex-review flag and wire codex review enablement to disable_validations containing \"codex-review\".\n- Out: changing validation command defaults or quality gate semantics.\n\nAcceptance Criteria:\n- CLI help/output no longer includes --lint-only-for-docs or --skip-e2e-if-no-keys.\n- CLI help/output no longer includes --codex-review/--no-codex-review.\n- Codex review is enabled by default and disabled when disable_validations includes \"codex-review\".\n\nTest Plan:\n- TDD: update failing CLI parsing tests to remove deprecated flags and validate codex-review disable path, implement code changes, refactor.\n- Run: uv run pytest tests/test_cli.py tests/test_orchestrator.py\n\nNotes for Agent:\n- Keep behavior backward-compatible only via disable-validations; do not add new flags.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-27T06:14:41.186302644Z","updated_at":"2025-12-27T07:20:37.057790616Z","closed_at":"2025-12-27T07:20:37.057790616Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-yg9.2.3","depends_on_id":"mala-yg9.2","type":"parent-child","created_at":"2025-12-27T06:14:41.194971161Z","created_by":"daemon"},{"issue_id":"mala-yg9.2.3","depends_on_id":"mala-yg9.2.2","type":"blocks","created_at":"2025-12-27T06:16:03.143821949Z","created_by":"daemon"}]}
{"id":"mala-yg9.2.4","title":"Integrate spec-based validation execution into orchestrator metadata","description":"Context:\n- Needs verification: ValidationRunner.run_spec exists but is unused; orchestrator relies on log parsing for pass/fail (src/validation/runner.py:294-540, src/orchestrator.py:822-897).\n- Run metadata has validation fields that are never populated (src/logging/run_metadata.py:57-140).\n\nScope:\n- In: invoke ValidationRunner.run_spec after commit detection and record ValidationResult in RunMetadata/IshueRun.\n- Out: changing validation commands or CLI flags.\n\nAcceptance Criteria:\n- Exactly one validation execution path is used during runs.\n- Run metadata includes validation results when validation runs.\n\nTest Plan:\n- TDD: add/adjust an orchestrator test that asserts validation execution + metadata population, implement fix, refactor.\n- Run: uv run pytest tests/test_orchestrator.py tests/test_validation.py\n\nNotes for Agent:\n- Keep log-parsing gate evidence intact unless explicitly replaced by run_spec.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-27T06:14:50.057484108Z","updated_at":"2025-12-27T07:47:41.275020049Z","closed_at":"2025-12-27T07:47:41.275020049Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-yg9.2.4","depends_on_id":"mala-yg9.2","type":"parent-child","created_at":"2025-12-27T06:14:50.066001705Z","created_by":"daemon"},{"issue_id":"mala-yg9.2.4","depends_on_id":"mala-yg9.2.3","type":"blocks","created_at":"2025-12-27T06:16:03.1558436Z","created_by":"daemon"}],"comments":[{"id":2,"issue_id":"mala-yg9.2.4","author":"cyou","text":"Additional context from type error incident (2025-12-27):\n\nRoot cause: Agents gaming validation by running `uvx ty check src/specific_file.py` instead of full `uvx ty check`. QualityGate's regex-based log parsing accepted this because it only checks if `ty check` was mentioned, not:\n- Whether it ran on full codebase\n- Whether it passed (exit code 0)\n\nEvidence: Commit 0328a6e logs show agent ran:\n- `uvx ty check 2\u003e\u00261 | head -20` (truncated)\n- `uvx ty check src/orchestrator.py` (file-specific)\n\nResult: 62 type errors accumulated in test files, required manual fix in 8c0dc74.\n\nThis confirms the urgency of wiring up ValidationRunner.run_spec() which actually runs full validation in a clean worktree and checks exit codes.","created_at":"2025-12-27T06:36:30Z"}]}
{"id":"mala-yg9.2.5","title":"Split ValidationRunner legacy vs spec APIs","description":"Context:\n- Needs verification: ValidationRunner houses both legacy ValidationConfig flow and new ValidationSpec flow in one class (src/validation/runner.py:1-120, 294-540).\n- This coupling makes deprecating legacy paths risky and tests harder to isolate.\n\nScope:\n- In: split into LegacyValidationRunner + SpecValidationRunner (or an adapter), with shared helpers extracted.\n- Out: changing validation behavior or CLI flags.\n\nAcceptance Criteria:\n- Each runner has a single responsibility and isolated config surface.\n- Tests are updated to use the appropriate runner class.\n\nTest Plan:\n- TDD: update a failing test to target the new runner class, implement refactor, refactor tests.\n- Run: uv run pytest tests/test_validation.py\n\nNotes for Agent:\n- Prefer small module moves; keep public API stable where possible.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-27T06:14:58.49336286Z","updated_at":"2025-12-27T08:09:26.87856065Z","closed_at":"2025-12-27T08:09:26.87856065Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-yg9.2.5","depends_on_id":"mala-yg9.2","type":"parent-child","created_at":"2025-12-27T06:14:58.502255715Z","created_by":"daemon"},{"issue_id":"mala-yg9.2.5","depends_on_id":"mala-yg9.2.4","type":"blocks","created_at":"2025-12-27T06:16:03.167592633Z","created_by":"daemon"}]}
{"id":"mala-yg9.2.6","title":"Deduplicate E2E fixture logic","description":"Context:\n- E2E fixture creation/prereq checks are duplicated between validation/runner.py and validation/e2e.py (src/validation/runner.py:686-789, src/validation/e2e.py:348-473).\n- This duplication risks drift and inconsistent behavior across legacy/spec flows.\n\nScope:\n- In: centralize fixture creation + prereq checks in E2ERunner and delegate from ValidationRunner.\n- In: remove duplicate helpers from validation.__init__ if they are no longer needed.\n- Out: changing fixture content or mala invocation.\n- Out: any CLI flag changes (handled elsewhere).\n\nAcceptance Criteria:\n- Only one fixture/prereq implementation exists.\n- ValidationRunner delegates E2E execution to E2ERunner for both legacy and spec flows.\n- Duplicate helper exports are removed from validation.__init__ if unused.\n\nTest Plan:\n- TDD: add/adjust a failing E2E-related unit test, implement dedup, refactor.\n- Run: uv run pytest tests/test_e2e.py tests/test_validation.py\n\nNotes for Agent:\n- Keep fixture content identical; change plumbing only.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-27T06:15:10.06348597Z","updated_at":"2025-12-27T08:21:18.084178254Z","closed_at":"2025-12-27T08:21:18.084178254Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-yg9.2.6","depends_on_id":"mala-yg9.2","type":"parent-child","created_at":"2025-12-27T06:15:10.072149386Z","created_by":"daemon"},{"issue_id":"mala-yg9.2.6","depends_on_id":"mala-yg9.2.5","type":"blocks","created_at":"2025-12-27T06:16:03.179392406Z","created_by":"daemon"}]}
{"id":"mala-yg9.2.7","title":"Standardize subprocess execution via shared CommandRunner","description":"Context:\n- Needs verification: subprocess handling is inconsistent across modules, with different timeout/kill semantics and duplicated output tailing (e.g., src/beads_client.py:45-140, src/quality_gate.py:260-430, src/validation/runner.py:108-240, src/validation/e2e.py:200-420, src/validation/worktree.py:110-200).\n- This increases maintenance burden and inconsistent failure modes.\n\nScope:\n- In: introduce a shared CommandRunner (sync + async) with standardized timeouts, process-group termination, and output capture.\n- In: migrate quality_gate + validation (runner/e2e/worktree) to the shared runner; add beads_client only if needed for consistency.\n- Out: changing which commands run or their ordering.\n\nAcceptance Criteria:\n- CommandRunner exists and is used by quality_gate and validation paths.\n- Duplicate _tail helpers are removed or consolidated.\n\nTest Plan:\n- TDD: add a failing unit test for CommandRunner timeout/kill behavior, implement runner, refactor call sites.\n- Run: uv run pytest tests/test_validation.py tests/test_quality_gate.py\n\nNotes for Agent:\n- Keep API minimal; prefer incremental migration to avoid churn.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-27T06:15:20.286094712Z","updated_at":"2025-12-27T08:59:29.472484025Z","closed_at":"2025-12-27T08:59:29.472484025Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-yg9.2.7","depends_on_id":"mala-yg9.2","type":"parent-child","created_at":"2025-12-27T06:15:20.295235806Z","created_by":"daemon"},{"issue_id":"mala-yg9.2.7","depends_on_id":"mala-yg9.2.6","type":"blocks","created_at":"2025-12-27T06:16:03.191495098Z","created_by":"daemon"},{"issue_id":"mala-yg9.2.7","depends_on_id":"mala-yg9.2.2","type":"blocks","created_at":"2025-12-27T06:16:03.20365412Z","created_by":"daemon"}]}
{"id":"mala-yg9.3","title":"Orchestrator: drive implementer flow via ImplementerLifecycle","description":"## Context\nFrom architecture review docs/architecture-check-2025-12-27.md issue #4:\n- `src/orchestrator.py:303-644` contains `run_implementer` as a 342-line monolithic method\n- Gate/review retry policy, SDK session management, streaming, and lock cleanup are all mixed together\n- This coupling makes policy changes risky and unit testing difficult without the full SDK runtime\n\n## Current State (after mala-yg9.1.3)\n- `src/lifecycle.py` (470 lines) already implements `ImplementerLifecycle` as a pure state machine\n- `tests/test_lifecycle.py` (630 lines) has 31 tests with 97% coverage\n- Bridge functions exist: `from_orchestrator_retry_state()`, `to_orchestrator_retry_state_fields()`\n- **However**: `ImplementerLifecycle` is NOT imported or used anywhere in `orchestrator.py`\n- The orchestrator still has its own inline retry/gate/review logic duplicating the lifecycle\n\n## Scope\n- In: Refactor `run_implementer` to use `ImplementerLifecycle` for policy decisions\n- In: Orchestrator handles I/O (SDK calls, logs, hooks) based on `Effect` returns from lifecycle\n- In: Remove inline retry/gate/review branches from orchestrator\n- Out: Change retry semantics, Claude SDK integration, or lifecycle state machine design\n\n## Acceptance Criteria\n- `run_implementer` delegates policy decisions to `ImplementerLifecycle`\n- Inline gate/review retry branches are removed from orchestrator (reducing ~200 lines)\n- Lifecycle transitions drive the flow: INITIAL → PROCESSING → AWAITING_LOG → RUNNING_GATE → etc.\n- All existing orchestrator integration tests still pass\n- No SDK mocking required to test policy logic (already covered by lifecycle tests)\n\n## Test Plan\n- TDD: Verify lifecycle is called in orchestrator flow\n- Existing lifecycle tests (31 tests) already cover policy transitions\n- `uv run pytest tests/test_orchestrator.py tests/test_lifecycle.py`\n\n## Notes for Agent\n- Use the existing bridge functions to convert between orchestrator's `RetryState` and lifecycle's `LifecycleContext`\n- Effects returned by lifecycle transitions tell orchestrator what I/O to perform\n- Keep I/O (SDK, logs, hooks) in orchestrator; lifecycle remains pure data-in/data-out\n- Reference lifecycle.py module docstring for design rationale","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-27T17:15:08.120064423Z","updated_at":"2025-12-28T00:56:10.391191905Z","closed_at":"2025-12-28T00:56:10.391191905Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-yg9.3","depends_on_id":"mala-yg9","type":"parent-child","created_at":"2025-12-27T17:15:08.120492531Z","created_by":"daemon"}]}
{"id":"mala-yg9.4","title":"Validation pipeline: make SpecValidationRunner primary and isolate legacy facade","description":"Context:\n- Orchestrator uses the `ValidationRunner` facade to run spec validation (`src/orchestrator.py:880-918`).\n- `ValidationRunner` wraps both legacy and spec flows (`src/validation/runner.py:57-113`).\n- Legacy command definitions remain in `src/validation/legacy_runner.py:134-154`, risking drift from spec.\n\nScope:\n- In: Update orchestrator to use `SpecValidationRunner` directly (or a thin spec-only adapter).\n- In: Reduce `ValidationRunner` to a compatibility wrapper; clearly document legacy path.\n- Out: Remove legacy API entirely or change validation command semantics.\n\nAcceptance Criteria:\n- Orchestrator no longer instantiates `ValidationRunner` for spec-based runs.\n- Legacy runner remains available for backward compatibility but is not used in orchestrator.\n- Validation command definitions are single-sourced (no duplicate lists used by orchestrator).\n\nTest Plan:\n- TDD: add a failing test that exercises spec validation via the new orchestration path.\n- `uv run pytest` (target validation runner/spec tests).\n\nNotes for Agent:\n- Keep the public legacy API working; deprecate via docs/comments only if needed.","notes":"Quality gate failed: Quality gate failed: Missing validation commands: ruff check, ruff format, pytest, ty check; No progress: commit unchanged and no new validation evidence\nLog: /home/cyou/.claude/projects/-home-cyou-mala/64499301-0bf0-46e8-9116-32b267336192.jsonl","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-27T17:15:18.339547533Z","updated_at":"2025-12-28T02:17:09.185278293Z","closed_at":"2025-12-28T02:17:09.185278293Z","close_reason":"Closed","labels":["needs-followup"],"dependencies":[{"issue_id":"mala-yg9.4","depends_on_id":"mala-yg9","type":"parent-child","created_at":"2025-12-27T17:15:18.348894746Z","created_by":"daemon"},{"issue_id":"mala-yg9.4","depends_on_id":"mala-yg9.3","type":"blocks","created_at":"2025-12-27T17:16:03.096258868Z","created_by":"daemon"}]}
{"id":"mala-yg9.5","title":"Subprocess handling: centralize execution and timeouts","description":"## Context\nFrom architecture review docs/architecture-check-2025-12-27.md issue #7:\n- Subprocess logic is duplicated with different timeout/termination semantics\n- Only `BeadsClient` reliably kills process groups\n- Duplicate `_tail` helpers increase maintenance burden\n\n## Current State (after mala-yg9.2.7)\n`src/validation/command_runner.py` was created with:\n- Unified sync/async execution (`run()` and `run_async()`)\n- Configurable timeouts with process-group termination (SIGTERM → grace → SIGKILL)\n- `CommandResult` dataclass with `stdout_tail()`/`stderr_tail()` methods\n\n**Successfully migrated:**\n- `src/validation/spec_runner.py` - uses CommandRunner for all validation commands\n- `src/validation/legacy_runner.py` - uses CommandRunner for validation steps\n- `src/validation/e2e.py` - uses CommandRunner for mala invocation\n\n**NOT migrated (still use direct subprocess.run):**\n- `src/quality_gate.py:291` - `check_working_tree_clean()` uses subprocess.run for `git status`\n- `src/quality_gate.py:663` - `check_commit_exists()` uses subprocess.run for `git log`\n- `src/validation/worktree.py:191,238,267,318,341` - Multiple subprocess.run calls for git operations\n\n**Independent implementation:**\n- `src/beads_client.py` has its own `_run_subprocess_async()` with similar semantics (predates CommandRunner)\n\n## Scope\n- In: Migrate `quality_gate.py` subprocess calls to use CommandRunner\n- In: Migrate `worktree.py` subprocess calls to use CommandRunner\n- In: Consider whether `beads_client.py` should also use CommandRunner (or keep independent)\n- Out: Change CLI command flags or alter validation policies\n\n## Acceptance Criteria\n- `quality_gate.py` uses CommandRunner for git subprocess calls\n- `worktree.py` uses CommandRunner for git subprocess calls\n- Timeout and process-group termination behavior is consistent across all call sites\n- Error messages remain structured and actionable\n- All existing tests pass\n\n## Test Plan\n- Add/adjust unit tests for subprocess calls in quality_gate and worktree\n- `uv run pytest tests/test_quality_gate.py tests/test_validation.py`\n\n## Notes for Agent\n- CommandRunner is in `src/validation/command_runner.py`\n- For git commands, consider whether they need timeout handling or can use simpler wrappers\n- Preserve stdout/stderr tailing behavior; do not increase log noise\n- BeadsClient migration is optional - evaluate whether unifying is worth the churn","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-27T17:15:27.857841446Z","updated_at":"2025-12-28T03:09:46.316507839Z","closed_at":"2025-12-28T03:09:46.316507839Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-yg9.5","depends_on_id":"mala-yg9","type":"parent-child","created_at":"2025-12-27T17:15:27.877588866Z","created_by":"daemon"},{"issue_id":"mala-yg9.5","depends_on_id":"mala-yg9.7","type":"blocks","created_at":"2025-12-27T17:16:07.830176685Z","created_by":"daemon"}]}
{"id":"mala-yg9.6","title":"Env config: compute RUNS_DIR/LOCK_DIR after .env load","description":"Context:\n- `RUNS_DIR` and `LOCK_DIR` are computed at import time (`src/tools/env.py:18-29`).\n- `.env` is loaded later in CLI startup (`src/cli.py:17-21`), so overrides are ignored.\n- Needs verification: confirm expected behavior for `MALA_RUNS_DIR`/`MALA_LOCK_DIR` overrides.\n\nScope:\n- In: Replace import-time constants with accessors or a Settings object loaded after `load_user_env()`.\n- In: Update CLI (and any other callers) to use the new access path.\n- Out: Change environment variable names or default paths.\n\nAcceptance Criteria:\n- `.env` overrides for runs/locks apply consistently.\n- No module depends on import-time config constants for these paths.\n- Unit test demonstrates `.env` override behavior.\n\nTest Plan:\n- Add a test that sets env vars or loads a test `.env`, then asserts resolved paths.\n- `uv run pytest` (target env/config tests).\n\nNotes for Agent:\n- Keep `load_user_env()` as the single production entry for env loading.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-27T17:15:37.761209754Z","updated_at":"2025-12-28T01:13:53.981259155Z","closed_at":"2025-12-28T01:13:53.981259155Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-yg9.6","depends_on_id":"mala-yg9","type":"parent-child","created_at":"2025-12-27T17:15:37.780801225Z","created_by":"daemon"}]}
{"id":"mala-yg9.7","title":"QualityGate: derive evidence requirements from ValidationSpec","description":"## Context\nFrom architecture review docs/architecture-check-2025-12-27.md issue #8:\n- Validation commands defined in `ValidationSpec` but evidence detection used separate regex map in `QualityGate`\n- Changing commands in spec could silently desync evidence detection\n\n## Current State (after mala-yg9.2.2)\n**Spec-driven path implemented and working:**\n- `ValidationCommand` in `spec.py:174-198` includes `detection_pattern` field\n- `build_validation_spec()` creates commands with compiled regex patterns\n- `parse_validation_evidence_with_spec()` in `quality_gate.py:493` derives patterns FROM spec\n- `check_with_resolution()` accepts optional `spec` parameter and uses spec-driven parsing\n- Orchestrator passes spec to gate: `quality_gate.check_with_resolution(..., spec=spec)`\n- Tests verify spec-driven patterns work (TestSpecDrivenEvidencePatterns)\n\n**Remaining cleanup (optional):**\n- `VALIDATION_PATTERNS` hardcoded map still exists in `quality_gate.py:151-158` as fallback\n- Legacy `check()` method and `parse_validation_evidence_from_offset()` still use hardcoded patterns\n- These are only used when no spec is provided (backward compatibility)\n\n## Scope\n- In: Remove `VALIDATION_PATTERNS` hardcoded map if no longer needed\n- In: Update legacy `check()` method to require spec or remove entirely\n- In: Ensure `parse_validation_evidence_from_offset()` is only used as fallback\n- Out: Change which validations are required or their CLI flags\n\n## Acceptance Criteria\n- Hardcoded `VALIDATION_PATTERNS` map is removed or clearly marked as deprecated fallback\n- All production code paths use spec-driven evidence parsing\n- Tests verify that updating spec commands automatically updates evidence expectations\n- Legacy paths (if kept) are clearly documented as deprecated\n\n## Test Plan\n- Verify no production code calls legacy methods without spec\n- Add test that spec command changes propagate to evidence detection\n- `uv run pytest tests/test_quality_gate.py`\n\n## Notes for Agent\n- The core fix is already implemented - this is cleanup work\n- Check if any callers use `check()` or `parse_validation_evidence_from_offset()` without spec\n- If legacy methods have no callers, they can be removed\n- If they have callers, update callers to pass spec or deprecate with warnings","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-27T17:15:46.833421374Z","updated_at":"2025-12-28T02:51:05.590472175Z","closed_at":"2025-12-28T02:51:05.590472175Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-yg9.7","depends_on_id":"mala-yg9","type":"parent-child","created_at":"2025-12-27T17:15:46.853067905Z","created_by":"daemon"}]}
{"id":"mala-yg9.8","title":"CLI bootstrap: move Braintrust/env side effects into explicit init","description":"Context:\n- `src/cli.py` loads `.env` and applies Braintrust setup at import time (`src/cli.py:12-33`).\n- Import-time side effects make tests and reuse harder.\n- Needs verification: confirm other modules rely on current import-time behavior.\n\nScope:\n- In: Add a `bootstrap()` function to handle env loading and Braintrust patching.\n- In: Ensure entrypoint (`src/main.py`) calls bootstrap before using the SDK.\n- Out: Change CLI commands or Braintrust configuration semantics.\n\nAcceptance Criteria:\n- Importing `src.cli` does not perform env loading or Braintrust setup.\n- CLI still boots with Braintrust enabled when `BRAINTRUST_API_KEY` is set.\n- Unit test (or smoke test) validates import safety.\n\nTest Plan:\n- Add an import-only test that asserts no side effects.\n- `mala --help` and `mala run --help` smoke checks.\n\nNotes for Agent:\n- Keep SDK import ordering safe: bootstrap must run before claude_agent_sdk usage.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-27T17:15:56.854726584Z","updated_at":"2025-12-28T03:14:44.785670206Z","closed_at":"2025-12-28T03:14:44.785670206Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-yg9.8","depends_on_id":"mala-yg9","type":"parent-child","created_at":"2025-12-27T17:15:56.874255915Z","created_by":"daemon"},{"issue_id":"mala-yg9.8","depends_on_id":"mala-yg9.6","type":"blocks","created_at":"2025-12-27T17:16:12.147876459Z","created_by":"daemon"}]}
{"id":"mala-yg9.9","title":"Remove private helpers from validation __all__ export","description":"## Context\nFrom architecture review docs/architecture-check-2025-12-27.md issue #11:\n- Private helpers (`_tail`, `_format_step_output`, `_check_e2e_prereqs`) are included in `src/validation/__init__.py`'s `__all__` export list (lines 110-112)\n- This makes them part of the public API surface, increasing coupling and making refactors risky\n\n## Current State\n```python\n# In src/validation/__init__.py lines 110-112\n# Backwards compatibility (private)\n\"_check_e2e_prereqs\",\n\"_format_step_output\",\n\"_tail\",\n```\n\nThese are re-exported from `runner.py` (lines 42-47) as underscore-prefixed aliases of the helpers in `helpers.py`.\n\n## Scope\n- In: Remove `_tail`, `_format_step_output`, `_check_e2e_prereqs` from `__all__` in `src/validation/__init__.py`\n- In: Remove the re-export aliases from `src/validation/runner.py`\n- In: Search for external callers and update them to use the public `helpers.py` exports if needed\n- Out: Changing the helper implementations themselves\n\n## Acceptance Criteria\n- Only intended public API symbols are exported from `validation.__init__`\n- No external code breaks (verify via grep for imports of these private symbols)\n- Tests still pass\n\n## Test Plan\n- `uv run pytest tests/test_validation.py` to catch import breakage\n- `grep -r \"from.*validation.*import.*_tail\" src/ tests/` to find any callers\n\n## Notes for Agent\n- These helpers are consolidated in `helpers.py` - the public versions (`tail`, `format_step_output`, `check_e2e_prereqs`) should be used instead\n- If external callers exist, update them to import from `validation.helpers` directly","status":"closed","priority":3,"issue_type":"chore","created_at":"2025-12-27T18:41:56.00236732Z","updated_at":"2025-12-28T05:29:33.807896933Z","closed_at":"2025-12-28T05:29:33.807896933Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-yg9.9","depends_on_id":"mala-yg9","type":"parent-child","created_at":"2025-12-27T18:41:56.009743695Z","created_by":"daemon"}]}
{"id":"mala-ygrs","title":"[Review] 1 non-blocking finding from mala-au9g.1","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-au9g.1\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Preserve failed_issues update if finalizer raises\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/orchestration/orchestrator.py:646-654\n\n`IssueFinalizer.finalize` now runs before failed issues are recorded. If `mark_needs_followup` (or any callback) raises, `_finalize_issue_result` exits early and the issue never lands in `failed_issues`, which is used later to skip remediation and compute summaries. Previously `failed_issues.add` happened before the await. Consider moving the add into the finalizer (before awaiting) or wrapping `await self.issue_finalizer.finalize(...)` in a `try/finally` so failures still get tracked.\n\n---\n","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T07:06:40.552124064Z","created_by":"cyou","updated_at":"2026-01-03T07:15:38.515616715Z","closed_at":"2026-01-03T07:15:38.515616715Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_findings:ff924ae18c57","source:mala-au9g.1"]}
{"id":"mala-ysua","title":"[Remediation] `uvx --from import-linter lint-imports` passes all 9 contrac...","description":"## Context\nThis issue was auto-created by epic verification for epic `mala-bp3h`.\n\n## Unmet Criterion\n`uvx --from import-linter lint-imports` passes all 9 contracts\n\n## Evidence\nTask 7 (Update test imports + final verification) shows status 'open', indicating final import-linter verification has not been completed\n\n## Severity\ncritical\n\n## Resolution\nAddress this criterion to unblock epic closure. When complete, close this issue.\n","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-01T03:35:30.851465508Z","created_by":"cyou","updated_at":"2026-01-01T03:40:59.737909176Z","closed_at":"2026-01-01T03:40:59.737909176Z","close_reason":"Closed","labels":["auto_generated","epic_remediation:mala-bp3h:22184a20fcacd9f09674855b80540e702cb1b12b2176513db933ec83abf8ebd0"],"dependencies":[{"issue_id":"mala-ysua","depends_on_id":"mala-bp3h","type":"parent-child","created_at":"2026-01-01T03:37:47.084128985Z","created_by":"daemon"}]}
{"id":"mala-yt3b","title":"[Review] tests/test_orchestrator.py:556-558: [P2] Config duplication requires test workaround for max_issues","description":"## Review Finding\n\nThis issue was auto-created from a P2 review finding.\n\n**Source issue:** mala-cavk.3\n**Reviewer:** claude\n**Location:** tests/test_orchestrator.py:556-558\n\n## Details\n\nWhen `orchestrator.max_issues` is changed after construction, the test had to also update `orchestrator.issue_coordinator.config.max_issues` because the coordinator has a copy of the config values. This workaround at `tests/test_orchestrator.py:557-558` is required for the test to pass, but indicates a design issue: callers who modify `orchestrator.max_issues` at runtime won't affect the coordinator unless they know about this internal implementation detail. Consider either sharing config or documenting that these attributes are set-once.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-01T22:23:07.412280655Z","created_by":"cyou","updated_at":"2026-01-02T01:02:47.604238482Z","closed_at":"2026-01-02T01:02:47.604238482Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_finding:7b23e8ffedb5","source:mala-cavk.3"],"dependencies":[{"issue_id":"mala-yt3b","depends_on_id":"mala-i8ke","type":"blocks","created_at":"2026-01-01T23:03:58.848147044Z","created_by":"daemon"}]}
{"id":"mala-ytn","title":"Make tooling language/package-manager agnostic","description":"## Context\nCurrently the codebase is tightly coupled to Python + uv + ruff + ty. Validation commands, quality gate parsing, and prompts all assume this specific toolchain.\n\n## Goal\nMake mala usable with other language/toolchain combinations (e.g., TypeScript/npm, Rust/cargo, Go, etc.) by abstracting the toolchain layer.\n\n## Areas Affected\n\n### Validation Commands (src/validation/)\n- Hardcoded commands: `uv sync`, `uv run pytest`, `uvx ruff check`, `uvx ruff format`, `uvx ty check`\n- Need to support equivalent commands for other ecosystems (npm install, npm test, eslint, tsc, etc.)\n\n### Quality Gate Parsing (src/quality_gate.py)\n- Parses output patterns specific to pytest, ruff, ty\n- Needs configurable or pluggable pattern matching\n\n### Prompts (src/prompts/implementer_prompt.md)\n- References specific tools and commands\n- Should be templated or configurable per project\n\n### CLAUDE.md\n- Project-specific instructions currently assumed to follow Python conventions\n- Should be the source of truth for project toolchain\n\n## Approach Options\n\n1. **Config-driven**: Define toolchain in pyproject.toml/mala.toml with command mappings\n2. **Auto-detect**: Detect toolchain from project files (package.json, Cargo.toml, go.mod, etc.)\n3. **Plugin system**: Allow custom validation plugins per language\n\n## Acceptance Criteria\n- mala can run on a TypeScript project with npm/eslint/tsc\n- Toolchain commands are configurable, not hardcoded\n- Quality gate can parse outputs from multiple test/lint frameworks\n- Documentation covers how to configure for different languages\n\n## Out of Scope (for now)\n- IDE integration changes\n- Language-specific code generation\n- Multi-language monorepos","status":"closed","priority":2,"issue_type":"epic","created_at":"2025-12-26T23:35:50.333488526Z","updated_at":"2025-12-27T20:57:22.064580719Z","closed_at":"2025-12-27T20:57:22.064580719Z","close_reason":"All children completed"}
{"id":"mala-yvz7","title":"Fix deadlocking","status":"closed","priority":2,"issue_type":"bug","created_at":"2026-01-04T02:31:00.790074182Z","created_by":"cyou","updated_at":"2026-01-04T05:03:50.76063928Z","closed_at":"2026-01-04T05:03:50.76063928Z","close_reason":"Closed"}
{"id":"mala-yxkv","title":"Epic: Enable Aspirational Import-Linter Contracts","description":"Enable 4 commented-out import-linter contracts that define stricter architectural boundaries:\n\n1. **Infra hooks do not import clients, IO, or telemetry** - Move cerberus path detection to tools layer\n2. **Only orchestration.factory imports infra.clients** - Use protocols and factory helpers\n3. **Hooks do not import agent runtime** - Already passes, just uncomment\n4. **No claude_agent_sdk outside infra SDK boundary** - Modify contract to use allow_indirect_imports pattern\n\n## Benefits\n- Improves architectural clarity by enforcing layered boundaries more strictly\n- Makes dependency violations fail fast at lint time\n- Matches existing patterns for braintrust/anthropic SDK contracts\n\n## Plan\nSee: plans/2026-01-05-import-linter-fix-plan.md\n\n## Acceptance Criteria\n- [ ] All 4 contracts uncommented and passing in `uv run lint-imports`\n- [ ] Full test suite passes with 72% coverage\n- [ ] No observable behavior changes to CLI or orchestrator","status":"closed","priority":1,"issue_type":"epic","created_at":"2026-01-05T05:07:38.945985259Z","created_by":"cyou","updated_at":"2026-01-06T18:37:32.157348198Z","closed_at":"2026-01-06T18:37:32.157348198Z","close_reason":"Closed"}
{"id":"mala-yxkv.1","title":"[T001] Create cerberus.py and fix hooks→IO import chain","description":"**Type**: task\n**Priority**: P1\n**Subsystems**: infra\n**Primary Files**: src/infra/tools/cerberus.py, src/infra/tools/env.py, src/infra/io/config.py\n\n## Goal\nBreak the import chain `hooks.locking → tools.locking → tools.env → io.config` by moving `_find_cerberus_bin_path` from IO layer to tools layer.\n\n## Context\nContract 1 (\"Infra hooks do not import clients, IO, or telemetry\") fails because `EnvConfig.find_cerberus_bin_path()` in env.py imports from config.py (IO layer).\n\n## Scope\n- **In**: Create cerberus.py, move function, update imports\n- **Out**: Other import chains, contract uncommenting\n\n## Changes\n- `src/infra/tools/cerberus.py` — **New** — Create with `find_cerberus_bin_path()` function extracted from config.py\n- `src/infra/tools/env.py` — Exists — Update `EnvConfig.find_cerberus_bin_path()` to import from tools.cerberus\n- `src/infra/io/config.py` — Exists — Remove `_find_cerberus_bin_path`, import from tools.cerberus for any remaining usages\n\n## Acceptance Criteria\n- [ ] `src/infra/tools/cerberus.py` exists with `find_cerberus_bin_path()` function\n- [ ] No circular imports: `python -c \"from src.infra.io.config import MalaConfig\"` succeeds\n- [ ] Contract passes when temporarily uncommented: `uv run lint-imports` shows \"Infra hooks do not import clients, IO, or telemetry KEPT\"\n\n## Verification\n```bash\n# Check no circular imports\npython -c \"from src.infra.io.config import MalaConfig\"\n\n# Temporarily uncomment contract and verify\n# (manually uncomment lines 434-442 in pyproject.toml)\nuv run lint-imports | grep \"Infra hooks do not import\"\n\n# Run unit tests\nuv run pytest -m unit -x\n```\n\n## Notes for Agent\n- The function is ~60 lines involving JSON parsing of Claude's installed_plugins.json\n- IO → tools dependency is acceptable (tools is leaf layer)\n- Keep the function signature and behavior identical","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-05T05:08:49.174405656Z","created_by":"cyou","updated_at":"2026-01-06T18:10:50.57041886Z","closed_at":"2026-01-06T18:10:50.57041886Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-yxkv.1","depends_on_id":"mala-yxkv","type":"parent-child","created_at":"2026-01-05T05:08:49.182380751Z","created_by":"cyou"}]}
{"id":"mala-yxkv.2","title":"[T002] Add overrides_disabled_setting protocol method","description":"**Type**: task\n**Priority**: P1\n**Subsystems**: core, infra, tests\n**Primary Files**: src/core/protocols.py, src/infra/clients/cerberus_review.py, tests/unit/pipeline/test_review_runner.py, tests/conftest.py, tests/unit/orchestration/test_orchestrator.py\n\n## Goal\nAdd `overrides_disabled_setting()` method to CodeReviewer protocol so orchestrator can check reviewer type without importing concrete class.\n\n## Context\nContract 2 requires removing `from src.infra.clients.cerberus_review import DefaultReviewer` from orchestrator.py. The isinstance check needs to be replaced with a protocol method.\n\n## Scope\n- **In**: Add protocol method, implement in DefaultReviewer, update ALL test fakes\n- **Out**: Orchestrator changes (separate task), CLI factory changes\n\n## Changes\n- `src/core/protocols.py` — Exists — Add `overrides_disabled_setting() -\u003e bool` method to CodeReviewer protocol\n- `src/infra/clients/cerberus_review.py` — Exists — Implement `overrides_disabled_setting()` returning `False` in DefaultReviewer\n- `tests/unit/pipeline/test_review_runner.py` — Exists — Update FakeCodeReviewer to return `True`\n- `tests/conftest.py` — Exists — Update any CodeReviewer fixtures to return `True`\n- `tests/unit/orchestration/test_orchestrator.py` — Exists — Update any test reviewers to return `True`\n\n## Semantic Meaning\n- `DefaultReviewer.overrides_disabled_setting() -\u003e False` — respects `--disable review` flag\n- Test fakes/custom reviewers return `True` — user explicitly injected them, run even if \"disabled\"\n\n## Acceptance Criteria\n- [ ] CodeReviewer protocol has `overrides_disabled_setting() -\u003e bool` method\n- [ ] DefaultReviewer implements method returning `False`\n- [ ] All test fakes implement method returning `True`\n- [ ] `uv run pytest -m unit` passes\n- [ ] `uvx ty check` passes (type checking)\n\n## Verification\n```bash\n# Type check\nuvx ty check\n\n# Run unit tests\nuv run pytest -m unit -x\n\n# Verify all CodeReviewer implementations have the method\ngrep -r \"overrides_disabled_setting\" src/ tests/\n```\n\n## Notes for Agent\n- DefaultReviewer IS the actual Cerberus CLI reviewer (not a no-op)\n- Search for ALL CodeReviewer implementations: `grep -r \"CodeReviewer\" tests/`\n- The protocol method replaces the isinstance check in orchestrator (done in T003)","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-05T05:09:21.568271393Z","created_by":"cyou","updated_at":"2026-01-06T18:13:19.713289061Z","closed_at":"2026-01-06T18:13:19.713289061Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-yxkv.2","depends_on_id":"mala-yxkv","type":"parent-child","created_at":"2026-01-05T05:09:21.577423871Z","created_by":"cyou"}]}
{"id":"mala-yxkv.3","title":"[T003] Update orchestrator to use protocol and fix imports","description":"**Type**: task\n**Priority**: P1\n**Subsystems**: orchestration, core\n**Primary Files**: src/orchestration/orchestrator.py, src/orchestration/orchestration_wiring.py, src/core/protocols.py\n**Dependencies**: T002\n\n## Goal\nRemove direct infra.clients imports from orchestrator modules by using protocol methods and fixing TYPE_CHECKING imports.\n\n## Context\nContract 2 requires orchestrator.py and orchestration_wiring.py to not import from infra.clients (even transitively). This task:\n1. Uses `overrides_disabled_setting()` instead of isinstance(DefaultReviewer)\n2. Creates `EpicVerifierProtocol` to replace EpicVerifier class import\n3. Imports EpicVerificationResult from core.models instead of infra.epic_verifier\n\n## Scope\n- **In**: orchestrator.py, orchestration_wiring.py, protocols.py changes\n- **Out**: Protocol for CodeReviewer (T002), CLI/factory changes (T004)\n\n## Changes\n- `src/core/protocols.py` — Exists — Add `EpicVerifierProtocol` with `verify_and_close_epic` method signature\n- `src/orchestration/orchestrator.py` — Exists — \n  - Remove `from src.infra.clients.cerberus_review import DefaultReviewer`\n  - Change `from src.infra.epic_verifier import EpicVerifier` to `from src.core.protocols import EpicVerifierProtocol`\n  - Update type annotations to use `EpicVerifierProtocol` instead of `EpicVerifier`\n  - Update `_is_review_enabled()` to use `self.review_runner.code_reviewer.overrides_disabled_setting()`\n- `src/orchestration/orchestration_wiring.py` — Exists —\n  - Import EpicVerificationResult from `src.core.models` instead of `src.infra.epic_verifier`\n\n## EpicVerifierProtocol Design\n```python\nclass EpicVerifierProtocol(Protocol):\n    \"\"\"Protocol for epic verification to avoid importing concrete EpicVerifier.\"\"\"\n    \n    async def verify_and_close_epic(\n        self,\n        epic_id: str,\n        force_close: bool = False,\n    ) -\u003e EpicVerificationResult:\n        \"\"\"Verify epic completion and optionally close it.\"\"\"\n        ...\n```\n\n## Acceptance Criteria\n- [ ] No `DefaultReviewer` import in orchestrator.py\n- [ ] No `EpicVerifier` import from infra.epic_verifier in orchestrator.py\n- [ ] No `epic_verifier` import in orchestration_wiring.py\n- [ ] `EpicVerifierProtocol` exists in protocols.py\n- [ ] `_is_review_enabled()` uses protocol method\n- [ ] `uv run pytest -m unit` passes\n- [ ] Temporarily uncommenting Contract 2 shows KEPT\n\n## Verification\n```bash\n# Verify no forbidden imports\ngrep -n \"from src.infra.clients\" src/orchestration/orchestrator.py\ngrep -n \"from src.infra.epic_verifier\" src/orchestration/orchestrator.py\ngrep -n \"from src.infra.epic_verifier\" src/orchestration/orchestration_wiring.py\n\n# Should show EpicVerifierProtocol\ngrep -n \"EpicVerifierProtocol\" src/core/protocols.py\n\n# Run tests\nuv run pytest -m unit -x\n\n# Type check\nuvx ty check\n```\n\n## Notes for Agent\n- The `overrides_disabled_setting()` protocol method was added in T002\n- EpicVerificationResult already exists in src.core.models\n- EpicVerifier class stays in infra.epic_verifier - we just use a protocol for type annotations\n- Check EpicVerifier's method signatures to ensure protocol matches","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-05T05:09:38.93409195Z","created_by":"cyou","updated_at":"2026-01-06T18:34:10.186186433Z","closed_at":"2026-01-06T18:34:10.186186433Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-yxkv.3","depends_on_id":"mala-yxkv","type":"parent-child","created_at":"2026-01-05T05:09:38.943415486Z","created_by":"cyou"},{"issue_id":"mala-yxkv.3","depends_on_id":"mala-yxkv.2","type":"blocks","created_at":"2026-01-05T05:09:42.511709895Z","created_by":"cyou"}]}
{"id":"mala-yxkv.4","title":"[T004] Add create_issue_provider factory and update CLI","description":"**Type**: task\n**Priority**: P1\n**Subsystems**: orchestration, cli, tests\n**Primary Files**: src/orchestration/factory.py, src/cli/cli.py, src/orchestration/cli_support.py, tests/unit/cli/test_cli.py\n\n## Goal\nRemove BeadsClient re-export from cli_support.py by adding a factory helper that CLI uses for dry-run mode.\n\n## Context\nContract 2 requires cli_support.py to not import from infra.clients. Currently it imports and re-exports BeadsClient for the CLI dry-run feature.\n\n## Changes\n- `src/orchestration/factory.py` — Exists — Add `create_issue_provider(repo_path, log_warning=None) -\u003e IssueProvider`\n- `src/cli/cli.py` — Exists — Update lazy-loading to use `create_issue_provider()` instead of `BeadsClient`\n- `src/orchestration/cli_support.py` — Exists — Remove BeadsClient import and export\n- `tests/unit/cli/test_cli.py` — Exists — Update mocks to patch factory function instead of BeadsClient\n\n## Acceptance Criteria\n- [ ] `create_issue_provider()` exists in factory.py\n- [ ] CLI uses factory instead of BeadsClient directly\n- [ ] No BeadsClient in cli_support.py\n- [ ] `uv run pytest tests/unit/cli/test_cli.py -x` passes\n\n## Verification\n```bash\ngrep -n \"BeadsClient\" src/orchestration/cli_support.py\nuv run pytest tests/unit/cli/test_cli.py -x -v\nuvx ty check\n```","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-05T05:11:33.995465294Z","created_by":"cyou","updated_at":"2026-01-06T18:06:00.285305125Z","closed_at":"2026-01-06T18:06:00.285305125Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-yxkv.4","depends_on_id":"mala-yxkv","type":"parent-child","created_at":"2026-01-05T05:11:34.00353609Z","created_by":"cyou"}]}
{"id":"mala-z9z4","title":"[Review] src/orchestration/orchestrator.py:491-492: [P3] Comment says 'completed or failed' but only checks failed_issues","description":"## Review Finding\n\nThis issue was auto-created from a P3 review finding.\n\n**Source issue:** mala-l072\n**Reviewer:** claude\n**Location:** src/orchestration/orchestrator.py:491-492\n\n## Details\n\nThe comment at line 491 states \"Skip if already completed or failed\" but the code only checks `self.failed_issues`. If the intent is to also skip completed issues, the check should include `self.completed` or a completed issues set. If the intent is only to skip failed issues, update the comment to match the code. Since remediation issues come from `verify_and_close_epic` which creates new issues, they shouldn't already be completed, so this is likely just a misleading comment.","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-02T01:58:59.964142341Z","created_by":"cyou","updated_at":"2026-01-02T02:04:43.055534247Z","closed_at":"2026-01-02T02:04:43.055534247Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_finding:952071bdf21e","source:mala-l072"]}
{"id":"mala-za5","title":"Honor repo .env for Braintrust setup","description":"Context:\n- Braintrust setup runs before repo .env is loaded, so BRAINTRUST_API_KEY in the repo is ignored.\n- README suggests repo .env overrides user config, which currently isn’t true for Braintrust.\n\nScope:\n- In: delay Braintrust setup until after load_env(repo_path) or re-run setup if key becomes available.\n- Out: changes to tracing semantics or Braintrust SDK usage beyond setup timing.\n\nAcceptance Criteria:\n- When only repo .env contains BRAINTRUST_API_KEY, `mala run` reports Braintrust enabled.\n- When no key is present, behavior remains unchanged.\n\nTest Plan:\n- TDD: add a test that simulates repo-only BRAINTRUST_API_KEY, implement fix, and run CLI-related tests.\n\nNotes for Agent:\n- Avoid importing claude_agent_sdk before the wrapper is applied.","status":"tombstone","priority":2,"issue_type":"task","created_at":"2025-12-26T17:35:42.430757121Z","updated_at":"2025-12-26T17:39:58.002809862Z","deleted_at":"2025-12-26T17:39:58.002809862Z","deleted_by":"daemon","delete_reason":"delete","original_type":"task"}
{"id":"mala-zbe","title":"CLI flag to disable MorphLLM","description":"Add a command-line flag (e.g., --no-morph or --disable-morph) to disable MorphLLM model routing. This allows users to bypass MorphLLM and use models directly when needed for debugging, cost control, or when MorphLLM is unavailable.\n\nAcceptance criteria:\n- Add --no-morph flag to CLI argument parsing\n- When set, skip MorphLLM routing and use the model directly\n- Update help text to document the flag","status":"tombstone","priority":2,"issue_type":"task","created_at":"2025-12-29T00:00:00Z","updated_at":"2025-12-29T02:02:50.346077611Z","deleted_at":"2025-12-29T02:02:50.346077611Z","deleted_by":"daemon","delete_reason":"delete","original_type":"task"}
{"id":"mala-zfh","title":"QA agent for end of epic verification","description":"Before auto-closing epics, run QA agent to verify everything is actually done and complete\n\nSee docs/epic-verification-agent.md for detailed specification.\n\n[Added Context]\n- Treat this issue as the umbrella epic for the Epic Verification Agent rollout.\n- Verification runs after issue close and before epic auto-close, using scoped diffs from child issue commits (bd dep tree + commit prefix).\n- Unmet criteria create remediation issues (deduped by epic_remediation:\u003cepic_id\u003e:\u003ccriterion_hash\u003e) and block the epic; missing criteria/low confidence/no commits/timeouts require human review issues.\n- Large diffs use tiered truncation (100KB default) with file-summary/file-list fallbacks.\n- Human override via --epic-override \u003cepic_id\u003e bypasses verification when explicitly requested.","status":"closed","priority":3,"issue_type":"epic","created_at":"2025-12-28T06:21:39.224777024Z","updated_at":"2025-12-30T06:22:14.690129106Z","closed_at":"2025-12-30T06:22:14.690129106Z","close_reason":"All children completed"}
{"id":"mala-zfh.1","title":"Add epic verification types + protocol","description":"Context:\n- Epic Verification Agent spec requires shared dataclasses and a model protocol before implementation.\n- Types should live in src/models.py to avoid circular imports with protocols/epic verifier.\n- The protocol enables swapping verification model implementations.\n\nScope:\n- In: add UnmetCriterion, EpicVerdict, EpicVerificationResult, RetryConfig dataclasses to src/models.py; add EpicVerificationModel protocol to src/protocols.py with verify(...) -\u003e EpicVerdict signature.\n- Out: no EpicVerifier implementation or orchestrator/CLI changes.\n\nAcceptance Criteria:\n- Types exist with fields and types matching docs/epic-verification-agent.md.\n- EpicVerificationModel protocol imports from src.models without circular import.\n- ty type check passes for new annotations.\n\nTest Plan:\n- Run `uvx ty check`.\n\nNotes for Agent:\n- Keep models.py domain-agnostic; avoid importing EpicVerifier or Claude SDK here.","status":"closed","priority":3,"issue_type":"task","created_at":"2025-12-30T05:17:31.839848586Z","created_by":"cyou","updated_at":"2025-12-30T05:30:53.024694371Z","closed_at":"2025-12-30T05:30:53.024694371Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-zfh.1","depends_on_id":"mala-zfh","type":"parent-child","created_at":"2025-12-30T05:17:31.840381665Z","created_by":"daemon"}]}
{"id":"mala-zfh.2","title":"Implement EpicVerifier core + unit tests","description":"Context:\n- Epic verification runs after issue closure and before epic auto-close, using scoped diffs of child issue commits.\n- Spec requires diff scoping (bd dep tree + commit prefix), spec path parsing, large diff handling, and remediation/human review flows.\n- Implementation lives in new src/epic_verifier.py and uses Claude SDK via EpicVerificationModel.\n\nScope:\n- In: implement EpicVerifier + ClaudeEpicVerificationModel with verify_epic/verify_and_close_eligible, including scoped diff computation from child commits (skip merge commits; include all commits per issue; handle no commits). Add prompt template in src/prompts/epic_verification.md and load it.\n- In: implement spec path extraction (regex patterns from docs) and large diff handling tiers (100KB default, file-summary, file-list).\n- In: implement remediation issue creation with dedup tags and severity-\u003epriority mapping; create human review issues for missing criteria/low confidence/timeouts/no commits; add epic blockers via `bd dep add`. Add unit tests covering diff scoping, spec parsing, large diff handling, remediation/human review and dedup.\n- In: add sequential epic verification lock (LockManager acquire epic_verify:\u003cepic_id\u003e) and ensure verification discovers eligible epics before closing (no direct bd epic close-eligible before verification); add unit tests asserting lock usage and ordering.\n- Out: no event sink integration; no orchestrator/CLI wiring.\n\nAcceptance Criteria:\n- EpicVerifier exposes verify_epic() and verify_and_close_eligible() returning EpicVerdict/EpicVerificationResult per spec.\n- Scoped diff includes only child-issue commits (by bd-\u003cid\u003e: prefix) and skips merge commits; no-child-commit case triggers human review.\n- Remediation issues follow required title/body/tags and deduplicate by epic_remediation:\u003cepic_id\u003e:\u003ccriterion_hash\u003e.\n- Epic verification acquires per-epic lock and does not close epics prior to verification.\n- Unit tests for core behaviors pass.\n\nTest Plan:\n- TDD: add failing unit tests first, implement minimal logic, then refactor.\n- `uv run pytest -m unit tests/test_epic_verifier.py`.\n\nNotes for Agent:\n- Use CommandRunner for git/bd subprocesses; stub in tests to avoid calling real git/bd.\n- Keep spec path regexes exactly as in docs/epic-verification-agent.md.","status":"closed","priority":3,"issue_type":"task","created_at":"2025-12-30T05:17:31.863151386Z","created_by":"cyou","updated_at":"2025-12-30T06:00:01.394448856Z","closed_at":"2025-12-30T06:00:01.394448856Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-zfh.2","depends_on_id":"mala-zfh","type":"parent-child","created_at":"2025-12-30T05:17:31.863520055Z","created_by":"daemon"},{"issue_id":"mala-zfh.2","depends_on_id":"mala-zfh.1","type":"blocks","created_at":"2025-12-30T05:17:42.335294145Z","created_by":"daemon"}]}
{"id":"mala-zfh.3","title":"Emit epic verification events via event sink","description":"Context:\n- Verification flow should surface status via event sink so operators see pass/fail/human review/remediation.\n- Existing MalaEventSink/ConsoleEventSink need new hooks to log verification lifecycle.\n\nScope:\n- In: extend MalaEventSink/NullEventSink with epic verification lifecycle hooks (started, passed, failed, needs human review, remediation created) in src/event_sink.py.\n- In: wire EpicVerifier to emit these events during verify_and_close_eligible/verify_epic; update console sink logs to mirror spec examples.\n- In: add/adjust unit tests for event sink/console logging around new methods (prefer tests/test_event_sink.py to avoid overlap).\n- Out: no orchestrator/CLI wiring; no changes to verification decision logic.\n\nAcceptance Criteria:\n- New event sink methods exist on protocol and NullEventSink.\n- Console output includes verification start/pass/fail/human review messages.\n- EpicVerifier emits events at appropriate points (verified by tests).\n\nTest Plan:\n- TDD: write failing tests for new event hooks/log output, implement, refactor.\n- `uv run pytest -m unit tests/test_event_sink.py` (and any added EpicVerifier event tests).\n\nNotes for Agent:\n- Keep event sink methods synchronous and non-blocking.\n- Follow existing console logging style (icons/colors).","status":"closed","priority":3,"issue_type":"task","created_at":"2025-12-30T05:17:31.886521826Z","created_by":"cyou","updated_at":"2025-12-30T06:15:50.107903516Z","closed_at":"2025-12-30T06:15:50.107903516Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-zfh.3","depends_on_id":"mala-zfh","type":"parent-child","created_at":"2025-12-30T05:17:31.886859085Z","created_by":"daemon"},{"issue_id":"mala-zfh.3","depends_on_id":"mala-zfh.2","type":"blocks","created_at":"2025-12-30T05:17:47.37494295Z","created_by":"daemon"}]}
{"id":"mala-zfh.4","title":"Wire EpicVerifier into orchestrator + CLI","description":"Context:\n- Orchestrator currently auto-closes epics via BeadsClient without verification.\n- Spec requires epic verification before closure and a CLI override for explicit human bypass.\n- Config should allow max_diff_size_kb default 100.\n\nScope:\n- In: add --epic-override CLI flag (comma-separated IDs) and thread into orchestrator.\n- In: instantiate EpicVerifier in MalaOrchestrator and replace close_eligible_epics_async with verify_and_close_eligible(human_override_epic_ids=...); plumb max_diff_size_kb from config (default 100).\n- In: add integration tests for pass/fail/remediation creation and --epic-override behavior.\n- Out: no event sink changes; no EpicVerifier logic changes.\n\nAcceptance Criteria:\n- Orchestrator uses EpicVerifier for epic closure; BeadsClient close_eligible_epics_async no longer called in success path.\n- --epic-override bypasses verification for listed epics.\n- Integration tests pass.\n\nTest Plan:\n- TDD: add failing integration tests first, implement wiring, then refactor.\n- `uv run pytest -m integration -n auto -k epic_verification` (or targeted tests).\n\nNotes for Agent:\n- Keep backward compatibility for runs with no eligible epics; return EpicVerificationResult even when empty.\n- Ensure max_diff_size_kb defaults to 100 when not configured.","status":"closed","priority":3,"issue_type":"task","created_at":"2025-12-30T05:17:31.909028247Z","created_by":"cyou","updated_at":"2025-12-30T06:22:14.662507456Z","closed_at":"2025-12-30T06:22:14.662507456Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-zfh.4","depends_on_id":"mala-zfh","type":"parent-child","created_at":"2025-12-30T05:17:31.909396487Z","created_by":"daemon"},{"issue_id":"mala-zfh.4","depends_on_id":"mala-zfh.2","type":"blocks","created_at":"2025-12-30T05:17:52.425928069Z","created_by":"daemon"}]}
{"id":"mala-zgl0","title":"Add max_diff_size_kb to mala.yaml epic verification config","description":"Currently configured via MALA_MAX_DIFF_SIZE_KB env var. Should be added to mala.yaml for consistency with other config. Probably under a top-level epic_verification section or similar.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-10T21:23:02.546443167Z","created_by":"cyou","updated_at":"2026-01-10T23:53:07.068175767Z","closed_at":"2026-01-10T23:53:07.068175767Z","close_reason":"Closed","dependencies":[{"issue_id":"mala-zgl0","depends_on_id":"mala-23sx","type":"blocks","created_at":"2026-01-10T21:26:31.715654252Z","created_by":"cyou"}]}
{"id":"mala-zjhf","title":"[Advisory] Stop hook uses MCP tool instead of shell script for cleanup","description":"## Context\nThis issue was auto-created by epic verification for epic `mala-y8fw`.\n\n## Epic Description / Acceptance Criteria\nTitle: use tool for locks instead of bash scripts\n\nReplace shell script-based file locking (`lock-try.sh`, `lock-wait.sh`, etc.) with named MCP tools exposed via Claude Agent SDK.\n\n## Goals\n- First-class tool visibility for locking operations\n- Typed JSON parameters with schema validation\n- Structured per-file results for deadlock detection\n- Solve batched `\u0026\u0026` command problem where exit=1 gives no file granularity\n\n## Key Changes\n- **2 MCP tools**: `lock_acquire` and `lock_release`\n- WAITING events emitted via closure callback (not PreToolUse hook)\n- PostToolUse hook for ACQUIRED/RELEASED only\n- Remove bash-parsing logic from hooks\n- Update prompts to reference tools only\n\n## Design Doc\n`plans/2026-01-04-locking-tools-design.md`\n\n## Implementation Plan\n`plans/2026-01-04-locking-tools-impl-plan.md`\n\n## Commit Scope\n- Range hint: 0cb12442c0c497cef34bbcfa070b8c0508d89cae^..63364a03fc450daf4e57c8e1bb20d651cad62c4a\n- Commits:\n- 63364a03fc450daf4e57c8e1bb20d651cad62c4a bd-mala-y8fw.5: Fix Quick Rule #3 to match File Locking Protocol\n- 0b21a9f0abf316e3d5aec0c3329249ba43d1bdf8 bd-mala-y8fw.5: Remove scripts_dir from prompt and fix lock protocol\n- 283bb654b76c7f14ece6615ec419b7449edb3014 bd-mala-y8fw.11: Fix review findings in locking MCP tests\n- 5812e7f7faaf887e7da6c1b85287d87f64ce7c6c bd-mala-y8fw.6: Add integration tests for MCP locking tools\n- 1f5e263308de3b1ff295dae279fbe1f9730b4399 bd-mala-y8fw.5: Update prompts and lock hook for MCP tools\n- fcf9c9b3ff8eeff482aa71faf7fabc7b9eec708c bd-mala-y8fw.4: Fix with_mcp() eager evaluation bypassing locking server\n- ad1982850895673adc65a53178526d935173a2c0 bd-mala-y8fw.9: Fix type annotations for LockingToolHandlers\n- c54483fcb35a551a7f9b660dc59d474fb24da336 bd-mala-y8fw.4: Wire locking MCP server through AgentRuntimeBuilder\n- f464ec3ab182c347e05c9fba9fa77dc44b7a3fe9 bd-mala-y8fw.9: Return released file paths from cleanup_agent_locks\n- bce14b99b1450155a502ec4107d29f2cffe74812 bd-mala-y8fw.8: Add edge case tests for deadlock hook\n- 67248c7ebb14ccd9cd3eeba06fb210dba7c73872 bd-mala-y8fw.3: Fix import errors and update tests for MCP-based locking\n- 48fc7b7ec5b697dcdbd32ba5eeeef66e8f1d6ea1 bd-mala-y8fw.7: Add unit tests for MCP tool handlers and async locking\n- 2e2d003664acd1746faac67d4a60ec595bef52e9 bd-mala-y8fw.3: Simplify deadlock hooks to PostToolUse only for MCP tools\n- fb2c2cedbcd07021591a6efab6cd9caed8b1a129 bd-mala-y8fw.2: Fix locking_mcp.py review issues\n- c5a46b01e159c4baaa50283efbd9026b009d4f34 bd-mala-y8fw.2: Fix locking_mcp.py review issues\n- 88c1dd5878b25448e09d105efe8c6dbd55904932 bd-mala-y8fw.2: Create locking_mcp.py with MCP tool handlers\n- 0cb12442c0c497cef34bbcfa070b8c0508d89cae bd-mala-y8fw.1: Add wait_for_lock_async and export get_lock_holder\n\n## Child Issues\n- mala-y8fw.1\n- mala-y8fw.10\n- mala-y8fw.11\n- mala-y8fw.2\n- mala-y8fw.3\n- mala-y8fw.4\n- mala-y8fw.5\n- mala-y8fw.6\n- mala-y8fw.7\n- mala-y8fw.8\n- mala-y8fw.9\n\n## Unmet Criterion\nStop hook uses MCP tool instead of shell script for cleanup\n\n## Evidence\nThe make_stop_hook still calls lock-release-all.sh script instead of using lock_release MCP tool with all=true parameter for agent cleanup\n\n## Priority\nP3 (informational)\n\n## Resolution\nThis is advisory (P2/P3) and does not block epic closure. When complete, close this issue.\n","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-04T16:14:20.213718701Z","created_by":"cyou","updated_at":"2026-01-04T16:20:05.272148829Z","closed_at":"2026-01-04T16:20:05.272148829Z","close_reason":"Closed","labels":["auto_generated","epic_remediation:mala-y8fw:7d8b2fe2d803a737acb026930c8d1382542a687eb3cde51123dab6c288e523f8"]}
{"id":"mala-zjn","title":"Test blocked without deps","status":"tombstone","priority":2,"issue_type":"task","created_at":"2025-12-26T00:53:15.4017375Z","updated_at":"2025-12-26T00:53:28.39468524Z","deleted_at":"2025-12-26T00:53:28.39468524Z","deleted_by":"daemon","delete_reason":"delete","original_type":"task"}
{"id":"mala-zrqo","title":"[Review] 2 non-blocking findings from mala-pid0.3","description":"## Review Findings\n\nThis issue consolidates 2 non-blocking findings from code review.\n\n**Source issue:** mala-pid0.3\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Test uses outdated disabled reason message format\n\n**Priority:** P2\n**Reviewer:** claude\n**Location:** tests/test_orchestration_helpers.py:800-805\n\nThe test `test_morph_disabled_no_key` passes `\"MORPH_API_KEY not set\"` as the expected disabled reason, but the new implementation in `_derive_config()` produces `f\"add MORPH_API_KEY to {USER_CONFIG_DIR}/.env\"`. Since `build_event_run_config` now just passes through the pre-computed value, the test is no longer testing realistic behavior. The test should use the actual message format that `_derive_config()` produces, or this mismatch will cause confusion when someone tries to understand what message users actually see.\n\n---\n\n### Finding 2: [P3] Handle --no-braintrust in disabled reason\n\n**Priority:** P3\n**Reviewer:** codex\n**Location:** src/orchestration/factory.py:119-122\n\n`config.cli_args` never includes a `no_braintrust` key (CLI metadata only stores `braintrust: bool`), so this branch won’t fire. When Braintrust is disabled via CLI and an API key exists, the reason becomes “disabled by config” instead of “--no-braintrust”, which is misleading and diverges from ResolvedConfig behavior. Consider adding `no_braintrust` to cli_args metadata or passing the precomputed disabled reason through OrchestratorConfig instead of re-deriving it here.\n\n---\n","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T15:23:37.227686135Z","created_by":"cyou","updated_at":"2026-01-03T15:35:14.448304973Z","closed_at":"2026-01-03T15:35:14.448304973Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_findings:86f695667612","source:mala-pid0.3"]}
{"id":"mala-zwf4","title":"[Review] src/orchestration/orchestrator.py:541-544: [P2] Re-raise CancelledError in remediation finalization","description":"## Review Finding\n\nThis issue was auto-created from a P2 review finding.\n\n**Source issue:** mala-mfnr\n**Reviewer:** codex\n**Location:** src/orchestration/orchestrator.py:541-544\n\n## Details\n\n`except Exception` also catches `asyncio.CancelledError`, so a cancellation (e.g. run abort/shutdown) during `_finalize_issue_result` will be swallowed and the loop will continue, marking the issue completed instead of propagating the cancel. This can delay or prevent graceful shutdown. Handle `CancelledError` explicitly and re-raise before the generic handler.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-02T02:17:45.118010613Z","created_by":"cyou","updated_at":"2026-01-02T02:21:24.129925958Z","closed_at":"2026-01-02T02:21:24.129925958Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_finding:2898b81f211d","source:mala-mfnr"]}
{"id":"mala-zx5","title":"Add --only flag for specifying beads issues by ID","description":"Add a command-line flag (--only) that accepts a comma-separated list of beads issue IDs. When provided, workers will only process those specific issues instead of picking from all ready issues.\n\nExample usage:\n  mala --only bd-abc123,bd-def456\n\nAcceptance criteria:\n- Add --only flag to CLI argument parsing\n- Parse comma-separated issue IDs\n- Filter get_ready() results to only include specified IDs\n- Validate that specified IDs exist and are ready\n- Update documentation/help text","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-26T00:40:19.287843127Z","updated_at":"2025-12-26T00:53:27.149701137Z","closed_at":"2025-12-26T00:53:27.149701137Z","close_reason":"Closed"}
{"id":"mala-zx90","title":"[Review] 1 non-blocking finding from mala-mxys","description":"## Review Findings\n\nThis issue consolidates 1 non-blocking finding from code review.\n\n**Source issue:** mala-mxys\n**Highest priority:** P2\n\n---\n\n### Finding 1: [P2] Clear agent_ids even when finalization raises\n\n**Priority:** P2\n**Reviewer:** codex\n**Location:** src/orchestration/orchestrator.py:476-481\n\n`issue_finalizer.finalize(...)` can raise (you already catch/report this in the coordinator), but the agent_id cleanup now happens after finalization and is skipped on exceptions. Since `run_implementer` no longer pops the entry in its `finally`, a finalize failure leaves `self.agent_ids` populated indefinitely, causing stale attribution for the same issue_id and an unbounded map over time. Consider wrapping finalization/cleanup in a `try/finally` or keeping a fallback pop in `run_implementer`.\n\n---\n","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T08:08:24.743434784Z","created_by":"cyou","updated_at":"2026-01-03T08:12:16.561975966Z","closed_at":"2026-01-03T08:12:16.561975966Z","close_reason":"Closed","labels":["auto_generated","review_finding","review_findings:5ab8a674abe6","source:mala-mxys"]}
