From 5dcea36f9785192c4c1614632935184ca9e18be5 Mon Sep 17 00:00:00 2001 From: Juan Barbat Date: Sat, 4 Jul 2026 15:03:50 -0300 Subject: [PATCH] chore: remove product code and documentation from dotfiles repo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This repo is for dotfiles only (config files, install script, shell aliases). Removed product code and documentation that belonged in cortex: **Deleted files:** - docs/ — all docs (agent-state spec, workflow, keymaps, release checklist) - ghostty/muxy.conf — external integration config - nvim/README.md — nvim config doc (config not versioned here) - scripts/check-agent-state.sh — product validator - zsh/scripts/agent-state.sh — product API component (300 lines) - zsh/scripts/claude-helpers.zsh — Claude Code launchers - zsh/scripts/git-helpers.zsh — git identity tools - zsh/scripts/herdr-helpers.zsh — session helpers - zsh/scripts/memsave-nudge.sh — brain hook - zsh/scripts/postcompact-hook.sh — brain hook - zsh/scripts/worktree-helpers.zsh — worktree management **Cleaned up:** - CLAUDE.md — removed product references, simplified to dotfiles structure - CONTRIBUTING.md — removed supply chain security section - CHANGELOG.md — removed references to deleted files - README.md — removed docs section and product references - zsh/zshrc — removed sourcing of deleted scripts --- CHANGELOG.md | 16 +- CLAUDE.md | 85 +++--- CONTRIBUTING.md | 23 +- README.md | 10 +- docs/agent-state-v1.md | 240 ---------------- docs/herdr-workflow.md | 92 ------- docs/keymaps.md | 59 ---- docs/release-checklist.md | 13 - ghostty/muxy.conf | 40 --- nvim/README.md | 97 ------- scripts/check-agent-state.sh | 75 ----- zsh/scripts/agent-state.sh | 301 -------------------- zsh/scripts/claude-helpers.zsh | 454 ------------------------------- zsh/scripts/git-helpers.zsh | 85 ------ zsh/scripts/herdr-helpers.zsh | 183 ------------- zsh/scripts/memsave-nudge.sh | 38 --- zsh/scripts/postcompact-hook.sh | 27 -- zsh/scripts/worktree-helpers.zsh | 129 --------- zsh/zshrc | 8 - 19 files changed, 39 insertions(+), 1936 deletions(-) delete mode 100644 docs/agent-state-v1.md delete mode 100644 docs/herdr-workflow.md delete mode 100644 docs/keymaps.md delete mode 100644 docs/release-checklist.md delete mode 100644 ghostty/muxy.conf delete mode 100644 nvim/README.md delete mode 100755 scripts/check-agent-state.sh delete mode 100755 zsh/scripts/agent-state.sh delete mode 100644 zsh/scripts/claude-helpers.zsh delete mode 100644 zsh/scripts/git-helpers.zsh delete mode 100644 zsh/scripts/herdr-helpers.zsh delete mode 100755 zsh/scripts/memsave-nudge.sh delete mode 100755 zsh/scripts/postcompact-hook.sh delete mode 100644 zsh/scripts/worktree-helpers.zsh diff --git a/CHANGELOG.md b/CHANGELOG.md index 5fa8227..834dc7d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,20 +5,10 @@ - Initial OSS-safe `cortex-dots` snapshot. - Generalized private paths and navigation defaults. - Removed custom font binary and private brand assets. -- Added MIT license, security policy, third-party notices, release checklist, and automated installer tests. +- Added MIT license, security policy, third-party notices, and automated installer tests. ## 0.1.1 - 2026-07-04 -### Breaking - -- Renamed `pcsoft-helpers.zsh` → `pc-helpers.zsh` (generic name for broader reuse). - -### Added - -- `scripts/test-install.sh` — automated installer tests (copy, symlink, stale-opposite scenarios). -- `scripts/check-agent-state.sh` — agent-state validator for release checklist. -- Supply chain security rules — external tools must be pinned to specific versions, 15+ days old. - -### Changed - +- Renamed `pcsoft-helpers.zsh` → `pc-helpers.zsh`. +- Added `scripts/test-install.sh` — automated installer tests. - CI — added installer integration tests step. diff --git a/CLAUDE.md b/CLAUDE.md index 8e2e003..b6637af 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2,88 +2,71 @@ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. -## Repositorio +## Repository -Dotfiles para terminal: configuración de Ghostty + Zsh + Starship. El instalador crea symlinks desde el directorio del repo hacia las ubicaciones estándar del sistema. +Terminal dotfiles: configuration for Ghostty + Zsh + Starship. The installer creates symlinks from the repo directory to standard system locations. -## Instalación y testing +## Installation and testing ```bash -# Instalar dotfiles (crea symlinks + instala dependencias) +# Install dotfiles (creates symlinks + installs local dependencies) ./install.sh -# Medir load time del profile zsh +# Measure zsh profile load time time zsh -i -c exit -# Verificar que los symlinks están correctos +# Verify symlinks are correct ls -la ~/.zshrc ~/.config/starship.toml ~/.config/ghostty/config -# Validar sintaxis sin instalar +# Validate syntax without installing bash install.sh --check ``` -No hay tests automatizados. La validación es manual: recargar la shell con `source ~/.zshrc` o `reload` y verificar que los comandos funcionen. +Manually validate by reloading the shell with `source ~/.zshrc` or `reload` and checking that commands work. -## Estructura y arquitectura +## Directory structure ``` dotfiles/ ├── zsh/ -│ ├── zshrc # Profile principal → symlink a ~/.zshrc -│ └── scripts/ # Scripts sourced desde zshrc -│ ├── agent-state.sh # Local CLI para cortex.agent_state.v1 -│ ├── claude-helpers.zsh # cc, ccb, ccx, ccd, ccclip -│ ├── git-helpers.zsh # git-workdev, git-personaldev, git-whoami, clone-* -│ ├── herdr-helpers.zsh # hhere, hfocus, hside, hscratch, hremote, hname, whereami -│ ├── pc-helpers.zsh # is-pc-forbidden, is-pc-editable -│ ├── screenshots.zsh # ss, last, ssd, imgclip -│ ├── ssh-helpers.zsh # sshx, moshx helpers -│ ├── worktree-helpers.zsh # wtadd, wtlist, wtremove -│ ├── memsave-nudge.sh # Hook UserPromptSubmit para recordar mem_save -│ └── postcompact-hook.sh # Hook PostCompact con orden explícito +│ ├── zshrc # Main profile → symlinked to ~/.zshrc +│ └── scripts/ # Scripts sourced from zshrc +│ ├── pc-helpers.zsh # is-pc-forbidden, is-pc-editable (PC file detection) +│ ├── screenshots.zsh # ss, last, ssd, imgclip (screenshot helpers) +│ └── ssh-helpers.zsh # sshx, moshx helpers (SSH/remote shortcuts) ├── starship/starship.toml # Prompt → ~/.config/starship.toml ├── ghostty/ -│ ├── config # Terminal → ~/.config/ghostty/config -│ ├── muxy.conf # Muxy integration config -│ └── shaders/ # 4 cursor shaders (glsl) -├── herdr/config.toml # Multiplexor → ~/.config/herdr/config.toml +│ ├── config # Terminal config → ~/.config/ghostty/config +│ └── shaders/ # Cursor shaders (4 glsl files) +├── herdr/config.toml # Multiplexer → ~/.config/herdr/config.toml ├── lazygit/config.yml # LazyGit → ~/.config/lazygit/config.yml -├── local/ -│ └── env.zsh.example # Template para ~/.config/cortex-dots/local/env.zsh ├── fonts/ │ └── FiraCodeNerdFontMonoBeard-Reg.ttf # Terminal font -├── scripts/ -│ ├── oss-audit.sh # OSS safety audit -│ ├── test-install.sh # Installation tests -│ └── check-agent-state.sh # Agent state validator +├── local/ +│ └── env.zsh.example # Template for ~/.config/cortex-dots/local/env.zsh ├── bun/bunfig.toml # Bun defaults ├── npm/npmrc # NPM defaults ├── pnpm/rc # PNPM defaults ├── uv/uv.toml # UV defaults -└── install.sh # Instalador +├── scripts/ +│ ├── test-install.sh # Installation tests +│ └── oss-audit.sh # OSS safety audit +└── install.sh # Installer ``` -**Flujo de carga del zshrc:** Las secciones están separadas por `#region`/`#endregion`. El orden importa: Brew → PATH → Zsh options → Editor → Env vars → Aliases → Source scripts → Local overrides → Starship → Welcome. Scripts en `zsh/scripts/` que son hooks de Claude Code (memsave-nudge.sh, postcompact-hook.sh) no se sourced desde zshrc, se usan directamente como hooks en la config de Claude Code. - -**`~/.config/cortex-dots/local/env.zsh`** contiene paths personales, tokens y overrides de variables de entorno (`SCREENSHOTS_DIR`, `WORKSPACE_DIR`, `WORK_PROJECTS_DIR`, `CORTEX_HOME`, `CORTEX_ROOT`, `CORTEX_CONFIG_HOME`, `CORTEX_MULTIPLEXER`). Se genera desde `env.zsh.example` en la primera instalación. - -## Múltiples identidades GitHub - -El repositorio maneja dos cuentas de GitHub con SSH host aliases: -- `github-workdev` → cuenta de trabajo/empresa (configurar con tu email de empresa) -- `github-personaldev` → cuenta personal (configurar con tu email personal) +**zshrc loading order:** Sections are split by `#region`/`#endregion`. The order matters: Setup → PATH → zsh options → Editor → Env vars → Aliases → Source scripts → Local overrides → Starship → Welcome. -Los helpers en `git-helpers.zsh` configuran la identidad local del repo Y actualizan el remote URL para usar el alias SSH correcto. Cuando se agregan nuevas funciones relacionadas con identidades, seguir el mismo patrón. +**`~/.config/cortex-dots/local/env.zsh`** contains personal paths, tokens and environment variable overrides (`SCREENSHOTS_DIR`, `WORKSPACE_DIR`, `WORK_PROJECTS_DIR`, `CORTEX_HOME`, `CORTEX_ROOT`, `CORTEX_CONFIG_HOME`, `CORTEX_MULTIPLEXER`). Generated from `env.zsh.example` on first install. -## Convenciones al editar +## Editing conventions -- Los scripts `.zsh` usan `#region`/`#endregion` para agrupar secciones lógicas. -- Cada función pública en los scripts debe tener un comentario explicando su uso. -- Variables de entorno con defaults se definen como `${VAR:-default}` para permitir override desde `~/.config/cortex-dots/local/env.zsh`. -- `install.sh` usa `set -e` y hace backup de configs existentes antes de copiar o linkear configs — mantener este comportamiento al agregar nuevas configs. +- `.zsh` scripts use `#region`/`#endregion` for logical groups. +- Each public function should have a comment explaining its use. +- Environment variables with defaults use `${VAR:-default}` for overrides from `~/.config/cortex-dots/local/env.zsh`. +- `install.sh` uses `set -e` and backs up existing configs before copying or symlinking — maintain this pattern when adding new configs. -## Agregar una nueva configuración +## Adding a new config -1. Crear el archivo de config en su directorio (`nombre-herramienta/config`) -2. Agregar backup + symlink en `install.sh` siguiendo el patrón existente -3. Actualizar el README.md con la nueva entrada en la tabla de estructura +1. Create the config file in its directory (`name/config`) +2. Add backup + symlink in `install.sh` following the existing pattern +3. Update the README.md with the new entry in the structure table diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3e58b5e..a673dd4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ # Contributing -Thanks for helping improve `cortex-dots`. This repository is public, but changes are intentionally gated because these dotfiles affect local shells, developer tooling, package-manager defaults, and AI CLI behavior. +Thanks for helping improve `cortex-dots`. This repository is public, but changes are intentionally gated because these dotfiles affect local shells, developer tooling, and package-manager defaults. ## Contribution Flow @@ -19,7 +19,6 @@ All changes to `main` require maintainer review. This is especially important fo - Shell startup files and helper scripts. - Installer behavior. - Package-manager configuration. -- AI CLI configuration or permission flags. - GitHub Actions and repository governance files. - Any file that could expose private paths, hostnames, emails, tokens, or machine-specific data. @@ -30,26 +29,6 @@ All changes to `main` require maintainer review. This is especially important fo - Keep examples generic and placeholder-based. - Run the validation commands listed in `README.md` when touching scripts or config formats. -## Supply Chain Security - -Any external tool, CLI, or dependency installed by `install.sh` or referenced in scripts must be pinned to a specific version. Do not use `latest`, `^ SemVer` ranges, or unpinned references in installation commands. Versions must be at least 15 days old before adoption — supply chain attacks typically target newly published releases. - -```bash -# Good — pinned version, 15+ days old -curl -fsSL https://starship.rs/install.sh | sh -s -- -b "$HOME/.local/bin" -y --commit abc123 - -# Good — pinned package manager version -npm install -g some-cli@2.4.1 - -# Bad — no version pinning -curl -fsSL https://example.com/install | bash - -# Bad — latest tag -npm install -g some-cli@latest -``` - -For GitHub Actions in workflows, always use explicit commit SHAs (`@commit-sha`) or pinned minor versions (`@v4`). Avoid `${{ github.sha }}` or floating tags in production workflows. - ## Pull Request Expectations Every PR should include: diff --git a/README.md b/README.md index 891652b..79a749c 100644 --- a/README.md +++ b/README.md @@ -41,8 +41,7 @@ bash install.sh --check | Shell | `zsh/zshrc`, `zsh/scripts/` | | Terminal | `ghostty/config`, `ghostty/shaders/` | | Prompt | `starship/starship.toml` using the generic `cortex_warm_slate` palette | -| Multiplexer | `herdr/config.toml` plus session helpers | -| AI CLI UX | Herdr helpers, agent-state docs | +| Multiplexer | `herdr/config.toml` | | Package guardrails | global defaults for `npm`, `pnpm`, `bun`, and `uv` | ## Navigation Defaults @@ -136,13 +135,6 @@ PY bash install.sh --check ``` -## Docs - -- [Agent State v1](docs/agent-state-v1.md) -- [Herdr workflow](docs/herdr-workflow.md) -- [Keymaps](docs/keymaps.md) -- [Release checklist](docs/release-checklist.md) - ## License MIT. See [LICENSE](LICENSE). diff --git a/docs/agent-state-v1.md b/docs/agent-state-v1.md deleted file mode 100644 index 62b80f7..0000000 --- a/docs/agent-state-v1.md +++ /dev/null @@ -1,240 +0,0 @@ -# Cortex Agent State v1 - -`cortex.agent_state.v1` defines a small event contract for reporting agent activity to Cortex UI surfaces. It is inspired by the separation in `gentle-agent-state` and by Herdr's pane-oriented reporting API: adapters translate native agent signals into this contract, and renderers consume only the normalized state. - -## Purpose - -- Provide one stable anti-corruption layer between agent-native events and Cortex surfaces such as Herdr, statuslines and desktop notifications. -- Make agent state comparable across tools without requiring every UI to understand every agent. -- Keep state ephemeral, privacy-preserving and safe to roll up across panes, sessions and workspaces. -- Define the contract before implementing runtime adapters. - -## Non-Goals - -- This is not a task tracker, job queue or durable audit log. -- This does not define agent-specific protocols, prompt formats or tool-call telemetry. -- This does not require storing prompts, completions, command output or file contents. -- This does not define UI layout, icons, colors or notification copy. -- This does not replace Herdr pane metadata; it maps into it. - -## Canonical States - -| State | Meaning | Examples | -|-------|---------|----------| -| `working` | The agent is actively processing, executing tools or waiting on a known in-flight operation. | Streaming response, running tests, applying a patch. | -| `blocked` | The agent cannot proceed without user input, external approval, credentials, a failed dependency or a recoverable error. | Permission needed, merge conflict, missing secret, test failure requiring decision. | -| `idle` | The agent is available and has no known active work. | Ready prompt, completed turn, waiting for next instruction. | -| `unknown` | The adapter cannot determine current state or the last event is stale. | Unsupported source, crashed adapter, expired TTL. | - -`done` is not canonical. If a source emits `done`, adapters should report a transition to `idle` with `reason: "done"` or `native_state: "done"`. Renderers may display that as a short-lived completed affordance, but persisted rollups must treat it as `idle`. - -## Event Schema - -Events are append-style reports. Consumers should treat the newest non-stale event per `agent_id` and `pane_id` as authoritative. - -Required fields: - -- `schema`: constant string `cortex.agent_state.v1`. -- `state`: one of `working`, `blocked`, `idle` or `unknown`. -- `source`: adapter identity. -- `agent`: logical agent identity. -- `context`: pane/session/workspace identity. -- `observed_at`: when the adapter observed the native state. -- `expires_at`: when consumers must treat the event as stale. - -Recommended optional fields: - -- `event_id`: unique event identifier for dedupe. -- `sequence`: monotonic counter scoped to the source and context. -- `reason`: short machine-readable transition reason. -- `message`: short user-facing summary safe for local display. -- `native_state`: original source state before normalization. -- `visibility`: whether notifications should be suppressed for visible surfaces. -- `metadata`: small, non-sensitive adapter metadata. - -```json -{ - "schema": "cortex.agent_state.v1", - "event_id": "01JZ7W31B6TNTV4V7TR8R3XG2D", - "sequence": 42, - "state": "blocked", - "reason": "needs_user_input", - "message": "Review required before continuing", - "native_state": "awaiting_approval", - "source": { - "adapter": "claude-code", - "adapter_version": "0.1.0", - "host": "macbook-pro" - }, - "agent": { - "agent_id": "claude:repo:cortex-dotfiles", - "agent_kind": "claude-code", - "display_name": "Claude Code" - }, - "context": { - "workspace_id": "cortex-dotfiles", - "repo_path": "/path/to/your/repo", - "session_id": "herdr:main:1", - "pane_id": "herdr:main:1.2", - "surface": "herdr" - }, - "visibility": { - "pane_visible": true, - "suppress_notifications": true - }, - "observed_at": "2026-07-01T15:04:05Z", - "expires_at": "2026-07-01T15:05:05Z", - "metadata": { - "branch": "docs/agent-state-v1", - "issue": 13 - } -} -``` - -## Identity Fields - -Identity must be stable enough for rollups and cheap enough for shell adapters. - -| Field | Stability | Guidance | -|-------|-----------|----------| -| `agent.agent_id` | Stable per logical agent in a workspace. | Prefer `::`. Do not include secrets or full prompt text. | -| `agent.agent_kind` | Stable per integration. | Examples: `claude-code`, `opencode`, `codex`, `custom`. | -| `agent.display_name` | Human-readable. | Safe to show in UI; not used for dedupe. | -| `context.workspace_id` | Stable per project/workspace. | Prefer repo basename or Herdr workspace name. | -| `context.repo_path` | Local-only absolute path. | Optional outside local adapters; never publish remotely. | -| `context.session_id` | Stable per terminal/session container. | Use Herdr session/window identity when available. | -| `context.pane_id` | Stable per pane. | Required for pane-level rollups. | -| `context.surface` | Reporting surface. | Examples: `herdr`, `statusline`. | - -## Timestamps, TTL And Staleness - -- `observed_at` and `expires_at` must be RFC 3339 UTC timestamps. -- Adapters should use short TTLs for active states: 30-120 seconds for `working` and `blocked`. -- `idle` may use a longer TTL, but consumers should still expire it to `unknown` after missed heartbeats. -- Consumers must treat events as `unknown` after `expires_at`, even if the last state was `working` or `blocked`. -- Consumers should prefer source timestamps over receive time, but may reject events too far in the future. -- Stale cleanup should delete or ignore expired records; it must not send notifications. - -## Sources And Adapters - -Adapters are responsible for translating native events into canonical states. - -- Claude Code hooks may map tool execution and permission waits to `working` or `blocked`. -- OpenCode hooks may map active turns to `working` and prompt-ready states to `idle`. -- Herdr pane observers may report `unknown` when a pane disappears or cannot be inspected. -- Shell wrappers may emit coarse `working` and `idle` transitions around long-running commands. - -Adapters should preserve the original source value in `native_state` when it helps debugging, but consumers must not depend on that value for behavior. - -## Rollup Order - -When multiple pane or agent states are summarized, consumers must use this priority order: - -```text -blocked > working > idle > unknown -``` - -Rules: - -- Any fresh `blocked` child makes the parent `blocked`. -- Otherwise any fresh `working` child makes the parent `working`. -- Otherwise any fresh `idle` child makes the parent `idle`. -- If no fresh known child exists, the parent is `unknown`. -- Rollups should include counts by state when space allows, but the primary parent state follows the priority order. - -## Notifications - -Notifications are transition-only. - -- Notify only when the effective state changes after staleness filtering and rollup. -- Do not notify on heartbeat refreshes with the same state. -- Do not notify when stale cleanup changes a state to `unknown`. -- Prefer notifying on `blocked` transitions; avoid noisy `working` notifications unless explicitly enabled. -- Treat `done` as an optional `idle` transition with a short-lived renderer affordance, not as a durable state. - -## Visibility Suppression - -Visible panes should not spam the user. - -- If the relevant pane or surface is focused/visible, adapters or renderers should set `suppress_notifications: true`. -- Hidden panes may notify on important transitions such as `working -> blocked`. -- Renderers should still update visual state even when notifications are suppressed. -- Visibility checks are best-effort. Missing visibility data must not block state reporting. - -## Minimal Local CLI - -The first runtime slice is a neutral local command backed only by POSIX shell and `python3`: - -```bash -cortex.agent_state.v1 report --source opencode --agent opencode:cortex-dotfiles --state working --message "running tests" -cortex.agent_state.v1 list -cortex.agent_state.v1 get --agent opencode:cortex-dotfiles -cortex.agent_state.v1 clear -``` - -`agent-state` is an alias for the same command when `zsh/zshrc` is loaded. The command records only caller-provided `message` text plus allowlisted Herdr context environment fields when present: `HERDR_PANE_ID`, `HERDR_SESSION` and `HERDR_WORKSPACE_ID`. It does not inspect prompts, command output, file contents or arbitrary environment variables. - -When running inside Herdr (`HERDR_ENV=1` or `CORTEX_MULTIPLEXER=herdr`) with `HERDR_PANE_ID` set and a `herdr` binary available on `PATH`, `report` also makes a best-effort pane update: - -```bash -herdr pane report-metadata "$HERDR_PANE_ID" --source cortex.agent-state --custom-status "$status" --state-label "$state=$label" -``` - -The Herdr call is optional and never blocks the local state write. Set `CORTEX_AGENT_STATE_HERDR=0` to disable it. The bridge skips `unknown` states because they are useful for the local contract but not useful as pane presentation updates. The bridge is presentation metadata first; it must not try to take lifecycle authority away from official Herdr integrations such as opencode pane detection. - -## Storage Guidance - -State storage should be local, ephemeral and overwrite-friendly. - -- Use a state path such as `${XDG_STATE_HOME:-~/.local/state}/cortex/agent-state/`. -- The minimal CLI appends events to `events.jsonl` and stores current records in `current/`. -- Store one latest JSON record per stable `context.pane_id` or per `agent_id` plus `pane_id`. -- Keep optional append logs disabled by default and rotate aggressively when enabled for debugging. -- Do not store prompts, model output, command output, file diffs, tokens, credentials or environment dumps. -- Expire stale files during reads or with a lightweight cleanup job. -- Runtime implementations may choose JSON files, SQLite or Herdr-native metadata, but the external shape remains `cortex.agent_state.v1`. - -## Herdr Integration - -Herdr is the preferred local surface for pane-scoped state. - -Mapping guidance: - -| Cortex field | Herdr API mapping | -|--------------|-------------------| -| `context.pane_id` | Herdr pane identifier. | -| `context.session_id` | Herdr session/window identifier. | -| `state` | `report-metadata --state-label` presentation label. | -| `agent.agent_id` | `report-metadata --agent` when safe to expose. | -| `agent.display_name` | `report-metadata --display-agent` when safe to expose. | -| `message`, `reason` | `report-metadata --custom-status` compact presentation text. | -| `source`, `observed_at`, `expires_at`, `visibility`, `metadata` | Local event payload; Herdr receives only allowlisted presentation metadata. | - -Expected adapter flow: - -1. Normalize the native agent event to `cortex.agent_state.v1`. -2. Send useful non-`unknown` states through Herdr `report-metadata` for pane-visible presentation. -3. Let Herdr or downstream renderers apply rollup order, TTL and notification rules. - -The first local bridge reports compact presentation metadata only. It does not override `agent_status` or otherwise take lifecycle authority from official integrations. A future adapter may add a `report-agent` fallback only for panes that have no official agent owner. - -Herdr-specific extensions must live under `metadata.herdr` so generic consumers can ignore them safely. - -## Security And Privacy - -- Never include secrets, tokens, credentials, private keys, cookies or full environment dumps. -- Never include prompt text, model output, command output, patch content or arbitrary file contents. -- Prefer local-only paths and identifiers; do not sync state files by default. -- Keep messages short and display-safe because status bars and notifications are visible in screenshots and screen shares. -- Treat `metadata` as allowlisted, not a dumping ground for raw adapter payloads. -- Adapters should fail closed to `unknown` when they cannot safely classify or sanitize an event. - -## Rollout Phases - -1. **Spec**: land this document and keep runtime behavior unchanged. -2. **Fixture**: add sample events and a tiny validator for local development. -3. **Adapter prototype**: implement one low-risk adapter, preferably Herdr-facing and local-only. -4. **Renderer integration**: show state in Herdr/statusline without notifications enabled by default. -5. **Notification gate**: enable transition-only notifications for hidden panes and `blocked` transitions. -6. **Multi-agent rollups**: aggregate pane, workspace and repo states using the canonical priority order. -7. **Hardening**: add TTL cleanup, privacy tests and adapter compatibility notes before broader use. diff --git a/docs/herdr-workflow.md b/docs/herdr-workflow.md deleted file mode 100644 index 5a8c122..0000000 --- a/docs/herdr-workflow.md +++ /dev/null @@ -1,92 +0,0 @@ -# Herdr Workflow Reference - -This workflow keeps terminal state persistent across local and remote work while leaving Ghostty as the outer terminal and Herdr as the workspace manager. - -## Quick Path - -1. Open a repo session with `hhere` or a role session with `hfocus`, `hside`, or `hscratch`. -2. Split work by Herdr workspace, tab, and pane instead of opening anonymous terminal windows. -3. Run `cc` or `oc` inside the relevant pane; Herdr provides persistence and reattach. -4. Use `whereami` when context is unclear. -5. Detach with `Ctrl+B q` and reattach with the same helper or `herdr --session `. - -## Local And Remote Modes - -| Mode | Use | Command | -|------|-----|---------| -| Local Herdr | Normal work on the current machine | `hhere [path]`, `hfocus [path]` | -| Remote Herdr bridge | Persistent remote session rendered through the local client | `hremote [session]` | -| Plain SSH/Mosh first | You need a normal remote shell before deciding what to run there | `sshx `, `moshx [path]` | - -`hremote` is not the same thing as `ssh` followed by `herdr`. `hremote` uses `herdr --remote` and attaches to a named Herdr session on the host through Herdr's remote bridge. `sshx` or `moshx` gives you a regular remote shell; from there, `hhere` starts or attaches Herdr as a local process on that remote host. - -In `hremote`, `Ctrl+V` is reserved by Herdr for remote image paste when supported by the terminal. Use normal shell paste bindings outside remote attach. - -## Naming Conventions - -| Level | Convention | Example | -|-------|------------|---------| -| Session | Host + repo + branch + optional role | `local-cortex-dotfiles-fix/oss-announce-focus` | -| Workspace | Repo/branch or operational label | `cortex-dotfiles-fix-oss-announce` | -| Tab | One objective or phase | `docs`, `verify`, `review` | -| Pane | One actor or process | `shell`, `cc`, `oc`, `tests`, `logs` | - -Prefer human names over physical monitor names. `focus`, `side`, and `scratch` describe intent and survive moving between laptops, external displays, and remote machines. - -## Daily Helpers - -| Helper | Use | -|--------|-----| -| `hhere [path]` / `hmain [path]` | Enter the main Herdr session for the repo/branch. | -| `hfocus [path]` | Enter the main intense-work role session. | -| `hside [path]` | Enter a lateral session for references, logs, or review. | -| `hscratch [path]` | Enter a disposable session for experiments. | -| `hname [label]` | Rename the current pane so the sidebar stays readable. | -| `whereami` | Print host, cwd, repo, branch, SSH context, and Herdr pane id. | -| `cc [path]` | Start Claude Code in the current or given directory. | -| `oc [path]` | Start OpenCode in the current or given directory. | - -When already inside Herdr, the `h*` helpers do not nest Herdr by default. They create or focus a workspace in the current Herdr session. - -## Workspace Layout - -Use this as the default shape for coding sessions: - -| Area | Contents | -|------|----------| -| Main workspace | Active repo, implementation pane, agent pane, verification pane. | -| Side workspace | Docs, notes, logs, PR context, references. | -| Scratch workspace | Risky commands, temporary experiments, reproduction attempts. | - -Tabs should mark objectives, not tools. Panes should mark long-lived actors or processes. If a pane will stay open, name it with `hname`. - -## Mouse And Prefix Keys - -Herdr captures mouse input by default, which is useful for focusing panes, using the sidebar, and direct pane actions. Prefix keys remain the reliable path for remote sessions, keyboard-only work, and terminals where modified mouse gestures are inconsistent. - -| Task | Prefer | -|------|--------| -| Quick pane focus or sidebar selection | Mouse | -| Remote attach, repeatable muscle memory, detach/reattach | Prefix keys | -| Pane splits, close, zoom, rename | Prefix keys | -| App-specific mouse inside lazygit/btop/etc. | Let the app request mouse support | - -The default Herdr prefix is `Ctrl+B`. See [keymaps](keymaps.md) for the practical table. - -## Detach And Reattach - -| Action | Command | -|--------|---------| -| Detach current Herdr client | `Ctrl+B q` | -| Reattach main local repo session | `hhere [path]` | -| Reattach role session | `hfocus [path]`, `hside [path]`, `hscratch [path]` | -| Reattach explicit session | `herdr --session ` or `herdr session attach ` | -| Reattach remote session | `hremote [session]` | - -Before detaching long-running work, name important panes and run `whereami` if the shell context is ambiguous. - -## Related Docs - -- [Keymaps](keymaps.md) -- [Cortex Agent State v1](agent-state-v1.md) -- [README](../README.md) diff --git a/docs/keymaps.md b/docs/keymaps.md deleted file mode 100644 index 9053b45..0000000 --- a/docs/keymaps.md +++ /dev/null @@ -1,59 +0,0 @@ -# Practical Keymaps - -This table favors the shortcuts used by these dotfiles and Herdr defaults. It is a working reference, not a full upstream manual. - -## Terminal And Multiplexer - -| Tool | Shortcut | Action | -|------|----------|--------| -| Ghostty | `Command+Shift+D` | New split down. | -| Ghostty | `Command+Shift+Z` | Toggle split zoom. | -| Ghostty | `Ctrl+Option+Left/Right/Up/Down` | Resize Ghostty split. | -| Ghostty | `Ctrl+Option+=` | Equalize Ghostty splits. | -| Ghostty | select text | Copy selection to clipboard. | -| Ghostty | right click | Paste. | -| Herdr | `Ctrl+B ?` | Help. | -| Herdr | `Ctrl+B q` | Detach client. | -| Herdr | `Ctrl+B w` | Workspace picker. | -| Herdr | `Ctrl+B Shift+N` | New workspace. | -| Herdr | `Ctrl+B Shift+W` | Rename workspace. | -| Herdr | `Ctrl+B Shift+D` | Close workspace. | -| Herdr | `Ctrl+B c` | New tab. | -| Herdr | `Ctrl+B Shift+T` | Rename tab. | -| Herdr | `Ctrl+B p` / `Ctrl+B n` | Previous / next tab. | -| Herdr | `Ctrl+B 1..9` | Switch tab. | -| Herdr | `Ctrl+B Shift+X` | Close tab. | -| Herdr | `Ctrl+B h/j/k/l` | Focus pane left/down/up/right. | -| Herdr | `Ctrl+B Tab` | Cycle pane next. | -| Herdr | `Ctrl+B Shift+Tab` | Cycle pane previous. | -| Herdr | `Ctrl+B v` | Split pane vertically. | -| Herdr | `Ctrl+B -` | Split pane horizontally. | -| Herdr | `Ctrl+B x` | Close pane. | -| Herdr | `Ctrl+B z` | Zoom pane. | -| Herdr | `Ctrl+B Shift+P` | Rename pane. | -| Herdr | `Ctrl+B r` | Resize mode. | -| Herdr | `Ctrl+B b` | Toggle sidebar. | -| Herdr remote | `Ctrl+V` | Remote image paste. | - -## AI CLIs - -| Tool | Shortcut / Command | Action | -|------|--------------------|--------| -| OpenCode | `oc [path]` | Start OpenCode in the current or given directory. | -| OpenCode | `ocb [path]` | Alias for `oc`, preserving default flags. | - -Claude Code and OpenCode keybindings are mostly app-native. This repo adds launch helpers (cc, ccb, ccx, ccclip) and Herdr integration so sessions survive terminal detach/reattach. - -## Collision Notes - -| Area | Note | -|------|------| -| Herdr prefix | `Ctrl+B` avoids most shell and app shortcuts while staying tmux-like. | -| macOS leader | `Option+Command` is reserved for windowing in this setup; avoid assigning it in Raycast. | -| Ghostty vs Herdr splits | Use Herdr splits for persistent work inside a session; use Ghostty splits for outer-terminal layout. | -| Mouse | Herdr captures mouse by default; prefix keys are more reliable over remote sessions. | - -## Related Docs - -- [Herdr workflow](herdr-workflow.md) -- [`ghostty/config`](../ghostty/config) diff --git a/docs/release-checklist.md b/docs/release-checklist.md deleted file mode 100644 index 48745de..0000000 --- a/docs/release-checklist.md +++ /dev/null @@ -1,13 +0,0 @@ -# Release Checklist - -Use this checklist before publishing a snapshot or tagging a release. - -1. Start from tracked files on `main` only. -2. Verify ignored files, generated local config, and `.git` history are not included in exported artifacts. -3. Run `scripts/oss-audit.sh`. -4. Run shell syntax checks for `install.sh`, `zsh/zshrc`, `zsh/scripts/*.zsh`, and `scripts/*.sh`. -5. Validate JSON with `jq`. -6. Validate TOML with Python `tomllib`. -7. Run `bash install.sh --check`. -8. Review `README.md`, `SECURITY.md`, `THIRD_PARTY.md`, and `CHANGELOG.md` for release accuracy. -9. Create the release archive from tracked files only. diff --git a/ghostty/muxy.conf b/ghostty/muxy.conf deleted file mode 100644 index b1a3e81..0000000 --- a/ghostty/muxy.conf +++ /dev/null @@ -1,40 +0,0 @@ -theme = dark:"Monospace Dark",light:"Muxy" -# Fuente -font-family = FiraCode Nerd Font Mono Beard -font-size = 14 - -# Transparencia -background-opacity = 1.0 -unfocused-split-opacity = 0.2 - -# Ventana -window-decoration = false -window-padding-balance = true -window-step-resize = false -window-padding-x = 8 -window-padding-y = 8 - -# Cursor -cursor-style = block_hollow - -# Cursor con efecto suave -# Opciones disponibles: cursor_smear_soft, cursor_blaze, cursor_frozen, cursor_smear_rainbow -custom-shader = shaders/cursor_smear_soft.glsl - -# Integración con shell -command = zsh -shell-integration = zsh - -# Comportamiento -scrollback-limit = 10000 -copy-on-select = clipboard -right-click-action = paste - -# Splits -keybind = cmd+shift+d=new_split:down -keybind = cmd+shift+z=toggle_split_zoom -keybind = ctrl+opt+left=resize_split:left,20 -keybind = ctrl+opt+right=resize_split:right,20 -keybind = ctrl+opt+up=resize_split:up,20 -keybind = ctrl+opt+down=resize_split:down,20 -keybind = ctrl+opt+equal=equalize_splits diff --git a/nvim/README.md b/nvim/README.md deleted file mode 100644 index 9c9f47f..0000000 --- a/nvim/README.md +++ /dev/null @@ -1,97 +0,0 @@ -# Neovim - -Estado documentado el 2026-06-19. La configuración activa vive por ahora en -`~/.config/nvim` y parte de una copia directa de una config comunitaria. - -## Base - -- Base: fork de una config comunitaria de nvim (LazyVim/Snacks) -- Config local activa: `~/.config/nvim` - -## Decisiones - -- Mantener la config base estable, evitando mezclar piezas sueltas. -- Mantener Oil como explorer principal en `-`. -- Mantener Neo-tree como explorer lateral en `e`. -- Mantener Oil flotante en `E`. -- Desactivar `mini.files` para evitar un tercer modelo de explorer. -- Mantener un dashboard genérico o local; no versionar logos privados en este repo OSS. -- Aplicar un overlay visual Cortex sobre el theme upstream, en vez de reemplazar todo el colorscheme. -- Usar como fuente cromática canónica la paleta Cortex warm/slate versionada en estos dotfiles. -- Usar `FiraCode Nerd Font` como fuente GUI. -- Hacer que `bq` cierre el buffer actual con `Snacks.bufdelete()`; no usar `edit #` porque puede saltar a buffers temporales del sistema. -- Pintar explícitamente `render-markdown.nvim` con la paleta Cortex; los grupos base no alcanzan porque Markdown usa highlights propios. -- Desactivar reglas ruidosas de markdownlint (`MD013`, `MD060`) para docs/runbooks largos. -- Habilitar ayudas de aprendizaje como `precognition.nvim`; todo lo que muestre movimientos/contexto útil es deseable en esta etapa. - -## Paleta usada para el overlay - -Derivada de la paleta Cortex warm/slate usada por Ghostty y Starship. - -| Uso | Color | -|-----|-------| -| Fondo base | `#0F1419` | -| Surface | `#1C2128` | -| Surface elevada / cursor line | `#2D333B` | -| Texto principal | `#F6F8FA` | -| Texto secundario | `#8B949E` | -| Texto muted/comment | `#57606A` | -| Azul primario | `#0054ff` | -| Azul hover | `#1a68ff` | -| Azul informativo | `#58A6FF` | -| Verde engineering | `#3FB950` | -| Verde hover | `#4CC55E` | -| Warning | `#D29922` | -| Error | `#F85149` | -| Purple sintaxis | `#A371F7` | - -## Archivos modificados en `~/.config/nvim` - -- `lua/plugins/ui.lua` - - Header del dashboard mantenido genérico o local. -- `lua/plugins/.lua` - - Overlay de highlights Cortex cargado como plugin spec de Lazy. - - Incluye grupos base, diagnostics y grupos `RenderMarkdown*`. -- `lua/plugins/markdown.lua` - - Config de `render-markdown.nvim` y override de `markdownlint-cli2`. -- `lua/plugins/precognition.lua` - - Hints de movimientos Vim activos por defecto. - - `up` alterna hints automáticos. - - `uP` muestra hints puntuales con `peek`. -- `markdownlint-cli2.yaml` - - Desactiva `MD013` y `MD060`. -- `lua/config/autocmds.lua` - - Queda sin lógica custom; el overlay vive en un plugin local para asegurar orden de carga. -- `lua/config/options.lua` - - `vim.opt.guifont = "FiraCode Nerd Font:h14"`. -- `lua/config/lazy.lua` - - `lazyvim.plugins.extras.editor.mini-files` comentado/desactivado. -- `lua/config/keymaps.lua` - - `bq` como alias seguro de `Snacks.bufdelete()`. - -## Estado de keymaps relevantes - -| Keymap | Acción | -|--------|--------| -| `-` | Abrir Oil normal | -| `E` | Abrir Oil flotante | -| `e` | Abrir Neo-tree | -| `bq` | Cerrar buffer actual | -| `bd` | Cerrar buffer actual, default LazyVim/Snacks | -| `bo` | Cerrar otros buffers | -| `bD` | Cerrar buffer y ventana | -| `up` | Alternar Precognition hints | -| `uP` | Mostrar Precognition hints puntuales | - -## Validación manual - -```bash -nvim --headless +'lua vim.defer_fn(function() print(vim.api.nvim_exec2("messages", { output = true }).output); vim.cmd("qa") end, 1500)' -``` - -Resultado esperado: sin errores en `:messages`. - -## Pendiente opcional - -Versionar la config completa en `dotfiles/nvim/` y hacer que `install.sh` cree el symlink a -`~/.config/nvim`, siguiendo el patrón del resto de herramientas. diff --git a/scripts/check-agent-state.sh b/scripts/check-agent-state.sh deleted file mode 100755 index db4e0ab..0000000 --- a/scripts/check-agent-state.sh +++ /dev/null @@ -1,75 +0,0 @@ -#!/bin/sh -set -eu - -ROOT_DIR=$(unset CDPATH; cd -- "$(dirname -- "$0")/.." && pwd) -TMP_STATE=$(mktemp -d) -TMP_BIN=$(mktemp -d) -trap 'rm -rf "$TMP_STATE" "$TMP_BIN"' EXIT INT TERM - -CMD="$ROOT_DIR/zsh/scripts/agent-state.sh" -export XDG_STATE_HOME="$TMP_STATE" -export PATH="$TMP_BIN:$PATH" -export HERDR_PANE_ID="pane-smoke" -export HERDR_ENV="1" -export HERDR_SESSION="session-smoke" -export HERDR_WORKSPACE_ID="workspace-smoke" -export HERDR_FAKE_LOG="$TMP_STATE/herdr.log" - -cat >"$TMP_BIN/herdr" <<'EOF' -#!/bin/sh -printf '%s\n' "$*" >>"$HERDR_FAKE_LOG" -EOF -chmod +x "$TMP_BIN/herdr" - -"$CMD" report --source smoke --agent smoke-agent --state working --message "smoke test" >/dev/null -python3 - "$HERDR_FAKE_LOG" <<'PY' -import pathlib -import sys - -log = pathlib.Path(sys.argv[1]).read_text(encoding="utf-8") -assert "pane report-metadata pane-smoke --source cortex.agent-state --custom-status Working: smoke test --state-label working=Working --agent smoke-agent --display-agent smoke-agent" in log -PY -"$CMD" list | python3 -c 'import sys; data=sys.stdin.read(); assert "smoke-agent" in data and "fresh" in data' -"$CMD" get --agent smoke-agent | python3 -c 'import json,sys; rec=json.load(sys.stdin); assert rec["schema"] == "cortex.agent_state.v1"; assert rec["context"]["pane_id"] == "pane-smoke"' -env HERDR_ENV= CORTEX_MULTIPLEXER=herdr "$CMD" report --source smoke --agent mux-agent --state working --message "mux test" >/dev/null -python3 - "$HERDR_FAKE_LOG" <<'PY' -import pathlib -import sys - -log = pathlib.Path(sys.argv[1]).read_text(encoding="utf-8") -assert "pane report-metadata pane-smoke --source cortex.agent-state --custom-status Working: mux test --state-label working=Working --agent mux-agent --display-agent mux-agent" in log -PY -"$CMD" report --source smoke --agent safe-agent --state blocked --message "line1 -line2 $(printf '\001')$(python3 -c 'print("x" * 260)')" >/dev/null -"$CMD" list | python3 -c 'import sys; rows=sys.stdin.read().splitlines(); row=next(r for r in rows if "safe-agent" in r); message=row.split("\t", 5)[5]; assert message.startswith("line1line2") and len(message) == 240 and "\t" not in message' -python3 - "$XDG_STATE_HOME" <<'PY' -import json -import pathlib -import sys - -current = pathlib.Path(sys.argv[1]) / "cortex" / "agent-state" / "current" -(current / "bad.json").write_text("{not json", encoding="utf-8") -(current / "missing-expires.json").write_text(json.dumps({ - "source": {"adapter": "bad"}, - "agent": {"agent_id": "missing-expires"}, - "state": "working", - "message": "bad\nmessage", -}), encoding="utf-8") -(current / "wrong-shape.json").write_text(json.dumps({ - "source": "bad", - "agent": "bad", - "context": "bad", - "state": 7, - "message": "bad\tmessage", -}), encoding="utf-8") -PY -"$CMD" list | python3 -c 'import sys; data=sys.stdin.read(); assert "missing-expires" in data and "unknown" in data' -before=$(wc -l <"$HERDR_FAKE_LOG") -CORTEX_AGENT_STATE_HERDR=0 "$CMD" report --source smoke --agent disabled-agent --state working --message "disabled" >/dev/null -after=$(wc -l <"$HERDR_FAKE_LOG") -test "$before" = "$after" -"$CMD" get --agent disabled-agent | python3 -c 'import json,sys; rec=json.load(sys.stdin); assert rec["state"] == "working"' -"$CMD" clear >/dev/null -"$CMD" list | python3 -c 'import sys; assert "no agent states" in sys.stdin.read()' - -printf '%s\n' "agent-state smoke ok" diff --git a/zsh/scripts/agent-state.sh b/zsh/scripts/agent-state.sh deleted file mode 100755 index 40921aa..0000000 --- a/zsh/scripts/agent-state.sh +++ /dev/null @@ -1,301 +0,0 @@ -#!/bin/sh - -# Minimal local reporter for the cortex.agent_state.v1 contract. -cortex_agent_state() { - if ! command -v python3 >/dev/null 2>&1; then - printf '%s\n' "agent-state: python3 is required" >&2 - return 127 - fi - - python3 - "$@" <<'PY' -import argparse -import hashlib -import json -import os -import shutil -import subprocess -import sys -import unicodedata -import uuid -from datetime import datetime, timedelta, timezone -from pathlib import Path - -SCHEMA = "cortex.agent_state.v1" -VALID_STATES = {"working", "blocked", "idle", "unknown"} -HERDR_SOURCE = "cortex.agent-state" -MAX_MESSAGE_LENGTH = 240 - - -def now_utc(): - return datetime.now(timezone.utc) - - -def parse_time(value): - return datetime.fromisoformat(value.replace("Z", "+00:00")) - - -def format_time(value): - return value.astimezone(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z") - - -def sanitize_message(value): - if not value: - return "" - clean = "".join(ch for ch in value if not unicodedata.category(ch).startswith("C")) - return clean[:MAX_MESSAGE_LENGTH] - - -def state_root(): - base = os.environ.get("XDG_STATE_HOME") or os.path.join(os.path.expanduser("~"), ".local", "state") - return Path(base) / "cortex" / "agent-state" - - -def paths(): - root = state_root() - return root, root / "events.jsonl", root / "current" - - -def current_key(source, agent, pane_id): - raw = "\0".join([source, agent, pane_id or ""]) - return hashlib.sha256(raw.encode("utf-8")).hexdigest() - - -def default_ttl(state): - if state in {"working", "blocked"}: - return 120 - if state == "idle": - return 3600 - return 60 - - -def build_event(args): - if args.state not in VALID_STATES: - raise SystemExit(f"invalid state: {args.state}") - - observed_at = now_utc() - expires_at = observed_at + timedelta(seconds=args.ttl or default_ttl(args.state)) - pane_id = os.environ.get("HERDR_PANE_ID") - session_id = os.environ.get("HERDR_SESSION") - workspace_id = os.environ.get("HERDR_WORKSPACE_ID") - - context = {} - if pane_id: - context["pane_id"] = pane_id - if session_id: - context["session_id"] = session_id - if workspace_id: - context["workspace_id"] = workspace_id - - event = { - "schema": SCHEMA, - "event_id": str(uuid.uuid4()), - "state": args.state, - "source": {"adapter": args.source}, - "agent": {"agent_id": args.agent, "display_name": args.agent}, - "context": context, - "observed_at": format_time(observed_at), - "expires_at": format_time(expires_at), - } - message = sanitize_message(args.message) - if message: - event["message"] = message - return event - - -def write_json_atomic(path, data): - path.parent.mkdir(parents=True, exist_ok=True) - tmp = path.with_suffix(path.suffix + ".tmp") - with tmp.open("w", encoding="utf-8") as handle: - json.dump(data, handle, sort_keys=True, separators=(",", ":")) - handle.write("\n") - tmp.replace(path) - - -def herdr_safe(value): - return "".join(ch if ch.isalnum() or ch in ".:_-" else "-" for ch in value) - - -def state_label(state): - return { - "working": "Working", - "blocked": "Blocked", - "idle": "Idle", - }.get(state, "Unknown") - - -def custom_status(state, message): - label = state_label(state) - return f"{label}: {message}" if message else label - - -def report_to_herdr(event): - if os.environ.get("CORTEX_AGENT_STATE_HERDR") == "0": - return - - pane_id = event.get("context", {}).get("pane_id") - if not pane_id: - return - if os.environ.get("HERDR_ENV") != "1" and os.environ.get("CORTEX_MULTIPLEXER") != "herdr": - return - - herdr = shutil.which("herdr") - if not herdr: - return - - state = event.get("state", "unknown") - if state == "unknown": - return - - agent = event.get("agent", {}).get("agent_id", "") - message = sanitize_message(event.get("message", "")) - command = [ - herdr, - "pane", - "report-metadata", - pane_id, - "--source", - HERDR_SOURCE, - "--custom-status", - custom_status(state, message), - "--state-label", - f"{state}={state_label(state)}", - ] - if agent: - command.extend(["--agent", herdr_safe(agent), "--display-agent", sanitize_message(agent)]) - - try: - subprocess.run(command, check=False, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) - except OSError: - return - - -def cmd_report(args): - root, events_path, current_dir = paths() - root.mkdir(parents=True, exist_ok=True) - current_dir.mkdir(parents=True, exist_ok=True) - - event = build_event(args) - with events_path.open("a", encoding="utf-8") as handle: - json.dump(event, handle, sort_keys=True, separators=(",", ":")) - handle.write("\n") - - pane_id = event.get("context", {}).get("pane_id") - key = current_key(event["source"]["adapter"], event["agent"]["agent_id"], pane_id) - write_json_atomic(current_dir / f"{key}.json", event) - report_to_herdr(event) - print(f"reported {event['state']} {event['agent']['agent_id']}") - - -def iter_current(): - _, _, current_dir = paths() - if not current_dir.exists(): - return [] - records = [] - for path in sorted(current_dir.glob("*.json")): - try: - record = json.loads(path.read_text(encoding="utf-8")) - except (OSError, json.JSONDecodeError): - continue - if isinstance(record, dict): - records.append(record) - return records - - -def stale_label(record, current_time): - try: - expires_at = parse_time(record["expires_at"]) - except (KeyError, TypeError, ValueError): - return "unknown" - if expires_at <= current_time: - return "stale" - return "fresh" - - -def nested_value(record, key, nested_key): - value = record.get(key, {}) - if not isinstance(value, dict): - return "" - nested = value.get(nested_key, "") - return nested if isinstance(nested, str) else "" - - -def cmd_list(_args): - records = iter_current() - current_time = now_utc() - if not records: - print("no agent states") - return - - print("SOURCE\tAGENT\tSTATE\tSTALE\tPANE\tMESSAGE") - for record in records: - print("\t".join([ - nested_value(record, "source", "adapter"), - nested_value(record, "agent", "agent_id"), - record.get("state") if isinstance(record.get("state"), str) else "unknown", - stale_label(record, current_time), - nested_value(record, "context", "pane_id"), - sanitize_message(record.get("message", "")), - ])) - - -def cmd_get(args): - matches = [] - for record in iter_current(): - if args.agent and nested_value(record, "agent", "agent_id") != args.agent: - continue - if args.source and nested_value(record, "source", "adapter") != args.source: - continue - matches.append(record) - - if not matches: - print("agent state not found", file=sys.stderr) - return 1 - for record in matches: - print(json.dumps(record, sort_keys=True)) - return 0 - - -def cmd_clear(_args): - root, events_path, current_dir = paths() - if current_dir.exists(): - shutil.rmtree(current_dir) - root.mkdir(parents=True, exist_ok=True) - events_path.write_text("", encoding="utf-8") - print("cleared agent states") - - -def main(argv): - parser = argparse.ArgumentParser(prog="cortex.agent_state.v1") - sub = parser.add_subparsers(dest="command", required=True) - - report = sub.add_parser("report", help="record a current agent state") - report.add_argument("--source", required=True, help="adapter identity") - report.add_argument("--agent", required=True, help="logical agent identity") - report.add_argument("--state", required=True, choices=sorted(VALID_STATES)) - report.add_argument("--message", help="short caller-provided display message") - report.add_argument("--ttl", type=int, help="seconds until the state is stale") - report.set_defaults(func=cmd_report) - - list_cmd = sub.add_parser("list", help="list current states") - list_cmd.set_defaults(func=cmd_list) - - get = sub.add_parser("get", help="print current state records as JSON") - get.add_argument("--agent", help="filter by logical agent identity") - get.add_argument("--source", help="filter by source adapter") - get.set_defaults(func=cmd_get) - - clear = sub.add_parser("clear", help="clear current state and local event log") - clear.set_defaults(func=cmd_clear) - - args = parser.parse_args(argv) - result = args.func(args) - return 0 if result is None else result - - -raise SystemExit(main(sys.argv[1:])) -PY -} - -if [ "${0##*/}" = "agent-state.sh" ] && [ -z "${ZSH_EVAL_CONTEXT:-}" ]; then - cortex_agent_state "$@" -fi diff --git a/zsh/scripts/claude-helpers.zsh b/zsh/scripts/claude-helpers.zsh deleted file mode 100644 index eef1de6..0000000 --- a/zsh/scripts/claude-helpers.zsh +++ /dev/null @@ -1,454 +0,0 @@ -#region Claude Code Helpers -# Funciones de integración con Claude Code CLI - -_cmux_sidebar_refresh() { - local target="${1:-$PWD}" - local dotfiles_dir="${_DOTFILES_DIR:-$CORTEX_DOTFILES_DIR}" - local script="$dotfiles_dir/zsh/scripts/cmux-sidebar-refresh.sh" - if [[ -x "$script" ]]; then - "$script" "$target" >/dev/null 2>&1 || true - else - : # cmux-sidebar-refresh.sh no está en este repo (externo) - fi -} - -_workspace_name_for_path() { - local target="${1:-$PWD}" - local git_root - git_root=$(git -C "$target" rev-parse --show-toplevel 2>/dev/null || true) - - if [[ -n "$git_root" ]]; then - local repo_name - repo_name=$(basename "$git_root" | tr '.' '-') - printf '%s' "$repo_name" - else - basename "$target" | tr '.' '-' - fi -} - -_cmux_rename_workspace() { - local workspace_id="$1" - local workspace_name="$2" - - if [[ -z "$workspace_id" || -z "$workspace_name" ]]; then - return 0 - fi - - if command -v cmux >/dev/null 2>&1; then - # env -u descarta el socket heredado del proceso padre (cc/oc/ccb/ocb operan sobre la - # intención explícita del usuario — el workspace destino que indicó al lanzar el comando —, - # no sobre el focused actual; por eso solo aplicamos env -u sin --no-caller) - env -u CMUX_SOCKET_PATH -u CMUX_SOCKET cmux rename-workspace --workspace "$workspace_id" "$workspace_name" >/dev/null 2>&1 || true - fi -} - -_cortex_use_herdr_runtime() { - [[ "${CORTEX_MULTIPLEXER:-}" == "herdr" || -n "${HERDR_ENV:-}" ]] -} - -# Abrir Claude Code en el runtime disponible (Herdr/cmux; tmux opcional) -# Si no hay multiplexer persistente, ejecuta directo en el directorio objetivo -cc() { - local target="${1:-.}" - local resolved - resolved=$(cd "$target" 2>/dev/null && pwd) - - if [[ -z "$resolved" ]]; then - echo "❌ Directorio no encontrado: $target" - return 1 - fi - - if _cortex_use_herdr_runtime; then - cd "$resolved" && claude --enable-auto-mode --dangerously-skip-permissions - return - fi - - if [[ -n "$CMUX_WORKSPACE_ID" ]]; then - # Estamos dentro de cmux - local workspace_name - workspace_name="$(_workspace_name_for_path "$resolved")" - - # Si el directorio objetivo es el actual, lanzar claude aquí mismo - if [[ "$resolved" == "$PWD" ]]; then - _cmux_rename_workspace "$CMUX_WORKSPACE_ID" "$workspace_name" - _cmux_sidebar_refresh "$resolved" - claude --enable-auto-mode --dangerously-skip-permissions - return - fi - - # Directorio diferente: verificar si ya existe un workspace para no duplicar - # env -u descarta el socket heredado: cc opera sobre el dir/workspace destino que el - # usuario indicó explícitamente (no sobre el focused), por eso solo env -u, sin --no-caller - local existing_id - existing_id=$(env -u CMUX_SOCKET_PATH -u CMUX_SOCKET cmux list-workspaces 2>/dev/null | jq -r --arg name "$workspace_name" '.[] | select(.title == $name) | .id' 2>/dev/null | head -1) - - if [[ -n "$existing_id" ]]; then - # El workspace ya existe: enfocarlo sin crear uno nuevo - _cmux_rename_workspace "$existing_id" "$workspace_name" - env -u CMUX_SOCKET_PATH -u CMUX_SOCKET cmux select-workspace --workspace "$existing_id" - _cmux_sidebar_refresh "$resolved" - else - # No existe: crear workspace nuevo con claude corriendo - if ! env -u CMUX_SOCKET_PATH -u CMUX_SOCKET cmux new-workspace --name "$workspace_name" --cwd "$resolved" --command "claude --enable-auto-mode --dangerously-skip-permissions"; then - echo "⚠️ cmux new-workspace falló, ejecutando claude en el directorio actual" - _cmux_sidebar_refresh "$resolved" - cd "$resolved" && claude --enable-auto-mode --dangerously-skip-permissions - fi - fi - elif [[ -n "$TMUX" ]]; then - _cmux_sidebar_refresh "$resolved" - cd "$resolved" && claude --enable-auto-mode --dangerously-skip-permissions - else - # No herdr, no cmux: try tmux as optional fallback - if command -v tmux &>/dev/null; then - local session - session="$(_workspace_name_for_path "$resolved")" - if tmux has-session -t "$session" 2>/dev/null; then - tmux attach -t "$session" - else - tmux new-session -d -s "$session" - tmux send-keys -t "$session" "cd '$resolved' && claude --enable-auto-mode --dangerously-skip-permissions" Enter - tmux attach -t "$session" - fi - else - echo "⚠️ Instalá herdr o tmux para sesiones persistentes" - cd "$resolved" && claude --enable-auto-mode --dangerously-skip-permissions - fi - fi -} - -# Abrir OpenCode en el runtime disponible -# Mantiene el mismo patrón de uso que cc() pero usando opencode -oc() { - local target="${1:-.}" - local resolved - resolved=$(cd "$target" 2>/dev/null && pwd) - - if [[ -z "$resolved" ]]; then - echo "❌ Directorio no encontrado: $target" - return 1 - fi - - local oc_cmd="opencode ${OPENCODE_DEFAULT_FLAGS:-}" - - if _cortex_use_herdr_runtime; then - cd "$resolved" && eval "$oc_cmd" - return - fi - - if [[ -n "$CMUX_WORKSPACE_ID" ]]; then - local workspace_name - workspace_name="$(_workspace_name_for_path "$resolved")" - - if [[ "$resolved" == "$PWD" ]]; then - _cmux_rename_workspace "$CMUX_WORKSPACE_ID" "$workspace_name" - _cmux_sidebar_refresh "$resolved" - eval "$oc_cmd" - return - fi - - # env -u descarta el socket heredado: oc opera sobre el dir/workspace destino que el - # usuario indicó explícitamente (no sobre el focused), por eso solo env -u, sin --no-caller - local existing_id - existing_id=$(env -u CMUX_SOCKET_PATH -u CMUX_SOCKET cmux list-workspaces 2>/dev/null | jq -r --arg name "$workspace_name" '.[] | select(.title == $name) | .id' 2>/dev/null | head -1) - - if [[ -n "$existing_id" ]]; then - _cmux_rename_workspace "$existing_id" "$workspace_name" - env -u CMUX_SOCKET_PATH -u CMUX_SOCKET cmux select-workspace --workspace "$existing_id" - _cmux_sidebar_refresh "$resolved" - else - if ! env -u CMUX_SOCKET_PATH -u CMUX_SOCKET cmux new-workspace --name "$workspace_name" --cwd "$resolved" --command "$oc_cmd"; then - echo "⚠️ cmux new-workspace falló, ejecutando OpenCode en el directorio actual" - _cmux_sidebar_refresh "$resolved" - cd "$resolved" && eval "$oc_cmd" - fi - fi - elif [[ -n "$TMUX" ]]; then - _cmux_sidebar_refresh "$resolved" - cd "$resolved" && eval "$oc_cmd" - else - # No herdr, no cmux: try tmux as optional fallback - if command -v tmux &>/dev/null; then - local session - session="$(_workspace_name_for_path "$resolved")" - if tmux has-session -t "$session" 2>/dev/null; then - tmux attach -t "$session" - else - tmux new-session -d -s "$session" - tmux send-keys -t "$session" "cd '$resolved' && $oc_cmd" Enter - tmux attach -t "$session" - fi - else - echo "⚠️ Instalá herdr o tmux para sesiones persistentes" - cd "$resolved" && eval "$oc_cmd" - fi - fi -} - -# Abrir Claude Code con bypass de permisos explícito -ccb() { - local target="${1:-.}" - local resolved - resolved=$(cd "$target" 2>/dev/null && pwd) - - if [[ -z "$resolved" ]]; then - echo "❌ Directorio no encontrado: $target" - return 1 - fi - - local cc_cmd="claude --dangerously-skip-permissions" - - if _cortex_use_herdr_runtime; then - cd "$resolved" && eval "$cc_cmd" - return - fi - - if [[ -n "$CMUX_WORKSPACE_ID" ]]; then - local workspace_name - workspace_name="$(_workspace_name_for_path "$resolved")" - - if [[ "$resolved" == "$PWD" ]]; then - _cmux_rename_workspace "$CMUX_WORKSPACE_ID" "$workspace_name" - _cmux_sidebar_refresh "$resolved" - eval "$cc_cmd" - return - fi - - # env -u descarta el socket heredado: ccb opera sobre el dir/workspace destino que el - # usuario indicó explícitamente (no sobre el focused), por eso solo env -u, sin --no-caller - local existing_id - existing_id=$(env -u CMUX_SOCKET_PATH -u CMUX_SOCKET cmux list-workspaces 2>/dev/null | jq -r --arg name "$workspace_name" '.[] | select(.title == $name) | .id' 2>/dev/null | head -1) - - if [[ -n "$existing_id" ]]; then - _cmux_rename_workspace "$existing_id" "$workspace_name" - env -u CMUX_SOCKET_PATH -u CMUX_SOCKET cmux select-workspace --workspace "$existing_id" - _cmux_sidebar_refresh "$resolved" - else - if ! env -u CMUX_SOCKET_PATH -u CMUX_SOCKET cmux new-workspace --name "$workspace_name" --cwd "$resolved" --command "$cc_cmd"; then - echo "⚠️ cmux new-workspace falló, ejecutando Claude Code en el directorio actual" - _cmux_sidebar_refresh "$resolved" - cd "$resolved" && eval "$cc_cmd" - fi - fi - elif [[ -n "$TMUX" ]]; then - _cmux_sidebar_refresh "$resolved" - cd "$resolved" && eval "$cc_cmd" - else - # No herdr, no cmux: try tmux as optional fallback - if command -v tmux &>/dev/null; then - local session - session="$(_workspace_name_for_path "$resolved")" - if tmux has-session -t "$session" 2>/dev/null; then - tmux attach -t "$session" - else - tmux new-session -d -s "$session" - tmux send-keys -t "$session" "cd '$resolved' && $cc_cmd" Enter - tmux attach -t "$session" - fi - else - echo "⚠️ Instalá herdr o tmux para sesiones persistentes" - cd "$resolved" && eval "$cc_cmd" - fi - fi -} - -# Abrir OpenCode con bypass de permisos explícito -ocb() { - local target="${1:-.}" - local resolved - resolved=$(cd "$target" 2>/dev/null && pwd) - - if [[ -z "$resolved" ]]; then - echo "❌ Directorio no encontrado: $target" - return 1 - fi - - local oc_cmd="opencode ${OPENCODE_DEFAULT_FLAGS:-}" - - if _cortex_use_herdr_runtime; then - cd "$resolved" && eval "$oc_cmd" - return - fi - - if [[ -n "$CMUX_WORKSPACE_ID" ]]; then - local workspace_name - workspace_name="$(_workspace_name_for_path "$resolved")" - - if [[ "$resolved" == "$PWD" ]]; then - _cmux_rename_workspace "$CMUX_WORKSPACE_ID" "$workspace_name" - _cmux_sidebar_refresh "$resolved" - eval "$oc_cmd" - return - fi - - # env -u descarta el socket heredado: ocb opera sobre el dir/workspace destino que el - # usuario indicó explícitamente (no sobre el focused), por eso solo env -u, sin --no-caller - local existing_id - existing_id=$(env -u CMUX_SOCKET_PATH -u CMUX_SOCKET cmux list-workspaces 2>/dev/null | jq -r --arg name "$workspace_name" '.[] | select(.title == $name) | .id' 2>/dev/null | head -1) - - if [[ -n "$existing_id" ]]; then - _cmux_rename_workspace "$existing_id" "$workspace_name" - env -u CMUX_SOCKET_PATH -u CMUX_SOCKET cmux select-workspace --workspace "$existing_id" - _cmux_sidebar_refresh "$resolved" - else - if ! env -u CMUX_SOCKET_PATH -u CMUX_SOCKET cmux new-workspace --name "$workspace_name" --cwd "$resolved" --command "$oc_cmd"; then - echo "⚠️ cmux new-workspace falló, ejecutando OpenCode en el directorio actual" - _cmux_sidebar_refresh "$resolved" - cd "$resolved" && eval "$oc_cmd" - fi - fi - elif [[ -n "$TMUX" ]]; then - _cmux_sidebar_refresh "$resolved" - cd "$resolved" && eval "$oc_cmd" - else - # No herdr, no cmux: try tmux as optional fallback - if command -v tmux &>/dev/null; then - local session - session="$(_workspace_name_for_path "$resolved")" - if tmux has-session -t "$session" 2>/dev/null; then - tmux attach -t "$session" - else - tmux new-session -d -s "$session" - tmux send-keys -t "$session" "cd '$resolved' && $oc_cmd" Enter - tmux attach -t "$session" - fi - else - echo "⚠️ Instalá herdr o tmux para sesiones persistentes" - cd "$resolved" && eval "$oc_cmd" - fi - fi -} - -# Abrir Claude Code con contexto inicial en el runtime disponible -ccx() { - local context="$1" - local target="${2:-.}" - - if [[ -z "$context" ]]; then - echo "Uso: ccx [directorio]" - return 1 - fi - - local resolved - resolved=$(cd "$target" 2>/dev/null && pwd) - - if [[ -z "$resolved" ]]; then - echo "❌ Directorio no encontrado: $target" - return 1 - fi - - if _cortex_use_herdr_runtime; then - cd "$resolved" && echo "$context" | claude - return - fi - - if [[ -n "$CMUX_WORKSPACE_ID" ]]; then - # Estamos dentro de cmux: escribir contexto a tempfile y abrir workspace propio - local workspace_name - workspace_name="$(_workspace_name_for_path "$resolved")" - - # Archivo de contexto en ~/.claude/ para garantizar accesibilidad desde el workspace nuevo - local ctxfile="$HOME/.claude/ccx-ctx-$$.txt" - echo "$context" > "$ctxfile" - chmod 600 "$ctxfile" - - # Crear workspace nuevo — el cleanup va dentro del comando para que - # el archivo siga existiendo cuando cmux lo lea en el workspace nuevo - # env -u descarta el socket heredado: ccx opera sobre el workspace destino explícito - if env -u CMUX_SOCKET_PATH -u CMUX_SOCKET cmux new-workspace --name "$workspace_name" --cwd "$resolved" --command "sh -c 'claude < $ctxfile; rm -f $ctxfile'"; then - : # limpieza la hace el comando en el workspace nuevo - else - # cmux falló: limpiar archivo y ejecutar claude directo con el contexto - rm -f "$ctxfile" - echo "⚠️ cmux new-workspace falló, ejecutando claude en el directorio actual" - cd "$resolved" && echo "$context" | claude - fi - elif [[ -n "$TMUX" ]]; then - cd "$resolved" && echo "$context" | claude - else - # No herdr, no cmux: try tmux as optional fallback - if command -v tmux &>/dev/null; then - local session - session="$(_workspace_name_for_path "$resolved")" - if tmux has-session -t "$session" 2>/dev/null; then - tmux attach -t "$session" - else - tmux new-session -d -s "$session" - tmux send-keys -t "$session" "cd '$resolved' && echo ${(q)context} | claude" Enter - tmux attach -t "$session" - fi - else - echo "⚠️ Instalá herdr o tmux para sesiones persistentes" - cd "$resolved" && echo "$context" | claude - fi - fi -} - -# Navegar al Claude workspace -ccd() { - local subpath="${1:-}" - local workspace="${WORKSPACE_DIR:-$HOME/dev}" - - local target - if [[ -n "$subpath" ]]; then - target="$workspace/$subpath" - else - target="$workspace" - fi - - if [[ -d "$target" ]]; then - cd "$target" - echo "📂 Navegando a: $target" - else - echo "❌ Directorio no encontrado: $target" - return 1 - fi -} - -# Copiar contexto de código al clipboard para Claude -ccclip() { - if [[ $# -eq 0 ]]; then - echo "Uso: ccclip [archivo2 ...] [-n|--line-numbers]" - return 1 - fi - - local with_numbers=false - local files=() - - for arg in "$@"; do - case "$arg" in - -n|--line-numbers) with_numbers=true ;; - *) files+=("$arg") ;; - esac - done - - local context="" - - for file in "${files[@]}"; do - if [[ ! -f "$file" ]]; then - echo "⚠️ Archivo no encontrado: $file" - continue - fi - - local ext="${file##*.}" - context+="\`\`\`$ext\n" - context+="// File: $file\n" - - if $with_numbers; then - local n=1 - while IFS= read -r line; do - context+=$(printf "%4d: %s\n" "$n" "$line") - (( n++ )) - done < "$file" - else - context+="$(cat "$file")\n" - fi - - context+="\`\`\`\n\n" - done - - echo -e "$context" | pbcopy - echo "✓ Contexto copiado al clipboard (${#files[@]} archivo$([ ${#files[@]} -ne 1 ] && echo 's'))" -} - -#endregion diff --git a/zsh/scripts/git-helpers.zsh b/zsh/scripts/git-helpers.zsh deleted file mode 100644 index 687d249..0000000 --- a/zsh/scripts/git-helpers.zsh +++ /dev/null @@ -1,85 +0,0 @@ -#region Git Helpers -# Helpers para manejo de múltiples identidades GitHub - -# Normaliza cualquier formato de URL de GitHub a "org/repo" -_parse_github_repo() { - local input="$1" - # Eliminar .git final - input="${input%.git}" - # https://github.com/org/repo → org/repo - input="${input#https://github.com/}" - # SSH GitHub URL → org/repo - input="${input#git@github.*:}" - # git@github-alias:org/repo → org/repo - input="${input#git@github*:}" - echo "$input" -} - -# Clonar repo de la cuenta workdev (tu cuenta de trabajo/empresa) -# Uso: clone-workdev -# Acepta: org/repo · org/repo.git · https://github.com/org/repo · SSH GitHub URL -clone-workdev() { - local raw="${1:?Uso: clone-workdev }" - local repo - repo=$(_parse_github_repo "$raw") - git clone "git@github-workdev:$repo.git" -} - -# Clonar repo de la cuenta personaldev (tu cuenta personal) -# Uso: clone-personaldev -# Acepta: org/repo · org/repo.git · https://github.com/org/repo · SSH GitHub URL -clone-personaldev() { - local raw="${1:?Uso: clone-personaldev }" - local repo - repo=$(_parse_github_repo "$raw") - git clone "git@github-personaldev:$repo.git" -} - -# Configurar identidad workdev (trabajo/empresa) en el repo actual -git-workdev() { - git config user.name "${GIT_WORK_NAME:-Your Name}" - git config user.email "${GIT_WORK_EMAIL:?Set GIT_WORK_EMAIL in ~/.config/cortex-dots/local/env.zsh}" - - # Actualizar remote si apunta a github.com o alias viejo - local url - url=$(git remote get-url origin 2>/dev/null) - if [[ "$url" == *"github.com"* || "$url" == *"github-work"* ]]; then - local new_url - new_url=$(echo "$url" | sed 's|git@github[^:]*:|git@github-workdev:|') - git remote set-url origin "$new_url" - echo "✓ Remote actualizado: $new_url" - fi - - echo "✓ Identidad: workdev (${GIT_WORK_EMAIL})" -} - -# Configurar identidad personaldev (cuenta personal) en el repo actual -git-personaldev() { - git config user.name "${GIT_PERSONAL_NAME:-Your Name}" - git config user.email "${GIT_PERSONAL_EMAIL:?Set GIT_PERSONAL_EMAIL in ~/.config/cortex-dots/local/env.zsh}" - - # Actualizar remote si apunta a github.com o alias viejo - local url - url=$(git remote get-url origin 2>/dev/null) - if [[ "$url" == *"github.com"* || "$url" == *"github-personal"* ]]; then - local new_url - new_url=$(echo "$url" | sed 's|git@github[^:]*:|git@github-personaldev:|') - git remote set-url origin "$new_url" - echo "✓ Remote actualizado: $new_url" - fi - - echo "✓ Identidad: personaldev (${GIT_PERSONAL_EMAIL})" -} - -# Ver identidad configurada en el repo actual -git-whoami() { - local name email remote - name=$(git config user.name 2>/dev/null || echo "no configurado") - email=$(git config user.email 2>/dev/null || echo "no configurado") - remote=$(git remote get-url origin 2>/dev/null || echo "sin remote") - echo " nombre : $name" - echo " email : $email" - echo " remote : $remote" -} - -#endregion diff --git a/zsh/scripts/herdr-helpers.zsh b/zsh/scripts/herdr-helpers.zsh deleted file mode 100644 index cdafa60..0000000 --- a/zsh/scripts/herdr-helpers.zsh +++ /dev/null @@ -1,183 +0,0 @@ -# herdr-helpers.zsh — Remote-first Herdr helpers. - -_herdr_context_label() { - local host - host="${HOST%%.*}" - host="${host:-$(hostname -s 2>/dev/null || hostname)}" - - if [[ -n "$SSH_CONNECTION" || -n "$SSH_CLIENT" || -n "$SSH_TTY" ]]; then - printf 'ssh-%s' "$host" - else - printf 'local-%s' "$host" - fi -} - -_herdr_workspace_name_for_path() { - local target="${1:-$PWD}" - local git_root repo_name branch - git_root=$(git -C "$target" rev-parse --show-toplevel 2>/dev/null || true) - - if [[ -n "$git_root" ]]; then - repo_name=$(basename "$git_root" | tr '.' '-') - branch=$(git -C "$git_root" branch --show-current 2>/dev/null || true) - if [[ -n "$branch" ]]; then - printf '%s-%s' "$repo_name" "${branch//[^A-Za-z0-9_.-]/-}" - else - printf '%s' "$repo_name" - fi - else - basename "$target" | tr '.' '-' - fi -} - -_herdr_session_name_for_path() { - printf '%s-%s' "$(_herdr_context_label)" "$(_herdr_workspace_name_for_path "${1:-$PWD}")" -} - -_herdr_session_name_with_suffix() { - local session_path="${1:-$PWD}" - local suffix="$2" - - if [[ -n "$suffix" ]]; then - printf '%s-%s' "$(_herdr_session_name_for_path "$session_path")" "${suffix//[^A-Za-z0-9_.-]/-}" - else - _herdr_session_name_for_path "$session_path" - fi -} - -_herdr_open_session_for_path() { - local target="${1:-$PWD}" - local suffix="${2:-}" - local resolved session workspace_label - - resolved=$(cd -q "$target" >/dev/null 2>&1 && pwd) || { - echo "Directorio no encontrado: $target" - return 1 - } - - if [[ "${HERDR_ENV:-}" == "1" ]]; then - workspace_label="$(_herdr_session_name_with_suffix "$resolved" "$suffix")" - workspace_label="${workspace_label#$(_herdr_context_label)-}" - _herdr_focus_or_create_workspace "$workspace_label" "$resolved" - return - fi - - session="$(_herdr_session_name_with_suffix "$resolved" "$suffix")" - CORTEX_MULTIPLEXER=herdr herdr --session "$session" -} - -_herdr_workspace_id_by_label() { - local label="$1" - herdr workspace list 2>/dev/null | python3 -c ' -import json -import sys - -label = sys.argv[1] -try: - data = json.load(sys.stdin) -except Exception: - sys.exit(0) - -for workspace in data.get("result", {}).get("workspaces", []): - if workspace.get("label") == label: - print(workspace.get("workspace_id", "")) - break -' "$label" 2>/dev/null -} - -_herdr_focus_or_create_workspace() { - local label="$1" - local cwd="$2" - local workspace_id - - workspace_id="$(_herdr_workspace_id_by_label "$label")" - if [[ -n "$workspace_id" ]]; then - herdr workspace focus "$workspace_id" >/dev/null && printf 'workspace: %s\n' "$label" - else - herdr workspace create --cwd "$cwd" --label "$label" --focus >/dev/null && printf 'workspace: %s\n' "$label" - fi -} - -_herdr_current_pane_id() { - command -v herdr >/dev/null 2>&1 || return 1 - herdr pane current --current 2>/dev/null | python3 -c 'import json,sys; print(json.load(sys.stdin)["result"]["pane"]["pane_id"])' 2>/dev/null -} - -# Entrar/crear una sesión Herdr nombrada por host + repo + branch del path actual. -hhere() { - _herdr_open_session_for_path "${1:-$PWD}" -} - -# Alias semántico de hhere: volver a la sesión principal del repo/branch actual. -hmain() { - hhere "${1:-$PWD}" -} - -# Entrar/crear una sesión Herdr con rol humano. Uso: hrole [path] -hrole() { - local role="${1:?Uso: hrole [path]}" - local target="${2:-$PWD}" - _herdr_open_session_for_path "$target" "$role" -} - -# Entrar/crear una sesión Herdr independiente con timestamp corto. -hnew() { - local target="${1:-$PWD}" - _herdr_open_session_for_path "$target" "new-$(date +%H%M%S)-$RANDOM" -} - -# Entrar/crear la sesión Herdr de trabajo principal/intenso para este repo/branch. -hfocus() { - _herdr_open_session_for_path "${1:-$PWD}" "focus" -} - -# Entrar/crear una sesión Herdr lateral para este repo/branch. -hside() { - _herdr_open_session_for_path "${1:-$PWD}" "side" -} - -# Entrar/crear una sesión Herdr temporal para pruebas o tareas descartables. -hscratch() { - _herdr_open_session_for_path "${1:-$PWD}" "scratch" -} - -# Attach remoto con Herdr. Uso: hremote [session] -hremote() { - local target="${1:?Uso: hremote [session]}" - local session="${2:-main}" - CORTEX_MULTIPLEXER=herdr CORTEX_SSH_TARGET="$target" herdr --remote "$target" --session "$session" -} - -# Renombrar el pane actual de Herdr con un label humano o uno derivado de repo/branch. -hname() { - local label="${1:-$(_herdr_workspace_name_for_path "$PWD")}" - local pane_id - pane_id="$(_herdr_current_pane_id)" || { - echo "No pude detectar el pane actual de Herdr" - return 1 - } - herdr pane rename "$pane_id" "$label" >/dev/null && printf 'pane: %s\n' "$label" -} - -# Mostrar contexto completo del shell/pane actual. -herdr-orient() { - printf 'host: %s\n' "$(hostname -s 2>/dev/null || hostname)" - printf 'cwd: %s\n' "$PWD" - if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then - printf 'repo: %s\n' "$(git rev-parse --show-toplevel)" - printf 'branch: %s\n' "$(git branch --show-current 2>/dev/null || printf detached)" - fi - [[ -n "$SSH_CONNECTION" ]] && printf 'ssh: %s\n' "${CORTEX_SSH_TARGET:-remote}" - - local pane_id - pane_id="$(_herdr_current_pane_id)" && printf 'herdr: %s\n' "$pane_id" -} - -# Mantener el comando muscular, pero con contexto Herdr incluido cuando existe. -whereami() { - herdr-orient -} - -alias h='herdr' -alias hs='herdr status' -alias hl='herdr workspace list' diff --git a/zsh/scripts/memsave-nudge.sh b/zsh/scripts/memsave-nudge.sh deleted file mode 100755 index 7c3a8f3..0000000 --- a/zsh/scripts/memsave-nudge.sh +++ /dev/null @@ -1,38 +0,0 @@ -#!/bin/sh -# Hook UserPromptSubmit: recuerda guardar contexto en work-brain cada 15 minutos. -# Si pasaron +15 min desde el último nudge y la sesión tiene +5 min, inyecta recordatorio. - -SESSION_ID="${CLAUDE_SESSION_ID:-$$}" -SESSION_FILE="/tmp/claude-nudge-${SESSION_ID}-start" -NUDGE_FILE="/tmp/claude-nudge-${SESSION_ID}-last" -NOW=$(date +%s) - -# Primer mensaje de la sesión: registrar inicio y salir sin nudge -if [ ! -f "$SESSION_FILE" ]; then - echo "$NOW" > "$SESSION_FILE" - exit 0 -fi - -SESSION_START=$(cat "$SESSION_FILE" 2>/dev/null || echo "$NOW") -SESSION_AGE=$((NOW - SESSION_START)) - -# Sesión menor a 5 minutos → no nudge todavía -[ "$SESSION_AGE" -lt 300 ] && exit 0 - -# Último nudge hace menos de 15 minutos → no repetir -if [ -f "$NUDGE_FILE" ]; then - LAST_NUDGE=$(cat "$NUDGE_FILE") - TIME_SINCE=$((NOW - LAST_NUDGE)) - [ "$TIME_SINCE" -lt 900 ] && exit 0 -fi - -# Registrar timestamp del nudge -echo "$NOW" > "$NUDGE_FILE" - -# Inyectar recordatorio como additionalContext -printf '%s\n' '{ - "hookSpecificOutput": { - "hookEventName": "UserPromptSubmit", - "additionalContext": "RECORDATORIO: Llevas más de 15 minutos sin guardar en work-brain. Si tomaste decisiones, resolviste algo no obvio, o completaste trabajo significativo, guardalo ahora con mem_save antes de continuar." - } -}' diff --git a/zsh/scripts/postcompact-hook.sh b/zsh/scripts/postcompact-hook.sh deleted file mode 100755 index 60b66da..0000000 --- a/zsh/scripts/postcompact-hook.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env bash -# Hook PostCompact: instruye recargar reglas y contexto después de compactación. - -_detect_project() { - local remote - remote=$(git remote get-url origin 2>/dev/null) - if [ -n "$remote" ]; then - basename "$remote" .git - return - fi - local git_root - git_root=$(git rev-parse --show-toplevel 2>/dev/null) - if [ -n "$git_root" ]; then - basename "$git_root" - return - fi - basename "$PWD" -} - -PROJECT=$(_detect_project) - -printf '%s\n' "{ - \"hookSpecificOutput\": { - \"hookEventName\": \"PostCompact\", - \"additionalContext\": \"Contexto compactado. Llamar mem_context() en work-brain para recargar reglas activas y contexto del proyecto: ${PROJECT}.\" - } -}" diff --git a/zsh/scripts/worktree-helpers.zsh b/zsh/scripts/worktree-helpers.zsh deleted file mode 100644 index 0c662c6..0000000 --- a/zsh/scripts/worktree-helpers.zsh +++ /dev/null @@ -1,129 +0,0 @@ -#region Worktree Helpers -# Funciones para gestión de git worktrees con integración Herdr. -# Detecta repos de proyectos PC automáticamente y bloquea la creación de worktrees en ellos. - -# Verifica si el repo actual contiene archivos de proyectos PC (Categoría B — prohibido worktree) -_wt_is_pc_repo() { - local git_root - git_root=$(git rev-parse --show-toplevel 2>/dev/null) || return 1 - - # Buscar cualquier extensión PC en el árbol (solo nombre, no contenido) - local pc_exts="wdp|wwp|wpp|wpj|wwh|wpw|prw|wdd|fic|mmo|ndx|wdr|wdq|wdi|wdt|wdv|wdk|rep|sty|cpl|bkp|wdg|wdc|wde|wdw" - git -C "$git_root" ls-files 2>/dev/null \ - | grep -qE "\.(${pc_exts})$" -} - -# Obtiene el directorio raíz del repo actual -_wt_git_root() { - git rev-parse --show-toplevel 2>/dev/null -} - -# Directorio base para worktrees por repo. -_wt_base_dir() { - printf '%s/dev/worktrees' "$HOME" -} - -# Calcula el path de worktree usando ~/dev/worktrees//. -_wt_path_for() { - local git_root="$1" - local name="$2" - local repo_name - repo_name=$(basename "$git_root") - printf '%s/%s/%s' "$(_wt_base_dir)" "$repo_name" "$name" -} - -# Crea un worktree en ~/dev/worktrees// y abre el agente desde Herdr. -# Uso: wtadd [branch] -# — nombre del worktree (crea ~/dev/worktrees//) -# [branch] — branch existente o nueva (default: crea branch nueva con el mismo nombre) -wtadd() { - local name="$1" - local branch="${2:-$1}" - - if [[ -z "$name" ]]; then - echo "Uso: wtadd [branch]" - echo " Ejemplos:" - echo " wtadd hotfix-login # crea branch hotfix-login" - echo " wtadd review-pr main # usa branch main existente" - return 1 - fi - - # Verificar que estamos en un repo git - local git_root - git_root=$(_wt_git_root) || { echo "❌ No estás dentro de un repo git"; return 1; } - - # Bloquear en repos PCSoft - if _wt_is_pc_repo; then - echo "❌ Repo de proyecto PC detectado — worktrees no permitidos" - echo " Riesgo: corrupción de archivos si el IDE abre el mismo proyecto desde dos directorios" - echo " Alternativa: hacé stash + checkout en el mismo directorio" - return 1 - fi - - local wt_path - wt_path=$(_wt_path_for "$git_root" "$name") - - # Verificar que no exista ya - if [[ -d "$wt_path" ]]; then - echo "❌ Ya existe: $wt_path" - return 1 - fi - - mkdir -p "$(dirname "$wt_path")" - - # Crear worktree: si el branch existe usarlo, si no crearlo nuevo - if git -C "$git_root" show-ref --verify --quiet "refs/heads/$branch" 2>/dev/null; then - echo "→ Usando branch existente: $branch" - git worktree add "$wt_path" "$branch" || return 1 - else - echo "→ Creando branch nueva: $branch" - git worktree add -b "$branch" "$wt_path" || return 1 - fi - - echo "✓ Worktree creado: $wt_path" - - # Abrir el agente en el flujo Herdr si está disponible. - if [[ "${CORTEX_MULTIPLEXER:-herdr}" == "herdr" ]]; then - echo "→ Abriendo agente para el worktree..." - cc "$wt_path" - fi -} - -# Lista los worktrees activos del repo actual. -# Uso: wtlist -wtlist() { - _wt_git_root &>/dev/null || { echo "❌ No estás dentro de un repo git"; return 1; } - - echo "" - git worktree list - echo "" -} - -# Elimina un worktree por nombre y hace prune de referencias obsoletas. -# Uso: wtremove -wtremove() { - local name="$1" - - if [[ -z "$name" ]]; then - echo "Uso: wtremove " - return 1 - fi - - local git_root - git_root=$(_wt_git_root) || { echo "❌ No estás dentro de un repo git"; return 1; } - - local wt_path - wt_path=$(_wt_path_for "$git_root" "$name") - - if [[ ! -d "$wt_path" ]]; then - echo "❌ No encontré worktree: $wt_path" - echo " Usá wtlist para ver los worktrees activos" - return 1 - fi - - git worktree remove "$wt_path" || return 1 - git worktree prune - echo "✓ Worktree eliminado: $wt_path" -} - -#endregion diff --git a/zsh/zshrc b/zsh/zshrc index e88e704..e410b66 100644 --- a/zsh/zshrc +++ b/zsh/zshrc @@ -220,16 +220,8 @@ _SCRIPTS_DIR="$_DOTFILES_DIR/zsh/scripts" if [[ -d "$_SCRIPTS_DIR" ]]; then [[ -f "$_SCRIPTS_DIR/pc-helpers.zsh" ]] && source "$_SCRIPTS_DIR/pc-helpers.zsh" [[ -f "$_SCRIPTS_DIR/screenshots.zsh" ]] && source "$_SCRIPTS_DIR/screenshots.zsh" - [[ -f "$_SCRIPTS_DIR/claude-helpers.zsh" ]] && source "$_SCRIPTS_DIR/claude-helpers.zsh" - [[ -f "$_SCRIPTS_DIR/git-helpers.zsh" ]] && source "$_SCRIPTS_DIR/git-helpers.zsh" [[ -f "$_SCRIPTS_DIR/ssh-helpers.zsh" ]] && source "$_SCRIPTS_DIR/ssh-helpers.zsh" - [[ -f "$_SCRIPTS_DIR/herdr-helpers.zsh" ]] && source "$_SCRIPTS_DIR/herdr-helpers.zsh" - [[ -f "$_SCRIPTS_DIR/worktree-helpers.zsh" ]] && source "$_SCRIPTS_DIR/worktree-helpers.zsh" - [[ -f "$_SCRIPTS_DIR/agent-state.sh" ]] && source "$_SCRIPTS_DIR/agent-state.sh" fi - -alias cortex.agent_state.v1="cortex_agent_state" -alias agent-state="cortex_agent_state" #endregion #region Local Overrides