# Plan: Resolve locode's 9 known issues ## Goals Fix all nine documented issues/limitations in one coherent pass, keep `typecheck` green, and add enough tests so `npm test` passes. ## Issues and implementation details ### 1. Grep bug: patterns starting with `-` are parsed as ripgrep flags **File:** `src/tools/grep.ts` **Approach:** Pass the pattern with ripgrep's `-e` option instead of as a positional argument, and put `--` before the path argument. This prevents any pattern (including `-foo`, `--foo`, `+foo`) from being interpreted as a flag. **Test:** Add `src/tools/grep.test.ts` that mocks `execa` and verifies the generated args for a leading-dash pattern. ### 2. Security: session id is not sanitized before building the file path **File:** `src/persistence/sessionStore.ts` **Approach:** - Derive a safe filename from the user-supplied id by replacing path separators and other unsafe characters with `_`. - Keep the original id inside the record (it already is), but use the sanitized id for the filesystem lookup. - Add a `safeId` helper and use it in `filePath`, `loadSession`, `deleteSession`, and `listSessions` so `--resume ../../etc/passwd` cannot escape the sessions directory. **Test:** Add `src/persistence/sessionStore.test.ts` verifying that malicious ids are contained, normal ids still work, and `listSessions` ignores non-`.json` files. ### 3. Tests: `npm test` fails because there are no test files **Approach:** Create the first test suite covering the fixes above. Add `vitest.config.ts` with `globals: true` and a `test/`/`src/**/*.test.ts` include. The tests will be pure unit tests that don't require a backend. **Files to add:** - `vitest.config.ts` - `src/tools/grep.test.ts` - `src/persistence/sessionStore.test.ts` - `src/plugins/expandTemplate.test.ts` (small sanity test for existing behavior) ### 4. MCP content types: non-text results are placeholder-only **Files:** `src/mcp/toolAdapter.ts`, `src/mcp/client.ts` (type update) **Approach:** - Update `McpToolInfo` and the call-tool handler to support `image`, `audio`, and `resource` content blocks from the MCP spec. - For `image`: include `mimeType` and a truncated base64 note; if the model path is vision-capable, return the actual `data:` URI so the model can see it (same pattern as `read_file`). - For `audio`: include `mimeType` and a note. - For `resource`: if it's a text resource, inline the text; if binary, note the URI and mime type. - Keep text blocks unchanged. **Test:** Add `src/mcp/toolAdapter.test.ts` with mocked MCP content blocks of each type. ### 5. Hooks: only 6 events; no structured JSON output; no HTTP/prompt/agent hook types **Files:** `src/hooks/types.ts`, `src/hooks/runner.ts`, `src/hooks/config.ts` **Approach (scoped but complete within reason):** - Expand the hook event set to the most useful missing events: `PermissionRequest`, `SubagentStart`, `SubagentStop`, `CwdChanged`, `FileChanged`, `ConfigChange`, `Stop` is already present. Final set: all 12 events from Claude Code's common set: `SessionStart`, `UserPromptSubmit`, `PreToolUse`, `PostToolUse`, `PermissionRequest`, `SubagentStart`, `SubagentStop`, `CwdChanged`, `FileChanged`, `ConfigChange`, `Stop`, `SessionEnd`. - Add typed payload shapes for each event in `src/hooks/types.ts`. - Add a structured output schema option: hooks can specify `outputSchema: "json"` in their config; when set, stdout is parsed as JSON and validated against a simple zod schema. If parsing fails, treat as a warning. - Add an `HTTPHook` type (simple GET/POST with optional headers/body) and a `PromptHook` type (shows a yes/no prompt to the user). Wire `HTTPHook` in the runner; `PromptHook` will be added to types but marked as not yet implemented in the runner to avoid UI-blocking complexity in this pass. - Fire `PermissionRequest` right before the user confirm gate in `agent/loop.ts gateAndRun`. - Fire `SubagentStart`/`SubagentStop` around `runSubAgentTurn`. - Fire `CwdChanged` when the session `cwd` would change (currently static; hook is informational for future use). - Fire `FileChanged` after a mutating `write_file`/`edit_file`/`bash` succeeds. - Fire `ConfigChange` when `/model`, `/backend`, `/mode`, or `locode config set` changes config. **Test:** Add `src/hooks/runner.test.ts` testing exit-code semantics (0/2/other), JSON output parsing, and tool-scoped matching. ### 6. Plugin collisions: MCP and skill name collisions are not resolved cleanly **Files:** `src/mcp/config.ts`, `src/plugins/skillTool.ts`, `src/plugins/registry.ts` **Approach:** - MCP servers: when a plugin provides a server whose name collides with a user or project server, log/record the collision and keep the more-specific tier (project > user > plugin). Add a `collisions` field to the returned merge result and surface it in `/mcp`. - Skills: namespace skills internally as `/` in the skill tool, but still accept the bare skill name for the `/name` shortcut. If two skills share a bare name, prefer the first loaded plugin and emit a warning in `/skills` listing duplicates. - Plugin agent names are already namespaced (`agent____`); commands are already namespaced by the fact that they share a single command namespace. Add a warning list for duplicate command names too. - Add a `PluginCollisionWarning` type and expose it via `getLoadedPlugins()` metadata. **Test:** Add `src/plugins/skillTool.test.ts` and `src/mcp/config.test.ts` for collision behavior. ### 7. Skill references: cannot load sibling reference files for a skill **Files:** `src/plugins/loader.ts`, `src/plugins/types.ts`, `src/plugins/skillTool.ts` **Approach:** - When loading a skill, also read any `references/*.md` files in the same skill directory. - Store them as `references: { name, content }[]` on `PluginSkill`. - When the skill is invoked (via the `skill` tool or `/name`), concatenate the references after the main SKILL.md body under a clear header so the model sees them. **Test:** Add `src/plugins/loader.test.ts` with a temporary in-memory plugin layout. ### 8. Git operations: `git_commit` covers a subset **File:** `src/tools/git.ts` **Approach:** Extend `git_commit` with these additional operations: - `reset` — `git reset` (mixed by default) with optional `ref` and `mode` (soft/mixed/hard). Requires confirmation; preview shows affected commits/files. - `stash` — `git stash push` (with optional message and paths) and `git stash pop` (with optional stash ref). Preview shows what will be stashed/popped. - `merge` — `git merge ` with optional `--no-ff`/`--ff-only`. Preview shows branches and commits. - `rebase` — `git rebase ` with optional `--onto`. Preview shows commits. - `delete_branch` — `git branch -d/-D `. Preview shows the branch and whether it has unmerged commits. - Add `operation` union entries and the necessary parameters (`ref`, `mode`, `stashRef`, `branchName`, `strategy`, `message`, `paths`, `force`). - Keep the existing preview/handler pattern. **Test:** Add `src/tools/git.test.ts` that validates argument generation for each operation (no real git exec). ### 9. Context window: 85% auto-compact threshold is hardcoded **Files:** `src/agent/loop.ts`, `src/config/config.ts`, `src/config/store.ts`, `src/config/defaults.ts`, `src/cli.ts` **Approach:** - Add `autoCompactThreshold` to `StoredConfig` and env var `LOCODE_AUTO_COMPACT_THRESHOLD`. - Default remains 0.85; allow values 0.1–0.95. - Add `locode config set autoCompactThreshold <0.0-1.0>` and `locode config get autoCompactThreshold`. - Read it in `resolveAutoCompactThreshold()` and use it in `shouldAutoCompact` in `agent/loop.ts`. - Pass the threshold into the `Session` object so sub-agents inherit it. **Test:** Add `src/config/config.test.ts` for threshold resolution and `src/agent/loop.test.ts` for the compact check. ## Files to modify 1. `src/tools/grep.ts` 2. `src/persistence/sessionStore.ts` 3. `src/mcp/toolAdapter.ts` 4. `src/mcp/client.ts` 5. `src/mcp/types.ts` (add status/collision type) 6. `src/mcp/config.ts` 7. `src/mcp/manager.ts` (surface collision warnings) 8. `src/hooks/types.ts` 9. `src/hooks/runner.ts` 10. `src/hooks/config.ts` 11. `src/agent/loop.ts` (fire new hooks, use threshold) 12. `src/agent/session.ts` (store threshold) 13. `src/plugins/loader.ts` 14. `src/plugins/types.ts` 15. `src/plugins/skillTool.ts` 16. `src/plugins/registry.ts` (collision tracking) 17. `src/plugins/agentTool.ts` (add command dup tracking if needed) 18. `src/tools/git.ts` 19. `src/config/config.ts` 20. `src/config/store.ts` 21. `src/config/defaults.ts` 22. `src/config/types.ts` 23. `src/cli.ts` (add config key) 24. `src/ui/ink/App.tsx` (pass threshold, fire ConfigChange) 25. `src/ui/ink/HistoryItemView.tsx` (show MCP collision warnings) 26. `README.md` (update limitations) ## Files to add 1. `vitest.config.ts` 2. `src/tools/grep.test.ts` 3. `src/persistence/sessionStore.test.ts` 4. `src/plugins/expandTemplate.test.ts` 5. `src/mcp/toolAdapter.test.ts` 6. `src/hooks/runner.test.ts` 7. `src/plugins/skillTool.test.ts` 8. `src/mcp/config.test.ts` 9. `src/plugins/loader.test.ts` 10. `src/tools/git.test.ts` 11. `src/config/config.test.ts` 12. `src/agent/loop.test.ts` ## Validation - Run `npm run typecheck` — must pass. - Run `npm test` — must pass. - Run `npm run build` — must succeed. ## Risks / trade-offs - Expanding hooks to 12 events touches `agent/loop.ts` in several places; need to keep event payloads consistent. - MCP content type support is best-effort; real vision models may still ignore audio/resource blocks. - Git operation expansion increases the chance of destructive commands (`reset --hard`, `rebase`, `delete_branch`); previews must be clear and the tool remains mutating/confirm-gated. - Making the auto-compact threshold configurable requires threading a new field through `Session` creation/resumption.