diff --git a/CHANGELOG.md b/CHANGELOG.md index f5da9d9..d930670 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,23 @@ Tracks the Python side (CLI + MCP server). The VSCode extension has its own [vsc Versions follow semver. Pre-1.0 — minor bumps may add features or break behavior; the README is the source-of-truth contract. +## 3.2.0 — 2026-07-04 (Enforcement hooks — 4.0 phase 2) + +### Added +- Claude Code enforcement hooks, installed by `canopy setup-agent --hooks` + (project-scoped `.claude/settings.json`): + - **PreToolUse git gate** (`canopy-hook-gate`): blocks git mutations whose + effective directory (after resolving `cd` chains, `git -C`, and heredoc + bodies) is outside a workspace repo (`outside_repo`), commits/pushes on + a branch belonging to a different registered feature + (`trunk_branch_drift` / `slot_branch_drift`), and pushes of branch names + that only exist in a different repo (`push_unknown_branch`). Fail-open + by design; `CANOPY_HOOKS_DISABLED=1` disables. + - **SessionStart brief** (`canopy-hook-context`): injects a compact + repo → branch → canonical-feature map at session start. +- `tests/fixtures/hook_gate_corpus.jsonl`: 680 real-world git command shapes + (mined from 35 days of transcripts) as a parser regression corpus. + ## 3.1.2 — 2026-07-04 Slot-model consistency fixes from canopy-test dogfooding. diff --git a/docs/commands.md b/docs/commands.md index 5ac0564..0e3b4fa 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -145,6 +145,41 @@ Install-staleness (canopy's installation around the workspace): | `skill_stale` | warn | installed skill drifted from bundled source | `install_skill(reinstall=True)` | | `vsix_duplicates` | info | multiple `singularityinc.canopy-*` extension dirs | requires `--clean-vsix` | +## Hooks (enforcement) + +Claude Code hooks that stop the agent from mutating git state in the wrong place. Separate from the drift-tracking `canopy hooks install|uninstall|status` (post-checkout, feeds `heads.json`) described in "Setup" above. + +| Command | What it does | +|---|---| +| `canopy setup-agent --hooks` | Installs (or refreshes) the enforcement hooks into `/.claude/settings.json`: a `PreToolUse` entry (matcher `Bash`) running `canopy-hook-gate`, and a `SessionStart` entry running `canopy-hook-context`. **Project-scoped, not user-scoped** — the workspace root is normally not itself a git repo (it's a container of repos), so nothing lands in `~/.claude/settings.json` or in any employer repo's tree. Merges into existing `settings.json`: other keys (`permissions`, foreign hooks) are preserved untouched; re-running is a no-op (`action: "unchanged"`) once both entries are present. If `settings.json` exists but isn't valid JSON, install is skipped with a `reason` rather than clobbering it. Combine with the other `setup-agent` flags (`--skill-only`, `--mcp-only`, `--reinstall`, `--check`) as usual. | + +`canopy-hook-gate` and `canopy-hook-context` are internal console scripts (registered in `pyproject.toml`, not meant to be run by hand) that Claude Code invokes per the `settings.json` entries above: + +- **`canopy-hook-gate`** (PreToolUse, matcher `Bash`) — reads the tool-call payload as JSON on stdin (`{tool_name, tool_input: {command}, cwd, ...}`). For non-`Bash` calls or commands with no `git` token, exits 0 immediately without touching disk. Otherwise it resolves the workspace from `cwd` (walking up for `canopy.toml`), splits the command on top-level shell operators, tracks the effective directory through `cd` chains and `git -C`, and judges only the mutating git subcommands (`commit`, `push`, `merge`, `rebase`, `reset`, `cherry-pick`, `add`, `rm`, `mv`, `am`, `revert`, mutating `stash` verbs). **Exit 0** = allow (nothing printed). **Exit 2** = block, with a one-line reason on stderr that Claude Code feeds back to the model. +- **`canopy-hook-context`** (SessionStart) — reads the same payload shape, resolves the workspace from `cwd`, and prints a compact brief to stdout (which becomes session context): workspace name, canonical feature, each repo's branch + dirty count, each warm slot's occupant, and a one-line reminder to `canopy switch` before working if the ticket doesn't match. Always exits 0; on any error it prints nothing. + +Deny codes (all four block with an explanatory message that also names the fix): + +| Code | Meaning | Fix the message names | +|---|---|---| +| `outside_repo` | The mutation's effective directory (after resolving `cd`/`git -C`) isn't inside any workspace repo or slot worktree. | `cd && git ...`, or use `canopy run`. | +| `trunk_branch_drift` | On **commit/push only**: a canonical-slot repo is on a branch owned by a different registered feature than the current canonical one. (Other mutations like `git add` on a drifted branch are allowed.) | `canopy switch ` (either the branch's owner, or back to canonical). | +| `slot_branch_drift` | On **commit/push only**: a warm-slot repo is on a branch that doesn't match the slot's recorded occupant feature. | `git checkout ` in that worktree, or `canopy doctor`. | +| `push_unknown_branch` | `git push`'s source refspec names a branch that doesn't exist in the effective repo (but does exist in a different one). | Check the branch for *this* repo with `git branch --list` or `canopy context`; likely the wrong repo. | + +**Fail-open contract.** The gate only blocks when it's sure the mutation targets the wrong place. It allows (exit 0) on: unparseable shell segments (`shlex` failure), unresolvable `cd` targets (`$VAR`, `~`, backticks, `cd -`), a `cwd` with no `canopy.toml` anywhere above it, non-`Bash` tool calls, commands with no `git` token, and any internal exception — `run_gate` never raises. `checkout`/`switch` are deliberately never gated: they're the recovery action for a drifted branch, and blocking them would trap the agent. + +**Escape hatch:** set `CANOPY_HOOKS_DISABLED=1` in the environment to make the gate a no-op (checked first, before any parsing). + +**Known bypasses** (deliberate fail-open — not bugs, documented so nobody relies on the gate as a security boundary): +- Env-prefix invocations: `GIT_TRACE=1 git push`, `env git push` — the leading token isn't `git`, so the segment isn't recognized as a git mutation. +- Non-literal git: `/usr/bin/git ...`, `command git ...`, `sh -c "git push"`, `xargs git push` — same reason, no literal `git` argv[0]. +- Subshells, loops, brace groups: `(cd x && git push)`, `for d in a b; do (cd "$d" && git push); done` — the gate's segment splitter is top-level-operator-aware but doesn't recurse into subshell/loop bodies. +- Unresolvable directories: `cd $DIR`, `cd ~/x`, `git -C "$dir"`, or any `--git-dir`/`--work-tree` override — these poison `dir_known` for the segment (or everything after, for an unresolvable `cd`), which fails open rather than guessing. +- Shlex-unparseable segments: unbalanced quotes cause that segment to be skipped entirely. +- Backslash-escape edge cases in the quote-tracking scanner (best-effort, not a full shell parser). +- Sessions whose `cwd` is outside the workspace entirely — no `canopy.toml` is found walking up, so the gate can't resolve repos/slots and allows everything. + ## Debug | Command | What it does | diff --git a/pyproject.toml b/pyproject.toml index 6b192f8..82d19df 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,6 +44,8 @@ dev = [ [project.scripts] canopy = "canopy.cli.main:main" canopy-mcp = "canopy.mcp.server:main" +canopy-hook-gate = "canopy.hooks_entry:gate_main" +canopy-hook-context = "canopy.hooks_entry:context_main" [tool.hatch.version] path = "src/canopy/__init__.py" diff --git a/src/canopy/__init__.py b/src/canopy/__init__.py index 92f39ab..a565ff5 100644 --- a/src/canopy/__init__.py +++ b/src/canopy/__init__.py @@ -1,2 +1,2 @@ """Canopy — workspace-first development orchestrator.""" -__version__ = "3.1.2" +__version__ = "3.2.0" diff --git a/src/canopy/actions/hook_context.py b/src/canopy/actions/hook_context.py new file mode 100644 index 0000000..f1ed3bb --- /dev/null +++ b/src/canopy/actions/hook_context.py @@ -0,0 +1,45 @@ +"""SessionStart brief — one compact block injected into a new session. + +Evidence: 111 midway branch switches in 35 days, 87 after 10+ edits. The +mismatch must be visible BEFORE the agent reads a single file. Keep this +under ~10 lines: it lands in every session's context budget. +""" +from __future__ import annotations + +import re + +from ..workspace.workspace import Workspace + +_SLOT_NUM = re.compile(r"worktree-(\d+)$") + + +def _slot_sort_key(sid: str) -> tuple[int, int, str]: + """Sort worktree-N slots numerically; other ids fall back to name order.""" + m = _SLOT_NUM.match(sid) + return (0, int(m.group(1)), "") if m else (1, 0, sid) + + +def context_brief(workspace: Workspace) -> str: + from . import slots as slots_mod + + state = slots_mod.read_state(workspace) + canonical = state.canonical.feature if state and state.canonical else None + lines = [ + f"canopy: workspace '{workspace.config.name}' — " + f"canonical feature: {canonical or '(none)'}", + ] + for rs in sorted(workspace.repos, key=lambda r: r.config.name): + name = rs.config.name + if not rs.abs_path.exists(): + lines.append(f" {name} → (missing on disk)") + continue + dirty = f"{rs.dirty_count} dirty" if rs.is_dirty else "clean" + lines.append(f" {name} → {rs.current_branch} ({dirty})") + if state and state.slots: + for sid in sorted(state.slots, key=_slot_sort_key): + lines.append(f" slot {sid} → {state.slots[sid].feature}") + lines.append( + " Before any work: confirm the branch above matches this chat's " + "ticket. If not, run `canopy switch ` FIRST." + ) + return "\n".join(lines) diff --git a/src/canopy/actions/hook_gate.py b/src/canopy/actions/hook_gate.py new file mode 100644 index 0000000..153a8e8 --- /dev/null +++ b/src/canopy/actions/hook_gate.py @@ -0,0 +1,509 @@ +"""PreToolUse Bash gate — blocks git mutations from the wrong place. + +Evidence base (35 days of work-machine transcripts, see +canopy-4.0-distillation.md#evidence): the agent's cwd never leaves the +workspace parent; repo work happens via ``cd && git ...`` chains. +So the gate resolves the EFFECTIVE directory per command segment (tracking +``cd`` and ``git -C``) and only judges git mutation segments. + +Fail-open contract: any parse failure, unresolvable path, or internal +error ⇒ allow. The gate blocks only when it is sure the mutation targets +the wrong place. Exit codes at the CLI layer: 0 = allow, 2 = block +(reason on stderr, which Claude Code feeds back to the model). +""" +from __future__ import annotations + +import re as _re +import shlex +from dataclasses import dataclass, field +from pathlib import Path +from typing import Any + +_GIT_WORD = _re.compile(r"\bgit\b") + + +def _heredoc_delimiter(command: str, i: int) -> tuple[str, int, bool] | None: + """If ``command[i:]`` starts a heredoc redirection (``<<``/``<<-`` plus + a delimiter word in any of the < 0 and command[i - 1] == "<": + return None # tail of a <<< herestring + j = i + 2 + tab_indent_ok = False + if j < n and command[j] == "-": + tab_indent_ok = True + j += 1 + while j < n and command[j] in " \t": + j += 1 + if j < n and command[j] in ("'", '"'): + k = command.find(command[j], j + 1) + if k == -1 or k == j + 1: + return None # unterminated/empty quoted delim + return command[j + 1:k], k + 1, tab_indent_ok + if j < n and command[j] == "\\": + j += 1 # <<\EOF — POSIX quoted form + k = j + while k < n and (command[k].isalnum() or command[k] == "_"): + k += 1 + if k == j: + return None # no delimiter word + return command[j:k], k, tab_indent_ok + + +def split_top_level(command: str) -> list[str]: + """Split a shell command on top-level ``&&``, ``||``, ``;``, ``|``, + and unquoted newlines. + + Quote- and subshell-aware: operators inside '...', "...", $(...), + backticks, or (...) do not split. Heredoc-aware: after an unquoted + depth-0 ``< list[str]: + """argv with ``git`` + global flags stripped → starts at subcommand.""" + i = 1 + n = len(self.argv) + while i < n: + tok = self.argv[i] + if tok == "-C" or tok == "-c": + i += 2 + continue + if tok.startswith("--git-dir") or tok.startswith("--work-tree"): + # exotic — subcommand detection still works; dir override + # already handled (fail-open) in resolve_segments + i += 1 if "=" in tok else 2 + continue + if tok.startswith("-"): + i += 1 + continue + return self.argv[i:] + return [] + + +_UNRESOLVABLE = ("$", "~", "`") # vars/home/expansion → don't guess + + +def _resolve_path(base: Path, raw: str) -> tuple[Path, bool]: + token = raw.strip() + if not token or any(m in token for m in _UNRESOLVABLE): + return base, False + p = Path(token) + return (p if p.is_absolute() else (base / p)), True + + +def resolve_segments(command: str, cwd: Path) -> list[GitSegment]: + """Walk the command's top-level segments tracking the effective dir. + + Returns only git segments. ``cd`` updates the tracked dir for later + segments; ``git -C `` overrides for that segment only. An + unresolvable ``cd`` (variables, ``~``, ``cd -``) poisons dir_known + for everything after it. + """ + out: list[GitSegment] = [] + cur = Path(cwd) + known = True + for part in split_top_level(command): + try: + argv = shlex.split(part, posix=True) + except ValueError: + continue # unparseable segment: skip, fail open + if not argv: + continue + if argv[0] == "cd": + rest = argv[1:] + while rest and rest[0] in ("-P", "-L", "-e", "--"): + rest = rest[1:] + if not rest or rest[0].startswith("-"): + known = False + continue + cur, known = _resolve_path(cur, rest[0]) + continue + if argv[0] != "git": + continue + seg_dir, seg_known = cur, known + # git -C (repeatable, cumulative per git semantics — apply in order) + i = 1 + while i < len(argv) - 1: + if argv[i] == "-c": + i += 2 + continue + if argv[i] == "-C": + seg_dir, ok = _resolve_path(seg_dir, argv[i + 1]) + seg_known = seg_known and ok + i += 2 + continue + if argv[i].startswith("--git-dir") or argv[i].startswith("--work-tree"): + seg_known = False # too exotic to judge — fail open + if not argv[i].startswith("-"): + break + i += 1 + out.append(GitSegment(argv=argv, effective_dir=seg_dir, dir_known=seg_known)) + return out + + +# Gated git subcommands. checkout/switch are deliberately ABSENT: they are +# the recovery action for wrong-branch states; blocking them traps the +# agent. Branch safety is enforced on commit/push instead. +MUTATION_SUBCOMMANDS = frozenset({ + "commit", "push", "merge", "rebase", "reset", + "cherry-pick", "add", "rm", "mv", "am", "revert", +}) + +# ``stash`` alone is a mutation subcommand, but its own sub-verb decides: +# push/pop/apply/drop/clear/save/branch mutate; list/show are reads. A flag +# right after ``stash`` (``stash -u``, ``stash --keep-index``) is an +# implicit ``stash push`` — a mutation. +_STASH_MUTATING_SUBCOMMANDS = frozenset({ + "push", "pop", "apply", "drop", "clear", "save", "branch", +}) + + +def is_mutation(seg: GitSegment) -> bool: + sub = seg.argv_after_globals + if not sub: + return False + if sub[0] == "stash": + return (len(sub) == 1 or sub[1].startswith("-") + or sub[1] in _STASH_MUTATING_SUBCOMMANDS) + return sub[0] in MUTATION_SUBCOMMANDS + + +@dataclass +class GateDecision: + allow: bool + code: str = "" # "outside_repo" | "trunk_branch_drift" | "slot_branch_drift" | "push_unknown_branch" + reason: str = "" # fed to the model on deny — must name the fix + + +def _repo_dirs(workspace) -> dict[Path, tuple[str, str | None]]: + """Map of every legal mutation dir → (repo_name, slot_id | None). + + Trunk checkouts map to (repo, None); slot worktrees to (repo, slot_id). + """ + from . import slots as slots_mod + + dirs: dict[Path, tuple[str, str | None]] = {} + repo_names = [rs.config.name for rs in workspace.repos] + for rs in workspace.repos: + dirs[rs.abs_path.resolve()] = (rs.config.name, None) + state = slots_mod.read_state(workspace) + if state is not None: + for sid in state.slots: + for name in repo_names: + p = slots_mod.slot_worktree_path(workspace, sid, name) + if p.exists(): + dirs[p.resolve()] = (name, sid) + return dirs + + +def _locate(dirs: dict[Path, tuple[str, str | None]], d: Path): + """Return (repo_root, repo_name, slot_id) if d is at/under a legal dir.""" + d = d.resolve() + for root, (name, sid) in dirs.items(): + if d == root or root in d.parents: + return root, name, sid + return None + + +def gate_command(workspace, command: str, cwd: Path) -> GateDecision: + """Decide allow/deny for one Bash command. + + No side effects; reads git + canopy state only (slots.json, features.json). + """ + segments = [s for s in resolve_segments(command, cwd) if is_mutation(s)] + if not segments: + return GateDecision(allow=True) + dirs = _repo_dirs(workspace) + for seg in segments: + if not seg.dir_known: + continue # fail open on this segment + hit = _locate(dirs, seg.effective_dir) + if hit is None: + repo_list = ", ".join(sorted(n for n, s in dirs.values() if s is None)) + return GateDecision( + allow=False, code="outside_repo", + reason=( + f"canopy: blocked `git {seg.argv_after_globals[0]}` — " + f"effective directory {seg.effective_dir} is not inside a " + f"workspace repo. Repos: {repo_list} (under " + f"{workspace.config.root}). Re-run from inside the target " + f"repo, e.g. `cd && git ...`, or use `canopy run`." + ), + ) + repo_root, repo_name, slot_id = hit + if seg.argv_after_globals[0] in _BRANCH_CHECK_SUBCOMMANDS: + deny = _check_branch(workspace, repo_root, repo_name, slot_id, seg) + if deny is not None: + return deny + if seg.argv_after_globals[0] == "push": + deny = _check_push_refspec(workspace, repo_root, repo_name, seg) + if deny is not None: + return deny + return GateDecision(allow=True) + + +_BRANCH_CHECK_SUBCOMMANDS = frozenset({"commit", "push"}) + + +_PUSH_VALUE_FLAGS = ("-o", "--push-option", "--repo", "--receive-pack", "--exec") + + +def _push_positional_args(args: list[str]) -> list[str]: + """Positional (non-flag) tokens from a push argv, stopping at the first + redirect/background operator so shell trailers (``2>&1``, ``> log``, + ``&``) are never mistaken for a refspec.""" + positional: list[str] = [] + skip_next = False + for a in args: + if skip_next: + skip_next = False + continue + if ">" in a or "<" in a or a == "&": + break # redirect / background — stop + if a.startswith("-"): + if a in _PUSH_VALUE_FLAGS: + skip_next = True # value is the next token + continue + positional.append(a) + return positional + + +def _check_push_refspec(workspace, repo_root: Path, repo_name: str, + seg: GitSegment) -> GateDecision | None: + """Deny pushes of branch names that don't exist in the effective repo.""" + from ..git import repo as git + + args = seg.argv_after_globals[1:] # after "push" + positional = _push_positional_args(args) + if len(positional) < 2: + return None # bare push / push origin + refspecs = positional[1:] # after the remote + if "--delete" in args or "-d" in args: + return None + for spec in refspecs: + src = spec.split(":", 1)[0].lstrip("+") + # «...» is the corpus miner's redaction placeholder for values it + # scrubbed — never a real branch name, so don't try to resolve it. + if not src or src in ("HEAD",) or "/" in src or "«" in src: + continue # HEAD/tags-with-path/redacted + try: + if git.branch_exists(repo_root, src): + continue + except Exception: + return None # fail open + elsewhere = [ + rs.config.name for rs in workspace.repos + if rs.config.name != repo_name and rs.abs_path.exists() + and git.branch_exists(rs.abs_path, src) + ] + hint = (f" That branch exists in {', '.join(elsewhere)} — wrong repo?" + if elsewhere else "") + return GateDecision( + allow=False, code="push_unknown_branch", + reason=( + f"canopy: blocked `git push` in {repo_name} — branch '{src}' " + f"does not exist here (src refspec would fail).{hint} " + f"Check the branch for THIS repo with `git branch --list` " + f"or `canopy context`." + ), + ) + return None + + +def _branch_owner_map(workspace) -> dict[tuple[str, str], str]: + """(repo_name, branch_name) → feature, for all registered features.""" + from ..features.coordinator import FeatureCoordinator + + out: dict[tuple[str, str], str] = {} + try: + features = FeatureCoordinator(workspace)._load_features() + except Exception: + return out + for feat, data in (features or {}).items(): + branches = (data or {}).get("branches") or {} + for repo_name in (data or {}).get("repos") or []: + out[(repo_name, branches.get(repo_name, feat))] = feat + return out + + +def _check_branch(workspace, repo_root: Path, repo_name: str, + slot_id: str | None, seg: GitSegment) -> GateDecision | None: + """Return a deny decision if the location's branch is drifted, else None.""" + from . import slots as slots_mod + from ..git import repo as git + + try: + current = git.current_branch(repo_root) + except Exception: + return None # fail open + owners = _branch_owner_map(workspace) + owner = owners.get((repo_name, current)) + state = slots_mod.read_state(workspace) + + if slot_id is None: + # Trunk: allowed = default_branch, canonical feature's branch, + # or any unregistered branch. + canonical = state.canonical.feature if state and state.canonical else None + default = workspace.get_repo(repo_name).config.default_branch + if current == default or owner is None or owner == canonical: + return None + if canonical is None: + reason = ( + f"canopy: blocked `git {seg.argv_after_globals[0]}` in trunk " + f"{repo_name} — it is on '{current}' (feature '{owner}') but " + f"no feature is canonical here. Run `canopy switch {owner}` " + f"to make '{owner}' official." + ) + else: + reason = ( + f"canopy: blocked `git {seg.argv_after_globals[0]}` in trunk " + f"{repo_name} — it is on '{current}' (feature '{owner}') but " + f"the canonical feature is '{canonical}'. Run " + f"`canopy switch {owner}` to make '{owner}' official, or " + f"`canopy switch {canonical}` to restore the trunk branch." + ) + return GateDecision(allow=False, code="trunk_branch_drift", reason=reason) + # Slot: current branch must be the occupant feature's branch for this repo. + entry = state.slots.get(slot_id) if state else None + if entry is None: + return None # doctor's problem, not the gate's + from .aliases import repos_for_feature + expected = (repos_for_feature(workspace, entry.feature) or {}).get(repo_name) + if expected is None or current == expected: + return None + return GateDecision( + allow=False, code="slot_branch_drift", + reason=( + f"canopy: blocked `git {seg.argv_after_globals[0]}` in {slot_id} " + f"({repo_name}) — it is on '{current}' but the slot belongs to " + f"feature '{entry.feature}' (branch '{expected}'). Run " + f"`git checkout {expected}` in this worktree, or `canopy doctor`." + ), + ) + + +def _load_workspace_from(start: Path): + """Walk up from ``start`` to find canopy.toml; None if not in a workspace.""" + from ..workspace.config import load_config + from ..workspace.workspace import Workspace + + cur = Path(start).resolve() + for candidate in (cur, *cur.parents): + if (candidate / "canopy.toml").exists(): + return Workspace(load_config(candidate)) + return None + + +def run_gate(payload: dict[str, Any]) -> tuple[int, str]: + """Full PreToolUse decision from the raw hook payload. + + Returns (exit_code, stderr_message): (0, "") allow, (2, reason) block. + NEVER raises — the CLI shim trusts this completely. + """ + import os + try: + if os.environ.get("CANOPY_HOOKS_DISABLED") == "1": + return 0, "" + if payload.get("tool_name") != "Bash": + return 0, "" + command = (payload.get("tool_input") or {}).get("command") or "" + if not _GIT_WORD.search(command): + return 0, "" # fast path: skip workspace load + cwd = payload.get("cwd") or "." + workspace = _load_workspace_from(Path(cwd)) + if workspace is None: + return 0, "" + decision = gate_command(workspace, command, Path(cwd)) + if decision.allow: + return 0, "" + return 2, decision.reason + except Exception: + return 0, "" # fail open, always diff --git a/src/canopy/agent_setup/__init__.py b/src/canopy/agent_setup/__init__.py index a058c1b..021f1c2 100644 --- a/src/canopy/agent_setup/__init__.py +++ b/src/canopy/agent_setup/__init__.py @@ -15,6 +15,7 @@ from __future__ import annotations import json +import os from dataclasses import dataclass, asdict from pathlib import Path @@ -161,6 +162,104 @@ def install_mcp(workspace_root: Path, *, reinstall: bool = False) -> McpResult: ) +_HOOK_GATE_ENTRY = { + "matcher": "Bash", + "hooks": [{"type": "command", "command": "canopy-hook-gate", "timeout": 15}], +} +_HOOK_CONTEXT_ENTRY = { + "hooks": [{"type": "command", "command": "canopy-hook-context"}], +} + + +_HOOK_COMMANDS = ("canopy-hook-gate", "canopy-hook-context") + + +def _entry_has_command(entry: object, command: str) -> bool: + """True if a hook-array entry registers ``command``. Shape-tolerant.""" + if not isinstance(entry, dict): + return False + return any( + isinstance(h, dict) and h.get("command") == command + for h in (entry.get("hooks") or []) + if isinstance(entry.get("hooks"), list) + ) + + +def hooks_configured(settings_path: Path) -> bool: + """True if settings.json exists, parses, and registers BOTH hook commands.""" + if not settings_path.exists(): + return False + try: + settings = json.loads(settings_path.read_text()) + except (ValueError, OSError): + return False + if not isinstance(settings, dict): + return False + hooks = settings.get("hooks") + if not isinstance(hooks, dict): + return False + all_entries = [ + e for lst in hooks.values() if isinstance(lst, list) for e in lst + ] + return all( + any(_entry_has_command(e, cmd) for e in all_entries) + for cmd in _HOOK_COMMANDS + ) + + +def install_hooks(workspace_root: Path) -> dict: + """Merge canopy's enforcement hooks into /.claude/settings.json. + + Project-scoped on purpose: the gate only makes sense inside a canopy + workspace. Non-destructive: existing settings and foreign hooks are + preserved; re-running is a no-op. Refuses (rather than clobbering) a + settings.json that isn't the shape we expect. + """ + path = workspace_root / ".claude" / "settings.json" + settings: dict = {} + if path.exists(): + try: + settings = json.loads(path.read_text()) + except (ValueError, OSError): + return {"action": "skipped", "path": str(path), + "reason": "existing settings.json is not valid JSON — fix it first"} + if not isinstance(settings, dict): + return {"action": "skipped", "path": str(path), + "reason": "existing settings.json has an unexpected shape — fix it first"} + existing_hooks = settings.get("hooks", {}) + if not isinstance(existing_hooks, dict): + return {"action": "skipped", "path": str(path), + "reason": "existing settings.json has an unexpected shape — fix it first"} + for event in ("PreToolUse", "SessionStart"): + if event not in existing_hooks: + continue + entries = existing_hooks[event] + if not isinstance(entries, list) or not all( + isinstance(e, dict) for e in entries + ): + return {"action": "skipped", "path": str(path), + "reason": "existing settings.json has an unexpected shape — fix it first"} + hooks = settings.setdefault("hooks", {}) + changed = False + for event, entry, command in ( + ("PreToolUse", _HOOK_GATE_ENTRY, "canopy-hook-gate"), + ("SessionStart", _HOOK_CONTEXT_ENTRY, "canopy-hook-context"), + ): + entries = hooks.setdefault(event, []) + present = any(_entry_has_command(e, command) for e in entries) + if not present: + entries.append(entry) + changed = True + if not changed: + return {"action": "unchanged", "path": str(path)} + path.parent.mkdir(parents=True, exist_ok=True) + # Atomic write: never leave a half-written settings.json behind. + tmp = path.with_name(path.name + ".canopy-tmp") + tmp.write_text(json.dumps(settings, indent=2) + "\n") + os.replace(tmp, path) + return {"action": "added", "path": str(path)} + + def check_status(workspace_root: Path) -> dict: """Report what's installed without changing anything. @@ -190,7 +289,14 @@ def check_status(workspace_root: Path) -> dict: except json.JSONDecodeError: mcp_state["error"] = "invalid JSON" - return {"skill": skill_state, "skills": skills_state, "mcp": mcp_state} + settings_path = workspace_root / ".claude" / "settings.json" + hooks_state = { + "path": str(settings_path), + "configured": hooks_configured(settings_path), + } + + return {"skill": skill_state, "skills": skills_state, + "mcp": mcp_state, "hooks": hooks_state} def check_skill_status(name: str) -> dict: diff --git a/src/canopy/agent_setup/skills/using-canopy/SKILL.md b/src/canopy/agent_setup/skills/using-canopy/SKILL.md index dcf8a5a..b19b68d 100644 --- a/src/canopy/agent_setup/skills/using-canopy/SKILL.md +++ b/src/canopy/agent_setup/skills/using-canopy/SKILL.md @@ -182,6 +182,13 @@ When `mcp__canopy__feature_state` returns state `awaiting_bot_resolution`, only - ❌ `mcp__canopy__run(repo='...', command='git commit ...')` to commit one repo at a time — use `mcp__canopy__commit(message=...)` so the whole canonical feature commits with one message and the wrong-branch / hooks-failed cases come back classified. - ❌ `mcp__canopy__run(repo='...', command='git push')` per repo — use `mcp__canopy__push()`. First push needs `set_upstream=True`; the `no_upstream` blocker tells you when (and the fix-action carries the same args + `set_upstream=True` so you can retry mechanically). +## If a git command is blocked by canopy + +A message starting with `canopy: blocked` means the enforcement hook stopped +a git mutation that targeted the wrong directory or branch. Do NOT retry the +same command or work around it with a different path. Read the reason — it +names the fix (usually `cd && ...` or `canopy switch `). + ## When canopy doesn't apply Use raw `Bash`, `Read`, `Edit` etc. as normal for: diff --git a/src/canopy/cli/main.py b/src/canopy/cli/main.py index 75c3876..041ea16 100644 --- a/src/canopy/cli/main.py +++ b/src/canopy/cli/main.py @@ -2077,6 +2077,11 @@ def cmd_setup_agent(args: argparse.Namespace) -> None: console.print(f" mcp [success]✓ configured[/] [muted]CANOPY_ROOT={root}[/]") else: console.print(f" mcp [error]✗ not configured[/] [muted]{mcp['path']}[/]") + hooks_state = status.get("hooks") or {} + if hooks_state.get("configured"): + console.print(f" hooks [success]✓ installed[/] [muted]{hooks_state.get('path', '')}[/]") + else: + console.print(f" hooks [muted]· not installed[/] [muted]{hooks_state.get('path', '')}[/]") console.print() return @@ -2101,6 +2106,27 @@ def cmd_setup_agent(args: argparse.Namespace) -> None: result = setup_agent( workspace_root, skills=skills, do_mcp=do_mcp, reinstall=args.reinstall, ) + + if args.hooks: + from ..agent_setup import install_hooks + if workspace_root is None: + # Resolve the workspace without _load_workspace()'s sys.exit — + # a missing workspace must not discard the skill/MCP result + # already computed above; report hooks as skipped instead. + from ..workspace.config import load_config + from ..workspace.workspace import Workspace + try: + workspace_root = Workspace(load_config()).config.root + except Exception: + workspace_root = None + if workspace_root is None: + result["hooks"] = { + "action": "skipped", "path": "", + "reason": "no canopy workspace found — run `canopy setup-agent --hooks` from inside one", + } + else: + result["hooks"] = install_hooks(workspace_root) + if args.json: _print_json(result) return @@ -2127,6 +2153,16 @@ def cmd_setup_agent(args: argparse.Namespace) -> None: console.print(f" mcp {glyph} [muted]{m['path']}[/]") if m.get("reason"): console.print(f" [muted]{m['reason']}[/]") + if "hooks" in result: + h = result["hooks"] + glyph = { + "added": "[success]✓ installed[/]", + "unchanged": "[muted]· already installed[/]", + "skipped": "[warning]● skipped[/]", + }.get(h["action"], h["action"]) + console.print(f" hooks {glyph} [muted]{h['path']}[/]") + if h.get("reason"): + console.print(f" [muted]{h['reason']}[/]") console.print() console.print(" [muted]Restart Claude Code (or open a new session) to pick up changes.[/]") console.print() @@ -3810,6 +3846,8 @@ def main() -> None: "Repeatable. The default 'using-canopy' skill is always installed.") setup_p.add_argument("--reinstall", action="store_true", help="Overwrite existing files even if foreign or current") + setup_p.add_argument("--hooks", action="store_true", + help="Install Claude Code enforcement hooks into /.claude/settings.json") setup_p.add_argument("--check", action="store_true", help="Report status without changing anything") setup_p.add_argument("--json", action="store_true", help="Output as JSON") diff --git a/src/canopy/hooks_entry.py b/src/canopy/hooks_entry.py new file mode 100644 index 0000000..a61f126 --- /dev/null +++ b/src/canopy/hooks_entry.py @@ -0,0 +1,42 @@ +"""Lightweight console-script entries for Claude Code hooks. + +Deliberately separate from cli/main.py: these run on EVERY Bash tool call, +so they must not import argparse/rich/the full CLI. Keep module-level +imports to stdlib-minimum; canopy modules load lazily inside the functions. +""" +from __future__ import annotations + +import json +import sys +from pathlib import Path + + +def gate_main() -> None: + """PreToolUse shim. Exit 0 = allow; exit 2 = block, reason on stderr.""" + try: + payload = json.loads(sys.stdin.read() or "{}") + except Exception: + sys.exit(0) + try: + from .actions.hook_gate import run_gate + code, message = run_gate(payload) + except Exception: + sys.exit(0) # fail open: even import errors + if message: + print(message, file=sys.stderr) + sys.exit(code) + + +def context_main() -> None: + """SessionStart shim. Prints the workspace brief to stdout (→ context).""" + try: + payload = json.loads(sys.stdin.read() or "{}") + cwd = Path(payload.get("cwd") or Path.cwd()) + from .actions.hook_gate import _load_workspace_from + from .actions.hook_context import context_brief + workspace = _load_workspace_from(cwd) + if workspace is not None: + print(context_brief(workspace)) + except Exception: + pass + sys.exit(0) diff --git a/tests/fixtures/hook_gate_corpus.jsonl b/tests/fixtures/hook_gate_corpus.jsonl new file mode 100644 index 0000000..f735aa8 --- /dev/null +++ b/tests/fixtures/hook_gate_corpus.jsonl @@ -0,0 +1,680 @@ +{"command": "git push", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["push"], "is_error": true, "ever_errored": true, "count": 80} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-ui && git branch --show-current", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": ["branch"], "is_error": false, "ever_errored": false, "count": 36} +{"command": "git commit -m \"$(cat <<'EOF'\nfix(word-addin): doc-2256 - make extraction resilient to OOXML tracked change failures\n\nThe OOXML redline path (DOC-2256) inserts tracked changes via raw XML\nwith tracking disabled. Office.js cannot resolve TrackedChange.type on\nthese synthetic revisions, causing a GeneralException that crashed\nextractParagraphsForValidation for the entire document.\n\nChanges:\n- Wrap batchLoadParagraphMetadata sync in try/catch; on TrackedChange\n failures, null out tracked changes an", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["commit"], "is_error": false, "ever_errored": true, "count": 28} +{"command": "git add apps/word-addin/src/features/document/services/document-modification-service.ts && git commit -m \"$(cat <<'EOF'\nfix(word-addin): doc-3213 - remove dead toggleTrackChanges method\n\nNo callers remain after removing the redundant pre-apply calls.\nTrack change toggling is handled inline by applyTextChange,\ndeleteParagraph, and applyViaOoxml.\nEOF\n)\"", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui/apps/word-addin", "subcommands": ["add", "commit"], "is_error": true, "ever_errored": true, "count": 26} +{"command": "git checkout DOC-3215-fix-extraction-after-ooxml-redline", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["checkout"], "is_error": false, "ever_errored": true, "count": 25} +{"command": "git status", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["status"], "is_error": false, "ever_errored": false, "count": 24} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-ui/apps/word-addin && git status", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": ["status"], "is_error": false, "ever_errored": false, "count": 24} +{"command": "git log --oneline -5", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["log"], "is_error": false, "ever_errored": true, "count": 23} +{"command": "git push -u origin DOC-2256-fix-extraction-after-ooxml-redline", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["push"], "is_error": false, "ever_errored": true, "count": 22} +{"command": "git diff --stat", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["diff"], "is_error": false, "ever_errored": true, "count": 19} +{"command": "git add apps/word-addin/src/features/document/services/document-extraction-service.ts apps/word-addin/src/features/devtools/components/DocumentProtectionInspector.tsx && git commit -m \"$(cat <<'EOF'\nfix(word-addin): doc-3213 - add allowOnlyComments to batchCheckEditability and devtools\nEOF\n)\"", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["add", "commit"], "is_error": false, "ever_errored": true, "count": 19} +{"command": "git diff", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["diff"], "is_error": false, "ever_errored": false, "count": 18} +{"command": "git branch --show-current", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["branch"], "is_error": false, "ever_errored": true, "count": 18} +{"command": "git status --short", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["status"], "is_error": false, "ever_errored": true, "count": 18} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-ui/apps/word-addin && git log --oneline -5", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["log"], "is_error": false, "ever_errored": false, "count": 16} +{"command": "git checkout -b DOC-2256-fix-extraction-after-ooxml-redline", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["checkout"], "is_error": false, "ever_errored": false, "count": 15} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-ui && git push", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": ["push"], "is_error": false, "ever_errored": true, "count": 14} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-ui && git diff --stat", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui/apps/word-addin", "subcommands": ["diff"], "is_error": false, "ever_errored": false, "count": 13} +{"command": "git add app/agents/tools/documents/read_attachment_document.py && git commit -m \"$(cat <<'EOF'\nfix(agents): address review comments on read_attachment_document\n\n- Remove silent fallback to first attachment on invalid UUID \u2014 return\n explicit error instead of reading the wrong file\n- Push chunk_start/chunk_end filters into SQL WHERE clauses so only\n requested chunks are loaded from the database\n- Use load_only() to exclude embedding/metadata columns from queries\n- Handle empty pagination range g", "cwd": "/Users/ashmitb/projects/docsum/docsum-api", "subcommands": ["add", "commit"], "is_error": false, "ever_errored": true, "count": 12} +{"command": "git commit -m \"refactor(agents): doc-3346 - delegate read_attachment_document through service layer\" -m \"Replaces direct repository access with ConversationMessageAttachmentService\ndelegation. Tools now call service methods (get_attachment_by_conversation,\nget_attachment_chunk_count, get_attachment_chunks_paginated) instead of\nreaching into repos directly. Updates DI wiring across AgentsContainer,\nAgentService, SupervisorDeps, and AppContainer to pass the service rather\nthan two separate reposit", "cwd": "/Users/ashmitb/projects/docsum/docsum-api", "subcommands": ["commit"], "is_error": true, "ever_errored": true, "count": 11} +{"command": "git add apps/word-addin/src/features/assistant/services/contract-edit-service.ts apps/word-addin/src/features/contract-review/services/contract-document-operations-service.ts apps/word-addin/src/features/document/services/document-modification-service.ts && git commit -m \"$(cat <<'EOF'\nfix(word-addin): doc-3213 - remove redundant toggleTrackChanges calls causing GeneralException\n\nThe callers (contractEditService.applyEdits, applySuggestionWithComment,\napplyBatchChanges) were calling toggleTrackC", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["add", "commit"], "is_error": false, "ever_errored": true, "count": 10} +{"command": "git log --oneline -3", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["log"], "is_error": false, "ever_errored": false, "count": 10} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-ui && git add apps/word-addin/src/features/document/services/document-modification-service.ts && git commit -m \"$(cat <<'EOF'\nfix(word-addin): doc-3213 - remove dead toggleTrackChanges method\n\nNo callers remain after removing the redundant pre-apply calls.\nTrack change toggling is handled inline by applyTextChange,\ndeleteParagraph, and applyViaOoxml.\nEOF\n)\"", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui/apps/word-addin", "subcommands": ["add", "commit"], "is_error": false, "ever_errored": true, "count": 8} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-ui && git diff", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["diff"], "is_error": false, "ever_errored": true, "count": 8} +{"command": "git push 2>&1 | tail -3", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["push"], "is_error": false, "ever_errored": false, "count": 8} +{"command": "git log --oneline dev..HEAD", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["log"], "is_error": false, "ever_errored": false, "count": 7} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-ui && git push -u origin doc-3005-add-ability-to-toggle-chat-with-view-widget", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": ["push"], "is_error": false, "ever_errored": false, "count": 7} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-ui && git status --short", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": ["status"], "is_error": false, "ever_errored": false, "count": 7} +{"command": "git push --force-with-lease", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["push"], "is_error": true, "ever_errored": true, "count": 7} +{"command": "git log --oneline -10", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["log"], "is_error": false, "ever_errored": false, "count": 6} +{"command": "git add app/conversations/v1/attachment/service.py && git commit -m \"refactor(agents): doc-3346 - delegate read_attachment_document through service layer\" -m \"Replaces direct repository access with ConversationMessageAttachmentService\ndelegation. Tools now call service methods (get_attachment_by_conversation,\nget_attachment_chunk_count, get_attachment_chunks_paginated) instead of\nreaching into repos directly. Updates DI wiring across AgentsContainer,\nAgentService, SupervisorDeps, and AppContaine", "cwd": "/Users/ashmitb/projects/docsum/docsum-api", "subcommands": ["add", "commit"], "is_error": false, "ever_errored": true, "count": 6} +{"command": "git add apps/web/src/components/Chat/InlineChat/Layout.tsx apps/web/src/hooks/useSubmitPromptToChat.ts && git commit -m \"fix(chat): doc-3005 - clear stale conversation on view change\" -m \"Layout.tsx kept a foreign conversation ID in state when switching\" -m \"views, causing the wrong conversation to show on reopen.\" -m \"Also deduplicates submitPromptToChat logic into the shared hook.\"", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["add", "commit"], "is_error": false, "ever_errored": true, "count": 6} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-ui && git checkout DOC-3028-word-addin-file-upload", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": ["checkout"], "is_error": false, "ever_errored": false, "count": 6} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-ui && git branch --show-current && git status --short", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": ["branch", "status"], "is_error": false, "ever_errored": false, "count": 5} +{"command": "git stash pop", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["stash"], "is_error": false, "ever_errored": false, "count": 5} +{"command": "git diff app/conversations/v1/service.py", "cwd": "/Users/ashmitb/projects/docsum/docsum-api", "subcommands": ["diff"], "is_error": false, "ever_errored": false, "count": 5} +{"command": "git push origin DOC-3346-word-addin-chat-ignores-uploaded-file-context", "cwd": "/Users/ashmitb/projects/docsum/docsum-api", "subcommands": ["push"], "is_error": false, "ever_errored": false, "count": 5} +{"command": "git commit -m \"fix(chat): doc-3325 - reset thinking ref between phases to prevent duplication\" -m \"When the agent does multi-turn reasoning (think \u2192 tool \u2192 think again), the currentThinkingContentRef accumulated across phases without resetting. This caused the second thinking block to contain all content from phase 1 + phase 2 concatenated.\" -m \"The fix resets the ref on each THINKING_START event so each phase only accumulates its own content.\"", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["commit"], "is_error": true, "ever_errored": true, "count": 5} +{"command": "git add apps/word-addin/src/features/assistant/pages/AssistantPage.tsx apps/word-addin/src/features/assistant/hooks/useChat.tsx && git commit --amend --no-edit", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["add", "commit"], "is_error": false, "ever_errored": true, "count": 5} +{"command": "git add apps/word-addin/src/features/assistant/hooks/useChat.tsx && git commit --amend --no-edit && git push --force-with-lease", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["add", "commit", "push"], "is_error": false, "ever_errored": true, "count": 5} +{"command": "git add app/query_engine/__init__.py app/query_engine/v1/__init__.py app/query_engine/v1/dsl_schema.py && git commit -m \"$(cat <<'EOF'\nfeat(query-engine): add DSL schema definitions\nEOF\n)\"", "cwd": "/Users/ashmitb/projects/docsum/docsum-api", "subcommands": ["add", "commit"], "is_error": true, "ever_errored": true, "count": 5} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-api && git add app/dashboards/router.py app/core/container.py && git commit -m \"feat(dashboards): add query execution endpoint\"", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["add", "commit"], "is_error": true, "ever_errored": true, "count": 5} +{"command": "git status --short | grep -v \"docs/handover\"", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["status"], "is_error": false, "ever_errored": false, "count": 5} +{"command": "git diff apps/word-addin/src/features/document/services/document-modification-service.ts", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["diff"], "is_error": false, "ever_errored": false, "count": 4} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-ui && git diff dev...HEAD --stat", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": ["diff"], "is_error": false, "ever_errored": true, "count": 4} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-ui && git checkout dev && git pull origin dev", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": ["checkout", "pull"], "is_error": false, "ever_errored": false, "count": 4} +{"command": "git log --oneline origin/DOC-3213-fix-toggle-track-changes-after-ooxml..HEAD", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui/apps/word-addin", "subcommands": ["log"], "is_error": false, "ever_errored": true, "count": 4} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-ui && git log --oneline dev..HEAD", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": ["log"], "is_error": false, "ever_errored": true, "count": 4} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-ui && git status && echo \"---\" && git branch --show-current", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": ["branch", "status"], "is_error": false, "ever_errored": false, "count": 4} +{"command": "git diff --name-only", "cwd": "/Users/ashmitb/projects/docsum/docsum-api", "subcommands": ["diff"], "is_error": false, "ever_errored": false, "count": 4} +{"command": "git diff --cached --stat", "cwd": "/Users/ashmitb/projects/docsum/docsum-api", "subcommands": ["diff"], "is_error": false, "ever_errored": false, "count": 4} +{"command": "git add tests/unit/conversations/v1/test_attachment_context_builder.py tests/unit/test_agents_supervisor.py && git commit -m \"test(chat): add unit tests for attachment context builder and force-tool-mode\" -m \"- 5 tests for _build_attachment_context branching (origin + mode combinations)\n- 2 tests for _resolve_attachment_context (Word add-in skips full context, web attempts it)\n- Fix existing test that asserted search_attachment_documents NOT registered for word-addin\"", "cwd": "/Users/ashmitb/projects/docsum/docsum-api", "subcommands": ["add", "commit"], "is_error": true, "ever_errored": true, "count": 4} +{"command": "git commit -m \"refactor(chat): doc-3005 - DRY cleanup for composite search dropdown\" -m \"Extract handleSearchKeyDownCapture helper, use truncate util,\" -m \"replace inline className concat with cn(), derive trimmedInput once,\" -m \"add KEYBOARD_SHORTCUT_CLASS_NAMES constant.\"", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["commit"], "is_error": false, "ever_errored": false, "count": 4} +{"command": "git push origin plugin/build-ui-skill", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": ["push"], "is_error": true, "ever_errored": true, "count": 4} +{"command": "git add apps/word-addin/src/features/assistant/components/MessagesList.tsx && git commit --amend --no-edit", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["add", "commit"], "is_error": false, "ever_errored": false, "count": 4} +{"command": "git -C /Users/ashmitb/projects/docsum/docsum-ui status --short", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["status"], "is_error": false, "ever_errored": false, "count": 4} +{"command": "git diff dev..HEAD -- app/conversations/v1/schemas.py", "cwd": "/Users/ashmitb/projects/docsum/docsum-api", "subcommands": ["diff"], "is_error": false, "ever_errored": false, "count": 4} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-api && git fetch origin dev && git checkout -b doc-3334-bug-file-size-incorrect-in-templates origin/dev", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["checkout", "fetch"], "is_error": false, "ever_errored": false, "count": 4} +{"command": "git rev-parse HEAD", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": [], "is_error": false, "ever_errored": true, "count": 4} +{"command": "git show 8fb2e5f13bb793f264a80dbc3bc88d89c7ca3188:apps/web/src/components/documents/Toolbar/settings/ShowFloatingChat.tsx", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": [], "is_error": false, "ever_errored": false, "count": 4} +{"command": "git add apps/word-addin/src/features/document/services/document-extraction-service.ts apps/word-addin/src/features/document/services/document-modification-service.ts apps/word-addin/src/features/document/tests/services/document-extraction-service.test.ts", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["add"], "is_error": false, "ever_errored": false, "count": 3} +{"command": "git push -u origin DOC-3213-fix-toggle-track-changes-after-ooxml 2>&1", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["push"], "is_error": false, "ever_errored": true, "count": 3} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-ui && git diff HEAD", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": ["diff"], "is_error": false, "ever_errored": false, "count": 3} +{"command": "git checkout dev && git pull origin dev", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["checkout", "pull"], "is_error": false, "ever_errored": false, "count": 3} +{"command": "git stash save \"feat: UIFreeSoloPopover extraction from TagInput\"", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["stash"], "is_error": false, "ever_errored": false, "count": 3} +{"command": "git diff --name-only --diff-filter=U", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["diff"], "is_error": false, "ever_errored": true, "count": 3} +{"command": "git fetch origin dev && git checkout -b DOC-3325-reasoning-blocks-duplicated origin/dev", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["checkout", "fetch"], "is_error": false, "ever_errored": false, "count": 3} +{"command": "git commit -m \"fix(chat): doc-3325 - reset thinking ref between phases to prevent duplication\" -m \"When the agent does multi-turn reasoning (think, tool, think\" -m \"again), currentThinkingContentRef accumulated across phases.\" -m \"The second thinking block got all content from phase 1+2.\" -m \"\" -m \"Fix: reset the ref on each THINKING_START event so each phase\" -m \"only accumulates its own content.\"", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["commit"], "is_error": false, "ever_errored": false, "count": 3} +{"command": "git status --short | head -20", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["status"], "is_error": false, "ever_errored": false, "count": 3} +{"command": "git add -A && git commit -m \"$(cat <<'EOF'\nrefactor(word-addin): doc-3028 - address code review findings\n\n- Extract FileTypeBadge component (DRY), fix hardcoded danger colors for DOC files\n- Replace all inline SVGs with @docsum/ui/icons (CancelIcon, CheckIcon)\n- Remove dead canSendMessage logic from useAttachments and consumers\n- Extract AttachmentPreview type to types/attachments, reuse across useChat/AssistantPage/Message\n- Move clearAttachments() after sendMessage() to eliminate race conditio", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["add", "commit"], "is_error": false, "ever_errored": false, "count": 3} +{"command": "git diff -- apps/word-addin/src/shared/styles/globals.css", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["diff"], "is_error": false, "ever_errored": false, "count": 3} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-api && git fetch origin main && git checkout -b doc-3478-bug-chat-non-responsive-in-menlo-production origin/main", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": ["checkout", "fetch"], "is_error": false, "ever_errored": true, "count": 3} +{"command": "git add app/dashboards/agent/__init__.py app/dashboards/agent/prompts.py && git commit -m \"$(cat <<'EOF'\nfeat(dashboards): add agent system prompt configuration\nEOF\n)\"", "cwd": "/Users/ashmitb/projects/docsum/docsum-api", "subcommands": ["add", "commit"], "is_error": false, "ever_errored": true, "count": 3} +{"command": "git push origin DOC-generative-dashboards 2>&1", "cwd": "/Users/ashmitb/projects/docsum/docsum-api", "subcommands": ["push"], "is_error": false, "ever_errored": false, "count": 3} +{"command": "git push -u origin DOC-3200-browser-parse-upload 2>&1 | tail -5", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["push"], "is_error": false, "ever_errored": true, "count": 3} +{"command": "git stash", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["stash"], "is_error": false, "ever_errored": false, "count": 3} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-api && git add tests/unit/agents/word_addin/test_validate_rule_agent.py && git commit -m \"fix(word-addin): doc-3455 - use module-level json import in test\"", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": ["add", "commit"], "is_error": true, "ever_errored": true, "count": 3} +{"command": "git add apps/word-addin/src/features/auth/services/auth-operations-service.ts apps/word-addin/src/features/feature-flags/use-feature-flags.ts && git commit -m \"fix(word-addin): doc-3447 - fix stale feature flags cache across account switches\" -m \"User-specific query key, skip localStorage cache for features, and clear cache on logout.\"", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["add", "commit"], "is_error": false, "ever_errored": true, "count": 3} +{"command": "git show dev:apps/word-addin/src/features/document/services/document-modification-service.ts 2>/dev/null | wc -l", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": [], "is_error": false, "ever_errored": false, "count": 2} +{"command": "git show dev:apps/word-addin/src/features/document/services/document-modification-service.ts 2>/dev/null", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": [], "is_error": false, "ever_errored": false, "count": 2} +{"command": "git show dev:apps/word-addin/src/features/document/services/ooxml-track-change-service.ts 2>/dev/null | head -60", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": [], "is_error": false, "ever_errored": false, "count": 2} +{"command": "git show dev:apps/word-addin/src/features/document/tests/services/ooxml-track-change-service.test.ts 2>/dev/null | head -80", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": [], "is_error": false, "ever_errored": false, "count": 2} +{"command": "git show dev:apps/word-addin/src/features/document/services/ooxml-track-change-service.ts 2>/dev/null | sed -n '95,170p'", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": [], "is_error": false, "ever_errored": false, "count": 2} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-ui && git log --oneline -3", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": ["log"], "is_error": false, "ever_errored": false, "count": 2} +{"command": "git add src/features/assistant/services/contract-edit-service.ts src/features/document/services/document-extraction-service.ts src/features/document/tests/services/document-extraction-service.test.ts && git commit -m \"$(cat <<'EOF'\nfeat(word-addin): doc-3213 - add document-level protection check to editability pre-flight\n\nCheck document.protectionType before attempting content control checks.\nDocuments with allowOnlyReading or allowOnlyFormFields mark all\nparagraphs as non-editable. Also classif", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui/apps/word-addin", "subcommands": ["add", "commit"], "is_error": false, "ever_errored": false, "count": 2} +{"command": "git add src/features/devtools/components/DocumentProtectionInspector.tsx src/features/devtools/components/index.ts src/features/devtools/components/ToolSelector.tsx src/features/devtools/pages/DevToolsPage.tsx src/features/devtools/types/devtools.ts src/features/assistant/services/contract-edit-service.ts src/features/document/services/document-extraction-service.ts && git commit -m \"$(cat <<'EOF'\nfeat(word-addin): doc-3213 - add document protection inspector devtool and debug logging\n\nAdds a \"D", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui/apps/word-addin", "subcommands": ["add", "commit"], "is_error": true, "ever_errored": true, "count": 2} +{"command": "git add src/features/document/services/document-modification-service.ts src/features/document/services/document-extraction-service.ts && git commit -m \"$(cat <<'EOF'\nfix(word-addin): doc-3213 - skip ooxml path under AllowOnlyRevisions protection\n\nWhen a document is protected for tracked changes (AllowOnlyRevisions),\nthe OOXML edit path fails because it attempts to disable tracking. Skip\nit entirely and use the text-based replacement path which works correctly\nwith trackAll mode.\n\nAlso adds allow", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui/apps/word-addin", "subcommands": ["add", "commit"], "is_error": false, "ever_errored": false, "count": 2} +{"command": "git add apps/word-addin/src/features/assistant/services/contract-edit-service.ts apps/word-addin/src/features/document/services/document-extraction-service.ts apps/word-addin/src/features/document/services/document-modification-service.ts apps/word-addin/src/features/document/services/line-by-line-suggestion-service.ts apps/word-addin/src/features/document/services/partial-text-replacement-service.ts apps/word-addin/src/features/document/services/precise-text-replacement-service.ts", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["add"], "is_error": false, "ever_errored": false, "count": 2} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-ui && git diff dev...HEAD --name-only", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": ["diff"], "is_error": false, "ever_errored": false, "count": 2} +{"command": "git log --oneline --all | grep \"deleteParagraph\\|6544ae94\"", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["log"], "is_error": false, "ever_errored": false, "count": 2} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-ui && git diff HEAD --name-only", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": ["diff"], "is_error": false, "ever_errored": false, "count": 2} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-ui && git show HEAD:apps/web/src/components/documents/ActionsToolbar/ViewDocumentsSearchInput.tsx | grep -n \"BubbleChat\\|text-primary\\|className.*primary\"", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": [], "is_error": false, "ever_errored": false, "count": 2} +{"command": "git status && echo \"---\" && git branch --show-current", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": ["branch", "status"], "is_error": true, "ever_errored": true, "count": 2} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-ui && git log --oneline --all -- apps/web/src/components/Chat/InlineChat/DocumentsChatWrapper.tsx", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": ["log"], "is_error": false, "ever_errored": false, "count": 2} +{"command": "git add apps/web/src/components/Chat/InlineChat/DocumentsChatWrapper.tsx apps/web/src/components/documents/ActionsToolbar/OpenChatButton.tsx apps/web/src/components/documents/ActionsToolbar/ViewDocumentsSearchInput.tsx apps/web/src/components/documents/Toolbar/settings/ShowFloatingChat.tsx apps/web/src/hooks/useSubmitPromptToChat.ts && git commit -m \"$(cat <<'EOF'\nfix(chat): doc-3005 - address PR review comments\n\n- Replace raw Listbox with UIDropdown for proper styling\n- Remove unnecessary store", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["add", "commit"], "is_error": false, "ever_errored": false, "count": 2} +{"command": "git log --oneline -20", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["log"], "is_error": false, "ever_errored": true, "count": 2} +{"command": "git stash list", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": ["stash"], "is_error": true, "ever_errored": true, "count": 2} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-api && git log --oneline -10", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": ["log"], "is_error": false, "ever_errored": false, "count": 2} +{"command": "git add app/agents/tools/documents/read_attachment_document.py app/agents/chat/supervisor.py app/conversations/v1/enums.py app/conversations/v1/service.py", "cwd": "/Users/ashmitb/projects/docsum/docsum-api", "subcommands": ["add"], "is_error": false, "ever_errored": false, "count": 2} +{"command": "git add tests/unit/agents/tools/documents/ && git commit -m \"$(cat <<'EOF'\ntest(agents): add unit tests for read_attachment_document tool\n\nCovers: no conversation context, invalid UUID, attachment not found,\nno chunks, happy path (all chunks), paginated subset, empty pagination\nrange (the IndexError fix), and tool registration in both WORD_ADD_IN\nand TASK origin allowlists.\nEOF\n)\"", "cwd": "/Users/ashmitb/projects/docsum/docsum-api", "subcommands": ["add", "commit"], "is_error": true, "ever_errored": true, "count": 2} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-ui && git diff --name-only", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": ["diff"], "is_error": false, "ever_errored": false, "count": 2} +{"command": "git diff dev...HEAD --name-only", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["diff"], "is_error": false, "ever_errored": false, "count": 2} +{"command": "git diff dev...HEAD", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["diff"], "is_error": false, "ever_errored": false, "count": 2} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-api && git checkout -- tests/unit/app/info_fields/extractions/test_info_field_extraction_service.py", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": ["checkout"], "is_error": false, "ever_errored": false, "count": 2} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-api && git push origin DOC-3346-word-addin-chat-ignores-uploaded-file-context", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["push"], "is_error": false, "ever_errored": false, "count": 2} +{"command": "git add apps/web/src/components/Conversations/ConversationSelector.tsx && git commit -m \"fix(chat): doc-3005 - allow view breadcrumb to shrink for dropdown space\" -m \"Changed breadcrumb from shrink-0 + fixed max-width to min-w-0\" -m \"shrink so the conversation dropdown gets layout priority.\"", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["add", "commit"], "is_error": false, "ever_errored": true, "count": 2} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-ui && git diff --stat HEAD -- apps/word-addin/src/features/assistant/", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": ["diff"], "is_error": false, "ever_errored": false, "count": 2} +{"command": "git branch | grep \"3028\"", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["branch"], "is_error": false, "ever_errored": false, "count": 2} +{"command": "git status --short | head -30", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["status"], "is_error": false, "ever_errored": false, "count": 2} +{"command": "git diff --stat 42439ab8..HEAD", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["diff"], "is_error": false, "ever_errored": false, "count": 2} +{"command": "git add apps/word-addin/package.json pnpm-lock.yaml apps/word-addin/src/features/assistant/components/upload/useFileUpload.ts apps/word-addin/src/features/assistant/components/upload/FileUploadPanel.tsx apps/word-addin/src/features/assistant/components/upload/UploadcareInlineUploader.tsx apps/word-addin/src/features/assistant/pages/AssistantPage.tsx && git commit -m \"$(cat <<'EOF'\nfeat(word-addin): doc-3028 - custom file upload dropzone panel\n\nReplace Uploadcare's FileUploaderInline widget with ", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["add", "commit"], "is_error": false, "ever_errored": false, "count": 2} +{"command": "git add -A && git commit --no-edit", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["add", "commit"], "is_error": false, "ever_errored": false, "count": 2} +{"command": "git add -A && git commit -m \"$(cat <<'EOF'\nfix(word-addin): doc-3028 - address PR review comments\n\n- Add VITE_UPLOAD_CARE_PUBLIC_KEY to turbo.json env array for cache invalidation\n- Remove unused getAttachments dead code from conversation-api-service\n- Add SSE stream abort on unmount to prevent setState on unmounted component\n- Move clearAttachments() before sendMessage() to clear UI immediately (webapp parity)\n- Add comment explaining why non-READY attachment IDs are included (backend handles w", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["add", "commit", "push"], "is_error": false, "ever_errored": true, "count": 2} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-ui && git branch --show-current && git log --oneline -3", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": ["branch", "log"], "is_error": false, "ever_errored": false, "count": 2} +{"command": "git add apps/word-addin/src/features/assistant/hooks/useChat.tsx apps/word-addin/src/features/assistant/pages/AssistantPage.tsx apps/word-addin/src/features/assistant/services/conversation-api-service.ts packages/ui/src/components/brand/AIChatInput/Composer/Composer.tsx && git commit -m \"$(cat <<'EOF'\nfeat(word-addin): doc-3246 - support web research toggle in chat\n\nWire up the existing WebResearchSwitch component from @docsum/ui\nto the Word add-in chat. The toggle adds web_research to the\nenabl", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["add", "commit"], "is_error": false, "ever_errored": false, "count": 2} +{"command": "git add apps/word-addin/src/features/assistant/pages/AssistantPage.tsx apps/word-addin/src/features/assistant/components/upload/FileTypeBadge.tsx apps/word-addin/src/features/assistant/components/upload/StagedFileRow.tsx apps/word-addin/src/features/assistant/components/upload/AttachmentBar.tsx apps/word-addin/src/features/assistant/components/MessagesList.tsx apps/word-addin/src/features/assistant/hooks/useAttachments.ts apps/word-addin/src/features/assistant/types/attachments.ts && git commit ", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["add", "commit"], "is_error": false, "ever_errored": true, "count": 2} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-ui && gh --version && gh auth status 2>&1 | head -3 && git rev-parse --is-inside-work-tree && git branch --show-current", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": ["branch"], "is_error": false, "ever_errored": false, "count": 2} +{"command": "git add apps/web/src/constants/file-uploader.ts && git commit -m \"$(cat <<'EOF'\nrefactor(web): doc-3028 - use UPLOADER_ALLOWED_SOURCES from domain\n\nReplace local hardcoded string with import from @docsum/domain/constants.\nEOF\n)\" && git push", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": ["add", "commit", "push"], "is_error": true, "ever_errored": true, "count": 2} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-ui && git add apps/word-addin/src/features/assistant/components/upload/SlideUpPanel.tsx && git commit --amend --no-edit", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": ["add", "commit"], "is_error": false, "ever_errored": true, "count": 2} +{"command": "git add apps/word-addin/src/features/assistant/components/upload/AttachmentPlusMenu.tsx apps/word-addin/src/features/assistant/pages/AssistantPage.tsx && git commit --amend --no-edit && git push --force-with-lease", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["add", "commit", "push"], "is_error": false, "ever_errored": false, "count": 2} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-ui && git add apps/word-addin/src/features/assistant/components/upload/AttachmentPlusMenu.tsx && git commit --amend --no-edit && git push --force-with-lease", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": ["add", "commit", "push"], "is_error": true, "ever_errored": true, "count": 2} +{"command": "git log -1 --stat", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["log"], "is_error": false, "ever_errored": false, "count": 2} +{"command": "git log --oneline -1", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["log"], "is_error": false, "ever_errored": false, "count": 2} +{"command": "gh --version && gh auth status 2>&1 | head -5 && git rev-parse --is-inside-work-tree && git branch --show-current", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": ["branch"], "is_error": true, "ever_errored": true, "count": 2} +{"command": "git fetch origin doc-3335-bug-flickering-on-download-template-doc && git checkout doc-3335-bug-flickering-on-download-template-doc", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["checkout", "fetch"], "is_error": false, "ever_errored": false, "count": 2} +{"command": "git clone https://github.com/wandb/openui /Users/ashmitb/projects/openui-wandb 2>&1 | tail -5", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": ["clone"], "is_error": false, "ever_errored": false, "count": 2} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-api && git status --short && echo \"---BRANCH---\" && git branch --show-current", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": ["branch", "status"], "is_error": false, "ever_errored": false, "count": 2} +{"command": "git checkout -b DOC-generative-dashboards", "cwd": "/Users/ashmitb/projects/docsum/docsum-api", "subcommands": ["checkout"], "is_error": true, "ever_errored": true, "count": 2} +{"command": "git add apps/web/src/components/layout/Sidebar/nav-sections/Workspace.tsx apps/web/src/hooks/useFeatures.ts apps/web/src/models/enums/features.ts && git commit -m \"feat(dashboards): gate sidebar nav behind generative-dashboards feature flag\" -m \"- Add GenerativeDashboards enum to AppFeatures\n- Add isGenerativeDashboardsEnabled to useFeatures hook\n- Move Dashboards nav item from static list to conditional push behind feature flag\"", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["add", "commit"], "is_error": false, "ever_errored": true, "count": 2} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-api && git log --oneline --all -- app/conversations/v1/attachment/service.py | head -20", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": ["log"], "is_error": true, "ever_errored": true, "count": 2} +{"command": "git add apps/word-addin/src/features/assistant/components/CitationChip.tsx packages/ui/src/components/design-system/Tooltip/Tooltip.tsx && git commit -m \"$(cat <<'EOF'\nfix(word-addin): doc-3267 - scope tooltip fix to CitationChip only\n\nMove z-50 and bg-background from shared UITooltip component to the\nCitationChip call site to avoid unintended design system changes.\nEOF\n)\"", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["add", "commit"], "is_error": false, "ever_errored": false, "count": 2} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-ui && git checkout -b test/convention-hook-check", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": ["checkout"], "is_error": false, "ever_errored": false, "count": 2} +{"command": "git show aaad5847 -- \"apps/word-addin/src/components/upload/FileUploadPanel.tsx\"", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": [], "is_error": false, "ever_errored": false, "count": 2} +{"command": "git diff -- \"apps/web/src/components/Chat/Content/Body/Attachments/AttachmentUploader.tsx\"", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["diff"], "is_error": false, "ever_errored": false, "count": 2} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-ui && git checkout DOC-3246-word-addin-web-research && git pull", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": ["checkout", "pull"], "is_error": false, "ever_errored": false, "count": 2} +{"command": "git add \"apps/web/src/app/api/conversations/[conversationId]/attachments/[attachmentId]/lite-preview/route.ts\" && git commit -m \"feat(chat): doc-3200 - add structured [UPLOAD-BENCH] logging to lite-preview route\"", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["add", "commit"], "is_error": true, "ever_errored": true, "count": 2} +{"command": "git add app/conversations/v1/router.py app/conversations/v1/schemas.py app/conversations/v1/service.py app/core/settings.py && git commit -m \"feat(chat): doc-3200 - add inline_attachment_context to /completion endpoint\" -m \"Adds InlineAttachmentText schema and inlineAttachmentContext field to ChatRequest.\" -m \"When ATTACHMENT_INLINE_CONTEXT_ENABLED=True and inline context is provided, the backend skips wait_until_no_processing_attachments and uses the client-provided text directly as lite_previe", "cwd": "/Users/ashmitb/projects/docsum/docsum-api", "subcommands": ["add", "commit"], "is_error": true, "ever_errored": true, "count": 2} +{"command": "git add app/conversations/v1/service.py && git commit -m \"feat(chat): doc-3200 - add inline_attachment_context to /completion endpoint\" -m \"Adds InlineAttachmentText schema and inlineAttachmentContext field to ChatRequest.\" -m \"When ATTACHMENT_INLINE_CONTEXT_ENABLED=True and inline context is provided, the backend skips wait_until_no_processing_attachments and uses the client-provided text directly as lite_preview context. This enables browser-side WASM parsing to bypass the entire server-side p", "cwd": "/Users/ashmitb/projects/docsum/docsum-api", "subcommands": ["add", "commit"], "is_error": false, "ever_errored": false, "count": 2} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-api && git diff --stat dev..HEAD", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": ["diff"], "is_error": false, "ever_errored": false, "count": 2} +{"command": "git push --force-with-lease 2>&1 | tail -3", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["push"], "is_error": false, "ever_errored": false, "count": 2} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-ui && git checkout DOC-3200-browser-parse-upload && git log --oneline -3", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": ["checkout", "log"], "is_error": false, "ever_errored": false, "count": 2} +{"command": "git add app/conversations/v1/service.py tests/integration/conversations/test_inline_attachment_context.py && git commit -m \"fix(chat): doc-3200 - validate inline IDs before poll decision, add integration tests\" -m \"Move ID validation to _initialize_chat_stream so poll is\" -m \"only skipped when at least one valid inline item exists.\" -m \"Add 4 integration tests: >5 items 400, valid inline context,\" -m \"filtered invalid IDs, and no-regression without inline.\"", "cwd": "/Users/ashmitb/projects/docsum/docsum-api", "subcommands": ["add", "commit"], "is_error": true, "ever_errored": true, "count": 2} +{"command": "git -C /Users/ashmitb/projects/docsum/docsum-ui push", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": ["push"], "is_error": false, "ever_errored": false, "count": 2} +{"command": "git add apps/word-addin/src/features/contract-review/components/AnalysisResult/AnalysisResult.tsx apps/word-addin/src/features/contract-review/components/AnalysisResult/DocumentInfoCard.tsx apps/word-addin/src/features/contract-review/hooks/useValidation.ts apps/word-addin/src/features/contract-review/types/review.ts apps/word-addin/src/features/contract-review/tests/components/DocumentInfoCard.test.tsx", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["add"], "is_error": false, "ever_errored": false, "count": 2} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-api && git stash -u && git checkout -b DOC-3455-fix-language-encoding-hotfix origin/main", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["checkout", "stash"], "is_error": false, "ever_errored": false, "count": 2} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-compound-engineering && git branch --show-current && echo \"---\" && git status --short", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": ["branch", "status"], "is_error": false, "ever_errored": false, "count": 2} +{"command": "git diff 3c8cb4bb..e0877e40 -- apps/web/src/models/api/templates.ts", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["diff"], "is_error": false, "ever_errored": false, "count": 2} +{"command": "git log --oneline 935018f..HEAD", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["log"], "is_error": false, "ever_errored": false, "count": 2} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-ui && git rm docs/designs/doc-3005-chat-visibility-mockups.html docs/handover/DOC-3315-external-file-upload.md docs/handover/okf-prototype-skill-improvement.md && git commit -m \"$(cat <<'EOF'\nchore: remove unrelated doc artifacts from DOC-3104 branch\n\nThese files were committed to this branch by mistake and belong in\ntheir own PRs.\nEOF\n)\"", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": ["commit"], "is_error": true, "ever_errored": true, "count": 2} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-api && git status && echo \"---\" && git diff --stat", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": ["diff", "status"], "is_error": false, "ever_errored": false, "count": 2} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-ui && git log --oneline --all | grep -i \"2256\\|redline\\|track\" | head -10", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": ["log"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-ui && git log --oneline dev..HEAD 2>/dev/null || git log --oneline -20", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": ["log"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git show 1b656f96 --stat | head -20", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": [], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git log --oneline --all --follow -- \"apps/word-addin/src/features/document/services/document-extraction-service.ts\" | head -10", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["log"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git diff 1b656f96^..1b656f96 -- \"apps/word-addin/src/features/document/services/document-extraction-service.ts\" 2>/dev/null | head -100", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["diff"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git show 1b656f96 --stat | grep -i \"extraction\"", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": [], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git log --all --oneline --diff-filter=M -- \"apps/word-addin/src/features/document/services/document-extraction-service.ts\" | head -5", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["log"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git log --oneline dev -5 2>/dev/null", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["log"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git show dev:apps/word-addin/src/features/document/services/document-extraction-service.ts 2>/dev/null | sed -n '307,348p'", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": [], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git show 1b656f96 --stat", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": [], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git show dev:apps/word-addin/src/features/document/services/ooxml-track-change-service.ts 2>/dev/null | grep -n \"w:ins\\|w:del\\|w:rPr\\|rsidR\\|type\" | head -30", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": [], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git show dev:apps/word-addin/src/features/document/services/ooxml-track-change-service.ts 2>/dev/null | sed -n '240,300p'", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": [], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git show dev:apps/word-addin/src/features/document/services/ooxml-track-change-service.ts 2>/dev/null | sed -n '400,490p'", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": [], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git show dev:apps/word-addin/src/features/document/services/ooxml-track-change-service.ts 2>/dev/null | grep -n \"wrapInFlatOpc\\|pkg:package\\|pkg:part\\|Types\\|Content_Type\\|Relationship\" | head -20", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": [], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git show dev:apps/word-addin/src/features/document/services/ooxml-track-change-service.ts 2>/dev/null | sed -n '330,350p'", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": [], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git show dev:apps/word-addin/src/features/document/services/ooxml-track-change-service.ts 2>/dev/null | sed -n '80,180p'", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": [], "is_error": false, "ever_errored": false, "count": 1} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-ui && git show dev:apps/word-addin/src/features/document/services/ooxml-track-change-service.ts 2>/dev/null | wc -l", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": [], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git show dev:apps/word-addin/src/features/document/services/ooxml-track-change-service.ts 2>/dev/null | sed -n '350,400p'", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": [], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git show dev:apps/word-addin/src/features/document/services/document-modification-service.ts 2>/dev/null | sed -n '92,155p'", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": [], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git show dev:apps/word-addin/src/features/contract-review/services/contract-document-operations-service.ts 2>/dev/null | grep -n \"applyTextChange\\|original_text\\|originalText\" | head -10", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": [], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git show dev:apps/word-addin/src/features/document/services/ooxml-track-change-service.ts 2>/dev/null | sed -n '170,210p'", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": [], "is_error": false, "ever_errored": false, "count": 1} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-ui && git show dev:apps/word-addin/src/features/document/services/ooxml-track-change-service.ts | sed -n '170,210p'", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": [], "is_error": false, "ever_errored": false, "count": 1} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-ui && git fetch origin dev && git checkout dev && git pull origin dev", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": ["checkout", "fetch", "pull"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git diff apps/word-addin/src/features/document/services/document-extraction-service.ts | head -80", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["diff"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git branch -m DOC-2256-fix-extraction-after-ooxml-redline DOC-3215-fix-extraction-after-ooxml-redline", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["branch"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git commit --amend -m \"$(cat <<'EOF'\nfix(word-addin): doc-3215 - make extraction resilient to OOXML tracked change failures\n\nWhen OOXML-inserted tracked changes cannot be resolved by Office.js,\nthe extraction now falls back gracefully \u2014 nulling out tracked changes\nand re-loading comments in a separate sync. This prevents one unreadable\nparagraph from crashing extraction for the entire document.\n\nAlso adds post-OOXML-apply verification warning and PostHog exception\ncapture for observability.\nEOF\n", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["commit"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git push origin --delete DOC-2256-fix-extraction-after-ooxml-redline 2>&1; git push -u origin DOC-3215-fix-extraction-after-ooxml-redline 2>&1", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["push"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git log --oneline --all | grep -i \"DOC-3213\\|toggle\\|track.change\" | head -10", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["log"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git log --oneline dev -- apps/word-addin/src/features/document/services/document-modification-service.ts | head -5", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["log"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git show dev:apps/word-addin/src/features/document/services/document-modification-service.ts | head -180", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": [], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git add apps/word-addin/src/features/document/types/document.ts apps/word-addin/src/features/document/services/document-extraction-service.ts apps/word-addin/src/features/document/services/index.ts apps/word-addin/src/features/assistant/types/contract-edit.ts apps/word-addin/src/features/assistant/types/index.ts apps/word-addin/src/features/assistant/services/contract-edit-service.ts apps/word-addin/src/features/assistant/services/selection-service.ts apps/word-addin/src/features/assistant/compo", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["add", "commit"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git diff HEAD -- src/features/assistant/services/contract-edit-service.ts | grep \"^\\+\" | grep \"!\"", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui/apps/word-addin", "subcommands": ["diff"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git diff HEAD -- src/features/assistant/services/contract-edit-service.ts", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui/apps/word-addin", "subcommands": ["diff"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-ui && git add apps/word-addin/src/features/assistant/services/contract-edit-service.ts apps/word-addin/src/features/document/services/document-modification-service.ts && git commit -m \"$(cat <<'EOF'\nchore(word-addin): doc-3213 - add TODO comments for Word.run round-trip optimization\nEOF\n)\"", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": ["add", "commit"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git diff dev...HEAD -- apps/word-addin/src/features/document/services/ apps/word-addin/src/features/assistant/services/contract-edit-service.ts apps/word-addin/src/features/assistant/services/selection-service.ts apps/word-addin/src/features/assistant/components/ToolMessage.tsx apps/word-addin/src/features/assistant/services/tool-execution-service.ts apps/word-addin/src/features/devtools/ --stat", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["diff"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git log --oneline dev..HEAD -- apps/word-addin/src/features/document/ apps/word-addin/src/features/assistant/services/contract-edit-service.ts apps/word-addin/src/features/assistant/services/selection-service.ts apps/word-addin/src/features/assistant/components/ToolMessage.tsx apps/word-addin/src/features/assistant/services/tool-execution-service.ts apps/word-addin/src/features/devtools/ apps/word-addin/src/features/contract-review/services/contract-document-operations-service.ts", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["log"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git log --oneline --all | head -15", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["log"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git add -A && git status --short", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["add", "status"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-ui && git diff HEAD -- apps/web/src/components/documents/ActionsToolbar/ViewDocumentsSearchInput.tsx packages/ui/src/components/design-system/Popover/index.ts packages/ui/src/components/design-system/TagInput/TagInput.tsx", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": ["diff"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-ui && git log --oneline dev...HEAD | head -20", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": ["log"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-ui && git diff dev...HEAD -- apps/web/src/components/documents/ActionsToolbar/ViewDocumentsSearchInput.tsx | grep -A2 -B2 \"BubbleChat\\|text-primary\"", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": ["diff"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-ui && git show HEAD:apps/web/src/components/documents/ActionsToolbar/ViewDocumentsSearchInput.tsx | grep -B5 -A15 \"handleSearchKeyDownCapture\"", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": [], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git add apps/web/src/components/Chat/InlineChat/Layout.tsx apps/web/src/components/documents/Toolbar/settings/Button.tsx apps/web/src/components/documents/Toolbar/settings/ShowFloatingChat.tsx apps/web/src/services/state/chat/atoms/state.ts && git commit -m \"$(cat <<'EOF'\nfeat(chat): doc-3005 - add toggle to hide floating chat input bar\n\nAdd a \"Show chat\" switch in the Display Settings dropdown that lets\nusers hide the floating chat input on document/view pages. Preference\npersists in localStora", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["add", "commit"], "is_error": true, "ever_errored": true, "count": 1} +{"command": "git log --oneline -1 dev -- apps/web/src/utils/browser-liteparse.ts", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["log"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git commit --no-verify -m \"$(cat <<'EOF'\nfeat(chat): doc-3005 - add toggle to hide floating chat input bar\n\nAdd a \"Show chat\" switch in the Display Settings dropdown that lets\nusers hide the floating chat input on document/view pages. Preference\npersists in localStorage.\nEOF\n)\"", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["commit"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-ui && git fetch origin dev && git log --oneline origin/dev -5", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": ["fetch", "log"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-ui && git config user.email && echo \"---global---\" && git config --global user.email", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": [], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git log -1 --format=\"%ae %ce\"", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["log"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-ui && git commit --allow-empty --no-verify -m \"chore: trigger vercel rebuild\" && git push", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": ["commit", "push"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "find /Users/ashmitb/projects/docsum/docsum-ui/apps/web/src -name \"ShowFloatingChat*\" 2>/dev/null; git -C /Users/ashmitb/projects/docsum/docsum-ui branch --show-current", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["branch"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git add apps/web/src/components/documents/Toolbar/settings/ShowFloatingChat.tsx && git commit -m \"$(cat <<'EOF'\nfix(chat): doc-3005 - gate toggle on isViewerOrItAdmin\n\nHide the \"Show floating chat input\" toggle for viewer/IT-admin roles\nsince the floating bar is never rendered for them.\nEOF\n)\"", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["add", "commit"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-ui && git checkout doc-3005-add-ability-to-toggle-chat-with-view-widget && git checkout -b doc-3005-composite-search-ask-ai", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": ["checkout"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git add apps/web/src/hooks/useSubmitPromptToChat.ts apps/web/src/hooks/index.ts apps/web/src/components/documents/ActionsToolbar/ViewDocumentsSearchInput.tsx apps/web/src/components/documents/ActionsToolbar/OpenChatButton.tsx apps/web/src/components/documents/ActionsToolbar/ActionsToolbar.tsx && git commit -m \"$(cat <<'EOF'\nfeat(chat): doc-3005 - composite search bar with Ask AI + chat button\n\nWhen the floating chat bar is hidden:\n- Search bar placeholder changes to \"Search or ask AI ...\"\n- Typi", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["add", "commit"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-ui && git stash && git checkout doc-3005-composite-search-ask-ai", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": ["checkout", "stash"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git log --format=\"%H %s\" f3ff2df38 -1", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["log"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "gh pr list --state merged --search \"f3ff2df38\" --json number,title,url 2>/dev/null; echo \"---\"; git log --format=\"%H %s %b\" f3ff2df38 -1 | grep -i \"pr\\|pull\\|#\"", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["log"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-ui && git log --format=\"%H %ai %s\" --diff-filter=A -- apps/web/src/components/Chat/InlineChat/DocumentsChatWrapper.tsx", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": ["log"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git status && echo \"---\" && git log --oneline -5", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["log", "status"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-ui && git diff HEAD -- apps/web/src/components/documents/ActionsToolbar/ViewDocumentsSearchInput.tsx | head -20", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": ["diff"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git checkout HEAD -- apps/web/src/components/documents/ActionsToolbar/ViewDocumentsSearchInput.tsx", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["checkout"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git add apps/web/src/app/\\(protected\\)/\\(web\\)/\\(main\\)/\\(pages\\)/view/\\[slug\\]/layout.tsx apps/web/src/components/Chat/InlineChat/DocumentsChatWrapper.tsx apps/web/src/components/Chat/InlineChat/ViewChatWrapper.tsx apps/web/src/components/Chat/InlineChat/__tests__/DocumentsChatWrapper.test.tsx apps/web/src/components/Chat/InlineChat/__tests__/ViewChatWrapper.test.tsx apps/web/src/components/Chat/index.ts && git commit -m \"$(cat <<'EOF'\nrefactor(chat): doc-3005 - consolidate ViewChatWrapper into", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["add", "commit"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git checkout HEAD -- packages/ui/src/components/design-system/Popover/Popover.tsx", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["checkout"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-ui && git stash", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": ["stash"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git show 9dfb99243 --stat", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": [], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git show 9dfb99243 -- apps/web/src/components/Conversations/ConversationSelector.tsx", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": [], "is_error": false, "ever_errored": false, "count": 1} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-ui && git log --oneline -20", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": ["log"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git add apps/web/src/components/Chat/Content/Header.tsx apps/web/src/components/Conversations/ConversationSelector.tsx apps/web/src/components/documents/ActionsToolbar/ViewDocumentsSearchInput.tsx packages/ui/src/components/brand/ChatMessages/AssistantMessage.tsx packages/ui/src/components/design-system/Popover/index.ts packages/ui/src/components/design-system/Popover/FreeSoloPopover.tsx packages/ui/src/components/design-system/TagInput/TagInput.tsx && git commit -m \"$(cat <<'EOF'\nfeat(chat): do", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["add", "commit"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git fetch origin dev && git merge origin/dev --no-commit --no-ff 2>&1 | head -50", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": ["fetch", "merge"], "is_error": true, "ever_errored": true, "count": 1} +{"command": "ls /Users/ashmitb/projects/docsum/docsum-ui/.git && cd /Users/ashmitb/projects/docsum/docsum-ui && git fetch origin dev && git merge origin/dev --no-commit --no-ff 2>&1 | head -50", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": ["fetch", "merge"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "find /Users/ashmitb/projects/docsum -type f \\( -name \"*.md\" -o -name \"ISSUES*\" -o -name \"TODO*\" \\) ! -path \"*/node_modules/*\" ! -path \"*/.git/*\" | head -20", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": [], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git -C /Users/ashmitb/projects/docsum log --all --oneline --grep=\"paragraph\" --grep=\"uniqueLocalId\" --grep=\"null\\|null\" --grep=\"Safari\\|WebView\\|Mac\" --since=\"2024-01-01\" 2>/dev/null | head -20", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": ["log"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-api && git show dev:app/conversations/v1/schemas.py | sed -n '295,310p'", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": [], "is_error": false, "ever_errored": false, "count": 1} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-api && git log --oneline --all --grep=\"DOC-3346\" | head -10", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["log"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git show 0d6db8a63 --stat && echo \"---\" && git show 0d6db8a63 --no-stat", "cwd": "/Users/ashmitb/projects/docsum/docsum-api", "subcommands": [], "is_error": true, "ever_errored": true, "count": 1} +{"command": "git log --oneline --all --grep=\"DOC-3346\" -- . | head -5 && echo \"---UI---\" && cd /Users/ashmitb/projects/docsum/docsum-ui && git log --oneline --all --grep=\"DOC-3346\" | head -5", "cwd": "/Users/ashmitb/projects/docsum/docsum-api", "subcommands": ["log"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-api && git show 0d6db8a63 --format=\"\" -- app/conversations/v1/service.py | head -100", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": [], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git show 0d6db8a63 --format=\"\" -- app/conversations/v1/service.py | tail -80", "cwd": "/Users/ashmitb/projects/docsum/docsum-api", "subcommands": [], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git status -u", "cwd": "/Users/ashmitb/projects/docsum/docsum-api", "subcommands": ["status"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git checkout -b feat/doc-3346-read-attachment-tool", "cwd": "/Users/ashmitb/projects/docsum/docsum-api", "subcommands": ["checkout"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git commit -m \"$(cat <<'EOF'\nfeat(agents): doc-3346 - add read_attachment_document tool for full-text file access\n\nThe chat supervisor only had semantic search (search_attachment_documents)\nfor uploaded files, which fails when the agent needs complete content\n(e.g. \"redline based on this analysis file\"). This adds a\nread_attachment_document tool that reads the full text of a conversation\nattachment with optional chunk pagination.\n\n- New tool: read_attachment_document (queries conversation_attach", "cwd": "/Users/ashmitb/projects/docsum/docsum-api", "subcommands": ["commit"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git push -u origin feat/doc-3346-read-attachment-tool", "cwd": "/Users/ashmitb/projects/docsum/docsum-api", "subcommands": ["push"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git add app/agents/tools/documents/read_attachment_document.py app/agents/chat/dependencies.py app/agents/service.py app/agents/container.py app/conversations/v1/attachment/chunk_repository.py app/conversations/v1/attachment/repository.py app/core/container.py tests/unit/agents/tools/documents/test_read_attachment_document.py && git status", "cwd": "/Users/ashmitb/projects/docsum/docsum-api", "subcommands": ["add", "status"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git add app/agents/chat/dependencies.py app/agents/container.py app/agents/service.py app/agents/tools/documents/read_attachment_document.py app/conversations/v1/attachment/service.py app/conversations/v1/container.py app/core/container.py tests/unit/agents/tools/documents/test_read_attachment_document.py", "cwd": "/Users/ashmitb/projects/docsum/docsum-api", "subcommands": ["add"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git ls-files --deleted", "cwd": "/Users/ashmitb/projects/docsum/docsum-api", "subcommands": [], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git ls-files app/agents/tools/documents/read_attachment_document.py", "cwd": "/Users/ashmitb/projects/docsum/docsum-api", "subcommands": [], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git add app/agents/tools/documents/read_attachment_document.py app/agents/tools/documents/read_attachment_document/", "cwd": "/Users/ashmitb/projects/docsum/docsum-api", "subcommands": ["add"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git rm --cached app/agents/tools/documents/read_attachment_document.py 2>&1", "cwd": "/Users/ashmitb/projects/docsum/docsum-api", "subcommands": [], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git checkout feat/doc-3346-read-attachment-tool", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": ["checkout"], "is_error": true, "ever_errored": true, "count": 1} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-api && git checkout feat/doc-3346-read-attachment-tool", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": ["checkout"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git pull", "cwd": "/Users/ashmitb/projects/docsum/docsum-api", "subcommands": ["pull"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git add app/conversations/v1/attachment/service.py app/agents/tools/documents/read_attachment_document/formatter.py && git commit -m \"refactor(agents): doc-3346 - make chunk_repository required, type formatter with Protocol\" -m \"conversation_attachment_chunk_repository is always wired via the container\nFactory \u2014 removes the | None and silent 0/[] degradation guards.\nReplaces list[Any] in ReadAttachmentFormatter with Sequence[ChunkProjection]\nProtocol for mypy-safe typed projection.\"", "cwd": "/Users/ashmitb/projects/docsum/docsum-api", "subcommands": ["add", "commit"], "is_error": true, "ever_errored": true, "count": 1} +{"command": "git add app/conversations/v1/attachment/service.py app/agents/tools/documents/read_attachment_document/formatter.py tests/unit/conversations/v1/attachment/test_upload_attachments.py tests/unit/conversations/v1/attachment/test_attachment_service.py tests/integration/documents/editing/test_chat_attachment_resolver.py && git commit -m \"refactor(agents): doc-3346 - make chunk_repository required, type formatter with Protocol\" -m \"conversation_attachment_chunk_repository is always wired via the conta", "cwd": "/Users/ashmitb/projects/docsum/docsum-api", "subcommands": ["add", "commit"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-api && git diff tests/unit/app/info_fields/extractions/test_info_field_extraction_service.py", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": ["diff"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git diff dev...HEAD -- apps/web/src/components/Chat/Content/Header.tsx apps/web/src/components/Chat/Content/Toolbar/Toolbar.tsx", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["diff"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git log --oneline dev..HEAD -- apps/web/src/components/Chat/Content/Header.tsx apps/web/src/components/Chat/Content/Toolbar/Toolbar.tsx", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["log"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git diff dev -- apps/web/src/components/Chat/Content/Header.tsx apps/web/src/components/Chat/Content/Toolbar/Toolbar.tsx", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["diff"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git diff dev -- apps/web/src/components/Conversations/ConversationSelector.tsx", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["diff"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-api && git stash && git checkout dev && git pull origin dev", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": ["checkout", "pull", "stash"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git status && git log --oneline -5", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": ["log", "status"], "is_error": true, "ever_errored": true, "count": 1} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-api && git diff app/agents/chat/supervisor.py app/conversations/v1/schemas.py app/conversations/v1/service.py", "cwd": "/Users/ashmitb/projects/docsum/docsum-api", "subcommands": ["diff"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git add app/agents/chat/supervisor.py app/conversations/v1/schemas.py app/conversations/v1/service.py && git commit -m \"fix(chat): DOC-3346 - force tool mode for word-addin file attachments\" -m \"Word add-in chat ignored uploaded file context because inline full-context got buried after get_all_paragraphs. Forces tool mode so attachment content arrives as a fresh tool response, adds system prompt reminder with filenames.\"", "cwd": "/Users/ashmitb/projects/docsum/docsum-api", "subcommands": ["add", "commit"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-ui && git checkout -b DOC-3346-word-addin-chat-attachment-fixes", "cwd": "/Users/ashmitb/projects/docsum/docsum-api", "subcommands": ["checkout"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git add apps/word-addin/src/features/assistant/components/upload/FileUploadPanel.tsx apps/word-addin/src/features/assistant/hooks/useChat.tsx apps/word-addin/src/features/assistant/pages/AssistantPage.tsx apps/word-addin/src/features/assistant/services/conversation-api-service.ts apps/web/src/hooks/useScrollToBottom.ts packages/domain/src/models/message.ts packages/ui/package.json packages/ui/src/components/brand/ChatMessages/MessagesList.tsx packages/ui/src/hooks/index.ts packages/ui/src/hooks/", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["add", "commit"], "is_error": true, "ever_errored": true, "count": 1} +{"command": "git stash push -m \"doc-3005 WIP changes\" -- apps/web/src/components/documents/ActionsToolbar/ViewDocumentsSearchInput.tsx", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["stash"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-ui && git add apps/word-addin/src/features/assistant/hooks/useAttachments.ts apps/word-addin/src/features/assistant/hooks/useChat.tsx apps/word-addin/src/features/assistant/pages/AssistantPage.tsx apps/word-addin/src/features/assistant/services/conversation-api-service.ts && git commit -m \"fix(word-addin): address PR review feedback\" -m \"- Keep upload panel open on failed upload (rethrow from uploadFiles)\n- Use AttachmentPreview type instead of inline lit", "cwd": "/Users/ashmitb/projects/docsum/docsum-api", "subcommands": ["add", "commit"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-api && git add app/conversations/v1/service.py && git commit -m \"fix(chat): use origin-neutral attachment prompt for non-Word origins\" -m \"The full-mode branch referenced Word-specific concepts (get_all_paragraphs,\nactive Word document) for all origins. Now only Word add-in gets that wording.\"", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["add", "commit"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-ui && git stash list", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": ["stash"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git checkout doc-3005-composite-search-ask-ai 2>&1", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["checkout"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git stash pop stash@{0}", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["stash"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git add apps/web/src/components/documents/ActionsToolbar/ViewDocumentsSearchInput.tsx && git commit -m \"refactor(chat): doc-3005 - DRY cleanup for composite search dropdown\" -m \"Extract handleSearchKeyDownCapture helper, use truncate util, replace inline className concat with cn(), add KEYBOARD_SHORTCUT_CLASS_NAMES constant, derive trimmedInput once.\"", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["add", "commit"], "is_error": true, "ever_errored": true, "count": 1} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-ui && git log --oneline -5 -- apps/web/src/components/Conversations/ConversationSelector.tsx", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": ["log"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git diff 9dfb9924..6ca4e7c1 -- apps/web/src/components/Conversations/ConversationSelector.tsx", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["diff"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git show 9dfb9924 -- apps/web/src/components/Conversations/ConversationSelector.tsx | grep -A20 \"return (\"", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": [], "is_error": false, "ever_errored": false, "count": 1} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-ui && git commit --allow-empty -m \"chore: trigger vercel build\"", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": ["commit"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-ui && git status && git branch --show-current", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": ["branch", "status"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git branch -a | grep 3325", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["branch"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git log --oneline DOC-3325-reasoning-blocks-duplicated -3", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["log"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git checkout DOC-3325-reasoning-blocks-duplicated && git cherry-pick 0900ff07", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["checkout"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git push origin DOC-3325-reasoning-blocks-duplicated 2>&1", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["push"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git checkout DOC-3200-browser-parse-upload && git reset --hard HEAD~1", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["checkout", "reset"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-compound-engineering && git fetch origin && git status", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": ["fetch", "status"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git merge --no-commit --no-ff origin/main 2>&1; git diff --name-only --diff-filter=U 2>/dev/null; git merge --abort 2>/dev/null", "cwd": "/Users/ashmitb/projects/docsum/docsum-compound-engineering", "subcommands": ["diff", "merge"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git diff --name-status origin/main..HEAD", "cwd": "/Users/ashmitb/projects/docsum/docsum-compound-engineering", "subcommands": ["diff"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git log --oneline -10 origin/main", "cwd": "/Users/ashmitb/projects/docsum/docsum-compound-engineering", "subcommands": ["log"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git show origin/main:.claude-plugin/plugin.json", "cwd": "/Users/ashmitb/projects/docsum/docsum-compound-engineering", "subcommands": [], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git show origin/main:CHANGELOG.md | head -30", "cwd": "/Users/ashmitb/projects/docsum/docsum-compound-engineering", "subcommands": [], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git show origin/main:README.md | head -80", "cwd": "/Users/ashmitb/projects/docsum/docsum-compound-engineering", "subcommands": [], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git merge origin/main --no-edit 2>&1 || true", "cwd": "/Users/ashmitb/projects/docsum/docsum-compound-engineering", "subcommands": ["merge"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git add .claude-plugin/plugin.json CHANGELOG.md README.md && git status", "cwd": "/Users/ashmitb/projects/docsum/docsum-compound-engineering", "subcommands": ["add", "status"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git -C /Users/ashmitb/projects/docsum/docsum-compound-engineering push origin plugin/build-ui-skill", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": ["push"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-ui && git diff fbb8450a..HEAD -- apps/word-addin/src/features/assistant/ packages/domain/src/", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": ["diff"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git checkout -b plugin/build-ui-skill", "cwd": "/Users/ashmitb/projects/docsum/docsum-compound-engineering", "subcommands": ["checkout"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git add skills/build-ui/ .claude-plugin/plugin.json CHANGELOG.md", "cwd": "/Users/ashmitb/projects/docsum/docsum-compound-engineering", "subcommands": ["add"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git push -u origin plugin/build-ui-skill", "cwd": "/Users/ashmitb/projects/docsum/docsum-compound-engineering", "subcommands": ["push"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git add skills/build-ui-feature/ skills/prototype-ui-mockup/ .claude-plugin/plugin.json CHANGELOG.md && git rm -r skills/build-ui/ 2>/dev/null; git status", "cwd": "/Users/ashmitb/projects/docsum/docsum-compound-engineering", "subcommands": ["add", "status"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git stash push -m \"staged files from doc-2226 branch\" && git checkout dev && git pull origin dev", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["checkout", "pull", "stash"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git add apps/word-addin/src/features/assistant/components/MessagesList.tsx apps/word-addin/src/features/assistant/components/upload/AttachmentBar.tsx apps/word-addin/src/features/assistant/components/upload/FileUploadPanel.tsx apps/word-addin/src/features/assistant/components/upload/RepositoryPickerPanel.tsx apps/word-addin/src/features/assistant/components/upload/SlideUpPanel.tsx apps/word-addin/src/features/assistant/hooks/useAttachments.ts apps/word-addin/src/features/assistant/hooks/useChat.", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["add"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git commit -m \"$(cat <<'EOF'\nfix(word-addin): doc-3028 - wire attachments to completion API and align with webapp conventions\n\n- Fix camelCase payload keys (documentIds, linkedAttachmentIds, enabledTools, etc.) matching webapp\n- Separate repository document IDs (documentIds) from file upload IDs (linkedAttachmentIds)\n- Add attachment previews to user messages so files appear in chat bubbles after send\n- Clear attachments from input bar on send\n- Capture attachment refs before async operations to", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["commit"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git add apps/word-addin/src/features/assistant/components/MessagesList.tsx apps/word-addin/src/features/assistant/components/upload/FileUploadPanel.tsx apps/word-addin/src/features/assistant/components/upload/useFileUpload.ts apps/word-addin/src/features/assistant/constants/attachments.ts apps/word-addin/src/features/assistant/hooks/useAttachments.ts apps/word-addin/src/features/assistant/hooks/useChat.tsx apps/word-addin/src/features/assistant/hooks/useFileUpload.ts apps/word-addin/src/features", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["add"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git log --oneline -4", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["log"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "cd /Users/ashmitb/projects/docsum/docsum-ui && git fetch origin dev && git merge origin/dev --no-edit 2>&1 | tail -30", "cwd": "/Users/ashmitb/projects/docsum", "subcommands": ["fetch", "merge"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git diff --name-only origin/dev...HEAD | xargs grep -l \"dangerouslySetInnerHTML\" 2>/dev/null", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["diff"], "is_error": true, "ever_errored": true, "count": 1} +{"command": "git diff origin/dev...HEAD | grep -i \"dangerously\" | head -5", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["diff"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git commit -m \"$(cat <<'EOF'\nfix(word-addin): doc-3028 - fix external source cancel and timing docs\n\n- Reset activeSource on cancel/failed states so external source\n dialogs can be re-triggered\n- Add comment documenting why clearAttachments before sendMessage\n is safe (IDs captured synchronously)\nEOF\n)\" && git push", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["commit", "push"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git log --oneline -3 --format=\"%s\"", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["log"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git diff packages/ui/src/components/brand/AIChatInput/Composer/Composer.tsx", "cwd": "/Users/ashmitb/projects/docsum/docsum-ui", "subcommands": ["diff"], "is_error": false, "ever_errored": false, "count": 1} +{"command": "git add apps/word-addin/src/features/assistant/components/upload/ && git commit -m \"$(cat <<'EOF'\nrefactor(word-addin): doc-3246 - replace raw HTML with UI component declarations\n\nReplace raw