chore: upgrade crossterm 0.29, ratatui 0.30, and toml 1.1#4
Merged
Conversation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
donbeave
pushed a commit
that referenced
this pull request
Apr 11, 2026
- Fix cross-reference link in load.mdx: use #4-agent-source-trust (matching the numbered heading in security-model.mdx) - Rename test to `load_trusted_namespaced_agent_builds_and_runs` since it no longer exercises auto-registration (that's covered by the config test `resolve_agent_source_adds_owner_repo_on_first_use`) https://claude.ai/code/session_01SkHoAne5Q5EpUybYk6ghUT
donbeave
added a commit
that referenced
this pull request
Apr 11, 2026
* feat: implement trust-on-first-use model for third-party agent sources Add a trust gate that prompts operators before building untrusted third-party agents. Built-in agents are always trusted. New namespaced agents default to untrusted and require explicit confirmation on first use. The trusted flag is persisted in config.toml so subsequent runs proceed without prompts. Non-interactive sessions bail with a clear error for untrusted sources. Also syncs the roadmap with TODO items: adds missing completed item (JACKIN_DIND_HOSTNAME), adds missing planned items (Bollard migration), and marks the agent source trust security finding as resolved. https://claude.ai/code/session_01SkHoAne5Q5EpUybYk6ghUT * improve trust prompt with clear risk details and mark finding #9 resolved Rewrite the trust confirmation message to clearly explain what trusting an agent means: Dockerfile execution, arbitrary commands on the host, and workspace file access. The non-interactive error now tells the user how to pre-trust via config.toml. The decline message explains how to retry. Also mark security finding #9 (mount policy guardrails) as resolved since sensitive mount warnings were already implemented. https://claude.ai/code/session_01SkHoAne5Q5EpUybYk6ghUT * fix: address PR review — docs, stale comment, save clarity, test, serde Review fixes for the agent source trust model: - Fix duplicate doc comment on trust_agent() (copy-paste from sync_builtin_agents) - Replace confusing `!source.trusted` save condition with explicit `newly_trusted` flag for readability - Add unit test for non-interactive trust gate (verifies the error includes agent name and git URL) - Skip serializing `trusted = false` to keep config.toml clean (only `trusted = true` appears) - Document trust in load.mdx (step 3 in "What happens", warning callout for CI) and security-model.mdx (new section 4 with config.toml example, threat model row, best practices update) https://claude.ai/code/session_01SkHoAne5Q5EpUybYk6ghUT * fix: broken docs anchor link and misleading test name - Fix cross-reference link in load.mdx: use #4-agent-source-trust (matching the numbered heading in security-model.mdx) - Rename test to `load_trusted_namespaced_agent_builds_and_runs` since it no longer exercises auto-registration (that's covered by the config test `resolve_agent_source_adds_owner_repo_on_first_use`) https://claude.ai/code/session_01SkHoAne5Q5EpUybYk6ghUT * feat: add jackin trust/untrust CLI, testable trust gate, revocation Address all follow-up items from the maintainer review: - Extract load_agent_with() with injected trust callback, following the resolve_agent_repo_with() pattern. Tests can now exercise accept and decline paths without mocking dialoguer. - Restore auto-registration test coverage: the accept test uses load_agent_with(auto_trust) so resolve_agent_source's is_new path is exercised again. Add decline test verifying no config persistence and no Docker commands when trust is refused. - Add `jackin trust <selector>` and `jackin untrust <selector>` CLI commands so operators can manage trust without editing config.toml. - Add untrust_agent() to AppConfig with tests. - Document trust/untrust CLI in security-model.mdx and load.mdx. - Document that trust is keyed by selector (not URL) with a note about remote-mismatch protection. https://claude.ai/code/session_01SkHoAne5Q5EpUybYk6ghUT * refactor: consolidate trust/untrust into single CLI command Follow mise's pattern: `jackin trust` is one command with flags instead of separate trust/untrust subcommands. jackin trust <selector> # grant trust jackin trust <selector> --untrust # revoke trust jackin trust <selector> --show # check status The --show and --untrust flags conflict with each other (enforced by clap). https://claude.ai/code/session_01SkHoAne5Q5EpUybYk6ghUT * refactor: move trust CLI under config with grant/revoke/list subcommands Follow the same pattern as `jackin config mount {add,remove,list}`: jackin config trust grant chainargos/the-architect jackin config trust revoke chainargos/the-architect jackin config trust list This keeps all config mutations under `jackin config` and uses the subcommand pattern consistently throughout the CLI. The `list` subcommand shows all currently trusted agents. https://claude.ai/code/session_01SkHoAne5Q5EpUybYk6ghUT * fix: refuse to revoke trust on builtins, add CLI parsing tests - is_builtin_agent() check prevents revoking trust on built-in agents (agent-smith, the-architect) which would be silently re-granted by sync_builtin_agents on next load - Add 3 CLI parsing tests for config trust {grant, revoke, list}, matching the existing coverage for config mount {add, remove, list} - Tighten TrustCommand::List doc comment to "List all currently trusted agent sources" https://claude.ai/code/session_01SkHoAne5Q5EpUybYk6ghUT --------- Co-authored-by: Claude <noreply@anthropic.com>
donbeave
added a commit
that referenced
this pull request
Apr 20, 2026
- Move 9 TODO items from monolithic TODO.md into separate files in todo/ - Each file is a self-contained design doc with problem, options, and related source files for easy agent handoff - Mark resolved security findings (#3, #4, #6, #7) in SECURITY_REVIEW_FINDINGS.md - Update PROJECT_STRUCTURE.md with todo/ section and TESTING.md entry - TODO.md becomes an index pointing to todo/ files Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
donbeave
added a commit
that referenced
this pull request
Apr 20, 2026
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
donbeave
added a commit
that referenced
this pull request
Apr 20, 2026
* feat: implement trust-on-first-use model for third-party agent sources Add a trust gate that prompts operators before building untrusted third-party agents. Built-in agents are always trusted. New namespaced agents default to untrusted and require explicit confirmation on first use. The trusted flag is persisted in config.toml so subsequent runs proceed without prompts. Non-interactive sessions bail with a clear error for untrusted sources. Also syncs the roadmap with TODO items: adds missing completed item (JACKIN_DIND_HOSTNAME), adds missing planned items (Bollard migration), and marks the agent source trust security finding as resolved. https://claude.ai/code/session_01SkHoAne5Q5EpUybYk6ghUT * improve trust prompt with clear risk details and mark finding #9 resolved Rewrite the trust confirmation message to clearly explain what trusting an agent means: Dockerfile execution, arbitrary commands on the host, and workspace file access. The non-interactive error now tells the user how to pre-trust via config.toml. The decline message explains how to retry. Also mark security finding #9 (mount policy guardrails) as resolved since sensitive mount warnings were already implemented. https://claude.ai/code/session_01SkHoAne5Q5EpUybYk6ghUT * fix: address PR review — docs, stale comment, save clarity, test, serde Review fixes for the agent source trust model: - Fix duplicate doc comment on trust_agent() (copy-paste from sync_builtin_agents) - Replace confusing `!source.trusted` save condition with explicit `newly_trusted` flag for readability - Add unit test for non-interactive trust gate (verifies the error includes agent name and git URL) - Skip serializing `trusted = false` to keep config.toml clean (only `trusted = true` appears) - Document trust in load.mdx (step 3 in "What happens", warning callout for CI) and security-model.mdx (new section 4 with config.toml example, threat model row, best practices update) https://claude.ai/code/session_01SkHoAne5Q5EpUybYk6ghUT * fix: broken docs anchor link and misleading test name - Fix cross-reference link in load.mdx: use #4-agent-source-trust (matching the numbered heading in security-model.mdx) - Rename test to `load_trusted_namespaced_agent_builds_and_runs` since it no longer exercises auto-registration (that's covered by the config test `resolve_agent_source_adds_owner_repo_on_first_use`) https://claude.ai/code/session_01SkHoAne5Q5EpUybYk6ghUT * feat: add jackin trust/untrust CLI, testable trust gate, revocation Address all follow-up items from the maintainer review: - Extract load_agent_with() with injected trust callback, following the resolve_agent_repo_with() pattern. Tests can now exercise accept and decline paths without mocking dialoguer. - Restore auto-registration test coverage: the accept test uses load_agent_with(auto_trust) so resolve_agent_source's is_new path is exercised again. Add decline test verifying no config persistence and no Docker commands when trust is refused. - Add `jackin trust <selector>` and `jackin untrust <selector>` CLI commands so operators can manage trust without editing config.toml. - Add untrust_agent() to AppConfig with tests. - Document trust/untrust CLI in security-model.mdx and load.mdx. - Document that trust is keyed by selector (not URL) with a note about remote-mismatch protection. https://claude.ai/code/session_01SkHoAne5Q5EpUybYk6ghUT * refactor: consolidate trust/untrust into single CLI command Follow mise's pattern: `jackin trust` is one command with flags instead of separate trust/untrust subcommands. jackin trust <selector> # grant trust jackin trust <selector> --untrust # revoke trust jackin trust <selector> --show # check status The --show and --untrust flags conflict with each other (enforced by clap). https://claude.ai/code/session_01SkHoAne5Q5EpUybYk6ghUT * refactor: move trust CLI under config with grant/revoke/list subcommands Follow the same pattern as `jackin config mount {add,remove,list}`: jackin config trust grant chainargos/the-architect jackin config trust revoke chainargos/the-architect jackin config trust list This keeps all config mutations under `jackin config` and uses the subcommand pattern consistently throughout the CLI. The `list` subcommand shows all currently trusted agents. https://claude.ai/code/session_01SkHoAne5Q5EpUybYk6ghUT * fix: refuse to revoke trust on builtins, add CLI parsing tests - is_builtin_agent() check prevents revoking trust on built-in agents (agent-smith, the-architect) which would be silently re-granted by sync_builtin_agents on next load - Add 3 CLI parsing tests for config trust {grant, revoke, list}, matching the existing coverage for config mount {add, remove, list} - Tighten TrustCommand::List doc comment to "List all currently trusted agent sources" https://claude.ai/code/session_01SkHoAne5Q5EpUybYk6ghUT --------- Co-authored-by: Claude <noreply@anthropic.com>
donbeave
added a commit
that referenced
this pull request
Apr 21, 2026
- Move 9 TODO items from monolithic TODO.md into separate files in todo/ - Each file is a self-contained design doc with problem, options, and related source files for easy agent handoff - Mark resolved security findings (#3, #4, #6, #7) in SECURITY_REVIEW_FINDINGS.md - Update PROJECT_STRUCTURE.md with todo/ section and TESTING.md entry - TODO.md becomes an index pointing to todo/ files Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
donbeave
added a commit
that referenced
this pull request
Apr 21, 2026
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
donbeave
added a commit
that referenced
this pull request
Apr 21, 2026
* feat: implement trust-on-first-use model for third-party agent sources Add a trust gate that prompts operators before building untrusted third-party agents. Built-in agents are always trusted. New namespaced agents default to untrusted and require explicit confirmation on first use. The trusted flag is persisted in config.toml so subsequent runs proceed without prompts. Non-interactive sessions bail with a clear error for untrusted sources. Also syncs the roadmap with TODO items: adds missing completed item (JACKIN_DIND_HOSTNAME), adds missing planned items (Bollard migration), and marks the agent source trust security finding as resolved. https://claude.ai/code/session_01SkHoAne5Q5EpUybYk6ghUT * improve trust prompt with clear risk details and mark finding #9 resolved Rewrite the trust confirmation message to clearly explain what trusting an agent means: Dockerfile execution, arbitrary commands on the host, and workspace file access. The non-interactive error now tells the user how to pre-trust via config.toml. The decline message explains how to retry. Also mark security finding #9 (mount policy guardrails) as resolved since sensitive mount warnings were already implemented. https://claude.ai/code/session_01SkHoAne5Q5EpUybYk6ghUT * fix: address PR review — docs, stale comment, save clarity, test, serde Review fixes for the agent source trust model: - Fix duplicate doc comment on trust_agent() (copy-paste from sync_builtin_agents) - Replace confusing `!source.trusted` save condition with explicit `newly_trusted` flag for readability - Add unit test for non-interactive trust gate (verifies the error includes agent name and git URL) - Skip serializing `trusted = false` to keep config.toml clean (only `trusted = true` appears) - Document trust in load.mdx (step 3 in "What happens", warning callout for CI) and security-model.mdx (new section 4 with config.toml example, threat model row, best practices update) https://claude.ai/code/session_01SkHoAne5Q5EpUybYk6ghUT * fix: broken docs anchor link and misleading test name - Fix cross-reference link in load.mdx: use #4-agent-source-trust (matching the numbered heading in security-model.mdx) - Rename test to `load_trusted_namespaced_agent_builds_and_runs` since it no longer exercises auto-registration (that's covered by the config test `resolve_agent_source_adds_owner_repo_on_first_use`) https://claude.ai/code/session_01SkHoAne5Q5EpUybYk6ghUT * feat: add jackin trust/untrust CLI, testable trust gate, revocation Address all follow-up items from the maintainer review: - Extract load_agent_with() with injected trust callback, following the resolve_agent_repo_with() pattern. Tests can now exercise accept and decline paths without mocking dialoguer. - Restore auto-registration test coverage: the accept test uses load_agent_with(auto_trust) so resolve_agent_source's is_new path is exercised again. Add decline test verifying no config persistence and no Docker commands when trust is refused. - Add `jackin trust <selector>` and `jackin untrust <selector>` CLI commands so operators can manage trust without editing config.toml. - Add untrust_agent() to AppConfig with tests. - Document trust/untrust CLI in security-model.mdx and load.mdx. - Document that trust is keyed by selector (not URL) with a note about remote-mismatch protection. https://claude.ai/code/session_01SkHoAne5Q5EpUybYk6ghUT * refactor: consolidate trust/untrust into single CLI command Follow mise's pattern: `jackin trust` is one command with flags instead of separate trust/untrust subcommands. jackin trust <selector> # grant trust jackin trust <selector> --untrust # revoke trust jackin trust <selector> --show # check status The --show and --untrust flags conflict with each other (enforced by clap). https://claude.ai/code/session_01SkHoAne5Q5EpUybYk6ghUT * refactor: move trust CLI under config with grant/revoke/list subcommands Follow the same pattern as `jackin config mount {add,remove,list}`: jackin config trust grant chainargos/the-architect jackin config trust revoke chainargos/the-architect jackin config trust list This keeps all config mutations under `jackin config` and uses the subcommand pattern consistently throughout the CLI. The `list` subcommand shows all currently trusted agents. https://claude.ai/code/session_01SkHoAne5Q5EpUybYk6ghUT * fix: refuse to revoke trust on builtins, add CLI parsing tests - is_builtin_agent() check prevents revoking trust on built-in agents (agent-smith, the-architect) which would be silently re-granted by sync_builtin_agents on next load - Add 3 CLI parsing tests for config trust {grant, revoke, list}, matching the existing coverage for config mount {add, remove, list} - Tighten TrustCommand::List doc comment to "List all currently trusted agent sources" https://claude.ai/code/session_01SkHoAne5Q5EpUybYk6ghUT --------- Co-authored-by: Claude <noreply@anthropic.com>
donbeave
added a commit
that referenced
this pull request
Apr 21, 2026
- Move 9 TODO items from monolithic TODO.md into separate files in todo/ - Each file is a self-contained design doc with problem, options, and related source files for easy agent handoff - Mark resolved security findings (#3, #4, #6, #7) in SECURITY_REVIEW_FINDINGS.md - Update PROJECT_STRUCTURE.md with todo/ section and TESTING.md entry - TODO.md becomes an index pointing to todo/ files Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
donbeave
added a commit
that referenced
this pull request
Apr 21, 2026
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
donbeave
added a commit
that referenced
this pull request
Apr 21, 2026
* feat: implement trust-on-first-use model for third-party agent sources Add a trust gate that prompts operators before building untrusted third-party agents. Built-in agents are always trusted. New namespaced agents default to untrusted and require explicit confirmation on first use. The trusted flag is persisted in config.toml so subsequent runs proceed without prompts. Non-interactive sessions bail with a clear error for untrusted sources. Also syncs the roadmap with TODO items: adds missing completed item (JACKIN_DIND_HOSTNAME), adds missing planned items (Bollard migration), and marks the agent source trust security finding as resolved. https://claude.ai/code/session_01SkHoAne5Q5EpUybYk6ghUT * improve trust prompt with clear risk details and mark finding #9 resolved Rewrite the trust confirmation message to clearly explain what trusting an agent means: Dockerfile execution, arbitrary commands on the host, and workspace file access. The non-interactive error now tells the user how to pre-trust via config.toml. The decline message explains how to retry. Also mark security finding #9 (mount policy guardrails) as resolved since sensitive mount warnings were already implemented. https://claude.ai/code/session_01SkHoAne5Q5EpUybYk6ghUT * fix: address PR review — docs, stale comment, save clarity, test, serde Review fixes for the agent source trust model: - Fix duplicate doc comment on trust_agent() (copy-paste from sync_builtin_agents) - Replace confusing `!source.trusted` save condition with explicit `newly_trusted` flag for readability - Add unit test for non-interactive trust gate (verifies the error includes agent name and git URL) - Skip serializing `trusted = false` to keep config.toml clean (only `trusted = true` appears) - Document trust in load.mdx (step 3 in "What happens", warning callout for CI) and security-model.mdx (new section 4 with config.toml example, threat model row, best practices update) https://claude.ai/code/session_01SkHoAne5Q5EpUybYk6ghUT * fix: broken docs anchor link and misleading test name - Fix cross-reference link in load.mdx: use #4-agent-source-trust (matching the numbered heading in security-model.mdx) - Rename test to `load_trusted_namespaced_agent_builds_and_runs` since it no longer exercises auto-registration (that's covered by the config test `resolve_agent_source_adds_owner_repo_on_first_use`) https://claude.ai/code/session_01SkHoAne5Q5EpUybYk6ghUT * feat: add jackin trust/untrust CLI, testable trust gate, revocation Address all follow-up items from the maintainer review: - Extract load_agent_with() with injected trust callback, following the resolve_agent_repo_with() pattern. Tests can now exercise accept and decline paths without mocking dialoguer. - Restore auto-registration test coverage: the accept test uses load_agent_with(auto_trust) so resolve_agent_source's is_new path is exercised again. Add decline test verifying no config persistence and no Docker commands when trust is refused. - Add `jackin trust <selector>` and `jackin untrust <selector>` CLI commands so operators can manage trust without editing config.toml. - Add untrust_agent() to AppConfig with tests. - Document trust/untrust CLI in security-model.mdx and load.mdx. - Document that trust is keyed by selector (not URL) with a note about remote-mismatch protection. https://claude.ai/code/session_01SkHoAne5Q5EpUybYk6ghUT * refactor: consolidate trust/untrust into single CLI command Follow mise's pattern: `jackin trust` is one command with flags instead of separate trust/untrust subcommands. jackin trust <selector> # grant trust jackin trust <selector> --untrust # revoke trust jackin trust <selector> --show # check status The --show and --untrust flags conflict with each other (enforced by clap). https://claude.ai/code/session_01SkHoAne5Q5EpUybYk6ghUT * refactor: move trust CLI under config with grant/revoke/list subcommands Follow the same pattern as `jackin config mount {add,remove,list}`: jackin config trust grant chainargos/the-architect jackin config trust revoke chainargos/the-architect jackin config trust list This keeps all config mutations under `jackin config` and uses the subcommand pattern consistently throughout the CLI. The `list` subcommand shows all currently trusted agents. https://claude.ai/code/session_01SkHoAne5Q5EpUybYk6ghUT * fix: refuse to revoke trust on builtins, add CLI parsing tests - is_builtin_agent() check prevents revoking trust on built-in agents (agent-smith, the-architect) which would be silently re-granted by sync_builtin_agents on next load - Add 3 CLI parsing tests for config trust {grant, revoke, list}, matching the existing coverage for config mount {add, remove, list} - Tighten TrustCommand::List doc comment to "List all currently trusted agent sources" https://claude.ai/code/session_01SkHoAne5Q5EpUybYk6ghUT --------- Co-authored-by: Claude <noreply@anthropic.com>
donbeave
added a commit
that referenced
this pull request
Apr 21, 2026
- Move 9 TODO items from monolithic TODO.md into separate files in todo/ - Each file is a self-contained design doc with problem, options, and related source files for easy agent handoff - Mark resolved security findings (#3, #4, #6, #7) in SECURITY_REVIEW_FINDINGS.md - Update PROJECT_STRUCTURE.md with todo/ section and TESTING.md entry - TODO.md becomes an index pointing to todo/ files Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
donbeave
added a commit
that referenced
this pull request
Apr 21, 2026
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
donbeave
added a commit
that referenced
this pull request
Apr 21, 2026
* feat: implement trust-on-first-use model for third-party agent sources Add a trust gate that prompts operators before building untrusted third-party agents. Built-in agents are always trusted. New namespaced agents default to untrusted and require explicit confirmation on first use. The trusted flag is persisted in config.toml so subsequent runs proceed without prompts. Non-interactive sessions bail with a clear error for untrusted sources. Also syncs the roadmap with TODO items: adds missing completed item (JACKIN_DIND_HOSTNAME), adds missing planned items (Bollard migration), and marks the agent source trust security finding as resolved. https://claude.ai/code/session_01SkHoAne5Q5EpUybYk6ghUT * improve trust prompt with clear risk details and mark finding #9 resolved Rewrite the trust confirmation message to clearly explain what trusting an agent means: Dockerfile execution, arbitrary commands on the host, and workspace file access. The non-interactive error now tells the user how to pre-trust via config.toml. The decline message explains how to retry. Also mark security finding #9 (mount policy guardrails) as resolved since sensitive mount warnings were already implemented. https://claude.ai/code/session_01SkHoAne5Q5EpUybYk6ghUT * fix: address PR review — docs, stale comment, save clarity, test, serde Review fixes for the agent source trust model: - Fix duplicate doc comment on trust_agent() (copy-paste from sync_builtin_agents) - Replace confusing `!source.trusted` save condition with explicit `newly_trusted` flag for readability - Add unit test for non-interactive trust gate (verifies the error includes agent name and git URL) - Skip serializing `trusted = false` to keep config.toml clean (only `trusted = true` appears) - Document trust in load.mdx (step 3 in "What happens", warning callout for CI) and security-model.mdx (new section 4 with config.toml example, threat model row, best practices update) https://claude.ai/code/session_01SkHoAne5Q5EpUybYk6ghUT * fix: broken docs anchor link and misleading test name - Fix cross-reference link in load.mdx: use #4-agent-source-trust (matching the numbered heading in security-model.mdx) - Rename test to `load_trusted_namespaced_agent_builds_and_runs` since it no longer exercises auto-registration (that's covered by the config test `resolve_agent_source_adds_owner_repo_on_first_use`) https://claude.ai/code/session_01SkHoAne5Q5EpUybYk6ghUT * feat: add jackin trust/untrust CLI, testable trust gate, revocation Address all follow-up items from the maintainer review: - Extract load_agent_with() with injected trust callback, following the resolve_agent_repo_with() pattern. Tests can now exercise accept and decline paths without mocking dialoguer. - Restore auto-registration test coverage: the accept test uses load_agent_with(auto_trust) so resolve_agent_source's is_new path is exercised again. Add decline test verifying no config persistence and no Docker commands when trust is refused. - Add `jackin trust <selector>` and `jackin untrust <selector>` CLI commands so operators can manage trust without editing config.toml. - Add untrust_agent() to AppConfig with tests. - Document trust/untrust CLI in security-model.mdx and load.mdx. - Document that trust is keyed by selector (not URL) with a note about remote-mismatch protection. https://claude.ai/code/session_01SkHoAne5Q5EpUybYk6ghUT * refactor: consolidate trust/untrust into single CLI command Follow mise's pattern: `jackin trust` is one command with flags instead of separate trust/untrust subcommands. jackin trust <selector> # grant trust jackin trust <selector> --untrust # revoke trust jackin trust <selector> --show # check status The --show and --untrust flags conflict with each other (enforced by clap). https://claude.ai/code/session_01SkHoAne5Q5EpUybYk6ghUT * refactor: move trust CLI under config with grant/revoke/list subcommands Follow the same pattern as `jackin config mount {add,remove,list}`: jackin config trust grant chainargos/the-architect jackin config trust revoke chainargos/the-architect jackin config trust list This keeps all config mutations under `jackin config` and uses the subcommand pattern consistently throughout the CLI. The `list` subcommand shows all currently trusted agents. https://claude.ai/code/session_01SkHoAne5Q5EpUybYk6ghUT * fix: refuse to revoke trust on builtins, add CLI parsing tests - is_builtin_agent() check prevents revoking trust on built-in agents (agent-smith, the-architect) which would be silently re-granted by sync_builtin_agents on next load - Add 3 CLI parsing tests for config trust {grant, revoke, list}, matching the existing coverage for config mount {add, remove, list} - Tighten TrustCommand::List doc comment to "List all currently trusted agent sources" https://claude.ai/code/session_01SkHoAne5Q5EpUybYk6ghUT --------- Co-authored-by: Claude <noreply@anthropic.com>
donbeave
added a commit
that referenced
this pull request
Apr 21, 2026
- Move 9 TODO items from monolithic TODO.md into separate files in todo/ - Each file is a self-contained design doc with problem, options, and related source files for easy agent handoff - Mark resolved security findings (#3, #4, #6, #7) in SECURITY_REVIEW_FINDINGS.md - Update PROJECT_STRUCTURE.md with todo/ section and TESTING.md entry - TODO.md becomes an index pointing to todo/ files Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
donbeave
added a commit
that referenced
this pull request
Apr 21, 2026
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
donbeave
added a commit
that referenced
this pull request
Apr 21, 2026
* feat: implement trust-on-first-use model for third-party agent sources Add a trust gate that prompts operators before building untrusted third-party agents. Built-in agents are always trusted. New namespaced agents default to untrusted and require explicit confirmation on first use. The trusted flag is persisted in config.toml so subsequent runs proceed without prompts. Non-interactive sessions bail with a clear error for untrusted sources. Also syncs the roadmap with TODO items: adds missing completed item (JACKIN_DIND_HOSTNAME), adds missing planned items (Bollard migration), and marks the agent source trust security finding as resolved. https://claude.ai/code/session_01SkHoAne5Q5EpUybYk6ghUT * improve trust prompt with clear risk details and mark finding #9 resolved Rewrite the trust confirmation message to clearly explain what trusting an agent means: Dockerfile execution, arbitrary commands on the host, and workspace file access. The non-interactive error now tells the user how to pre-trust via config.toml. The decline message explains how to retry. Also mark security finding #9 (mount policy guardrails) as resolved since sensitive mount warnings were already implemented. https://claude.ai/code/session_01SkHoAne5Q5EpUybYk6ghUT * fix: address PR review — docs, stale comment, save clarity, test, serde Review fixes for the agent source trust model: - Fix duplicate doc comment on trust_agent() (copy-paste from sync_builtin_agents) - Replace confusing `!source.trusted` save condition with explicit `newly_trusted` flag for readability - Add unit test for non-interactive trust gate (verifies the error includes agent name and git URL) - Skip serializing `trusted = false` to keep config.toml clean (only `trusted = true` appears) - Document trust in load.mdx (step 3 in "What happens", warning callout for CI) and security-model.mdx (new section 4 with config.toml example, threat model row, best practices update) https://claude.ai/code/session_01SkHoAne5Q5EpUybYk6ghUT * fix: broken docs anchor link and misleading test name - Fix cross-reference link in load.mdx: use #4-agent-source-trust (matching the numbered heading in security-model.mdx) - Rename test to `load_trusted_namespaced_agent_builds_and_runs` since it no longer exercises auto-registration (that's covered by the config test `resolve_agent_source_adds_owner_repo_on_first_use`) https://claude.ai/code/session_01SkHoAne5Q5EpUybYk6ghUT * feat: add jackin trust/untrust CLI, testable trust gate, revocation Address all follow-up items from the maintainer review: - Extract load_agent_with() with injected trust callback, following the resolve_agent_repo_with() pattern. Tests can now exercise accept and decline paths without mocking dialoguer. - Restore auto-registration test coverage: the accept test uses load_agent_with(auto_trust) so resolve_agent_source's is_new path is exercised again. Add decline test verifying no config persistence and no Docker commands when trust is refused. - Add `jackin trust <selector>` and `jackin untrust <selector>` CLI commands so operators can manage trust without editing config.toml. - Add untrust_agent() to AppConfig with tests. - Document trust/untrust CLI in security-model.mdx and load.mdx. - Document that trust is keyed by selector (not URL) with a note about remote-mismatch protection. https://claude.ai/code/session_01SkHoAne5Q5EpUybYk6ghUT * refactor: consolidate trust/untrust into single CLI command Follow mise's pattern: `jackin trust` is one command with flags instead of separate trust/untrust subcommands. jackin trust <selector> # grant trust jackin trust <selector> --untrust # revoke trust jackin trust <selector> --show # check status The --show and --untrust flags conflict with each other (enforced by clap). https://claude.ai/code/session_01SkHoAne5Q5EpUybYk6ghUT * refactor: move trust CLI under config with grant/revoke/list subcommands Follow the same pattern as `jackin config mount {add,remove,list}`: jackin config trust grant chainargos/the-architect jackin config trust revoke chainargos/the-architect jackin config trust list This keeps all config mutations under `jackin config` and uses the subcommand pattern consistently throughout the CLI. The `list` subcommand shows all currently trusted agents. https://claude.ai/code/session_01SkHoAne5Q5EpUybYk6ghUT * fix: refuse to revoke trust on builtins, add CLI parsing tests - is_builtin_agent() check prevents revoking trust on built-in agents (agent-smith, the-architect) which would be silently re-granted by sync_builtin_agents on next load - Add 3 CLI parsing tests for config trust {grant, revoke, list}, matching the existing coverage for config mount {add, remove, list} - Tighten TrustCommand::List doc comment to "List all currently trusted agent sources" https://claude.ai/code/session_01SkHoAne5Q5EpUybYk6ghUT --------- Co-authored-by: Claude <noreply@anthropic.com>
3 tasks
donbeave
added a commit
that referenced
this pull request
Apr 24, 2026
Previously, `commit_editor_save` mutated `editor.mode` to the new name immediately after a successful `ce.rename_workspace` -- leaving the editor UI reflecting the new name if a later step (`ce.edit_workspace` / `ce.save`) failed, while disk/config still held the old name. Thread the pending-rename through the inner match as an `Option<String>` return value, and apply it to `editor.mode` only inside the `ce.save()` success arm after config has been reloaded. Addresses finding #4 of the PR #166 current-branch review. Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
donbeave
added a commit
that referenced
this pull request
Apr 24, 2026
Mechanical cleanup of clippy warnings in files added or modified by PR #166. Addresses finding #4 of the second-pass review: tests/manager_flow.rs: - missing_const_for_fn on the `key` helper -> mark `const fn`. - redundant_clone on the third use of `host_path` -> drop the trailing `.clone()` so the value moves on its last use. - default_trait_access on `Default::default()` for env/agents fields -> use the explicit `BTreeMap::new()` constructor. src/launch/manager/input/save.rs (test module): - used_underscore_binding on `_tmp` -> rename to `tmp`. The binding has to be held to keep the TempDir alive for the test, but it is also read via `.path()`, so the underscore was misleading; the rename makes the read explicit. - used_underscore_binding on `_config0` -> swap for the `_` bit-bucket pattern; the loaded config is genuinely unused in those tests (they re-open the config via ConfigEditor). - redundant_clone on `ws_b.clone()` at the last use site -> drop the clone and let the value move. No #[allow(...)] suppressions -- every warning is addressed by code change. Warnings in files outside this PR remain as documented pre-existing drift (finding #9 of the first-pass review). Addresses finding #4 of the second-pass PR #166 review. Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
donbeave
added a commit
that referenced
this pull request
Apr 26, 2026
… line count corrections - Add instance/auth.rs to //! priority queue at #4: four security invariants (0o600 perms, symlink rejection, TOCTOU-safe writes, macOS Keychain) documented in draft //! content - Add state.rs as new Rule 5 violator: 992L/628L production; 26+ types mixed with impl blocks; propose 5-file types/behavior split - Correct stale line counts: render/list.rs 1122→1989 (PR #171 added render_environments_subpanel); state.rs 865→992; priorities upgraded - Fix §7.9 snapshot function line refs: sentinel_description_pane 306→332, mounts_subpanel 408→433, render_tab_strip 180→269, test ref 720→944 - Renumber //! priority queue to 11 entries (was 10) Co-authored-by: Claude <noreply@anthropic.com>
donbeave
added a commit
that referenced
this pull request
Apr 26, 2026
… line count corrections - Add instance/auth.rs to //! priority queue at #4: four security invariants (0o600 perms, symlink rejection, TOCTOU-safe writes, macOS Keychain) documented in draft //! content - Add state.rs as new Rule 5 violator: 992L/628L production; 26+ types mixed with impl blocks; propose 5-file types/behavior split - Correct stale line counts: render/list.rs 1122→1989 (PR #171 added render_environments_subpanel); state.rs 865→992; priorities upgraded - Fix §7.9 snapshot function line refs: sentinel_description_pane 306→332, mounts_subpanel 408→433, render_tab_strip 180→269, test ref 720→944 - Renumber //! priority queue to 11 entries (was 10) Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
donbeave
added a commit
that referenced
this pull request
Apr 26, 2026
… line count corrections - Add instance/auth.rs to //! priority queue at #4: four security invariants (0o600 perms, symlink rejection, TOCTOU-safe writes, macOS Keychain) documented in draft //! content - Add state.rs as new Rule 5 violator: 992L/628L production; 26+ types mixed with impl blocks; propose 5-file types/behavior split - Correct stale line counts: render/list.rs 1122→1989 (PR #171 added render_environments_subpanel); state.rs 865→992; priorities upgraded - Fix §7.9 snapshot function line refs: sentinel_description_pane 306→332, mounts_subpanel 408→433, render_tab_strip 180→269, test ref 720→944 - Renumber //! priority queue to 11 entries (was 10) Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
donbeave
added a commit
that referenced
this pull request
Apr 26, 2026
… line count corrections - Add instance/auth.rs to //! priority queue at #4: four security invariants (0o600 perms, symlink rejection, TOCTOU-safe writes, macOS Keychain) documented in draft //! content - Add state.rs as new Rule 5 violator: 992L/628L production; 26+ types mixed with impl blocks; propose 5-file types/behavior split - Correct stale line counts: render/list.rs 1122→1989 (PR #171 added render_environments_subpanel); state.rs 865→992; priorities upgraded - Fix §7.9 snapshot function line refs: sentinel_description_pane 306→332, mounts_subpanel 408→433, render_tab_strip 180→269, test ref 720→944 - Renumber //! priority queue to 11 entries (was 10) Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
donbeave
added a commit
that referenced
this pull request
Apr 26, 2026
… line count corrections - Add instance/auth.rs to //! priority queue at #4: four security invariants (0o600 perms, symlink rejection, TOCTOU-safe writes, macOS Keychain) documented in draft //! content - Add state.rs as new Rule 5 violator: 992L/628L production; 26+ types mixed with impl blocks; propose 5-file types/behavior split - Correct stale line counts: render/list.rs 1122→1989 (PR #171 added render_environments_subpanel); state.rs 865→992; priorities upgraded - Fix §7.9 snapshot function line refs: sentinel_description_pane 306→332, mounts_subpanel 408→433, render_tab_strip 180→269, test ref 720→944 - Renumber //! priority queue to 11 entries (was 10) Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
donbeave
added a commit
that referenced
this pull request
Apr 26, 2026
…183) * docs(roadmap): iteration 13 — AI code verifiability framing, config/types.rs full spec Primary goal shift: codebase must be verifiable for AI-generated code. - §0: replace generic description with explicit verifiability rationale (module contracts, localised concerns, types/behaviour separation) - §4 intro: add "Why structure matters for AI-generated code" section with audit-units table mapping each post-split file to one reviewable question - §4 4a: expand config/types.rs from description to full execution spec — exact type list, post-split mod.rs content, zero-change submodule guarantee (verified: agents.rs/persist.rs/workspaces.rs use super::T which resolves through mod.rs re-exports unchanged), impl-extension pattern already in use documented Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 14 — editor method map, app helpers, //! queue - §4 4c: config/editor.rs split is now execution-ready — complete 6-file method-to-file table with private helper placement verified (validate_candidate→io.rs, table_path_mut→mod.rs pub(super), auth_forward_str→agent_ops.rs, create_workspace delegates to AppConfig) - §4 4e: app/mod.rs split complete — all private helpers mapped (parse_auth_forward_mode_from_cli→config_cmd.rs, workspace_env_scope→workspace_cmd.rs, print_env_table note, remove_data_dir_if_exists→dispatch.rs) - §10 step 5: add //! priority queue — 10 files with draft content, prioritised by cold-landing impact and AI audit risk; selector.rs and instance/mod.rs explicitly document the /→__ invariant Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 15 — dep graph fix, trust.rs safety, OQ1 closed - §4 4d: correct operator_env dependency graph — layers.rs imports both mod.rs (OpRunner) AND client.rs (OpCli for non-injectable resolve_operator_env wrapper at line 797); still a valid DAG - §4 4f: verify trust.rs split safety — FnOnce injection pattern means launch_pipeline.rs has zero dependency on trust.rs; import chain documented; trust bypass audit now requires reading only ~60L - §9 OQ1 closed: op_cache.rs read in full — 4-level structure, per-level invalidation, no TTL/expiry (expiry handled at OpCli subprocess level), DEFAULT_ACCOUNT_KEY sentinel documented Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 16 — CommandRunner Rule 3, render/editor split, 4a/4c independence - Fix duplicate Rule 3 section introduced by previous edit; add docker.rs co-location note as third edge case (three edge cases, not two) - Add render/editor.rs as new Rule 5 violator: 1666L post-PR #171 (was listed as 782L); propose 6-file tab-by-tab split with auditability note on the security-adjacent Secrets tab - Add §10 execution-order note: 4a and 4c are independent — editor.rs imports AppConfig via crate::config re-exports regardless of 4a order - Append iteration 16 log entry with confidence table and weakest sections Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 17 — instance/auth.rs audit, state.rs split, line count corrections - Add instance/auth.rs to //! priority queue at #4: four security invariants (0o600 perms, symlink rejection, TOCTOU-safe writes, macOS Keychain) documented in draft //! content - Add state.rs as new Rule 5 violator: 992L/628L production; 26+ types mixed with impl blocks; propose 5-file types/behavior split - Correct stale line counts: render/list.rs 1122→1989 (PR #171 added render_environments_subpanel); state.rs 865→992; priorities upgraded - Fix §7.9 snapshot function line refs: sentinel_description_pane 306→332, mounts_subpanel 408→433, render_tab_strip 180→269, test ref 720→944 - Renumber //! priority queue to 11 entries (was 10) Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 18 — agent_allow OQ2 closed, render/list.rs split proposal - Close OQ2: agent_allow.rs read in full — 55L, correct //! doc, design sound; serves as model for //! priority queue pattern - Add render/list.rs as new Rule 5 violator: 668L production (PR #171 added render_environments_subpanel); propose 3-file split (mod.rs, details.rs, subpanels.rs); note import-path change for agents_block_agent_count - Update §1 module map: agent_allow.rs entry corrected with size/API Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 19 — input/editor.rs critical correction, split proposal - Correct input/editor.rs: 2349L total (was 1304L), 1141L production (was 547L) — PR #171 added Secrets-tab handlers; pub(super) fn handle_editor_modal at line 618 was invisible to previous grep pattern; now the largest production file in the codebase; priority → Critical - Correct input/save.rs: 1472L total, 661L production (was 567L) - Add 5-file split proposal for input/editor.rs: mod.rs (two dispatchers), secrets.rs (~500L AI-generated Secrets-tab), agents.rs, mounts.rs, general.rs - Update key insight paragraph naming input/editor.rs as largest production file Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 20 — console splits in §10, MSRV evidence, animation.rs verdict - Add console/manager/ as §10 Step 4f group with 5 sub-steps in priority order; rename existing 4f (launch.rs) → 4g; add circular-import risk note for ManagerStage/EditorState split sequencing - Analyze tui/animation.rs: 582L all-production, no split needed (banner_grid is a tightly-coupled rendering loop); section comments compensate for missing //! - Partially close OQ3: u64::is_multiple_of (stabilized 1.86) found in animation.rs; within declared MSRV 1.94; full cargo +1.94.0 check deferred (toolchain unavailable) Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 21 — input/save.rs split, //! queue fix, save.rs corrections - Add input/save.rs split proposal: 4 pub(super) fns discovered; 3-file split (mod.rs + flow.rs + preview.rs); no cross-dependency between flow and preview groups; §10 4f-v updated from Optional to concrete plan - Fix //! queue preamble: "first 10 files" → "first 11 files" - Correct save.rs module map (1418→1472L, correct key exports) and hot-spot table note (begin_editor_save ~280L → ~118L; commit_editor_save is the Phase 2 partner at ~149L) Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 22 — input/list.rs and mount_info.rs analysis - Analyze input/list.rs: 214L production (tests at 215); has //! doc; two focused pub(super) fns; no split needed; Low priority; correct module map - Add mount_info.rs to hot-spot table: 277L production; Low priority; has //! doc; correct module map with 3 public enums + inspect fn - Fix stale §2 diagnosis note: docs/internal/roadmap/ now exists Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 23 — audit units table +5 console rows, input/mod.rs corrected - Expand audit units table from 8 to 13 entries: add state/types.rs, state/editor.rs, input/editor/secrets.rs, render/list/subpanels.rs, input/save/preview.rs — all targeting PR #171 AI-generated console code - Add PR #171 context note linking 5 new entries to AI-generated code concern - Correct input/mod.rs module map: 369L, add InputOutcome enum to exports - Verify rust-toolchain.toml absence; §7.7 and §2 concept 25 already correct Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 24 — render/mod.rs analysis, //! exemplars table, EditorTab confirmed - Add §4 Rule 7 positive exemplars table: 7 files with //! docs graded 1-element (render/mod.rs), 2-element (input/save.rs etc), 3-element (env_model.rs, agent_allow.rs); PR #171 docs-discipline pattern noted - Correct render/mod.rs module map: 421L; FooterItem + palette constants + render_header + centered_rect_fixed added to key exports - Confirm EditorTab variants: General, Mounts, Agents, Secrets (Rust enum) vs "Secrets / Environments" (UI label); /stub qualifier already removed Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 25 — too_many_lines recount, FooterItem PR, MountConfig caveat - Correct too_many_lines count: 13 across 8 → 16 across 11 files (PR #171 added 5 suppressions in console/manager); add full breakdown table; update all 3 occurrences in roadmap - Fix FooterItem PR reference: #165 → #166 (confirmed by git log --follow) - Add MountConfig → MountSpec rename caveat to §7.5 snapshot test description Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 26 — console/mod.rs and op_picker/render.rs analyzed - Add console/mod.rs to hot-spot table: 406L/307L production (Low); correct module map from ~200 → 406L; note missing //! doc with ConsoleStage design block comment worth promoting - Add op_picker/render.rs to hot-spot table: 865L/545L production (Medium); PR #171 AI-generated; 14 functions in two logical groups (entry/helpers vs level renderers); split into levels.rs proposed - Correct 3 stale ~200L estimates for console/mod.rs across roadmap Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 27 — op_picker/mod.rs discovery, render split, operator_env correction - Add op_picker/mod.rs to hot-spot table: 1712L/775L production (High); PR #171 AI-generated; OpPickerState types+behavior split opportunity; has 7-line //! doc; module map split into two rows (mod.rs + render.rs) - Add op_picker/render.rs 2-file split proposal: render.rs (coordinator) + render_pane.rs (pane/level renderers); no cross-dependency confirmed - Correct operator_env.rs total: 1569→2130L (880L production); update 4 occurrences across hot-spot table, ASCII tree, §4 analysis Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 28 — op_picker/mod.rs 3-file split, count corrections - Add op_picker/mod.rs formal 3-file split: loading.rs (async load family ~120L) + keys.rs (4 level key handlers ~315L) + mod.rs (types/constructors) - Correct "24 files" → "28+" for 500L threshold count - Update total LOC: ~40,664 → ~43,587 (2 occurrences, with provenance note) Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 29 — op_picker execution order + file_browser analysis - §10 Step 4f: expand from 5 to 7 sub-steps; add 4f-vi (op_picker/mod.rs → mod.rs + loading.rs + keys.rs) and 4f-vii (op_picker/render.rs → render.rs + pane.rs); document impl-extension and import-path caveats - §4 //! exemplars: add file_browser/ subsystem analysis — all 5 files have //! docs, no file exceeds ~350L production; classified as exemplar (not a split candidate); document git_prompt.rs coupling-density justification and input.rs as 28-file false positive (144L production) - §1 module map: expand single file_browser/ row to 5 individual rows with production LOC and dominant concern per file Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 30 — challenge split-first thesis, fresh LOC corrections - §4: Add "Alternative thesis: documentation-first verification" — challenges the two core assumptions behind file splitting (files-as-audit-unit and file-size-as-context-constraint); adds 7-criterion comparison table vs structure-first approach; introduces phased combined recommendation: Phase 1 = doc sprint (//! contracts + specs/ for 3 subsystems, 2-3 PRs, zero structural change); Phase 2 = splits only for >600L production files (reduces scope from 14+ to 4 files); Phase 3 = workspace if LOC > 150K - Fix stale LOC: app/mod.rs 951→979, config/editor.rs 1467→1548 (7 and 8 locations respectively; verified by fresh find|xargs wc -l scan) - §1 module map: add agent_picker.rs (436L), scope_picker.rs (201L), source_picker.rs (244L) — all PR #171 additions with //! docs Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 31 — fix 600L→800L threshold error, correct LOC - §4 alternative thesis: correct ">600L production → 4 files" claim introduced in iteration 30; re-verified all 9 candidate files via #[cfg(test)] line position; threshold must be >800L to get exactly 4 files (9 exceed 600L); add verification table with test-start lines - Production LOC corrections (5+ locations each): launch.rs 1085→~1077, operator_env.rs 810→~880, app/mod.rs 928→~957, config/editor.rs 503→~584 - §2 OpPicker row: replace vague "no entry yet" with confirmed gap: PROJECT_STRUCTURE.md line 53 still lists pre-PR#171 widget set (10 named); omits op_picker/, agent_picker.rs, scope_picker.rs, source_picker.rs and pre-dates the manager/ sub-structure split Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 32 — two-tier spec arch, behavioral spec template - §8.1: Add two-tier spec architecture table distinguishing feature specs (public Starlight MDX, user-facing) from behavioral specs (internal docs/internal/specs/, for AI code verification) — resolves contradiction between §4 (which said docs/internal/specs/) and §8.1 (which said "no longer needed; specs are public") - §8.1: Add concrete behavioral spec template for op_picker/ with state machine table and 3 INV invariant entries each with a grep-executable "Verify by:" command; template directly usable for the 3 Phase 1 specs - §8.1: Remove erroneous "docs/internal/specs/ no longer needed" claim - Confirmed render/editor.rs ~736L and render/list.rs ~668L production (no interspersed production code — all test blocks follow consecutively) Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 33 — executive summary, §0 correctness - §0: Add executive summary (~300 words) with core problem, 3-phase recommendation, key counter-argument, and navigation table pointing to §2/§4/§7/§8/§10 by question — resolves the meta-irony of a readability roadmap with no entry-point orientation - §0 item 2: "1569-line monolith" → "2130-line monolith" (operator_env.rs current verified size; stale reference was in the first section readers see) - §0 item 3: Add "(selective)" qualifier and explicit note that standard Rust co-locates struct+impl — impl-extension pattern is justified only for files >800L production, not as a universal rule Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 34 — spec priority reorder, §10 Phase 1 track - §0 + §4 Phase 1: Prioritize runtime/launch.rs behavioral spec (no //! doc, ~1077L production, critical path — all jackin load failures trace here); drop config/editor.rs from Phase 1 (its 963L test suite already serves as behavioral spec — tests are behavioral examples); reduce Phase 1 from 3 specs to 2 specs; add reasoning for the priority ordering - §10 Step 2: Split into two parallel tracks — Track A (cc-sdd tooling setup) + Track B (Phase 1 behavioral spec authoring); Track B includes specific INV invariants to capture for runtime/launch.rs grounded in reading the actual function structure (step comment positions); adds sequencing rationale: spec must precede structural splits Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 35 — verified INV entries for runtime/launch.rs Read load_agent_with lines 553-892 in full. Replaced 3 draft INVs from iteration 34 (inferred from step comment positions) with 5 verified INVs citing exact line numbers: - INV-1: trust gate (line 594) precedes image build (line 736) - INV-2: container name claimed (line 754) between image build and network - INV-3: token verified (line 763) before network creation (line 827) - INV-4: render_exit called at lines 886 AND 890 (all exit paths) - INV-5: cleanup disarm semantics — Running→disarm, clean exit→cleanup, crash→disarm (explains jackin hardline compatibility) Corrected wrong line number: claim_container_name call is at 754, not 918 (918 is the function definition). Each INV has a grep-executable Verify by. Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 36 — CI gate for PROJECT_STRUCTURE.md freshness §3: Add "Preventing future PROJECT_STRUCTURE.md staleness" subsection with three concrete options: - Option A: CONTRIBUTING.md rule (necessary but insufficient) - Option B: ci.yml git-diff-scoped shell check (recommended) — only checks files added in the current PR so it doesn't require fixing existing stale entries before merging; greps for module directory name in prose - Option C: Structured TOML module registry (over-engineered for scale) Includes concrete YAML snippet for Option B grounded in the check:repo-links.ts pattern already established in docs/scripts/ Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iterations 36-37 — CI gate + greenfield workspace architecture Iteration 36: - §3: Add "Preventing future PROJECT_STRUCTURE.md staleness" subsection with 3 options (CONTRIBUTING.md rule / ci.yml git-diff check / TOML registry); recommend Option B (git-diff-scoped YAML step) with concrete snippet grounded in existing check:repo-links.ts pattern from docs/scripts/ Iteration 37 (operator directive: greenfield Rust structure): - §4: Add "Greenfield architecture — ideal structure for a growing project" section based on verified cross-module dependency graph (grep iteration 37) - Confirms dependency tiers: workspace/manifest/docker/paths/selector = Tier 0; config/tui/instance = Tier 1; operator_env/runtime/repo = Tier 2; console = Tier 3 - Key finding: workspace/ is LOWER-level than config/ (config re-exports workspace types at lines 5-6); ideal naming inverted in greenfield (jackin-core > jackin-config) - Documents ideal 6-crate workspace: jackin-core, jackin-config, jackin-tui, jackin-runtime, jackin-console, jackin-shell + thin binary - Notes console/ has NO runtime/ import — cleanest pre-existing crate boundary - Bridge: incremental splits (4a, 4d, 4g) are pre-work toward workspace migration Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 38 — Rust workspace standards, community evidence Ground workspace recommendation in real-world project research: - ripgrep (9 crates), gitui (5 crates) went workspace due to library consumers - starship and fd-find stay single-crate at 1M+ LOC — no library use case - jackin (43K LOC, no external consumers) maps to starship/fd pattern → single-crate is community-standard; "stay single-crate" recommendation confirmed Update greenfield workspace structure to follow matklad's pattern: - Virtual manifest at root (no [package] in root Cargo.toml) - Flat crates/ directory (not nested); crate names match folder names - version = "0.0.0" for unpublished internal crates - Add inline dep comments to each crate in the ASCII structure Add research notes: ripgrep/starship/gitui/fd-find Cargo.toml findings + Cargo workspaces reference + matklad "Large Rust Workspaces" (2021-08-22) Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): revise §7.9 + §3 — adopt per-directory README.md §7.9: Reverse previous "reject" recommendation to "adopt" per-directory README.md for major src/ module directories. Rationale: README.md is AI-native — Claude Code, Copilot, Cursor load it automatically on directory entry, giving AI agents orientation before they decide which file to open. PROJECT_STRUCTURE.md being confirmed stale removes the main argument for the "single root file" approach. Add three-layer documentation model table: - README.md: directory orientation (AI + human, on entry) - AGENTS.md: agent workflow rules (root, session start) - CLAUDE.md: @AGENTS.md pointer only — NEVER add content here - //! docs: file-level contracts (when reading/editing) Add specific README.md content targets for 7 directories (src/, src/runtime/, src/console/, src/console/manager/, src/console/widgets/, docs/, docs/internal/). §3 target document shape: Add per-directory README.md to proposed hierarchy; add docs/internal/specs/ explicitly; note CLAUDE.md design principle (single-line @AGENTS.md — never duplicate content). Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): internal docs are browsable — unified Starlight site Operator directive: internal docs (architecture, specs, ADRs, roadmap) should be browsable, not hidden filesystem files. They are a different TYPE of docs focused on implementation details and vision, published as a "Developer Reference" section of the Starlight site. §3 target document shape: - docs/internal/ moves into docs/src/content/docs/internal/ (Starlight pages) - Browsable at jackin.tailrocks.com/internal/ - Sidebar: "Developer Reference" group (collapsed by default) with sub-sections for architecture, code-tour, contributing, testing, decisions, specs, roadmap - Include astro.config.ts sidebar config snippet §8.1 two-tier spec distinction eliminated: - Feature specs and behavioral specs both live at docs/src/content/docs/internal/specs/ - Type expressed via spec_type: behavioral | feature frontmatter, not filesystem location - Both browsable and searchable via Starlight; AI agents can be pointed to URLs §8.3 + §4: - All docs/internal/specs/ paths → docs/src/content/docs/internal/specs/ - ADRs: docs/internal/decisions/ → docs/src/content/docs/internal/decisions/ (browsable) - README.md pointer for src/runtime/ updated to URL reference Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): §11 — modern Rust docs platform (future project) Add §11 capturing the vision for a modern docs.rs alternative with: - rustdoc JSON ingestion → Astro Starlight presentation - MCP server for AI agent queries (Context7 alternative for Rust) - Rust-specific query types: rust_get_context(), rust_find_impls(), rust_search_types() — things Context7 cannot provide - Comparison table vs Context7 - Architecture diagram (ingestion → processing → Starlight + MCP) - Name candidates: rustlight, ferrodoc, cargo-starlight / starlight.rs - Note that jackin's §7.15 gen-rust-api.ts pipeline is the intentional prototype for the platform's processing and presentation layers Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 39 — update §0, fix stale internal/ paths §0 executive summary: rewrite to reflect decisions from iterations 30-38: - browsable internal docs (jackin.tailrocks.com/internal/) - per-directory README.md adoption (§7.9 reversed) - CLAUDE.md = @AGENTS.md single-line pointer only - greenfield workspace architecture (matklad's virtual manifest pattern) - §11 future project: modern Rust docs platform / Context7-for-Rust - document size 1800+ → 2200+ Fix stale docs/internal/ bare paths not caught by iteration 38 sweep: - Mermaid diagram: INTERNAL_ROADMAP, INTERNAL_CODE_TOUR → Starlight paths - §7.10 ADRs: docs/internal/decisions/NNN-title.md → .mdx Starlight path - §10 Track B item 2: op-picker spec path → Starlight MDX Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 40 — §7.15 pipeline + Rule 4 pub audit §7.15 (new): rustdoc JSON → Astro Starlight API documentation pipeline - Three options: rustdoc HTML publish / rustdoc JSON + bun script (recommended) / rustdoc-json crate as Rust binary - Option B recommended: matches existing docs/scripts/ pattern, nightly isolated to separate CI step, zero effect on stable build - Key design: URL at /internal/api/, cross-links to behavioral specs, Starlight unified search, prototype for §11 future project - Pub(crate) note: gen-rust-api.ts can feed Rule 4 visibility audit - Recommend: adopt after Phase 1 //! sprint (value ∝ coverage) §4 Rule 4 pub discipline: replace estimated "50-100 items" guess with verified numbers from iteration 40 grep: - 257 bare pub items, 21 pub(crate), 61 pub(super) across 94 files - 0 uses of unreachable_pub lint — no enforcement gate - Top violators: operator_env.rs (17), tui/output.rs (13), planner.rs (8) - Add concrete Cargo.toml [lints.rust] snippet: unreachable_pub = "warn" - Revised scope: ~150-200 mechanical conversions (excludes entry points) Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): split research into 19 actionable items Delete _research_notes.md (no longer needed). Replace 2343L READABILITY_AND_MODERNIZATION.md with: - README.md: index of all 19 items with phase, ordering notes, links - READABILITY_AND_MODERNIZATION.md: lightweight research summary (63L) - items/ITEM-001 through ITEM-019: individual actionable items Items by phase: Phase 1 (low risk, no confirmation): ITEM-001..004, 006..011 Phase 1 (needs confirmation): ITEM-005, 016, 018 Phase 2 (structural splits, confirmation required): ITEM-012..015 Phase 3 (deferred): ITEM-017 (rustdoc pipeline), ITEM-019 (workspace) Each item has: summary, key files with line numbers, steps, what needs confirmation, and relevant research backing from the 40-iteration analysis loop. Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): migrate 19 items to Starlight reference/roadmap section Move all codebase health roadmap items from docs/internal/roadmap/items/ (plain Markdown, not browsable) to docs/src/content/docs/reference/roadmap/ (MDX pages, browsable at jackin.tailrocks.com/reference/roadmap/). Adds a new "Codebase health" sidebar group (Phase 1 → Phase 3) to astro.config.ts. Deletes the old items/ directory. Updates the internal README to redirect to the new location. Also adds codebase-readability.mdx — a new overview item that captures the overall readability/restructuring program with a recommended execution order: file splits first, then greenfield workspace, then per-directory README+AGENTS.md, then docs and specs. Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@chainargos.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): remove premature internal/roadmap/README.md The internal/ structure doesn't exist yet — it will be created as part of the roadmap items themselves. No need for a redirect stub now. Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@chainargos.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): remove READABILITY_AND_MODERNIZATION.md research archive All content has been distilled into the individual Starlight roadmap pages. The full 2343L research is preserved in git history at commit b7e9fc2. Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@chainargos.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): fix check:repo-links errors + remove iteration log - Replace plain code spans with <RepoFile> for validate.rs, mise.toml, Cargo.toml, and op_picker/mod.rs - Remove deleted READABILITY_AND_MODERNIZATION.md reference from codebase-readability.mdx - Delete _iteration_log.md (git history is the archive) Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@chainargos.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): fix lychee false-positive link in move-contributing-testing The example redirect text contained a markdown hyperlink to a proposed future file path that doesn't exist yet. Changed to a code span. Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@chainargos.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> --------- Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Signed-off-by: Alexey Zhokhov <alexey@chainargos.com> Co-authored-by: Claude <noreply@anthropic.com>
donbeave
added a commit
that referenced
this pull request
Apr 26, 2026
Second deep code review of PR #177 surfaced three more issues after the first round of merge-blocker fixes shipped: P1. `force_cleanup_isolated` failure mid-loop in finalize_clean_exit propagated as Err via `?`, leaving the operator with a raw cleanup error from deep in finalize, no Preserved signal to the caller, the container left running without explicit teardown decision, and subsequent records in the loop never prompted. This regression was introduced by the round-1 multi-record loop fix (the single-record path always succeeded). Now caught per-record, eprintln'd as a warning, and treated as `any_preserved_after_prompt = true` so the loop continues and the caller gets `Preserved`. P2. `inspect_attach_outcome` only treated `status == "running"` as still-alive. `paused | restarting | removing | created | dead` all fell through to `stopped(0)` → entered `finalize_clean_exit` → could auto-delete worktrees of containers that may resume any moment. Concrete: `docker pause jackin-x` while jackin re-attaches → status="paused" → SafeToDelete on a clean tree → operator unpauses to find the worktree gone. Replaced if-cascade with an explicit `match status` that only routes `exited` through stopped() and treats unknown status strings conservatively as still_running. P3. `purge_isolated_for_container` swallowed per-record errors with eprintln warnings and returned `Ok(())`. Exacerbated by the round-1 fix #4 (force_cleanup_isolated now bails more often on real failures). Operator runs `jackin purge`, sees a warning scroll past, gets exit-code-0 prompt back, may believe purge completed. Now collects failures and surfaces an aggregate Err with the failed mount list so the exit code reflects reality. Test coverage for these fixes: - 2 new tests in finalize.rs: ReturnToAgent on the 2nd-of-3 prompt (early-return short-circuits), and force_cleanup_isolated failing mid-loop (loop continues, returns Preserved). - 8 new tests in launch.rs covering every status code path: exited(0/non-zero/oom), running, paused, transient (restarting/ removing/created), dead, unknown. - 2 new tests in cleanup.rs: purge bails on partial failure, branch_still_present returning None proceeds (pins the doc-comment contract against future refactors to `unwrap_or(true)`). Net: 1186 → 1198 tests, +12 additions, all passing. fmt/clippy clean. Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
donbeave
added a commit
that referenced
this pull request
Apr 26, 2026
* docs(spec): per-mount isolation V1 implementation spec
Captures roadmap → executable design: module layout under
src/isolation/, MountIsolation enum + MountConfig.isolation field,
materialization runtime hook, foreground finalizer with
safe/preserved/force cleanup, source-drift detection, jackin cd
command, TUI integration. Test list and docs touchpoints enumerated
for the implementation plan.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(isolation): introduce MountIsolation enum
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(workspace): add isolation field to MountConfig
Defaults to Shared; serde skips emitting the field when Shared so
existing TOMLs round-trip unchanged.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(workspace): reject nested isolated mounts
Two worktree-isolated mounts whose dsts nest have no safe on-disk
layout. Sibling isolated mounts and isolated-parent-with-shared-child
remain allowed.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(config): reject isolation field on global mounts
Adds a strict GlobalMountConfig wire-format struct that mirrors
MountConfig minus the isolation field, with deny_unknown_fields
so operators get a clear parse error if they try to set isolation
on a global mount. Isolation remains a workspace-mount concept.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* refactor(config): drop dead code and tighten global-mount API
- Delete unused From<MountConfig> for GlobalMountConfig (silently
dropped isolation; no callers).
- Delete unused get_mut and remove on DockerMounts along with their
#[allow(dead_code)] annotations.
- Tighten AppConfig::add_mount: debug_assert that incoming
MountConfig has Shared isolation, and construct GlobalMountConfig
explicitly from src/dst/readonly rather than via the (now-deleted)
From impl. Keeps the public signature stable so callers in CLI,
resolve.rs, and preview.rs don't need to change (Issue 2 option B).
- Add wire-path rejection test that goes through MountEntry's
untagged enum and asserts on the actual serde error
("data did not match any variant of untagged enum MountEntry").
- Soften GlobalMountConfig's doc comment to reflect the actual
serde error shape at the wire path.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(isolation): IsolationRecord + isolation.json IO
Atomic write via tmp+rename. Version-1 envelope leaves room for schema
evolution. Read/upsert/remove keyed by mount destination.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* test(isolation): drop redundant clones in state tests
Bring the warning baseline back to 87 after Task 2.1 introduced
two clippy::style hits (cloned_ref_to_slice_refs, redundant_clone).
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(isolation): list_records_for_workspace walks data dir
Used by workspace-edit drift detection to find which containers have
preserved isolated state for a given workspace.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(isolation): branch_name renderer with namespace + suffix support
Suffix is appended to the final selector segment so namespaced agents
keep their selector shape and the disambiguator goes on the leaf name.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(isolation): MaterializedWorkspace types
Third workspace shape (Config -> Resolved -> Materialized) used as the
runtime handoff into Docker launch.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(isolation): worktree_path_for derives on-disk path from mount dst
Uses dst verbatim (leading/trailing slashes stripped) under
isolated/, so the layout mirrors the container path.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(isolation): ensure_worktree_config_enabled
One-shot enabler for extensions.worktreeConfig on the host repo.
Bumps core.repositoryformatversion to 1 when needed.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(isolation): preflight checks for worktree materialization
Sensitive-mount, readonly, repo-root, and mid-operation guards.
Errors cite the mount destination and the worktree mode.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(isolation): dirty-host preflight gate with --force opt-out
Non-interactive load without --force rejects a dirty host tree.
Interactive contexts are expected to obtain ack upstream.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(isolation): materialize_workspace orchestrator
Per-mount worktree materialization with idempotent reuse, source-drift
guard, and branch-name disambiguation when multiple isolated mounts
target the same host repo.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* test(isolation): cover branch disambiguation for same-repo mounts
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(isolation): order Docker mounts parent-before-child
Length-ascending sort so shared cache children overlay isolated
worktree parents at container start.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(runtime): hook materialize_workspace between AgentState and Docker
Workspace mounts now flow Config -> Resolved -> Materialized before
reaching the docker run command, with parent-before-child ordering.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(isolation): force_cleanup_isolated removes worktree + branch + record
Best-effort git invocations that tolerate missing host repo and
already-removed worktree. Used by purge and the finalizer's force-delete
branch.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(isolation): finalizer skeleton + AttachOutcome shape
Decides Preserved when container still running, OOMed, or exited
non-zero. Clean-exit path stubbed - implemented in follow-ups.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(isolation): safe-cleanup deletes branches with no commits
When the worktree is clean and HEAD equals the recorded base, the
scratch branch is removed automatically.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(isolation): consult upstream when deciding safe cleanup
Pushed commits (reachable from upstream) are safe to delete; local-only
commits or no-upstream divergence preserve the worktree.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* test(isolation): cover interactive unsafe-cleanup prompt branches
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(runtime): finalize foreground session after attach in load + hardline
Both load and hardline now consult inspect_attach_outcome and dispatch
the shared finalizer. Return-to-agent retries safe cleanup once after
the operator returns.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* fix(purge): refuse to run on a live container
Closes a pre-existing gap where purge could delete state out from
under a running agent. Operator must eject first.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(purge): remove isolated worktrees and scratch branches
Reads isolation.json and runs force_cleanup_isolated for each record
before deleting the per-container state directory.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(cli): --mount-isolation DST=TYPE on workspace create/edit
Repeatable. Rejects clone before persistence with the canonical
"reserved but not implemented yet" message.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(workspace): add Isolation column to workspace show
Renders canonical lowercase name for every mount so CLI output matches
TOML/CLI input verbatim.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(load): add --force to acknowledge dirty host tree
Required for non-interactive isolated-mount materialization when the
host working tree is dirty.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(workspace): detect source drift on edit affecting isolated mounts
Edits that change src for a mount with preserved isolated state are
rejected unless --delete-isolated-state is passed and no related
container is running.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(cli): jackin cd opens a child shell in an isolated worktree
Single-mount-no-dst → uses it. Dst-provided → exact match. Multi-mount
no-dst → interactive picker on TTY, error on non-TTY. Sets JACKIN_*
env vars. Does not modify the parent shell.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(console): show isolation badge per mount in editor + preview
Adds an Iso column to the workspace-manager mount table (editor and
list-pane sub-panel) and a `[shared|worktree|clone]` tag to the agent
preview's resolved-mounts lines. Per the per-mount-isolation spec the
badge renders the canonical spelling for every mount, including
`shared`, so operators always see which strategy applies.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(console): I hotkey cycles isolation on the selected mount
Mirrors the existing R (readonly) toggle. Cycles Shared -> Worktree
-> Shared; Clone is reserved-but-rejected in V1 and is not entered
through this hotkey, but a saved Clone mount snaps back to Shared on
the first I press rather than getting stuck. The cycling rule lives in
EditorState::cycle_isolation_for_selected_mount so the input dispatch
arm stays trivial.
Also surfaces the new key in the Mounts-tab footer hint alongside the
existing R toggle so the affordance is discoverable.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(console): source-drift confirm modal in workspace editor
Save flow runs the same drift detection as `jackin workspace edit`:
detect_workspace_edit_drift evaluates the prospective mount list
(post-collapse, post-upsert) against IsolationRecords on disk before
the on-disk write.
Running container drift -> ErrorPopup ("eject first"); save aborted.
Stopped container drift -> Confirm modal listing the affected
container names with a Yes/No prompt. On Yes the modal handler
re-stashes the plan with delete_isolated_acknowledged = true and the
second commit pass calls force_cleanup_isolated for each affected
record before writing.
Reduced scope vs the original three-button "Delete preserved state and
save / Cancel / Open mount details" dialog: the modal is the existing
two-button Confirm widget (Yes/No). The third "open mount details"
affordance is omitted — operators dismiss with N/Esc, find the
offending mount in the editor, and revert the src by hand. Adding it
would require either a custom widget or repurposing an existing
multi-choice one and threading mount-row focus through the modal
plumbing; the safety value is in the block-and-ack semantics, which
the two-button form covers.
Adds a commit_editor_save_with_runner test seam so the FakeRunner can
drive the drift branch without a real Docker daemon.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* docs(workspaces): add per-mount isolation section
Document the per-mount isolation feature in the workspaces guide:
the three modes (shared default, worktree, clone reserved-but-rejected),
validation preconditions, the isolated-source + shared-cache child
pattern with TOML, and pointers to --mount-isolation and jackin cd.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* docs(mounts): document mount isolation field
Add an "Mount isolation" section to the mounts guide covering the
shared/worktree values, the global-mount rejection at parse time,
and the isolated-source + shared-cache child composition pattern.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* docs(configuration): add MountConfig.isolation field
Document the new mounts[].isolation field in the configuration
reference: shared default, worktree opt-in, clone reserved-but-rejected,
and the global-mount parse-time rejection.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* docs(architecture): document materialization flow + isolation.json
Add a "Workspace materialization" section to the architecture reference
covering the WorkspaceConfig -> ResolvedWorkspace -> MaterializedWorkspace
shapes, the per-container isolation.json layout, and the post-attach
foreground finalizer's Preserved/Cleaned/ReturnToAgent decision matrix.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* docs(workspace): document --mount-isolation and Isolation column
Add --mount-isolation to workspace create/edit option tables (with the
clone "planned but not implemented" note), document the new
--delete-isolated-state flag for non-interactive source-drift edits,
and note the Isolation column on workspace show.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* docs(load): document --force dirty-host acknowledgement
Add --force to the option table and a dedicated section explaining
when it's required (non-interactive load with a worktree-isolated
mount + dirty host tree) and what it does NOT do (no stash, no
discard, no relaxation of other validation).
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* docs(purge): document running-agent guard and isolated cleanup
Document purge's new behavior: refuses to run on a running container
(eject first), force-removes isolated worktrees + scratch branches
recorded in isolation.json, and tolerates a missing host repo on
best-effort cleanup.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* docs(cd): add jackin cd command reference
Create a reference page for jackin cd <container> [dst] covering
arguments, the mount-selection behavior matrix (zero/one/many isolated
records, with and without dst), the JACKIN_* env vars set in the
child shell, exit-code passthrough, and the no-parent-mutation
guarantee. Wire it into the Commands sidebar between console and
launch.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* docs(roadmap): mark per-mount isolation V1 implemented
Flip the per-mount-isolation roadmap status to "Implemented in V1"
and replace the duplicate-mounts-allowed line with the actual V1
rule: multiple isolated mounts are allowed (with branch-name
disambiguation), but nested isolated dst paths are rejected at
validation because the inner worktree's .git would land inside the
outer worktree's tree.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* docs(structure): add isolation module tree and cd command
Update PROJECT_STRUCTURE.md to document the new per-mount-isolation
work: isolation/ module row (mod/branch/materialize/state/finalize/
cleanup), cli/cd.rs entry on the cli/ row, --mount-isolation /
--delete-isolated-state / --force notes on the relevant CLI rows,
foreground-finalizer mention on the runtime row, the new
commands/cd.mdx in the docs map, and a code->docs cross-reference
row mapping src/isolation/** to all the doc pages it touches.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* test(isolation): end-to-end materialize -> clean-exit -> cleanup
Exercises the full lifecycle through public APIs with a small inline
scripted runner. No real git or docker.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* docs(isolation): note finalizer is local-only for hardline lockdown
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* style(test): apply rustfmt to per-mount isolation e2e
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* ci(docs): verify docs links in PRs and on the deployed site (#173)
* ci(docs): add link checking
Co-authored-by: Codex <codex@openai.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* ci(docs): stabilize lychee checks
Co-authored-by: Codex <codex@openai.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* ci(docs): validate edit links with lychee
Co-authored-by: Codex <codex@openai.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* ci(docs): close link check gaps
Co-authored-by: Codex <codex@openai.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* docs: add repo file link component
Co-authored-by: Codex <codex@openai.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* docs: explain repo link source check
Co-authored-by: Codex <codex@openai.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* ci(docs): allow manual dispatch of deployed link check
Two refinements from PR review:
- The check-deployed job now triggers on workflow_dispatch in addition
to schedule, so maintainers can manually verify the live deployed
docs without waiting for the daily cron or pushing to main. This
closes the gap against goal "manual workflow to verify both built
site and deployed documentation".
- Drop github.sha from the deploy job's lychee cache primary key so it
matches across runs (the SHA-keyed primary was guaranteed to miss,
forcing fallback to restore-keys). Now mirrors the cache key shape
used by check-deployed.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* ci(docs): harden link-check workflow and broaden source lint
Address review feedback on PR #173.
Workflow (.github/workflows/docs.yml):
- Split concurrency group by event_name so a scheduled or
workflow_dispatch run cannot cancel an in-flight push deploy. Cancel
in-progress is now scoped to pull_request only.
- Exclude main from workflow_dispatch on check-deployed. The deploy job
already verifies the just-deployed site, so running both in parallel
would race against the publish window. Manual verification of the live
site from main flows through deploy; from feature branches it flows
through check-deployed.
- Add the build cache as a fallback restore-key for check-deployed so
the daily cron and manual runs warm-start from the last build cache
when the lychee.toml fingerprint changes.
RepoFile component (docs/src/components/RepoFile.astro):
- Add target=_blank and rel=noopener noreferrer so GitHub source links
open in a new tab, matching the behavior rehype-external-links applies
to plain markdown external links in MDX.
Source lint (docs/scripts/check-repo-links.ts):
- Normalize leading slashes when checking RepoFile path existence so the
validator agrees with the component's own normalization.
- Cover top-level repo-specific files (Cargo.toml, Cargo.lock, Justfile,
build.rs, docker-bake.hcl, mise.toml, release.toml, renovate.json) so
a rename of any of those also breaks the docs CI gate, not only paths
under src/, docs/, docker/, .github/.
Content (docs/src/content/docs/developing/construct-image.mdx):
- Convert the remaining inline-code references to docker-bake.hcl and
Justfile to <RepoFile />. These were the references that motivated
extending the lint to top-level files.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* docs(roadmap): apply RepoFile lint to multi-runtime proposal
After merging main into codex/docs-link-checks, check-repo-links flagged
37 plain inline-code references to existing repo files in the new
multi-runtime-support.mdx (added in #174 before this PR's lint existed
on main). Convert each one to <RepoFile /> so renames or deletions of
those source files break the docs gate before merge, the same way the
rest of the roadmap is now protected.
No prose changes — every conversion is one-for-one (`src/foo.rs` →
<RepoFile path="src/foo.rs" />). The trailing-slash directory reference
to `docker/runtime/` is left as a code span, since the lint correctly
skips it (it's a directory, not a file).
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* fix(roadmap): repair broken Amp CLI link in multi-runtime proposal
CI failed on this PR's last build because lychee found a 404 on
https://github.com/sourcegraph/amp in multi-runtime-support.mdx (added
in #174). That repo does not exist publicly — Amp's source is not on
GitHub.
Point the link at https://ampcode.com instead, which is already the
canonical Amp URL used elsewhere in the docs (getting-started/why.mdx).
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
---------
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
Co-authored-by: Codex <codex@openai.com>
Co-authored-by: Claude <noreply@anthropic.com>
(cherry picked from commit f3f3e5e2d386a9cb3c2537be1e51b60f3fc09e6e)
* docs(roadmap): wrap src/cli/cd.rs in <RepoFile> on per-mount-isolation page
The check-repo-links script (added in #173) flags any inline-code
reference to a real repo file that isn't wrapped in <RepoFile />.
src/cli/cd.rs was created on this branch, so once #173's lint reaches
this branch via the previous cherry-pick, the bare reference fails
the Docs CI check.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* docs: switch cross-doc links to absolute URL form
The link-check job's lychee step (added on main in #173, hardened in
#176) verifies built-site links against the on-disk dist tree. Relative
`.mdx`-suffixed links break that check because lychee resolves them as
literal file paths under the rendered URL's directory — e.g.
`./workspaces.mdx` rendered from `/guides/mounts/` resolves to
`/guides/mounts/workspaces.mdx`, not `/guides/workspaces/`.
Switch the four cross-doc links added by the per-mount-isolation work
to the rendered-URL form (`/guides/workspaces/#per-mount-isolation`
etc.) — same convention as the existing `[mount collapse](/commands/workspace/#mount-collapse)`
link in the same file.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(isolation): emit verbose debug-mode trace for worktree lifecycle
Operators sharing logs to debug worktree behavior had no visibility into
the lifecycle — only three error/warning sites fired output, and none of
them ran on the happy path. `--debug` toggled a display mode (preserve
scrollback, clear spinner) but was not a verbose-trace facility.
This adds a `debug_log!(category, fmt, ...)` macro in `src/tui/mod.rs`
that gates on the existing `DEBUG_MODE` atomic so disabled call sites
cost only an atomic load (formatting is deferred behind the gate).
Output uses a `[jackin debug <category>]` prefix so shared logs are
greppable.
Instrumented sites (all under `category = "isolation"`):
- `materialize_workspace`: per-call summary (workspace, container,
selector, mount counts, force/interactive flags).
- `materialize_one`: per-mount decision trail — drift detection,
worktree reuse, preflight, base-commit lookup, branch derivation
with selected suffix, and the `git worktree add` invocation itself.
- `ensure_worktree_config_enabled`: every state transition (already
enabled vs. bumping repositoryformatversion vs. flipping the flag)
with the host repo path.
- `state.rs`: write_records (count + path), upsert_record (insert vs.
replace), remove_record (drop vs. no-op).
- `cleanup.rs`: force_cleanup_isolated entry, the two git invocations,
the rm -rf fallback, and the host-repo-missing skip path.
purge_isolated_for_container per-container summary.
- `finalize.rs`: foreground-session entry with exit code/oom/interactive
flags, the early-return path for non-clean exits, per-record cleanup
assessment.
- `runtime/launch.rs`: load_agent's call into materialize_workspace.
Read paths (`read_records`, `read_record`) are intentionally NOT logged
— they fire on every invocation and would drown the log.
Manual verification (since binary-level stderr capture would need a new
test dependency):
cargo run --release --bin jackin -- --debug load <agent>
cargo run --release --bin jackin -- --debug workspace edit <ws> \
--mount-isolation /workspace/proj=worktree
cargo run --release --bin jackin -- --debug purge <container>
Each emits a chronologically ordered `[jackin debug isolation] ...`
trace covering every git invocation, isolation.json mutation, and
finalize decision — suitable for sharing in bug reports.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* fix(tui): rename Iso column to Isolation, count isolation flips as one change
Two related TUI papercuts surfaced together when an operator flipped a
mount's isolation from `shared` to `worktree` via the `I` hotkey:
1. The mount-table header read `Iso` — opaque on first sight. Replaced
with `Isolation` (the full word). Bumped the column-width constant
from 8 → 9 so the header label fits without disturbing data-row
alignment, and renamed `MOUNT_ISO_COL_WIDTH` →
`MOUNT_ISOLATION_COL_WIDTH` for consistency. Updated the
alignment-regression test that asserted on the old label.
2. Cycling isolation on an existing mount (same `dst`, same `src`)
reported "2 changes" in the save-row footer and rendered the
Confirm Save dialog with a `+`/`-` pair for the same path. Both
sites used `MountConfig::contains()` — full-struct equality — so
any isolation/readonly drift made the row appear as remove + add.
Extracted a `MountDiff` classifier in `console/manager/state.rs`
that keys on `dst` (the identity used by upsert/remove everywhere
else). Same-`dst` matches with structural drift are now reported
as a single `Modified`, counted as one change in `change_count`
and rendered as a `~ <new>` line with a dimmed `was: <old>` follow-up
in the Confirm Save summary so the operator sees exactly what
changed without parsing a remove + add pair.
Extended `mount_summary` to include the isolation tag so the
delta is visible in both the new and old lines:
`~/foo (rw, worktree, github · main)`.
Records a new shared rule in `RULES.md` ("TUI Labels") to prevent
future short-form labels in user-facing TUI surfaces — operators
read the TUI in passing and cannot afford to decode `Iso`/`Cfg`/`Env`/
`WD`-style abbreviations. Lists the established short forms that are
NOT considered abbreviations (`dst`, `src`, `git`, `op`).
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* fix(isolation): make worktree mode actually work inside the container
V1's worktree mode shipped with a gap: the materialized worktree was
bind-mounted into the container at <dst>, but the worktree's `.git`
text file (a pointer back to <host_repo>/.git/worktrees/<n>/) referenced
an absolute host path that didn't exist inside the container. Every git
command — `git status`, `git log`, `git commit`, `git push` — failed
with "fatal: not a git repository". The agent could read source files
but could not commit work, defeating worktree mode's whole purpose.
Fix: wire up three additional bind mounts at docker-run time, plus two
jackin-owned override files written at materialization, so git's gitdir
relationship resolves consistently inside the container without
modifying any host-side files.
For each isolated worktree, the container now sees:
1. The worktree at <dst> (existing).
2. The host repo's `.git/` at /jackin-isolation/<container>-git/ rw,
so git can find objects, refs, and the per-worktree admin dir.
3. A jackin-owned `.git` text file at <dst>/.git overriding the
worktree's host-side pointer with one targeting the container path.
4. A jackin-owned back-pointer at /jackin-isolation/<container>-git/
worktrees/<n>/gitdir overriding git's verification check (host's
absolute path doesn't match <dst> inside the container).
Override files live under <data_dir>/jackin-<container>/.git-overrides/
and are written once at materialize time. Host files (worktree's `.git`
and the admin dir's `gitdir`) are NEVER modified — host-side
`git worktree list` continues to work identically.
Three layouts were considered (Docker Sandboxes-style `.jackin/` in the
host repo, indirect mount with override files, jackin-owned bare repo).
The chosen approach preserves jackin's dst-based mount model (operator
configures dst=/workspace/jackin → agent works at that exact path), keeps
the host repo clean (no `.jackin/` directory), and exposes only the
worktree to the agent (not the entire host main tree). Full design
rationale and the comparison with Docker Sandboxes, Conductor, and
clone mode (planned for V1.1) are in
docs/src/content/docs/reference/roadmap/per-mount-isolation.mdx
under "Design Decision: Worktree Materialization Layout" and
"Comparison with Other Tools".
Trust trade-off: the agent has rw access to the host repo's `.git/`
since refs/objects are inherently shared in git worktrees. Worktree
mode is appropriate for trusted agents on personal projects where
immediate ref visibility on the host is valuable. Operators who want
ref isolation should use clone mode (planned).
Tests:
- write_git_overrides_writes_both_files_with_correct_content asserts
override file content matches the design doc verbatim.
- write_git_overrides_is_idempotent confirms re-running on a reused
worktree (load → eject → load) doesn't drift.
- override_id_strips_slashes_and_trims pins the file-naming scheme.
- container_git_dir_path_namespaces_by_container_name pins the
hardcoded container-side path so two parallel agents don't collide.
- Extended per_mount_isolation_e2e to assert MaterializedMount carries
WorktreeAuxMounts on the worktree path and that override files land
on disk at the documented locations.
Manual verification recipe (add after running once):
cargo run --release --bin jackin -- --debug load <agent>
docker exec -ti <container> git status # was failing, now works
docker exec -ti <container> git log # works
docker exec -ti <container> git commit -m test --allow-empty
git -C <host-repo> branch -a # shows new branch
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* refactor(isolation): /jackin/{host,admin}/<dst> mounts, container-name basename, :ro hardening
Three related changes that finalize the worktree-mode mount layout:
1. Container-side path scheme renamed and reorganized.
/jackin-isolation/<container>-git/... → /jackin/host/<dst-stripped>/.git
/jackin/admin/<dst-stripped>/{commondir,gitdir}
- Single top-level /jackin/ namespace for everything jackin contributes
to the agent's filesystem (room to grow with /jackin/cache/, etc.)
- host/ category mirrors host topology so docker inspect shows symmetric
Source/Destination paths both ending in `.git`
- admin/ category lives at a separate top level so the override files
(which sit on top of files inside the admin dir) do NOT visually nest
inside /jackin/host/.../.git/. Two top-level concerns, no overlap.
2. Host-side storage groups all git artifacts for one mount under
<state>/git/<dst-stripped>/, with override-file names matching their
docker mount destinations:
<state>/git/<dst-stripped>/
├── <container>/ (the worktree; basename = container name)
└── overrides/
├── .git
├── commondir
└── gitdir
Replaces the prior <state>/.git-overrides/ flat layout with underscored
slug filenames. New layout uses dst as a real directory tree (no slug)
and source filenames identical to destination filenames — the source/
destination relationship is obvious in `docker inspect`.
3. Worktree subdir basename = container name. `git worktree add` derives
the host-side admin entry name from the worktree path's basename (no
--name flag exists upstream). Using the container name (which jackin
guarantees is globally unique) makes admin entries in
<host_repo>/.git/worktrees/ globally unique per (host_repo, container)
— `git worktree list` on the host immediately shows which container
owns each worktree.
This required a new validation rule:
`workspace::validate_isolation_layout` now rejects two isolated mounts
that resolve to the same host repository within one workspace.
Allowing them would force the same container-name basename twice in
one host repo's .git/worktrees/ namespace; no real operator workflow
has surfaced for this case. Revisit if one does.
Removes the now-dead suffix logic from materialize.rs:
- `count_isolated_per_repo` (helper)
- `canonicalize_or_clone` (helper)
- `dst_to_branch_suffix` (in src/isolation/branch.rs — no callers left)
The `branch_name` function keeps its optional suffix parameter for
future clone-mode use; V1 worktree always passes None.
4. The three override files (replacement `.git` pointer, `commondir`,
`gitdir` back-pointer) are mounted `:ro` as defensive hardening. Git
only reads them during normal agent work, and a misbehaving agent
could otherwise rewrite the gitdir pointer to redirect operations at
a different repo entirely. The host `.git/` and admin mounts stay rw
because git writes refs/objects/HEAD/index/logs there.
Tests:
- workspace::tests::isolation_layout_rejects_two_worktree_mounts_on_same_repo
- workspace::tests::isolation_layout_allows_different_host_repos_in_one_workspace
- materialize::tests::worktree_path_uses_container_name_as_basename
- materialize::tests::container_host_git_path_mirrors_dst_under_jackin_host
- materialize::tests::container_admin_path_lives_under_jackin_admin
- materialize::tests::host_and_admin_paths_disambiguate_per_mount_in_one_container
- materialize::tests::write_git_overrides_writes_three_files_with_correct_content
- materialize::tests::write_git_overrides_is_idempotent
- launch::tests::build_workspace_mount_strings_marks_overrides_readonly
(asserts all 6 mounts in correct order with correct :ro placement)
- per_mount_isolation_e2e: updated for new path scheme + admin name
Removed:
- materialize::tests::two_isolated_mounts_same_repo_get_dst_suffixed_branches
(case is now rejected at the workspace-validation level)
Roadmap MDX (per-mount-isolation.mdx):
- Container-side mount layout section: 4 mounts → 6, new path scheme,
override-file storage layout
- Composition Rules: documents the new same-host-repo rejection
- Comparison table: bind mount count for jackin worktree updated 4 → 6
- V1 Scope: ship list updated with new layout and the new validation rule
Manual verification (after merge):
cargo run --release --bin jackin -- --debug load <agent> <workspace>
docker inspect <container> | jq '.[0].Mounts' # see /jackin/{host,admin}/...
docker exec -w <dst> <container> git status # works
git -C <host_repo> worktree list # admin name = <container>
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* refactor(isolation): single /jackin/host/ root, no commondir override, Model B branch naming
Final V1 design after extended brainstorming with the operator. Drops
the `/jackin/admin/<dst>` namespace and the `commondir` override file:
the per-worktree admin entry now lives natively at
`worktrees/<container>/` inside the host `.git/` mount, so git's
on-disk default `commondir = ../..` resolves correctly without an
override.
Container-side topology, per isolated mount (4 binds total, down from 6):
- `<dst>` (rw) — the materialized worktree
- `/jackin/host/<dst-tree>/.git` (rw) — host repo's `.git/`
- `<dst>/.git` (`:ro`) — replacement gitdir pointer
- `/jackin/host/<dst-tree>/.git/worktrees/<container>/gitdir` (`:ro`)
— replacement back-pointer
Host-side layout under each per-container state dir:
- `git/worktree/repo/<dst-tree>/<container>/` — git's territory
- `git/overrides/<dst-tree>/{.git,gitdir}` — jackin-owned overrides
Branch naming follows Model B: `jackin/scratch/<container_name>`
verbatim. Admin entry name = container name (deterministic, globally
unique because container names are workspace-unique and
`validate_isolation_layout` rejects two isolated mounts on the same
host repo within one workspace — no auto-suffix or read-back needed).
Roadmap doc updated to reflect the final design.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* style(isolation): rustfmt assert_eq! width
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* docs(isolation): align stale references with shipped V1 design
Sweep stale references across roadmap, guides, command and architecture
docs into alignment with what the code actually does. No content added —
the doc just told two contradictory stories before (Model B branch
naming alongside the old selector-key derivation; the new
git/worktree/repo/<dst>/<container>/ on-disk layout alongside the
proposed-but-never-implemented isolated/<slug>/ layout). Now there is
one consistent story end-to-end.
Touches:
- docs/src/content/docs/reference/roadmap/per-mount-isolation.mdx
- docs/src/content/docs/guides/workspaces.mdx
- docs/src/content/docs/commands/purge.mdx
- docs/src/content/docs/reference/architecture.mdx
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* chore(cli): remove jackin cd command from V1
Operationally redundant with `git worktree list` + native shell `cd`.
For worktree mode, the host's `git worktree list` already enumerates
every isolated worktree by branch and absolute path, so a plain
`cd $(...)` reaches the same destination. The remaining edge cases
(preserved-dirty inspection, multi-mount picker) are rare enough that a
dedicated subcommand is net cost rather than net benefit.
Removes:
- src/cli/cd.rs (CdArgs + select_record + tests)
- Command::Cd enum variant in src/cli/mod.rs
- handle_cd dispatch in src/app/mod.rs
- docs/src/content/docs/commands/cd.mdx and its sidebar entry
Updates:
- src/isolation/finalize.rs preserved-state warning: drop "jackin cd ..."
hint, point operator to the printed worktree path instead
- src/isolation/materialize.rs source-drift error: same treatment
- guides/workspaces.mdx + reference/architecture.mdx: drop cd references
- roadmap entry: replace "Convenience navigation" paragraph with a
removed-from-V1 note explaining the rationale; add cd to the Defer
list so it's recoverable if a real workflow surfaces
isolation.json schema, the preserved-state machinery, and `hardline` /
`purge` flows are unaffected — only the inspection convenience layer
is gone.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* chore(isolation): drop clone from V1 enum/parser/CLI
Per principle: don't pre-add API for unimplemented features. The `clone`
keyword was previously parsed by TOML/CLI and then rejected at validation
with a "planned but not implemented yet" error. Operators got false
positives in linting tools and confusing late failures with no benefit —
nothing in V1 actually does anything useful with the value.
Removes from the runtime:
- MountIsolation::Clone enum variant (src/isolation/mod.rs)
- explicit Clone-rejection in parse_mount_isolation (src/cli/workspace.rs):
FromStr now produces "invalid isolation `clone`" naturally
- MountIsolation::Clone match arm in materialize_workspace
(src/isolation/materialize.rs)
- console comments referencing the reserved-but-rejected wording
Tests:
- New `rejects_clone_until_implemented` test on FromStr asserts the
standard "invalid isolation `clone`; expected one of: shared, worktree"
error so this stays locked down
- parse_mount_isolation_rejects_clone updated to assert the new error
shape
Docs:
- guides/workspaces.mdx, commands/workspace.mdx,
reference/configuration.mdx, reference/roadmap.mdx: drop the
"reserved keyword" wording, point at the V1.1 roadmap entry instead
- roadmap/per-mount-isolation.mdx: keep the `clone` design discussion,
rephrase the V1 vocabulary section to make it explicit that the keyword
is added back when clone mode ships, not pre-shipped now
apply_isolation_overrides already enforces "--mount-isolation must
reference an existing mount destination" (planner.rs); no change needed
for that requirement, just clarified in the roadmap CLI-behavior bullet.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* fix(workspace): always write mount isolation field explicitly on save
Old configs without the `isolation` field still deserialize to Shared
(the enum default). On save, drop the `skip_serializing_if = is_shared`
guard so every mount writes its isolation level explicitly — including
`shared`. Old TOMLs migrate to the new shape on first save instead of
silently retaining their pre-isolation form.
Rationale: when the operator opens the saved config, every mount should
name its isolation level. No "field is missing therefore implicitly
shared" — the value is always present and the source of truth in the
file matches the source of truth at runtime.
Touches:
- src/workspace/mod.rs MountConfig.isolation: drop skip_serializing_if
- mount_config_omits_isolation_field_when_shared_on_serialize → renamed
to mount_config_writes_isolation_field_even_when_shared_on_serialize,
asserts the field IS written
- guides/mounts.mdx + reference/configuration.mdx: update wording
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* docs: remove stale per-mount-isolation design spec
The brainstorming spec at docs/superpowers/specs/ predated the V1 design
iterations and no longer reflects what shipped (it still describes the
old `/jackin-isolation/` mount layout, the selector-key-based branch
naming, the reserved `clone` enum value, and `jackin cd`). The roadmap
entry at docs/src/content/docs/reference/roadmap/per-mount-isolation.mdx
is now the single source of truth.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* docs(roadmap): improve per-mount isolation entry — accurate Sandboxes
comparison, V1 overview, concrete clone-mode personas
Four targeted improvements to the per-mount-isolation roadmap doc:
1. Add a "How V1 worktree mode works (TL;DR)" overview at the top of
Host-Side Materialization. 5-step walkthrough of the materialize +
mount + commit flow before the deep-dive subsections, so readers
land on the entry can understand the shipped V1 in 90 seconds
without scrolling through the layout/lifecycle/etc.
2. Rewrite the Docker Sandboxes comparison for accuracy. The old
description got it wrong on multiple points:
- Sandboxes use a microVM with hypervisor isolation, not Docker
containers (Linux namespaces) like jackin.
- The host filesystem is exposed via filesystem passthrough at the
SAME absolute path as the host, not a bind mount of the entire
repo at "the same relative paths".
- Worktree path is `<host_repo>/.sbx/<sandbox-name>-worktrees/<branch>/`
(sandbox name is in the path), not `<host_repo>/.sbx/<branch>/`.
- Sandboxes do NOT expose the host main working tree to the agent
— only the worktree subdir and the parent `.git/`. Our table
previously said "✓ (entire host repo mounted)".
The architectural insight is now explicit: Sandboxes' absolute-path
equivalence makes git's on-disk absolute pointers resolve natively,
which is why they don't need override files. We pay that cost
because Docker containers translate host paths to operator-chosen
`dst` values, breaking absolute-path equivalence.
3. Concrete clone-mode operator personas. The previous description
was abstract ("complementary mode for ref isolation"). Replaced
with four named situations clone mode targets: untrusted/experimental
agents, parallel-fan-out scratch-branch noise, editor watcher
churn, and teams whose workflow is push-to-share anyway.
4. New "Sandbox runtime" and "Host file exposure" rows in the
comparison table to make the underlying architectural choice
immediately visible. Cross-referenced from the Sandboxes prose.
Net: ~80 lines changed/added, primarily replacement of the wrong
Docker Sandboxes facts and addition of the V1-overview block.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* fix(isolation): close four data-loss windows in finalize/cleanup paths
Four merge-blocking issues surfaced by deep code review of PR #177.
Each was a silent-failure window that could destroy operator data on
the unhappy path while the happy-path manual smoke test stayed green.
1. assess_cleanup now treats every git capture failure as
PreservedUnpushed (was: unwrap_or_default → empty string → could
land in SafeToDelete). Without this, a transient `git rev-list`
failure (corrupted pack mid-traversal, broken pipe under load,
index.lock from a backgrounded git GC) would auto-delete the
worktree and scratch branch, garbage-collecting unpushed commits.
Each capture site now uses an explicit `match` that returns
PreservedUnpushed with debug_log of the underlying error, plus a
defense-in-depth empty-HEAD guard.
2. finalize_clean_exit now collects ALL preserved records and prompts
per-record (was: needs_prompt.get_or_insert reached only the first
one). On a multi-mount workspace where the operator chooses
force-delete on the first prompt, the second preserved worktree was
silently orphaned and the container torn down anyway — the only
reconnection path (jackin hardline) was lost. Now each preserved
record gets its own prompt; "return to agent" short-circuits the
loop; "preserve" propagates as Preserved and skips container
teardown.
3. inspect_attach_outcome now returns still_running() on docker capture
failure (was: stopped(0) → entered finalize_clean_exit → could
compound with #1 to delete worktrees of containers that may still
be alive). The conservative direction is "preserve when we don't
know" — `jackin hardline` recovers from there.
4. force_cleanup_isolated now verifies cleanup actually completed
before removing the isolation.json record (was: let _ on git ops +
unconditional remove_record → orphan worktree admin entries on the
host repo and orphan branches with no jackin reference). Tolerates
the idempotent paths (already-removed worktree, already-deleted
branch verified absent via `git branch --list`); bails on real
failures with a clear "record retained, re-run jackin purge after
resolving the issue" message.
Test coverage:
- 5 new tests in isolation/finalize.rs pinning the assess_cleanup
capture-failure → PreservedUnpushed contract for each git command
in the assessment chain, plus the empty-HEAD guard.
- 3 new tests pinning the multi-record finalize path (force-delete-all,
mixed force/preserve, non-interactive multi-mount warning).
- 1 new test for inspect_attach_outcome capture-failure fallback.
- 3 new cleanup tests (branch-already-deleted tolerance, real-failure
retention, error-message contract).
- 1 new test for validate_workspace_config integration (catches the
validate_isolation_layout call site if anyone refactors it away).
- 1 new test for build_workspace_mount_strings on a multi-mount
isolated workspace (8 distinct binds, no path collisions, :ro
hardening on every override file).
- 2 new drift-detection tests (dst-removed flagged; isolation-mode
flips not-flagged with explanatory note for future improvement).
Net: 1170 → 1186 tests, 16 additions, all passing.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* fix(isolation): close P1/P2/P3 — follow-on bugs from second review
Second deep code review of PR #177 surfaced three more issues after
the first round of merge-blocker fixes shipped:
P1. `force_cleanup_isolated` failure mid-loop in finalize_clean_exit
propagated as Err via `?`, leaving the operator with a raw cleanup
error from deep in finalize, no Preserved signal to the caller, the
container left running without explicit teardown decision, and
subsequent records in the loop never prompted. This regression was
introduced by the round-1 multi-record loop fix (the single-record
path always succeeded). Now caught per-record, eprintln'd as a
warning, and treated as `any_preserved_after_prompt = true` so the
loop continues and the caller gets `Preserved`.
P2. `inspect_attach_outcome` only treated `status == "running"` as
still-alive. `paused | restarting | removing | created | dead` all
fell through to `stopped(0)` → entered `finalize_clean_exit` →
could auto-delete worktrees of containers that may resume any
moment. Concrete: `docker pause jackin-x` while jackin re-attaches
→ status="paused" → SafeToDelete on a clean tree → operator
unpauses to find the worktree gone. Replaced if-cascade with an
explicit `match status` that only routes `exited` through stopped()
and treats unknown status strings conservatively as still_running.
P3. `purge_isolated_for_container` swallowed per-record errors with
eprintln warnings and returned `Ok(())`. Exacerbated by the
round-1 fix #4 (force_cleanup_isolated now bails more often on
real failures). Operator runs `jackin purge`, sees a warning
scroll past, gets exit-code-0 prompt back, may believe purge
completed. Now collects failures and surfaces an aggregate Err
with the failed mount list so the exit code reflects reality.
Test coverage for these fixes:
- 2 new tests in finalize.rs: ReturnToAgent on the 2nd-of-3 prompt
(early-return short-circuits), and force_cleanup_isolated failing
mid-loop (loop continues, returns Preserved).
- 8 new tests in launch.rs covering every status code path:
exited(0/non-zero/oom), running, paused, transient (restarting/
removing/created), dead, unknown.
- 2 new tests in cleanup.rs: purge bails on partial failure,
branch_still_present returning None proceeds (pins the doc-comment
contract against future refactors to `unwrap_or(true)`).
Net: 1186 → 1198 tests, +12 additions, all passing. fmt/clippy clean.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
---------
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Codex <codex@openai.com>
donbeave
added a commit
that referenced
this pull request
May 6, 2026
…183) * docs(roadmap): iteration 13 — AI code verifiability framing, config/types.rs full spec Primary goal shift: codebase must be verifiable for AI-generated code. - §0: replace generic description with explicit verifiability rationale (module contracts, localised concerns, types/behaviour separation) - §4 intro: add "Why structure matters for AI-generated code" section with audit-units table mapping each post-split file to one reviewable question - §4 4a: expand config/types.rs from description to full execution spec — exact type list, post-split mod.rs content, zero-change submodule guarantee (verified: agents.rs/persist.rs/workspaces.rs use super::T which resolves through mod.rs re-exports unchanged), impl-extension pattern already in use documented Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 14 — editor method map, app helpers, //! queue - §4 4c: config/editor.rs split is now execution-ready — complete 6-file method-to-file table with private helper placement verified (validate_candidate→io.rs, table_path_mut→mod.rs pub(super), auth_forward_str→agent_ops.rs, create_workspace delegates to AppConfig) - §4 4e: app/mod.rs split complete — all private helpers mapped (parse_auth_forward_mode_from_cli→config_cmd.rs, workspace_env_scope→workspace_cmd.rs, print_env_table note, remove_data_dir_if_exists→dispatch.rs) - §10 step 5: add //! priority queue — 10 files with draft content, prioritised by cold-landing impact and AI audit risk; selector.rs and instance/mod.rs explicitly document the /→__ invariant Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 15 — dep graph fix, trust.rs safety, OQ1 closed - §4 4d: correct operator_env dependency graph — layers.rs imports both mod.rs (OpRunner) AND client.rs (OpCli for non-injectable resolve_operator_env wrapper at line 797); still a valid DAG - §4 4f: verify trust.rs split safety — FnOnce injection pattern means launch_pipeline.rs has zero dependency on trust.rs; import chain documented; trust bypass audit now requires reading only ~60L - §9 OQ1 closed: op_cache.rs read in full — 4-level structure, per-level invalidation, no TTL/expiry (expiry handled at OpCli subprocess level), DEFAULT_ACCOUNT_KEY sentinel documented Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 16 — CommandRunner Rule 3, render/editor split, 4a/4c independence - Fix duplicate Rule 3 section introduced by previous edit; add docker.rs co-location note as third edge case (three edge cases, not two) - Add render/editor.rs as new Rule 5 violator: 1666L post-PR #171 (was listed as 782L); propose 6-file tab-by-tab split with auditability note on the security-adjacent Secrets tab - Add §10 execution-order note: 4a and 4c are independent — editor.rs imports AppConfig via crate::config re-exports regardless of 4a order - Append iteration 16 log entry with confidence table and weakest sections Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 17 — instance/auth.rs audit, state.rs split, line count corrections - Add instance/auth.rs to //! priority queue at #4: four security invariants (0o600 perms, symlink rejection, TOCTOU-safe writes, macOS Keychain) documented in draft //! content - Add state.rs as new Rule 5 violator: 992L/628L production; 26+ types mixed with impl blocks; propose 5-file types/behavior split - Correct stale line counts: render/list.rs 1122→1989 (PR #171 added render_environments_subpanel); state.rs 865→992; priorities upgraded - Fix §7.9 snapshot function line refs: sentinel_description_pane 306→332, mounts_subpanel 408→433, render_tab_strip 180→269, test ref 720→944 - Renumber //! priority queue to 11 entries (was 10) Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 18 — agent_allow OQ2 closed, render/list.rs split proposal - Close OQ2: agent_allow.rs read in full — 55L, correct //! doc, design sound; serves as model for //! priority queue pattern - Add render/list.rs as new Rule 5 violator: 668L production (PR #171 added render_environments_subpanel); propose 3-file split (mod.rs, details.rs, subpanels.rs); note import-path change for agents_block_agent_count - Update §1 module map: agent_allow.rs entry corrected with size/API Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 19 — input/editor.rs critical correction, split proposal - Correct input/editor.rs: 2349L total (was 1304L), 1141L production (was 547L) — PR #171 added Secrets-tab handlers; pub(super) fn handle_editor_modal at line 618 was invisible to previous grep pattern; now the largest production file in the codebase; priority → Critical - Correct input/save.rs: 1472L total, 661L production (was 567L) - Add 5-file split proposal for input/editor.rs: mod.rs (two dispatchers), secrets.rs (~500L AI-generated Secrets-tab), agents.rs, mounts.rs, general.rs - Update key insight paragraph naming input/editor.rs as largest production file Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 20 — console splits in §10, MSRV evidence, animation.rs verdict - Add console/manager/ as §10 Step 4f group with 5 sub-steps in priority order; rename existing 4f (launch.rs) → 4g; add circular-import risk note for ManagerStage/EditorState split sequencing - Analyze tui/animation.rs: 582L all-production, no split needed (banner_grid is a tightly-coupled rendering loop); section comments compensate for missing //! - Partially close OQ3: u64::is_multiple_of (stabilized 1.86) found in animation.rs; within declared MSRV 1.94; full cargo +1.94.0 check deferred (toolchain unavailable) Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 21 — input/save.rs split, //! queue fix, save.rs corrections - Add input/save.rs split proposal: 4 pub(super) fns discovered; 3-file split (mod.rs + flow.rs + preview.rs); no cross-dependency between flow and preview groups; §10 4f-v updated from Optional to concrete plan - Fix //! queue preamble: "first 10 files" → "first 11 files" - Correct save.rs module map (1418→1472L, correct key exports) and hot-spot table note (begin_editor_save ~280L → ~118L; commit_editor_save is the Phase 2 partner at ~149L) Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 22 — input/list.rs and mount_info.rs analysis - Analyze input/list.rs: 214L production (tests at 215); has //! doc; two focused pub(super) fns; no split needed; Low priority; correct module map - Add mount_info.rs to hot-spot table: 277L production; Low priority; has //! doc; correct module map with 3 public enums + inspect fn - Fix stale §2 diagnosis note: docs/internal/roadmap/ now exists Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 23 — audit units table +5 console rows, input/mod.rs corrected - Expand audit units table from 8 to 13 entries: add state/types.rs, state/editor.rs, input/editor/secrets.rs, render/list/subpanels.rs, input/save/preview.rs — all targeting PR #171 AI-generated console code - Add PR #171 context note linking 5 new entries to AI-generated code concern - Correct input/mod.rs module map: 369L, add InputOutcome enum to exports - Verify rust-toolchain.toml absence; §7.7 and §2 concept 25 already correct Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 24 — render/mod.rs analysis, //! exemplars table, EditorTab confirmed - Add §4 Rule 7 positive exemplars table: 7 files with //! docs graded 1-element (render/mod.rs), 2-element (input/save.rs etc), 3-element (env_model.rs, agent_allow.rs); PR #171 docs-discipline pattern noted - Correct render/mod.rs module map: 421L; FooterItem + palette constants + render_header + centered_rect_fixed added to key exports - Confirm EditorTab variants: General, Mounts, Agents, Secrets (Rust enum) vs "Secrets / Environments" (UI label); /stub qualifier already removed Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 25 — too_many_lines recount, FooterItem PR, MountConfig caveat - Correct too_many_lines count: 13 across 8 → 16 across 11 files (PR #171 added 5 suppressions in console/manager); add full breakdown table; update all 3 occurrences in roadmap - Fix FooterItem PR reference: #165 → #166 (confirmed by git log --follow) - Add MountConfig → MountSpec rename caveat to §7.5 snapshot test description Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 26 — console/mod.rs and op_picker/render.rs analyzed - Add console/mod.rs to hot-spot table: 406L/307L production (Low); correct module map from ~200 → 406L; note missing //! doc with ConsoleStage design block comment worth promoting - Add op_picker/render.rs to hot-spot table: 865L/545L production (Medium); PR #171 AI-generated; 14 functions in two logical groups (entry/helpers vs level renderers); split into levels.rs proposed - Correct 3 stale ~200L estimates for console/mod.rs across roadmap Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 27 — op_picker/mod.rs discovery, render split, operator_env correction - Add op_picker/mod.rs to hot-spot table: 1712L/775L production (High); PR #171 AI-generated; OpPickerState types+behavior split opportunity; has 7-line //! doc; module map split into two rows (mod.rs + render.rs) - Add op_picker/render.rs 2-file split proposal: render.rs (coordinator) + render_pane.rs (pane/level renderers); no cross-dependency confirmed - Correct operator_env.rs total: 1569→2130L (880L production); update 4 occurrences across hot-spot table, ASCII tree, §4 analysis Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 28 — op_picker/mod.rs 3-file split, count corrections - Add op_picker/mod.rs formal 3-file split: loading.rs (async load family ~120L) + keys.rs (4 level key handlers ~315L) + mod.rs (types/constructors) - Correct "24 files" → "28+" for 500L threshold count - Update total LOC: ~40,664 → ~43,587 (2 occurrences, with provenance note) Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 29 — op_picker execution order + file_browser analysis - §10 Step 4f: expand from 5 to 7 sub-steps; add 4f-vi (op_picker/mod.rs → mod.rs + loading.rs + keys.rs) and 4f-vii (op_picker/render.rs → render.rs + pane.rs); document impl-extension and import-path caveats - §4 //! exemplars: add file_browser/ subsystem analysis — all 5 files have //! docs, no file exceeds ~350L production; classified as exemplar (not a split candidate); document git_prompt.rs coupling-density justification and input.rs as 28-file false positive (144L production) - §1 module map: expand single file_browser/ row to 5 individual rows with production LOC and dominant concern per file Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 30 — challenge split-first thesis, fresh LOC corrections - §4: Add "Alternative thesis: documentation-first verification" — challenges the two core assumptions behind file splitting (files-as-audit-unit and file-size-as-context-constraint); adds 7-criterion comparison table vs structure-first approach; introduces phased combined recommendation: Phase 1 = doc sprint (//! contracts + specs/ for 3 subsystems, 2-3 PRs, zero structural change); Phase 2 = splits only for >600L production files (reduces scope from 14+ to 4 files); Phase 3 = workspace if LOC > 150K - Fix stale LOC: app/mod.rs 951→979, config/editor.rs 1467→1548 (7 and 8 locations respectively; verified by fresh find|xargs wc -l scan) - §1 module map: add agent_picker.rs (436L), scope_picker.rs (201L), source_picker.rs (244L) — all PR #171 additions with //! docs Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 31 — fix 600L→800L threshold error, correct LOC - §4 alternative thesis: correct ">600L production → 4 files" claim introduced in iteration 30; re-verified all 9 candidate files via #[cfg(test)] line position; threshold must be >800L to get exactly 4 files (9 exceed 600L); add verification table with test-start lines - Production LOC corrections (5+ locations each): launch.rs 1085→~1077, operator_env.rs 810→~880, app/mod.rs 928→~957, config/editor.rs 503→~584 - §2 OpPicker row: replace vague "no entry yet" with confirmed gap: PROJECT_STRUCTURE.md line 53 still lists pre-PR#171 widget set (10 named); omits op_picker/, agent_picker.rs, scope_picker.rs, source_picker.rs and pre-dates the manager/ sub-structure split Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 32 — two-tier spec arch, behavioral spec template - §8.1: Add two-tier spec architecture table distinguishing feature specs (public Starlight MDX, user-facing) from behavioral specs (internal docs/internal/specs/, for AI code verification) — resolves contradiction between §4 (which said docs/internal/specs/) and §8.1 (which said "no longer needed; specs are public") - §8.1: Add concrete behavioral spec template for op_picker/ with state machine table and 3 INV invariant entries each with a grep-executable "Verify by:" command; template directly usable for the 3 Phase 1 specs - §8.1: Remove erroneous "docs/internal/specs/ no longer needed" claim - Confirmed render/editor.rs ~736L and render/list.rs ~668L production (no interspersed production code — all test blocks follow consecutively) Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 33 — executive summary, §0 correctness - §0: Add executive summary (~300 words) with core problem, 3-phase recommendation, key counter-argument, and navigation table pointing to §2/§4/§7/§8/§10 by question — resolves the meta-irony of a readability roadmap with no entry-point orientation - §0 item 2: "1569-line monolith" → "2130-line monolith" (operator_env.rs current verified size; stale reference was in the first section readers see) - §0 item 3: Add "(selective)" qualifier and explicit note that standard Rust co-locates struct+impl — impl-extension pattern is justified only for files >800L production, not as a universal rule Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 34 — spec priority reorder, §10 Phase 1 track - §0 + §4 Phase 1: Prioritize runtime/launch.rs behavioral spec (no //! doc, ~1077L production, critical path — all jackin load failures trace here); drop config/editor.rs from Phase 1 (its 963L test suite already serves as behavioral spec — tests are behavioral examples); reduce Phase 1 from 3 specs to 2 specs; add reasoning for the priority ordering - §10 Step 2: Split into two parallel tracks — Track A (cc-sdd tooling setup) + Track B (Phase 1 behavioral spec authoring); Track B includes specific INV invariants to capture for runtime/launch.rs grounded in reading the actual function structure (step comment positions); adds sequencing rationale: spec must precede structural splits Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 35 — verified INV entries for runtime/launch.rs Read load_agent_with lines 553-892 in full. Replaced 3 draft INVs from iteration 34 (inferred from step comment positions) with 5 verified INVs citing exact line numbers: - INV-1: trust gate (line 594) precedes image build (line 736) - INV-2: container name claimed (line 754) between image build and network - INV-3: token verified (line 763) before network creation (line 827) - INV-4: render_exit called at lines 886 AND 890 (all exit paths) - INV-5: cleanup disarm semantics — Running→disarm, clean exit→cleanup, crash→disarm (explains jackin hardline compatibility) Corrected wrong line number: claim_container_name call is at 754, not 918 (918 is the function definition). Each INV has a grep-executable Verify by. Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 36 — CI gate for PROJECT_STRUCTURE.md freshness §3: Add "Preventing future PROJECT_STRUCTURE.md staleness" subsection with three concrete options: - Option A: CONTRIBUTING.md rule (necessary but insufficient) - Option B: ci.yml git-diff-scoped shell check (recommended) — only checks files added in the current PR so it doesn't require fixing existing stale entries before merging; greps for module directory name in prose - Option C: Structured TOML module registry (over-engineered for scale) Includes concrete YAML snippet for Option B grounded in the check:repo-links.ts pattern already established in docs/scripts/ Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iterations 36-37 — CI gate + greenfield workspace architecture Iteration 36: - §3: Add "Preventing future PROJECT_STRUCTURE.md staleness" subsection with 3 options (CONTRIBUTING.md rule / ci.yml git-diff check / TOML registry); recommend Option B (git-diff-scoped YAML step) with concrete snippet grounded in existing check:repo-links.ts pattern from docs/scripts/ Iteration 37 (operator directive: greenfield Rust structure): - §4: Add "Greenfield architecture — ideal structure for a growing project" section based on verified cross-module dependency graph (grep iteration 37) - Confirms dependency tiers: workspace/manifest/docker/paths/selector = Tier 0; config/tui/instance = Tier 1; operator_env/runtime/repo = Tier 2; console = Tier 3 - Key finding: workspace/ is LOWER-level than config/ (config re-exports workspace types at lines 5-6); ideal naming inverted in greenfield (jackin-core > jackin-config) - Documents ideal 6-crate workspace: jackin-core, jackin-config, jackin-tui, jackin-runtime, jackin-console, jackin-shell + thin binary - Notes console/ has NO runtime/ import — cleanest pre-existing crate boundary - Bridge: incremental splits (4a, 4d, 4g) are pre-work toward workspace migration Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 38 — Rust workspace standards, community evidence Ground workspace recommendation in real-world project research: - ripgrep (9 crates), gitui (5 crates) went workspace due to library consumers - starship and fd-find stay single-crate at 1M+ LOC — no library use case - jackin (43K LOC, no external consumers) maps to starship/fd pattern → single-crate is community-standard; "stay single-crate" recommendation confirmed Update greenfield workspace structure to follow matklad's pattern: - Virtual manifest at root (no [package] in root Cargo.toml) - Flat crates/ directory (not nested); crate names match folder names - version = "0.0.0" for unpublished internal crates - Add inline dep comments to each crate in the ASCII structure Add research notes: ripgrep/starship/gitui/fd-find Cargo.toml findings + Cargo workspaces reference + matklad "Large Rust Workspaces" (2021-08-22) Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): revise §7.9 + §3 — adopt per-directory README.md §7.9: Reverse previous "reject" recommendation to "adopt" per-directory README.md for major src/ module directories. Rationale: README.md is AI-native — Claude Code, Copilot, Cursor load it automatically on directory entry, giving AI agents orientation before they decide which file to open. PROJECT_STRUCTURE.md being confirmed stale removes the main argument for the "single root file" approach. Add three-layer documentation model table: - README.md: directory orientation (AI + human, on entry) - AGENTS.md: agent workflow rules (root, session start) - CLAUDE.md: @AGENTS.md pointer only — NEVER add content here - //! docs: file-level contracts (when reading/editing) Add specific README.md content targets for 7 directories (src/, src/runtime/, src/console/, src/console/manager/, src/console/widgets/, docs/, docs/internal/). §3 target document shape: Add per-directory README.md to proposed hierarchy; add docs/internal/specs/ explicitly; note CLAUDE.md design principle (single-line @AGENTS.md — never duplicate content). Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): internal docs are browsable — unified Starlight site Operator directive: internal docs (architecture, specs, ADRs, roadmap) should be browsable, not hidden filesystem files. They are a different TYPE of docs focused on implementation details and vision, published as a "Developer Reference" section of the Starlight site. §3 target document shape: - docs/internal/ moves into docs/src/content/docs/internal/ (Starlight pages) - Browsable at jackin.tailrocks.com/internal/ - Sidebar: "Developer Reference" group (collapsed by default) with sub-sections for architecture, code-tour, contributing, testing, decisions, specs, roadmap - Include astro.config.ts sidebar config snippet §8.1 two-tier spec distinction eliminated: - Feature specs and behavioral specs both live at docs/src/content/docs/internal/specs/ - Type expressed via spec_type: behavioral | feature frontmatter, not filesystem location - Both browsable and searchable via Starlight; AI agents can be pointed to URLs §8.3 + §4: - All docs/internal/specs/ paths → docs/src/content/docs/internal/specs/ - ADRs: docs/internal/decisions/ → docs/src/content/docs/internal/decisions/ (browsable) - README.md pointer for src/runtime/ updated to URL reference Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): §11 — modern Rust docs platform (future project) Add §11 capturing the vision for a modern docs.rs alternative with: - rustdoc JSON ingestion → Astro Starlight presentation - MCP server for AI agent queries (Context7 alternative for Rust) - Rust-specific query types: rust_get_context(), rust_find_impls(), rust_search_types() — things Context7 cannot provide - Comparison table vs Context7 - Architecture diagram (ingestion → processing → Starlight + MCP) - Name candidates: rustlight, ferrodoc, cargo-starlight / starlight.rs - Note that jackin's §7.15 gen-rust-api.ts pipeline is the intentional prototype for the platform's processing and presentation layers Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 39 — update §0, fix stale internal/ paths §0 executive summary: rewrite to reflect decisions from iterations 30-38: - browsable internal docs (jackin.tailrocks.com/internal/) - per-directory README.md adoption (§7.9 reversed) - CLAUDE.md = @AGENTS.md single-line pointer only - greenfield workspace architecture (matklad's virtual manifest pattern) - §11 future project: modern Rust docs platform / Context7-for-Rust - document size 1800+ → 2200+ Fix stale docs/internal/ bare paths not caught by iteration 38 sweep: - Mermaid diagram: INTERNAL_ROADMAP, INTERNAL_CODE_TOUR → Starlight paths - §7.10 ADRs: docs/internal/decisions/NNN-title.md → .mdx Starlight path - §10 Track B item 2: op-picker spec path → Starlight MDX Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 40 — §7.15 pipeline + Rule 4 pub audit §7.15 (new): rustdoc JSON → Astro Starlight API documentation pipeline - Three options: rustdoc HTML publish / rustdoc JSON + bun script (recommended) / rustdoc-json crate as Rust binary - Option B recommended: matches existing docs/scripts/ pattern, nightly isolated to separate CI step, zero effect on stable build - Key design: URL at /internal/api/, cross-links to behavioral specs, Starlight unified search, prototype for §11 future project - Pub(crate) note: gen-rust-api.ts can feed Rule 4 visibility audit - Recommend: adopt after Phase 1 //! sprint (value ∝ coverage) §4 Rule 4 pub discipline: replace estimated "50-100 items" guess with verified numbers from iteration 40 grep: - 257 bare pub items, 21 pub(crate), 61 pub(super) across 94 files - 0 uses of unreachable_pub lint — no enforcement gate - Top violators: operator_env.rs (17), tui/output.rs (13), planner.rs (8) - Add concrete Cargo.toml [lints.rust] snippet: unreachable_pub = "warn" - Revised scope: ~150-200 mechanical conversions (excludes entry points) Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): split research into 19 actionable items Delete _research_notes.md (no longer needed). Replace 2343L READABILITY_AND_MODERNIZATION.md with: - README.md: index of all 19 items with phase, ordering notes, links - READABILITY_AND_MODERNIZATION.md: lightweight research summary (63L) - items/ITEM-001 through ITEM-019: individual actionable items Items by phase: Phase 1 (low risk, no confirmation): ITEM-001..004, 006..011 Phase 1 (needs confirmation): ITEM-005, 016, 018 Phase 2 (structural splits, confirmation required): ITEM-012..015 Phase 3 (deferred): ITEM-017 (rustdoc pipeline), ITEM-019 (workspace) Each item has: summary, key files with line numbers, steps, what needs confirmation, and relevant research backing from the 40-iteration analysis loop. Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): migrate 19 items to Starlight reference/roadmap section Move all codebase health roadmap items from docs/internal/roadmap/items/ (plain Markdown, not browsable) to docs/src/content/docs/reference/roadmap/ (MDX pages, browsable at jackin.tailrocks.com/reference/roadmap/). Adds a new "Codebase health" sidebar group (Phase 1 → Phase 3) to astro.config.ts. Deletes the old items/ directory. Updates the internal README to redirect to the new location. Also adds codebase-readability.mdx — a new overview item that captures the overall readability/restructuring program with a recommended execution order: file splits first, then greenfield workspace, then per-directory README+AGENTS.md, then docs and specs. Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@chainargos.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): remove premature internal/roadmap/README.md The internal/ structure doesn't exist yet — it will be created as part of the roadmap items themselves. No need for a redirect stub now. Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@chainargos.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): remove READABILITY_AND_MODERNIZATION.md research archive All content has been distilled into the individual Starlight roadmap pages. The full 2343L research is preserved in git history at commit b7e9fc2. Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@chainargos.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): fix check:repo-links errors + remove iteration log - Replace plain code spans with <RepoFile> for validate.rs, mise.toml, Cargo.toml, and op_picker/mod.rs - Remove deleted READABILITY_AND_MODERNIZATION.md reference from codebase-readability.mdx - Delete _iteration_log.md (git history is the archive) Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@chainargos.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): fix lychee false-positive link in move-contributing-testing The example redirect text contained a markdown hyperlink to a proposed future file path that doesn't exist yet. Changed to a code span. Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@chainargos.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> --------- Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Signed-off-by: Alexey Zhokhov <alexey@chainargos.com> Co-authored-by: Claude <noreply@anthropic.com>
donbeave
added a commit
that referenced
this pull request
May 6, 2026
* docs(spec): per-mount isolation V1 implementation spec
Captures roadmap → executable design: module layout under
src/isolation/, MountIsolation enum + MountConfig.isolation field,
materialization runtime hook, foreground finalizer with
safe/preserved/force cleanup, source-drift detection, jackin cd
command, TUI integration. Test list and docs touchpoints enumerated
for the implementation plan.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(isolation): introduce MountIsolation enum
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(workspace): add isolation field to MountConfig
Defaults to Shared; serde skips emitting the field when Shared so
existing TOMLs round-trip unchanged.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(workspace): reject nested isolated mounts
Two worktree-isolated mounts whose dsts nest have no safe on-disk
layout. Sibling isolated mounts and isolated-parent-with-shared-child
remain allowed.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(config): reject isolation field on global mounts
Adds a strict GlobalMountConfig wire-format struct that mirrors
MountConfig minus the isolation field, with deny_unknown_fields
so operators get a clear parse error if they try to set isolation
on a global mount. Isolation remains a workspace-mount concept.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* refactor(config): drop dead code and tighten global-mount API
- Delete unused From<MountConfig> for GlobalMountConfig (silently
dropped isolation; no callers).
- Delete unused get_mut and remove on DockerMounts along with their
#[allow(dead_code)] annotations.
- Tighten AppConfig::add_mount: debug_assert that incoming
MountConfig has Shared isolation, and construct GlobalMountConfig
explicitly from src/dst/readonly rather than via the (now-deleted)
From impl. Keeps the public signature stable so callers in CLI,
resolve.rs, and preview.rs don't need to change (Issue 2 option B).
- Add wire-path rejection test that goes through MountEntry's
untagged enum and asserts on the actual serde error
("data did not match any variant of untagged enum MountEntry").
- Soften GlobalMountConfig's doc comment to reflect the actual
serde error shape at the wire path.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(isolation): IsolationRecord + isolation.json IO
Atomic write via tmp+rename. Version-1 envelope leaves room for schema
evolution. Read/upsert/remove keyed by mount destination.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* test(isolation): drop redundant clones in state tests
Bring the warning baseline back to 87 after Task 2.1 introduced
two clippy::style hits (cloned_ref_to_slice_refs, redundant_clone).
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(isolation): list_records_for_workspace walks data dir
Used by workspace-edit drift detection to find which containers have
preserved isolated state for a given workspace.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(isolation): branch_name renderer with namespace + suffix support
Suffix is appended to the final selector segment so namespaced agents
keep their selector shape and the disambiguator goes on the leaf name.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(isolation): MaterializedWorkspace types
Third workspace shape (Config -> Resolved -> Materialized) used as the
runtime handoff into Docker launch.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(isolation): worktree_path_for derives on-disk path from mount dst
Uses dst verbatim (leading/trailing slashes stripped) under
isolated/, so the layout mirrors the container path.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(isolation): ensure_worktree_config_enabled
One-shot enabler for extensions.worktreeConfig on the host repo.
Bumps core.repositoryformatversion to 1 when needed.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(isolation): preflight checks for worktree materialization
Sensitive-mount, readonly, repo-root, and mid-operation guards.
Errors cite the mount destination and the worktree mode.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(isolation): dirty-host preflight gate with --force opt-out
Non-interactive load without --force rejects a dirty host tree.
Interactive contexts are expected to obtain ack upstream.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(isolation): materialize_workspace orchestrator
Per-mount worktree materialization with idempotent reuse, source-drift
guard, and branch-name disambiguation when multiple isolated mounts
target the same host repo.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* test(isolation): cover branch disambiguation for same-repo mounts
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(isolation): order Docker mounts parent-before-child
Length-ascending sort so shared cache children overlay isolated
worktree parents at container start.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(runtime): hook materialize_workspace between AgentState and Docker
Workspace mounts now flow Config -> Resolved -> Materialized before
reaching the docker run command, with parent-before-child ordering.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(isolation): force_cleanup_isolated removes worktree + branch + record
Best-effort git invocations that tolerate missing host repo and
already-removed worktree. Used by purge and the finalizer's force-delete
branch.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(isolation): finalizer skeleton + AttachOutcome shape
Decides Preserved when container still running, OOMed, or exited
non-zero. Clean-exit path stubbed - implemented in follow-ups.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(isolation): safe-cleanup deletes branches with no commits
When the worktree is clean and HEAD equals the recorded base, the
scratch branch is removed automatically.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(isolation): consult upstream when deciding safe cleanup
Pushed commits (reachable from upstream) are safe to delete; local-only
commits or no-upstream divergence preserve the worktree.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* test(isolation): cover interactive unsafe-cleanup prompt branches
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(runtime): finalize foreground session after attach in load + hardline
Both load and hardline now consult inspect_attach_outcome and dispatch
the shared finalizer. Return-to-agent retries safe cleanup once after
the operator returns.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* fix(purge): refuse to run on a live container
Closes a pre-existing gap where purge could delete state out from
under a running agent. Operator must eject first.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(purge): remove isolated worktrees and scratch branches
Reads isolation.json and runs force_cleanup_isolated for each record
before deleting the per-container state directory.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(cli): --mount-isolation DST=TYPE on workspace create/edit
Repeatable. Rejects clone before persistence with the canonical
"reserved but not implemented yet" message.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(workspace): add Isolation column to workspace show
Renders canonical lowercase name for every mount so CLI output matches
TOML/CLI input verbatim.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(load): add --force to acknowledge dirty host tree
Required for non-interactive isolated-mount materialization when the
host working tree is dirty.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(workspace): detect source drift on edit affecting isolated mounts
Edits that change src for a mount with preserved isolated state are
rejected unless --delete-isolated-state is passed and no related
container is running.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(cli): jackin cd opens a child shell in an isolated worktree
Single-mount-no-dst → uses it. Dst-provided → exact match. Multi-mount
no-dst → interactive picker on TTY, error on non-TTY. Sets JACKIN_*
env vars. Does not modify the parent shell.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(console): show isolation badge per mount in editor + preview
Adds an Iso column to the workspace-manager mount table (editor and
list-pane sub-panel) and a `[shared|worktree|clone]` tag to the agent
preview's resolved-mounts lines. Per the per-mount-isolation spec the
badge renders the canonical spelling for every mount, including
`shared`, so operators always see which strategy applies.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(console): I hotkey cycles isolation on the selected mount
Mirrors the existing R (readonly) toggle. Cycles Shared -> Worktree
-> Shared; Clone is reserved-but-rejected in V1 and is not entered
through this hotkey, but a saved Clone mount snaps back to Shared on
the first I press rather than getting stuck. The cycling rule lives in
EditorState::cycle_isolation_for_selected_mount so the input dispatch
arm stays trivial.
Also surfaces the new key in the Mounts-tab footer hint alongside the
existing R toggle so the affordance is discoverable.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(console): source-drift confirm modal in workspace editor
Save flow runs the same drift detection as `jackin workspace edit`:
detect_workspace_edit_drift evaluates the prospective mount list
(post-collapse, post-upsert) against IsolationRecords on disk before
the on-disk write.
Running container drift -> ErrorPopup ("eject first"); save aborted.
Stopped container drift -> Confirm modal listing the affected
container names with a Yes/No prompt. On Yes the modal handler
re-stashes the plan with delete_isolated_acknowledged = true and the
second commit pass calls force_cleanup_isolated for each affected
record before writing.
Reduced scope vs the original three-button "Delete preserved state and
save / Cancel / Open mount details" dialog: the modal is the existing
two-button Confirm widget (Yes/No). The third "open mount details"
affordance is omitted — operators dismiss with N/Esc, find the
offending mount in the editor, and revert the src by hand. Adding it
would require either a custom widget or repurposing an existing
multi-choice one and threading mount-row focus through the modal
plumbing; the safety value is in the block-and-ack semantics, which
the two-button form covers.
Adds a commit_editor_save_with_runner test seam so the FakeRunner can
drive the drift branch without a real Docker daemon.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* docs(workspaces): add per-mount isolation section
Document the per-mount isolation feature in the workspaces guide:
the three modes (shared default, worktree, clone reserved-but-rejected),
validation preconditions, the isolated-source + shared-cache child
pattern with TOML, and pointers to --mount-isolation and jackin cd.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* docs(mounts): document mount isolation field
Add an "Mount isolation" section to the mounts guide covering the
shared/worktree values, the global-mount rejection at parse time,
and the isolated-source + shared-cache child composition pattern.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* docs(configuration): add MountConfig.isolation field
Document the new mounts[].isolation field in the configuration
reference: shared default, worktree opt-in, clone reserved-but-rejected,
and the global-mount parse-time rejection.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* docs(architecture): document materialization flow + isolation.json
Add a "Workspace materialization" section to the architecture reference
covering the WorkspaceConfig -> ResolvedWorkspace -> MaterializedWorkspace
shapes, the per-container isolation.json layout, and the post-attach
foreground finalizer's Preserved/Cleaned/ReturnToAgent decision matrix.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* docs(workspace): document --mount-isolation and Isolation column
Add --mount-isolation to workspace create/edit option tables (with the
clone "planned but not implemented" note), document the new
--delete-isolated-state flag for non-interactive source-drift edits,
and note the Isolation column on workspace show.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* docs(load): document --force dirty-host acknowledgement
Add --force to the option table and a dedicated section explaining
when it's required (non-interactive load with a worktree-isolated
mount + dirty host tree) and what it does NOT do (no stash, no
discard, no relaxation of other validation).
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* docs(purge): document running-agent guard and isolated cleanup
Document purge's new behavior: refuses to run on a running container
(eject first), force-removes isolated worktrees + scratch branches
recorded in isolation.json, and tolerates a missing host repo on
best-effort cleanup.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* docs(cd): add jackin cd command reference
Create a reference page for jackin cd <container> [dst] covering
arguments, the mount-selection behavior matrix (zero/one/many isolated
records, with and without dst), the JACKIN_* env vars set in the
child shell, exit-code passthrough, and the no-parent-mutation
guarantee. Wire it into the Commands sidebar between console and
launch.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* docs(roadmap): mark per-mount isolation V1 implemented
Flip the per-mount-isolation roadmap status to "Implemented in V1"
and replace the duplicate-mounts-allowed line with the actual V1
rule: multiple isolated mounts are allowed (with branch-name
disambiguation), but nested isolated dst paths are rejected at
validation because the inner worktree's .git would land inside the
outer worktree's tree.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* docs(structure): add isolation module tree and cd command
Update PROJECT_STRUCTURE.md to document the new per-mount-isolation
work: isolation/ module row (mod/branch/materialize/state/finalize/
cleanup), cli/cd.rs entry on the cli/ row, --mount-isolation /
--delete-isolated-state / --force notes on the relevant CLI rows,
foreground-finalizer mention on the runtime row, the new
commands/cd.mdx in the docs map, and a code->docs cross-reference
row mapping src/isolation/** to all the doc pages it touches.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* test(isolation): end-to-end materialize -> clean-exit -> cleanup
Exercises the full lifecycle through public APIs with a small inline
scripted runner. No real git or docker.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* docs(isolation): note finalizer is local-only for hardline lockdown
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* style(test): apply rustfmt to per-mount isolation e2e
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* ci(docs): verify docs links in PRs and on the deployed site (#173)
* ci(docs): add link checking
Co-authored-by: Codex <codex@openai.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* ci(docs): stabilize lychee checks
Co-authored-by: Codex <codex@openai.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* ci(docs): validate edit links with lychee
Co-authored-by: Codex <codex@openai.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* ci(docs): close link check gaps
Co-authored-by: Codex <codex@openai.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* docs: add repo file link component
Co-authored-by: Codex <codex@openai.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* docs: explain repo link source check
Co-authored-by: Codex <codex@openai.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* ci(docs): allow manual dispatch of deployed link check
Two refinements from PR review:
- The check-deployed job now triggers on workflow_dispatch in addition
to schedule, so maintainers can manually verify the live deployed
docs without waiting for the daily cron or pushing to main. This
closes the gap against goal "manual workflow to verify both built
site and deployed documentation".
- Drop github.sha from the deploy job's lychee cache primary key so it
matches across runs (the SHA-keyed primary was guaranteed to miss,
forcing fallback to restore-keys). Now mirrors the cache key shape
used by check-deployed.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* ci(docs): harden link-check workflow and broaden source lint
Address review feedback on PR #173.
Workflow (.github/workflows/docs.yml):
- Split concurrency group by event_name so a scheduled or
workflow_dispatch run cannot cancel an in-flight push deploy. Cancel
in-progress is now scoped to pull_request only.
- Exclude main from workflow_dispatch on check-deployed. The deploy job
already verifies the just-deployed site, so running both in parallel
would race against the publish window. Manual verification of the live
site from main flows through deploy; from feature branches it flows
through check-deployed.
- Add the build cache as a fallback restore-key for check-deployed so
the daily cron and manual runs warm-start from the last build cache
when the lychee.toml fingerprint changes.
RepoFile component (docs/src/components/RepoFile.astro):
- Add target=_blank and rel=noopener noreferrer so GitHub source links
open in a new tab, matching the behavior rehype-external-links applies
to plain markdown external links in MDX.
Source lint (docs/scripts/check-repo-links.ts):
- Normalize leading slashes when checking RepoFile path existence so the
validator agrees with the component's own normalization.
- Cover top-level repo-specific files (Cargo.toml, Cargo.lock, Justfile,
build.rs, docker-bake.hcl, mise.toml, release.toml, renovate.json) so
a rename of any of those also breaks the docs CI gate, not only paths
under src/, docs/, docker/, .github/.
Content (docs/src/content/docs/developing/construct-image.mdx):
- Convert the remaining inline-code references to docker-bake.hcl and
Justfile to <RepoFile />. These were the references that motivated
extending the lint to top-level files.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* docs(roadmap): apply RepoFile lint to multi-runtime proposal
After merging main into codex/docs-link-checks, check-repo-links flagged
37 plain inline-code references to existing repo files in the new
multi-runtime-support.mdx (added in #174 before this PR's lint existed
on main). Convert each one to <RepoFile /> so renames or deletions of
those source files break the docs gate before merge, the same way the
rest of the roadmap is now protected.
No prose changes — every conversion is one-for-one (`src/foo.rs` →
<RepoFile path="src/foo.rs" />). The trailing-slash directory reference
to `docker/runtime/` is left as a code span, since the lint correctly
skips it (it's a directory, not a file).
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* fix(roadmap): repair broken Amp CLI link in multi-runtime proposal
CI failed on this PR's last build because lychee found a 404 on
https://github.com/sourcegraph/amp in multi-runtime-support.mdx (added
in #174). That repo does not exist publicly — Amp's source is not on
GitHub.
Point the link at https://ampcode.com instead, which is already the
canonical Amp URL used elsewhere in the docs (getting-started/why.mdx).
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
---------
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
Co-authored-by: Codex <codex@openai.com>
Co-authored-by: Claude <noreply@anthropic.com>
(cherry picked from commit f3f3e5e2d386a9cb3c2537be1e51b60f3fc09e6e)
* docs(roadmap): wrap src/cli/cd.rs in <RepoFile> on per-mount-isolation page
The check-repo-links script (added in #173) flags any inline-code
reference to a real repo file that isn't wrapped in <RepoFile />.
src/cli/cd.rs was created on this branch, so once #173's lint reaches
this branch via the previous cherry-pick, the bare reference fails
the Docs CI check.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* docs: switch cross-doc links to absolute URL form
The link-check job's lychee step (added on main in #173, hardened in
#176) verifies built-site links against the on-disk dist tree. Relative
`.mdx`-suffixed links break that check because lychee resolves them as
literal file paths under the rendered URL's directory — e.g.
`./workspaces.mdx` rendered from `/guides/mounts/` resolves to
`/guides/mounts/workspaces.mdx`, not `/guides/workspaces/`.
Switch the four cross-doc links added by the per-mount-isolation work
to the rendered-URL form (`/guides/workspaces/#per-mount-isolation`
etc.) — same convention as the existing `[mount collapse](/commands/workspace/#mount-collapse)`
link in the same file.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(isolation): emit verbose debug-mode trace for worktree lifecycle
Operators sharing logs to debug worktree behavior had no visibility into
the lifecycle — only three error/warning sites fired output, and none of
them ran on the happy path. `--debug` toggled a display mode (preserve
scrollback, clear spinner) but was not a verbose-trace facility.
This adds a `debug_log!(category, fmt, ...)` macro in `src/tui/mod.rs`
that gates on the existing `DEBUG_MODE` atomic so disabled call sites
cost only an atomic load (formatting is deferred behind the gate).
Output uses a `[jackin debug <category>]` prefix so shared logs are
greppable.
Instrumented sites (all under `category = "isolation"`):
- `materialize_workspace`: per-call summary (workspace, container,
selector, mount counts, force/interactive flags).
- `materialize_one`: per-mount decision trail — drift detection,
worktree reuse, preflight, base-commit lookup, branch derivation
with selected suffix, and the `git worktree add` invocation itself.
- `ensure_worktree_config_enabled`: every state transition (already
enabled vs. bumping repositoryformatversion vs. flipping the flag)
with the host repo path.
- `state.rs`: write_records (count + path), upsert_record (insert vs.
replace), remove_record (drop vs. no-op).
- `cleanup.rs`: force_cleanup_isolated entry, the two git invocations,
the rm -rf fallback, and the host-repo-missing skip path.
purge_isolated_for_container per-container summary.
- `finalize.rs`: foreground-session entry with exit code/oom/interactive
flags, the early-return path for non-clean exits, per-record cleanup
assessment.
- `runtime/launch.rs`: load_agent's call into materialize_workspace.
Read paths (`read_records`, `read_record`) are intentionally NOT logged
— they fire on every invocation and would drown the log.
Manual verification (since binary-level stderr capture would need a new
test dependency):
cargo run --release --bin jackin -- --debug load <agent>
cargo run --release --bin jackin -- --debug workspace edit <ws> \
--mount-isolation /workspace/proj=worktree
cargo run --release --bin jackin -- --debug purge <container>
Each emits a chronologically ordered `[jackin debug isolation] ...`
trace covering every git invocation, isolation.json mutation, and
finalize decision — suitable for sharing in bug reports.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* fix(tui): rename Iso column to Isolation, count isolation flips as one change
Two related TUI papercuts surfaced together when an operator flipped a
mount's isolation from `shared` to `worktree` via the `I` hotkey:
1. The mount-table header read `Iso` — opaque on first sight. Replaced
with `Isolation` (the full word). Bumped the column-width constant
from 8 → 9 so the header label fits without disturbing data-row
alignment, and renamed `MOUNT_ISO_COL_WIDTH` →
`MOUNT_ISOLATION_COL_WIDTH` for consistency. Updated the
alignment-regression test that asserted on the old label.
2. Cycling isolation on an existing mount (same `dst`, same `src`)
reported "2 changes" in the save-row footer and rendered the
Confirm Save dialog with a `+`/`-` pair for the same path. Both
sites used `MountConfig::contains()` — full-struct equality — so
any isolation/readonly drift made the row appear as remove + add.
Extracted a `MountDiff` classifier in `console/manager/state.rs`
that keys on `dst` (the identity used by upsert/remove everywhere
else). Same-`dst` matches with structural drift are now reported
as a single `Modified`, counted as one change in `change_count`
and rendered as a `~ <new>` line with a dimmed `was: <old>` follow-up
in the Confirm Save summary so the operator sees exactly what
changed without parsing a remove + add pair.
Extended `mount_summary` to include the isolation tag so the
delta is visible in both the new and old lines:
`~/foo (rw, worktree, github · main)`.
Records a new shared rule in `RULES.md` ("TUI Labels") to prevent
future short-form labels in user-facing TUI surfaces — operators
read the TUI in passing and cannot afford to decode `Iso`/`Cfg`/`Env`/
`WD`-style abbreviations. Lists the established short forms that are
NOT considered abbreviations (`dst`, `src`, `git`, `op`).
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* fix(isolation): make worktree mode actually work inside the container
V1's worktree mode shipped with a gap: the materialized worktree was
bind-mounted into the container at <dst>, but the worktree's `.git`
text file (a pointer back to <host_repo>/.git/worktrees/<n>/) referenced
an absolute host path that didn't exist inside the container. Every git
command — `git status`, `git log`, `git commit`, `git push` — failed
with "fatal: not a git repository". The agent could read source files
but could not commit work, defeating worktree mode's whole purpose.
Fix: wire up three additional bind mounts at docker-run time, plus two
jackin-owned override files written at materialization, so git's gitdir
relationship resolves consistently inside the container without
modifying any host-side files.
For each isolated worktree, the container now sees:
1. The worktree at <dst> (existing).
2. The host repo's `.git/` at /jackin-isolation/<container>-git/ rw,
so git can find objects, refs, and the per-worktree admin dir.
3. A jackin-owned `.git` text file at <dst>/.git overriding the
worktree's host-side pointer with one targeting the container path.
4. A jackin-owned back-pointer at /jackin-isolation/<container>-git/
worktrees/<n>/gitdir overriding git's verification check (host's
absolute path doesn't match <dst> inside the container).
Override files live under <data_dir>/jackin-<container>/.git-overrides/
and are written once at materialize time. Host files (worktree's `.git`
and the admin dir's `gitdir`) are NEVER modified — host-side
`git worktree list` continues to work identically.
Three layouts were considered (Docker Sandboxes-style `.jackin/` in the
host repo, indirect mount with override files, jackin-owned bare repo).
The chosen approach preserves jackin's dst-based mount model (operator
configures dst=/workspace/jackin → agent works at that exact path), keeps
the host repo clean (no `.jackin/` directory), and exposes only the
worktree to the agent (not the entire host main tree). Full design
rationale and the comparison with Docker Sandboxes, Conductor, and
clone mode (planned for V1.1) are in
docs/src/content/docs/reference/roadmap/per-mount-isolation.mdx
under "Design Decision: Worktree Materialization Layout" and
"Comparison with Other Tools".
Trust trade-off: the agent has rw access to the host repo's `.git/`
since refs/objects are inherently shared in git worktrees. Worktree
mode is appropriate for trusted agents on personal projects where
immediate ref visibility on the host is valuable. Operators who want
ref isolation should use clone mode (planned).
Tests:
- write_git_overrides_writes_both_files_with_correct_content asserts
override file content matches the design doc verbatim.
- write_git_overrides_is_idempotent confirms re-running on a reused
worktree (load → eject → load) doesn't drift.
- override_id_strips_slashes_and_trims pins the file-naming scheme.
- container_git_dir_path_namespaces_by_container_name pins the
hardcoded container-side path so two parallel agents don't collide.
- Extended per_mount_isolation_e2e to assert MaterializedMount carries
WorktreeAuxMounts on the worktree path and that override files land
on disk at the documented locations.
Manual verification recipe (add after running once):
cargo run --release --bin jackin -- --debug load <agent>
docker exec -ti <container> git status # was failing, now works
docker exec -ti <container> git log # works
docker exec -ti <container> git commit -m test --allow-empty
git -C <host-repo> branch -a # shows new branch
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* refactor(isolation): /jackin/{host,admin}/<dst> mounts, container-name basename, :ro hardening
Three related changes that finalize the worktree-mode mount layout:
1. Container-side path scheme renamed and reorganized.
/jackin-isolation/<container>-git/... → /jackin/host/<dst-stripped>/.git
/jackin/admin/<dst-stripped>/{commondir,gitdir}
- Single top-level /jackin/ namespace for everything jackin contributes
to the agent's filesystem (room to grow with /jackin/cache/, etc.)
- host/ category mirrors host topology so docker inspect shows symmetric
Source/Destination paths both ending in `.git`
- admin/ category lives at a separate top level so the override files
(which sit on top of files inside the admin dir) do NOT visually nest
inside /jackin/host/.../.git/. Two top-level concerns, no overlap.
2. Host-side storage groups all git artifacts for one mount under
<state>/git/<dst-stripped>/, with override-file names matching their
docker mount destinations:
<state>/git/<dst-stripped>/
├── <container>/ (the worktree; basename = container name)
└── overrides/
├── .git
├── commondir
└── gitdir
Replaces the prior <state>/.git-overrides/ flat layout with underscored
slug filenames. New layout uses dst as a real directory tree (no slug)
and source filenames identical to destination filenames — the source/
destination relationship is obvious in `docker inspect`.
3. Worktree subdir basename = container name. `git worktree add` derives
the host-side admin entry name from the worktree path's basename (no
--name flag exists upstream). Using the container name (which jackin
guarantees is globally unique) makes admin entries in
<host_repo>/.git/worktrees/ globally unique per (host_repo, container)
— `git worktree list` on the host immediately shows which container
owns each worktree.
This required a new validation rule:
`workspace::validate_isolation_layout` now rejects two isolated mounts
that resolve to the same host repository within one workspace.
Allowing them would force the same container-name basename twice in
one host repo's .git/worktrees/ namespace; no real operator workflow
has surfaced for this case. Revisit if one does.
Removes the now-dead suffix logic from materialize.rs:
- `count_isolated_per_repo` (helper)
- `canonicalize_or_clone` (helper)
- `dst_to_branch_suffix` (in src/isolation/branch.rs — no callers left)
The `branch_name` function keeps its optional suffix parameter for
future clone-mode use; V1 worktree always passes None.
4. The three override files (replacement `.git` pointer, `commondir`,
`gitdir` back-pointer) are mounted `:ro` as defensive hardening. Git
only reads them during normal agent work, and a misbehaving agent
could otherwise rewrite the gitdir pointer to redirect operations at
a different repo entirely. The host `.git/` and admin mounts stay rw
because git writes refs/objects/HEAD/index/logs there.
Tests:
- workspace::tests::isolation_layout_rejects_two_worktree_mounts_on_same_repo
- workspace::tests::isolation_layout_allows_different_host_repos_in_one_workspace
- materialize::tests::worktree_path_uses_container_name_as_basename
- materialize::tests::container_host_git_path_mirrors_dst_under_jackin_host
- materialize::tests::container_admin_path_lives_under_jackin_admin
- materialize::tests::host_and_admin_paths_disambiguate_per_mount_in_one_container
- materialize::tests::write_git_overrides_writes_three_files_with_correct_content
- materialize::tests::write_git_overrides_is_idempotent
- launch::tests::build_workspace_mount_strings_marks_overrides_readonly
(asserts all 6 mounts in correct order with correct :ro placement)
- per_mount_isolation_e2e: updated for new path scheme + admin name
Removed:
- materialize::tests::two_isolated_mounts_same_repo_get_dst_suffixed_branches
(case is now rejected at the workspace-validation level)
Roadmap MDX (per-mount-isolation.mdx):
- Container-side mount layout section: 4 mounts → 6, new path scheme,
override-file storage layout
- Composition Rules: documents the new same-host-repo rejection
- Comparison table: bind mount count for jackin worktree updated 4 → 6
- V1 Scope: ship list updated with new layout and the new validation rule
Manual verification (after merge):
cargo run --release --bin jackin -- --debug load <agent> <workspace>
docker inspect <container> | jq '.[0].Mounts' # see /jackin/{host,admin}/...
docker exec -w <dst> <container> git status # works
git -C <host_repo> worktree list # admin name = <container>
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* refactor(isolation): single /jackin/host/ root, no commondir override, Model B branch naming
Final V1 design after extended brainstorming with the operator. Drops
the `/jackin/admin/<dst>` namespace and the `commondir` override file:
the per-worktree admin entry now lives natively at
`worktrees/<container>/` inside the host `.git/` mount, so git's
on-disk default `commondir = ../..` resolves correctly without an
override.
Container-side topology, per isolated mount (4 binds total, down from 6):
- `<dst>` (rw) — the materialized worktree
- `/jackin/host/<dst-tree>/.git` (rw) — host repo's `.git/`
- `<dst>/.git` (`:ro`) — replacement gitdir pointer
- `/jackin/host/<dst-tree>/.git/worktrees/<container>/gitdir` (`:ro`)
— replacement back-pointer
Host-side layout under each per-container state dir:
- `git/worktree/repo/<dst-tree>/<container>/` — git's territory
- `git/overrides/<dst-tree>/{.git,gitdir}` — jackin-owned overrides
Branch naming follows Model B: `jackin/scratch/<container_name>`
verbatim. Admin entry name = container name (deterministic, globally
unique because container names are workspace-unique and
`validate_isolation_layout` rejects two isolated mounts on the same
host repo within one workspace — no auto-suffix or read-back needed).
Roadmap doc updated to reflect the final design.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* style(isolation): rustfmt assert_eq! width
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* docs(isolation): align stale references with shipped V1 design
Sweep stale references across roadmap, guides, command and architecture
docs into alignment with what the code actually does. No content added —
the doc just told two contradictory stories before (Model B branch
naming alongside the old selector-key derivation; the new
git/worktree/repo/<dst>/<container>/ on-disk layout alongside the
proposed-but-never-implemented isolated/<slug>/ layout). Now there is
one consistent story end-to-end.
Touches:
- docs/src/content/docs/reference/roadmap/per-mount-isolation.mdx
- docs/src/content/docs/guides/workspaces.mdx
- docs/src/content/docs/commands/purge.mdx
- docs/src/content/docs/reference/architecture.mdx
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* chore(cli): remove jackin cd command from V1
Operationally redundant with `git worktree list` + native shell `cd`.
For worktree mode, the host's `git worktree list` already enumerates
every isolated worktree by branch and absolute path, so a plain
`cd $(...)` reaches the same destination. The remaining edge cases
(preserved-dirty inspection, multi-mount picker) are rare enough that a
dedicated subcommand is net cost rather than net benefit.
Removes:
- src/cli/cd.rs (CdArgs + select_record + tests)
- Command::Cd enum variant in src/cli/mod.rs
- handle_cd dispatch in src/app/mod.rs
- docs/src/content/docs/commands/cd.mdx and its sidebar entry
Updates:
- src/isolation/finalize.rs preserved-state warning: drop "jackin cd ..."
hint, point operator to the printed worktree path instead
- src/isolation/materialize.rs source-drift error: same treatment
- guides/workspaces.mdx + reference/architecture.mdx: drop cd references
- roadmap entry: replace "Convenience navigation" paragraph with a
removed-from-V1 note explaining the rationale; add cd to the Defer
list so it's recoverable if a real workflow surfaces
isolation.json schema, the preserved-state machinery, and `hardline` /
`purge` flows are unaffected — only the inspection convenience layer
is gone.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* chore(isolation): drop clone from V1 enum/parser/CLI
Per principle: don't pre-add API for unimplemented features. The `clone`
keyword was previously parsed by TOML/CLI and then rejected at validation
with a "planned but not implemented yet" error. Operators got false
positives in linting tools and confusing late failures with no benefit —
nothing in V1 actually does anything useful with the value.
Removes from the runtime:
- MountIsolation::Clone enum variant (src/isolation/mod.rs)
- explicit Clone-rejection in parse_mount_isolation (src/cli/workspace.rs):
FromStr now produces "invalid isolation `clone`" naturally
- MountIsolation::Clone match arm in materialize_workspace
(src/isolation/materialize.rs)
- console comments referencing the reserved-but-rejected wording
Tests:
- New `rejects_clone_until_implemented` test on FromStr asserts the
standard "invalid isolation `clone`; expected one of: shared, worktree"
error so this stays locked down
- parse_mount_isolation_rejects_clone updated to assert the new error
shape
Docs:
- guides/workspaces.mdx, commands/workspace.mdx,
reference/configuration.mdx, reference/roadmap.mdx: drop the
"reserved keyword" wording, point at the V1.1 roadmap entry instead
- roadmap/per-mount-isolation.mdx: keep the `clone` design discussion,
rephrase the V1 vocabulary section to make it explicit that the keyword
is added back when clone mode ships, not pre-shipped now
apply_isolation_overrides already enforces "--mount-isolation must
reference an existing mount destination" (planner.rs); no change needed
for that requirement, just clarified in the roadmap CLI-behavior bullet.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* fix(workspace): always write mount isolation field explicitly on save
Old configs without the `isolation` field still deserialize to Shared
(the enum default). On save, drop the `skip_serializing_if = is_shared`
guard so every mount writes its isolation level explicitly — including
`shared`. Old TOMLs migrate to the new shape on first save instead of
silently retaining their pre-isolation form.
Rationale: when the operator opens the saved config, every mount should
name its isolation level. No "field is missing therefore implicitly
shared" — the value is always present and the source of truth in the
file matches the source of truth at runtime.
Touches:
- src/workspace/mod.rs MountConfig.isolation: drop skip_serializing_if
- mount_config_omits_isolation_field_when_shared_on_serialize → renamed
to mount_config_writes_isolation_field_even_when_shared_on_serialize,
asserts the field IS written
- guides/mounts.mdx + reference/configuration.mdx: update wording
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* docs: remove stale per-mount-isolation design spec
The brainstorming spec at docs/superpowers/specs/ predated the V1 design
iterations and no longer reflects what shipped (it still describes the
old `/jackin-isolation/` mount layout, the selector-key-based branch
naming, the reserved `clone` enum value, and `jackin cd`). The roadmap
entry at docs/src/content/docs/reference/roadmap/per-mount-isolation.mdx
is now the single source of truth.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* docs(roadmap): improve per-mount isolation entry — accurate Sandboxes
comparison, V1 overview, concrete clone-mode personas
Four targeted improvements to the per-mount-isolation roadmap doc:
1. Add a "How V1 worktree mode works (TL;DR)" overview at the top of
Host-Side Materialization. 5-step walkthrough of the materialize +
mount + commit flow before the deep-dive subsections, so readers
land on the entry can understand the shipped V1 in 90 seconds
without scrolling through the layout/lifecycle/etc.
2. Rewrite the Docker Sandboxes comparison for accuracy. The old
description got it wrong on multiple points:
- Sandboxes use a microVM with hypervisor isolation, not Docker
containers (Linux namespaces) like jackin.
- The host filesystem is exposed via filesystem passthrough at the
SAME absolute path as the host, not a bind mount of the entire
repo at "the same relative paths".
- Worktree path is `<host_repo>/.sbx/<sandbox-name>-worktrees/<branch>/`
(sandbox name is in the path), not `<host_repo>/.sbx/<branch>/`.
- Sandboxes do NOT expose the host main working tree to the agent
— only the worktree subdir and the parent `.git/`. Our table
previously said "✓ (entire host repo mounted)".
The architectural insight is now explicit: Sandboxes' absolute-path
equivalence makes git's on-disk absolute pointers resolve natively,
which is why they don't need override files. We pay that cost
because Docker containers translate host paths to operator-chosen
`dst` values, breaking absolute-path equivalence.
3. Concrete clone-mode operator personas. The previous description
was abstract ("complementary mode for ref isolation"). Replaced
with four named situations clone mode targets: untrusted/experimental
agents, parallel-fan-out scratch-branch noise, editor watcher
churn, and teams whose workflow is push-to-share anyway.
4. New "Sandbox runtime" and "Host file exposure" rows in the
comparison table to make the underlying architectural choice
immediately visible. Cross-referenced from the Sandboxes prose.
Net: ~80 lines changed/added, primarily replacement of the wrong
Docker Sandboxes facts and addition of the V1-overview block.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* fix(isolation): close four data-loss windows in finalize/cleanup paths
Four merge-blocking issues surfaced by deep code review of PR #177.
Each was a silent-failure window that could destroy operator data on
the unhappy path while the happy-path manual smoke test stayed green.
1. assess_cleanup now treats every git capture failure as
PreservedUnpushed (was: unwrap_or_default → empty string → could
land in SafeToDelete). Without this, a transient `git rev-list`
failure (corrupted pack mid-traversal, broken pipe under load,
index.lock from a backgrounded git GC) would auto-delete the
worktree and scratch branch, garbage-collecting unpushed commits.
Each capture site now uses an explicit `match` that returns
PreservedUnpushed with debug_log of the underlying error, plus a
defense-in-depth empty-HEAD guard.
2. finalize_clean_exit now collects ALL preserved records and prompts
per-record (was: needs_prompt.get_or_insert reached only the first
one). On a multi-mount workspace where the operator chooses
force-delete on the first prompt, the second preserved worktree was
silently orphaned and the container torn down anyway — the only
reconnection path (jackin hardline) was lost. Now each preserved
record gets its own prompt; "return to agent" short-circuits the
loop; "preserve" propagates as Preserved and skips container
teardown.
3. inspect_attach_outcome now returns still_running() on docker capture
failure (was: stopped(0) → entered finalize_clean_exit → could
compound with #1 to delete worktrees of containers that may still
be alive). The conservative direction is "preserve when we don't
know" — `jackin hardline` recovers from there.
4. force_cleanup_isolated now verifies cleanup actually completed
before removing the isolation.json record (was: let _ on git ops +
unconditional remove_record → orphan worktree admin entries on the
host repo and orphan branches with no jackin reference). Tolerates
the idempotent paths (already-removed worktree, already-deleted
branch verified absent via `git branch --list`); bails on real
failures with a clear "record retained, re-run jackin purge after
resolving the issue" message.
Test coverage:
- 5 new tests in isolation/finalize.rs pinning the assess_cleanup
capture-failure → PreservedUnpushed contract for each git command
in the assessment chain, plus the empty-HEAD guard.
- 3 new tests pinning the multi-record finalize path (force-delete-all,
mixed force/preserve, non-interactive multi-mount warning).
- 1 new test for inspect_attach_outcome capture-failure fallback.
- 3 new cleanup tests (branch-already-deleted tolerance, real-failure
retention, error-message contract).
- 1 new test for validate_workspace_config integration (catches the
validate_isolation_layout call site if anyone refactors it away).
- 1 new test for build_workspace_mount_strings on a multi-mount
isolated workspace (8 distinct binds, no path collisions, :ro
hardening on every override file).
- 2 new drift-detection tests (dst-removed flagged; isolation-mode
flips not-flagged with explanatory note for future improvement).
Net: 1170 → 1186 tests, 16 additions, all passing.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* fix(isolation): close P1/P2/P3 — follow-on bugs from second review
Second deep code review of PR #177 surfaced three more issues after
the first round of merge-blocker fixes shipped:
P1. `force_cleanup_isolated` failure mid-loop in finalize_clean_exit
propagated as Err via `?`, leaving the operator with a raw cleanup
error from deep in finalize, no Preserved signal to the caller, the
container left running without explicit teardown decision, and
subsequent records in the loop never prompted. This regression was
introduced by the round-1 multi-record loop fix (the single-record
path always succeeded). Now caught per-record, eprintln'd as a
warning, and treated as `any_preserved_after_prompt = true` so the
loop continues and the caller gets `Preserved`.
P2. `inspect_attach_outcome` only treated `status == "running"` as
still-alive. `paused | restarting | removing | created | dead` all
fell through to `stopped(0)` → entered `finalize_clean_exit` →
could auto-delete worktrees of containers that may resume any
moment. Concrete: `docker pause jackin-x` while jackin re-attaches
→ status="paused" → SafeToDelete on a clean tree → operator
unpauses to find the worktree gone. Replaced if-cascade with an
explicit `match status` that only routes `exited` through stopped()
and treats unknown status strings conservatively as still_running.
P3. `purge_isolated_for_container` swallowed per-record errors with
eprintln warnings and returned `Ok(())`. Exacerbated by the
round-1 fix #4 (force_cleanup_isolated now bails more often on
real failures). Operator runs `jackin purge`, sees a warning
scroll past, gets exit-code-0 prompt back, may believe purge
completed. Now collects failures and surfaces an aggregate Err
with the failed mount list so the exit code reflects reality.
Test coverage for these fixes:
- 2 new tests in finalize.rs: ReturnToAgent on the 2nd-of-3 prompt
(early-return short-circuits), and force_cleanup_isolated failing
mid-loop (loop continues, returns Preserved).
- 8 new tests in launch.rs covering every status code path:
exited(0/non-zero/oom), running, paused, transient (restarting/
removing/created), dead, unknown.
- 2 new tests in cleanup.rs: purge bails on partial failure,
branch_still_present returning None proceeds (pins the doc-comment
contract against future refactors to `unwrap_or(true)`).
Net: 1186 → 1198 tests, +12 additions, all passing. fmt/clippy clean.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
---------
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Codex <codex@openai.com>
donbeave
added a commit
that referenced
this pull request
May 7, 2026
- Move 9 TODO items from monolithic TODO.md into separate files in todo/ - Each file is a self-contained design doc with problem, options, and related source files for easy agent handoff - Mark resolved security findings (#3, #4, #6, #7) in SECURITY_REVIEW_FINDINGS.md - Update PROJECT_STRUCTURE.md with todo/ section and TESTING.md entry - TODO.md becomes an index pointing to todo/ files Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Co-authored-by: Codex <codex@openai.com>
donbeave
added a commit
that referenced
this pull request
May 7, 2026
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Co-authored-by: Codex <codex@openai.com>
donbeave
added a commit
that referenced
this pull request
May 7, 2026
* feat: implement trust-on-first-use model for third-party agent sources Add a trust gate that prompts operators before building untrusted third-party agents. Built-in agents are always trusted. New namespaced agents default to untrusted and require explicit confirmation on first use. The trusted flag is persisted in config.toml so subsequent runs proceed without prompts. Non-interactive sessions bail with a clear error for untrusted sources. Also syncs the roadmap with TODO items: adds missing completed item (JACKIN_DIND_HOSTNAME), adds missing planned items (Bollard migration), and marks the agent source trust security finding as resolved. https://claude.ai/code/session_01SkHoAne5Q5EpUybYk6ghUT * improve trust prompt with clear risk details and mark finding #9 resolved Rewrite the trust confirmation message to clearly explain what trusting an agent means: Dockerfile execution, arbitrary commands on the host, and workspace file access. The non-interactive error now tells the user how to pre-trust via config.toml. The decline message explains how to retry. Also mark security finding #9 (mount policy guardrails) as resolved since sensitive mount warnings were already implemented. https://claude.ai/code/session_01SkHoAne5Q5EpUybYk6ghUT * fix: address PR review — docs, stale comment, save clarity, test, serde Review fixes for the agent source trust model: - Fix duplicate doc comment on trust_agent() (copy-paste from sync_builtin_agents) - Replace confusing `!source.trusted` save condition with explicit `newly_trusted` flag for readability - Add unit test for non-interactive trust gate (verifies the error includes agent name and git URL) - Skip serializing `trusted = false` to keep config.toml clean (only `trusted = true` appears) - Document trust in load.mdx (step 3 in "What happens", warning callout for CI) and security-model.mdx (new section 4 with config.toml example, threat model row, best practices update) https://claude.ai/code/session_01SkHoAne5Q5EpUybYk6ghUT * fix: broken docs anchor link and misleading test name - Fix cross-reference link in load.mdx: use #4-agent-source-trust (matching the numbered heading in security-model.mdx) - Rename test to `load_trusted_namespaced_agent_builds_and_runs` since it no longer exercises auto-registration (that's covered by the config test `resolve_agent_source_adds_owner_repo_on_first_use`) https://claude.ai/code/session_01SkHoAne5Q5EpUybYk6ghUT * feat: add jackin trust/untrust CLI, testable trust gate, revocation Address all follow-up items from the maintainer review: - Extract load_agent_with() with injected trust callback, following the resolve_agent_repo_with() pattern. Tests can now exercise accept and decline paths without mocking dialoguer. - Restore auto-registration test coverage: the accept test uses load_agent_with(auto_trust) so resolve_agent_source's is_new path is exercised again. Add decline test verifying no config persistence and no Docker commands when trust is refused. - Add `jackin trust <selector>` and `jackin untrust <selector>` CLI commands so operators can manage trust without editing config.toml. - Add untrust_agent() to AppConfig with tests. - Document trust/untrust CLI in security-model.mdx and load.mdx. - Document that trust is keyed by selector (not URL) with a note about remote-mismatch protection. https://claude.ai/code/session_01SkHoAne5Q5EpUybYk6ghUT * refactor: consolidate trust/untrust into single CLI command Follow mise's pattern: `jackin trust` is one command with flags instead of separate trust/untrust subcommands. jackin trust <selector> # grant trust jackin trust <selector> --untrust # revoke trust jackin trust <selector> --show # check status The --show and --untrust flags conflict with each other (enforced by clap). https://claude.ai/code/session_01SkHoAne5Q5EpUybYk6ghUT * refactor: move trust CLI under config with grant/revoke/list subcommands Follow the same pattern as `jackin config mount {add,remove,list}`: jackin config trust grant chainargos/the-architect jackin config trust revoke chainargos/the-architect jackin config trust list This keeps all config mutations under `jackin config` and uses the subcommand pattern consistently throughout the CLI. The `list` subcommand shows all currently trusted agents. https://claude.ai/code/session_01SkHoAne5Q5EpUybYk6ghUT * fix: refuse to revoke trust on builtins, add CLI parsing tests - is_builtin_agent() check prevents revoking trust on built-in agents (agent-smith, the-architect) which would be silently re-granted by sync_builtin_agents on next load - Add 3 CLI parsing tests for config trust {grant, revoke, list}, matching the existing coverage for config mount {add, remove, list} - Tighten TrustCommand::List doc comment to "List all currently trusted agent sources" https://claude.ai/code/session_01SkHoAne5Q5EpUybYk6ghUT --------- Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Co-authored-by: Codex <codex@openai.com>
donbeave
added a commit
that referenced
this pull request
May 7, 2026
…183) * docs(roadmap): iteration 13 — AI code verifiability framing, config/types.rs full spec Primary goal shift: codebase must be verifiable for AI-generated code. - §0: replace generic description with explicit verifiability rationale (module contracts, localised concerns, types/behaviour separation) - §4 intro: add "Why structure matters for AI-generated code" section with audit-units table mapping each post-split file to one reviewable question - §4 4a: expand config/types.rs from description to full execution spec — exact type list, post-split mod.rs content, zero-change submodule guarantee (verified: agents.rs/persist.rs/workspaces.rs use super::T which resolves through mod.rs re-exports unchanged), impl-extension pattern already in use documented Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): iteration 14 — editor method map, app helpers, //! queue - §4 4c: config/editor.rs split is now execution-ready — complete 6-file method-to-file table with private helper placement verified (validate_candidate→io.rs, table_path_mut→mod.rs pub(super), auth_forward_str→agent_ops.rs, create_workspace delegates to AppConfig) - §4 4e: app/mod.rs split complete — all private helpers mapped (parse_auth_forward_mode_from_cli→config_cmd.rs, workspace_env_scope→workspace_cmd.rs, print_env_table note, remove_data_dir_if_exists→dispatch.rs) - §10 step 5: add //! priority queue — 10 files with draft content, prioritised by cold-landing impact and AI audit risk; selector.rs and instance/mod.rs explicitly document the /→__ invariant Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): iteration 15 — dep graph fix, trust.rs safety, OQ1 closed - §4 4d: correct operator_env dependency graph — layers.rs imports both mod.rs (OpRunner) AND client.rs (OpCli for non-injectable resolve_operator_env wrapper at line 797); still a valid DAG - §4 4f: verify trust.rs split safety — FnOnce injection pattern means launch_pipeline.rs has zero dependency on trust.rs; import chain documented; trust bypass audit now requires reading only ~60L - §9 OQ1 closed: op_cache.rs read in full — 4-level structure, per-level invalidation, no TTL/expiry (expiry handled at OpCli subprocess level), DEFAULT_ACCOUNT_KEY sentinel documented Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): iteration 16 — CommandRunner Rule 3, render/editor split, 4a/4c independence - Fix duplicate Rule 3 section introduced by previous edit; add docker.rs co-location note as third edge case (three edge cases, not two) - Add render/editor.rs as new Rule 5 violator: 1666L post-PR #171 (was listed as 782L); propose 6-file tab-by-tab split with auditability note on the security-adjacent Secrets tab - Add §10 execution-order note: 4a and 4c are independent — editor.rs imports AppConfig via crate::config re-exports regardless of 4a order - Append iteration 16 log entry with confidence table and weakest sections Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): iteration 17 — instance/auth.rs audit, state.rs split, line count corrections - Add instance/auth.rs to //! priority queue at #4: four security invariants (0o600 perms, symlink rejection, TOCTOU-safe writes, macOS Keychain) documented in draft //! content - Add state.rs as new Rule 5 violator: 992L/628L production; 26+ types mixed with impl blocks; propose 5-file types/behavior split - Correct stale line counts: render/list.rs 1122→1989 (PR #171 added render_environments_subpanel); state.rs 865→992; priorities upgraded - Fix §7.9 snapshot function line refs: sentinel_description_pane 306→332, mounts_subpanel 408→433, render_tab_strip 180→269, test ref 720→944 - Renumber //! priority queue to 11 entries (was 10) Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): iteration 18 — agent_allow OQ2 closed, render/list.rs split proposal - Close OQ2: agent_allow.rs read in full — 55L, correct //! doc, design sound; serves as model for //! priority queue pattern - Add render/list.rs as new Rule 5 violator: 668L production (PR #171 added render_environments_subpanel); propose 3-file split (mod.rs, details.rs, subpanels.rs); note import-path change for agents_block_agent_count - Update §1 module map: agent_allow.rs entry corrected with size/API Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): iteration 19 — input/editor.rs critical correction, split proposal - Correct input/editor.rs: 2349L total (was 1304L), 1141L production (was 547L) — PR #171 added Secrets-tab handlers; pub(super) fn handle_editor_modal at line 618 was invisible to previous grep pattern; now the largest production file in the codebase; priority → Critical - Correct input/save.rs: 1472L total, 661L production (was 567L) - Add 5-file split proposal for input/editor.rs: mod.rs (two dispatchers), secrets.rs (~500L AI-generated Secrets-tab), agents.rs, mounts.rs, general.rs - Update key insight paragraph naming input/editor.rs as largest production file Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): iteration 20 — console splits in §10, MSRV evidence, animation.rs verdict - Add console/manager/ as §10 Step 4f group with 5 sub-steps in priority order; rename existing 4f (launch.rs) → 4g; add circular-import risk note for ManagerStage/EditorState split sequencing - Analyze tui/animation.rs: 582L all-production, no split needed (banner_grid is a tightly-coupled rendering loop); section comments compensate for missing //! - Partially close OQ3: u64::is_multiple_of (stabilized 1.86) found in animation.rs; within declared MSRV 1.94; full cargo +1.94.0 check deferred (toolchain unavailable) Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): iteration 21 — input/save.rs split, //! queue fix, save.rs corrections - Add input/save.rs split proposal: 4 pub(super) fns discovered; 3-file split (mod.rs + flow.rs + preview.rs); no cross-dependency between flow and preview groups; §10 4f-v updated from Optional to concrete plan - Fix //! queue preamble: "first 10 files" → "first 11 files" - Correct save.rs module map (1418→1472L, correct key exports) and hot-spot table note (begin_editor_save ~280L → ~118L; commit_editor_save is the Phase 2 partner at ~149L) Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): iteration 22 — input/list.rs and mount_info.rs analysis - Analyze input/list.rs: 214L production (tests at 215); has //! doc; two focused pub(super) fns; no split needed; Low priority; correct module map - Add mount_info.rs to hot-spot table: 277L production; Low priority; has //! doc; correct module map with 3 public enums + inspect fn - Fix stale §2 diagnosis note: docs/internal/roadmap/ now exists Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): iteration 23 — audit units table +5 console rows, input/mod.rs corrected - Expand audit units table from 8 to 13 entries: add state/types.rs, state/editor.rs, input/editor/secrets.rs, render/list/subpanels.rs, input/save/preview.rs — all targeting PR #171 AI-generated console code - Add PR #171 context note linking 5 new entries to AI-generated code concern - Correct input/mod.rs module map: 369L, add InputOutcome enum to exports - Verify rust-toolchain.toml absence; §7.7 and §2 concept 25 already correct Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): iteration 24 — render/mod.rs analysis, //! exemplars table, EditorTab confirmed - Add §4 Rule 7 positive exemplars table: 7 files with //! docs graded 1-element (render/mod.rs), 2-element (input/save.rs etc), 3-element (env_model.rs, agent_allow.rs); PR #171 docs-discipline pattern noted - Correct render/mod.rs module map: 421L; FooterItem + palette constants + render_header + centered_rect_fixed added to key exports - Confirm EditorTab variants: General, Mounts, Agents, Secrets (Rust enum) vs "Secrets / Environments" (UI label); /stub qualifier already removed Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): iteration 25 — too_many_lines recount, FooterItem PR, MountConfig caveat - Correct too_many_lines count: 13 across 8 → 16 across 11 files (PR #171 added 5 suppressions in console/manager); add full breakdown table; update all 3 occurrences in roadmap - Fix FooterItem PR reference: #165 → #166 (confirmed by git log --follow) - Add MountConfig → MountSpec rename caveat to §7.5 snapshot test description Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): iteration 26 — console/mod.rs and op_picker/render.rs analyzed - Add console/mod.rs to hot-spot table: 406L/307L production (Low); correct module map from ~200 → 406L; note missing //! doc with ConsoleStage design block comment worth promoting - Add op_picker/render.rs to hot-spot table: 865L/545L production (Medium); PR #171 AI-generated; 14 functions in two logical groups (entry/helpers vs level renderers); split into levels.rs proposed - Correct 3 stale ~200L estimates for console/mod.rs across roadmap Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): iteration 27 — op_picker/mod.rs discovery, render split, operator_env correction - Add op_picker/mod.rs to hot-spot table: 1712L/775L production (High); PR #171 AI-generated; OpPickerState types+behavior split opportunity; has 7-line //! doc; module map split into two rows (mod.rs + render.rs) - Add op_picker/render.rs 2-file split proposal: render.rs (coordinator) + render_pane.rs (pane/level renderers); no cross-dependency confirmed - Correct operator_env.rs total: 1569→2130L (880L production); update 4 occurrences across hot-spot table, ASCII tree, §4 analysis Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): iteration 28 — op_picker/mod.rs 3-file split, count corrections - Add op_picker/mod.rs formal 3-file split: loading.rs (async load family ~120L) + keys.rs (4 level key handlers ~315L) + mod.rs (types/constructors) - Correct "24 files" → "28+" for 500L threshold count - Update total LOC: ~40,664 → ~43,587 (2 occurrences, with provenance note) Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): iteration 29 — op_picker execution order + file_browser analysis - §10 Step 4f: expand from 5 to 7 sub-steps; add 4f-vi (op_picker/mod.rs → mod.rs + loading.rs + keys.rs) and 4f-vii (op_picker/render.rs → render.rs + pane.rs); document impl-extension and import-path caveats - §4 //! exemplars: add file_browser/ subsystem analysis — all 5 files have //! docs, no file exceeds ~350L production; classified as exemplar (not a split candidate); document git_prompt.rs coupling-density justification and input.rs as 28-file false positive (144L production) - §1 module map: expand single file_browser/ row to 5 individual rows with production LOC and dominant concern per file Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): iteration 30 — challenge split-first thesis, fresh LOC corrections - §4: Add "Alternative thesis: documentation-first verification" — challenges the two core assumptions behind file splitting (files-as-audit-unit and file-size-as-context-constraint); adds 7-criterion comparison table vs structure-first approach; introduces phased combined recommendation: Phase 1 = doc sprint (//! contracts + specs/ for 3 subsystems, 2-3 PRs, zero structural change); Phase 2 = splits only for >600L production files (reduces scope from 14+ to 4 files); Phase 3 = workspace if LOC > 150K - Fix stale LOC: app/mod.rs 951→979, config/editor.rs 1467→1548 (7 and 8 locations respectively; verified by fresh find|xargs wc -l scan) - §1 module map: add agent_picker.rs (436L), scope_picker.rs (201L), source_picker.rs (244L) — all PR #171 additions with //! docs Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): iteration 31 — fix 600L→800L threshold error, correct LOC - §4 alternative thesis: correct ">600L production → 4 files" claim introduced in iteration 30; re-verified all 9 candidate files via #[cfg(test)] line position; threshold must be >800L to get exactly 4 files (9 exceed 600L); add verification table with test-start lines - Production LOC corrections (5+ locations each): launch.rs 1085→~1077, operator_env.rs 810→~880, app/mod.rs 928→~957, config/editor.rs 503→~584 - §2 OpPicker row: replace vague "no entry yet" with confirmed gap: PROJECT_STRUCTURE.md line 53 still lists pre-PR#171 widget set (10 named); omits op_picker/, agent_picker.rs, scope_picker.rs, source_picker.rs and pre-dates the manager/ sub-structure split Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): iteration 32 — two-tier spec arch, behavioral spec template - §8.1: Add two-tier spec architecture table distinguishing feature specs (public Starlight MDX, user-facing) from behavioral specs (internal docs/internal/specs/, for AI code verification) — resolves contradiction between §4 (which said docs/internal/specs/) and §8.1 (which said "no longer needed; specs are public") - §8.1: Add concrete behavioral spec template for op_picker/ with state machine table and 3 INV invariant entries each with a grep-executable "Verify by:" command; template directly usable for the 3 Phase 1 specs - §8.1: Remove erroneous "docs/internal/specs/ no longer needed" claim - Confirmed render/editor.rs ~736L and render/list.rs ~668L production (no interspersed production code — all test blocks follow consecutively) Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): iteration 33 — executive summary, §0 correctness - §0: Add executive summary (~300 words) with core problem, 3-phase recommendation, key counter-argument, and navigation table pointing to §2/§4/§7/§8/§10 by question — resolves the meta-irony of a readability roadmap with no entry-point orientation - §0 item 2: "1569-line monolith" → "2130-line monolith" (operator_env.rs current verified size; stale reference was in the first section readers see) - §0 item 3: Add "(selective)" qualifier and explicit note that standard Rust co-locates struct+impl — impl-extension pattern is justified only for files >800L production, not as a universal rule Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): iteration 34 — spec priority reorder, §10 Phase 1 track - §0 + §4 Phase 1: Prioritize runtime/launch.rs behavioral spec (no //! doc, ~1077L production, critical path — all jackin load failures trace here); drop config/editor.rs from Phase 1 (its 963L test suite already serves as behavioral spec — tests are behavioral examples); reduce Phase 1 from 3 specs to 2 specs; add reasoning for the priority ordering - §10 Step 2: Split into two parallel tracks — Track A (cc-sdd tooling setup) + Track B (Phase 1 behavioral spec authoring); Track B includes specific INV invariants to capture for runtime/launch.rs grounded in reading the actual function structure (step comment positions); adds sequencing rationale: spec must precede structural splits Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): iteration 35 — verified INV entries for runtime/launch.rs Read load_agent_with lines 553-892 in full. Replaced 3 draft INVs from iteration 34 (inferred from step comment positions) with 5 verified INVs citing exact line numbers: - INV-1: trust gate (line 594) precedes image build (line 736) - INV-2: container name claimed (line 754) between image build and network - INV-3: token verified (line 763) before network creation (line 827) - INV-4: render_exit called at lines 886 AND 890 (all exit paths) - INV-5: cleanup disarm semantics — Running→disarm, clean exit→cleanup, crash→disarm (explains jackin hardline compatibility) Corrected wrong line number: claim_container_name call is at 754, not 918 (918 is the function definition). Each INV has a grep-executable Verify by. Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): iteration 36 — CI gate for PROJECT_STRUCTURE.md freshness §3: Add "Preventing future PROJECT_STRUCTURE.md staleness" subsection with three concrete options: - Option A: CONTRIBUTING.md rule (necessary but insufficient) - Option B: ci.yml git-diff-scoped shell check (recommended) — only checks files added in the current PR so it doesn't require fixing existing stale entries before merging; greps for module directory name in prose - Option C: Structured TOML module registry (over-engineered for scale) Includes concrete YAML snippet for Option B grounded in the check:repo-links.ts pattern already established in docs/scripts/ Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): iterations 36-37 — CI gate + greenfield workspace architecture Iteration 36: - §3: Add "Preventing future PROJECT_STRUCTURE.md staleness" subsection with 3 options (CONTRIBUTING.md rule / ci.yml git-diff check / TOML registry); recommend Option B (git-diff-scoped YAML step) with concrete snippet grounded in existing check:repo-links.ts pattern from docs/scripts/ Iteration 37 (operator directive: greenfield Rust structure): - §4: Add "Greenfield architecture — ideal structure for a growing project" section based on verified cross-module dependency graph (grep iteration 37) - Confirms dependency tiers: workspace/manifest/docker/paths/selector = Tier 0; config/tui/instance = Tier 1; operator_env/runtime/repo = Tier 2; console = Tier 3 - Key finding: workspace/ is LOWER-level than config/ (config re-exports workspace types at lines 5-6); ideal naming inverted in greenfield (jackin-core > jackin-config) - Documents ideal 6-crate workspace: jackin-core, jackin-config, jackin-tui, jackin-runtime, jackin-console, jackin-shell + thin binary - Notes console/ has NO runtime/ import — cleanest pre-existing crate boundary - Bridge: incremental splits (4a, 4d, 4g) are pre-work toward workspace migration Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): iteration 38 — Rust workspace standards, community evidence Ground workspace recommendation in real-world project research: - ripgrep (9 crates), gitui (5 crates) went workspace due to library consumers - starship and fd-find stay single-crate at 1M+ LOC — no library use case - jackin (43K LOC, no external consumers) maps to starship/fd pattern → single-crate is community-standard; "stay single-crate" recommendation confirmed Update greenfield workspace structure to follow matklad's pattern: - Virtual manifest at root (no [package] in root Cargo.toml) - Flat crates/ directory (not nested); crate names match folder names - version = "0.0.0" for unpublished internal crates - Add inline dep comments to each crate in the ASCII structure Add research notes: ripgrep/starship/gitui/fd-find Cargo.toml findings + Cargo workspaces reference + matklad "Large Rust Workspaces" (2021-08-22) Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): revise §7.9 + §3 — adopt per-directory README.md §7.9: Reverse previous "reject" recommendation to "adopt" per-directory README.md for major src/ module directories. Rationale: README.md is AI-native — Claude Code, Copilot, Cursor load it automatically on directory entry, giving AI agents orientation before they decide which file to open. PROJECT_STRUCTURE.md being confirmed stale removes the main argument for the "single root file" approach. Add three-layer documentation model table: - README.md: directory orientation (AI + human, on entry) - AGENTS.md: agent workflow rules (root, session start) - CLAUDE.md: @AGENTS.md pointer only — NEVER add content here - //! docs: file-level contracts (when reading/editing) Add specific README.md content targets for 7 directories (src/, src/runtime/, src/console/, src/console/manager/, src/console/widgets/, docs/, docs/internal/). §3 target document shape: Add per-directory README.md to proposed hierarchy; add docs/internal/specs/ explicitly; note CLAUDE.md design principle (single-line @AGENTS.md — never duplicate content). Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): internal docs are browsable — unified Starlight site Operator directive: internal docs (architecture, specs, ADRs, roadmap) should be browsable, not hidden filesystem files. They are a different TYPE of docs focused on implementation details and vision, published as a "Developer Reference" section of the Starlight site. §3 target document shape: - docs/internal/ moves into docs/src/content/docs/internal/ (Starlight pages) - Browsable at jackin.tailrocks.com/internal/ - Sidebar: "Developer Reference" group (collapsed by default) with sub-sections for architecture, code-tour, contributing, testing, decisions, specs, roadmap - Include astro.config.ts sidebar config snippet §8.1 two-tier spec distinction eliminated: - Feature specs and behavioral specs both live at docs/src/content/docs/internal/specs/ - Type expressed via spec_type: behavioral | feature frontmatter, not filesystem location - Both browsable and searchable via Starlight; AI agents can be pointed to URLs §8.3 + §4: - All docs/internal/specs/ paths → docs/src/content/docs/internal/specs/ - ADRs: docs/internal/decisions/ → docs/src/content/docs/internal/decisions/ (browsable) - README.md pointer for src/runtime/ updated to URL reference Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): §11 — modern Rust docs platform (future project) Add §11 capturing the vision for a modern docs.rs alternative with: - rustdoc JSON ingestion → Astro Starlight presentation - MCP server for AI agent queries (Context7 alternative for Rust) - Rust-specific query types: rust_get_context(), rust_find_impls(), rust_search_types() — things Context7 cannot provide - Comparison table vs Context7 - Architecture diagram (ingestion → processing → Starlight + MCP) - Name candidates: rustlight, ferrodoc, cargo-starlight / starlight.rs - Note that jackin's §7.15 gen-rust-api.ts pipeline is the intentional prototype for the platform's processing and presentation layers Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): iteration 39 — update §0, fix stale internal/ paths §0 executive summary: rewrite to reflect decisions from iterations 30-38: - browsable internal docs (jackin.tailrocks.com/internal/) - per-directory README.md adoption (§7.9 reversed) - CLAUDE.md = @AGENTS.md single-line pointer only - greenfield workspace architecture (matklad's virtual manifest pattern) - §11 future project: modern Rust docs platform / Context7-for-Rust - document size 1800+ → 2200+ Fix stale docs/internal/ bare paths not caught by iteration 38 sweep: - Mermaid diagram: INTERNAL_ROADMAP, INTERNAL_CODE_TOUR → Starlight paths - §7.10 ADRs: docs/internal/decisions/NNN-title.md → .mdx Starlight path - §10 Track B item 2: op-picker spec path → Starlight MDX Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): iteration 40 — §7.15 pipeline + Rule 4 pub audit §7.15 (new): rustdoc JSON → Astro Starlight API documentation pipeline - Three options: rustdoc HTML publish / rustdoc JSON + bun script (recommended) / rustdoc-json crate as Rust binary - Option B recommended: matches existing docs/scripts/ pattern, nightly isolated to separate CI step, zero effect on stable build - Key design: URL at /internal/api/, cross-links to behavioral specs, Starlight unified search, prototype for §11 future project - Pub(crate) note: gen-rust-api.ts can feed Rule 4 visibility audit - Recommend: adopt after Phase 1 //! sprint (value ∝ coverage) §4 Rule 4 pub discipline: replace estimated "50-100 items" guess with verified numbers from iteration 40 grep: - 257 bare pub items, 21 pub(crate), 61 pub(super) across 94 files - 0 uses of unreachable_pub lint — no enforcement gate - Top violators: operator_env.rs (17), tui/output.rs (13), planner.rs (8) - Add concrete Cargo.toml [lints.rust] snippet: unreachable_pub = "warn" - Revised scope: ~150-200 mechanical conversions (excludes entry points) Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): split research into 19 actionable items Delete _research_notes.md (no longer needed). Replace 2343L READABILITY_AND_MODERNIZATION.md with: - README.md: index of all 19 items with phase, ordering notes, links - READABILITY_AND_MODERNIZATION.md: lightweight research summary (63L) - items/ITEM-001 through ITEM-019: individual actionable items Items by phase: Phase 1 (low risk, no confirmation): ITEM-001..004, 006..011 Phase 1 (needs confirmation): ITEM-005, 016, 018 Phase 2 (structural splits, confirmation required): ITEM-012..015 Phase 3 (deferred): ITEM-017 (rustdoc pipeline), ITEM-019 (workspace) Each item has: summary, key files with line numbers, steps, what needs confirmation, and relevant research backing from the 40-iteration analysis loop. Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): migrate 19 items to Starlight reference/roadmap section Move all codebase health roadmap items from docs/internal/roadmap/items/ (plain Markdown, not browsable) to docs/src/content/docs/reference/roadmap/ (MDX pages, browsable at jackin.tailrocks.com/reference/roadmap/). Adds a new "Codebase health" sidebar group (Phase 1 → Phase 3) to astro.config.ts. Deletes the old items/ directory. Updates the internal README to redirect to the new location. Also adds codebase-readability.mdx — a new overview item that captures the overall readability/restructuring program with a recommended execution order: file splits first, then greenfield workspace, then per-directory README+AGENTS.md, then docs and specs. Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@chainargos.com> * docs(roadmap): remove premature internal/roadmap/README.md The internal/ structure doesn't exist yet — it will be created as part of the roadmap items themselves. No need for a redirect stub now. Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@chainargos.com> * docs(roadmap): remove READABILITY_AND_MODERNIZATION.md research archive All content has been distilled into the individual Starlight roadmap pages. The full 2343L research is preserved in git history at commit b7e9fc2. Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@chainargos.com> * docs(roadmap): fix check:repo-links errors + remove iteration log - Replace plain code spans with <RepoFile> for validate.rs, mise.toml, Cargo.toml, and op_picker/mod.rs - Remove deleted READABILITY_AND_MODERNIZATION.md reference from codebase-readability.mdx - Delete _iteration_log.md (git history is the archive) Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@chainargos.com> * docs(roadmap): fix lychee false-positive link in move-contributing-testing The example redirect text contained a markdown hyperlink to a proposed future file path that doesn't exist yet. Changed to a code span. Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@chainargos.com> --------- Signed-off-by: Alexey Zhokhov <alexey@chainargos.com> Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Co-authored-by: Codex <codex@openai.com>
donbeave
added a commit
that referenced
this pull request
May 7, 2026
* docs(spec): per-mount isolation V1 implementation spec
Captures roadmap → executable design: module layout under
src/isolation/, MountIsolation enum + MountConfig.isolation field,
materialization runtime hook, foreground finalizer with
safe/preserved/force cleanup, source-drift detection, jackin cd
command, TUI integration. Test list and docs touchpoints enumerated
for the implementation plan.
Co-authored-by: Claude <noreply@anthropic.com>
* feat(isolation): introduce MountIsolation enum
Co-authored-by: Claude <noreply@anthropic.com>
* feat(workspace): add isolation field to MountConfig
Defaults to Shared; serde skips emitting the field when Shared so
existing TOMLs round-trip unchanged.
Co-authored-by: Claude <noreply@anthropic.com>
* feat(workspace): reject nested isolated mounts
Two worktree-isolated mounts whose dsts nest have no safe on-disk
layout. Sibling isolated mounts and isolated-parent-with-shared-child
remain allowed.
Co-authored-by: Claude <noreply@anthropic.com>
* feat(config): reject isolation field on global mounts
Adds a strict GlobalMountConfig wire-format struct that mirrors
MountConfig minus the isolation field, with deny_unknown_fields
so operators get a clear parse error if they try to set isolation
on a global mount. Isolation remains a workspace-mount concept.
Co-authored-by: Claude <noreply@anthropic.com>
* refactor(config): drop dead code and tighten global-mount API
- Delete unused From<MountConfig> for GlobalMountConfig (silently
dropped isolation; no callers).
- Delete unused get_mut and remove on DockerMounts along with their
#[allow(dead_code)] annotations.
- Tighten AppConfig::add_mount: debug_assert that incoming
MountConfig has Shared isolation, and construct GlobalMountConfig
explicitly from src/dst/readonly rather than via the (now-deleted)
From impl. Keeps the public signature stable so callers in CLI,
resolve.rs, and preview.rs don't need to change (Issue 2 option B).
- Add wire-path rejection test that goes through MountEntry's
untagged enum and asserts on the actual serde error
("data did not match any variant of untagged enum MountEntry").
- Soften GlobalMountConfig's doc comment to reflect the actual
serde error shape at the wire path.
Co-authored-by: Claude <noreply@anthropic.com>
* feat(isolation): IsolationRecord + isolation.json IO
Atomic write via tmp+rename. Version-1 envelope leaves room for schema
evolution. Read/upsert/remove keyed by mount destination.
Co-authored-by: Claude <noreply@anthropic.com>
* test(isolation): drop redundant clones in state tests
Bring the warning baseline back to 87 after Task 2.1 introduced
two clippy::style hits (cloned_ref_to_slice_refs, redundant_clone).
Co-authored-by: Claude <noreply@anthropic.com>
* feat(isolation): list_records_for_workspace walks data dir
Used by workspace-edit drift detection to find which containers have
preserved isolated state for a given workspace.
Co-authored-by: Claude <noreply@anthropic.com>
* feat(isolation): branch_name renderer with namespace + suffix support
Suffix is appended to the final selector segment so namespaced agents
keep their selector shape and the disambiguator goes on the leaf name.
Co-authored-by: Claude <noreply@anthropic.com>
* feat(isolation): MaterializedWorkspace types
Third workspace shape (Config -> Resolved -> Materialized) used as the
runtime handoff into Docker launch.
Co-authored-by: Claude <noreply@anthropic.com>
* feat(isolation): worktree_path_for derives on-disk path from mount dst
Uses dst verbatim (leading/trailing slashes stripped) under
isolated/, so the layout mirrors the container path.
Co-authored-by: Claude <noreply@anthropic.com>
* feat(isolation): ensure_worktree_config_enabled
One-shot enabler for extensions.worktreeConfig on the host repo.
Bumps core.repositoryformatversion to 1 when needed.
Co-authored-by: Claude <noreply@anthropic.com>
* feat(isolation): preflight checks for worktree materialization
Sensitive-mount, readonly, repo-root, and mid-operation guards.
Errors cite the mount destination and the worktree mode.
Co-authored-by: Claude <noreply@anthropic.com>
* feat(isolation): dirty-host preflight gate with --force opt-out
Non-interactive load without --force rejects a dirty host tree.
Interactive contexts are expected to obtain ack upstream.
Co-authored-by: Claude <noreply@anthropic.com>
* feat(isolation): materialize_workspace orchestrator
Per-mount worktree materialization with idempotent reuse, source-drift
guard, and branch-name disambiguation when multiple isolated mounts
target the same host repo.
Co-authored-by: Claude <noreply@anthropic.com>
* test(isolation): cover branch disambiguation for same-repo mounts
Co-authored-by: Claude <noreply@anthropic.com>
* feat(isolation): order Docker mounts parent-before-child
Length-ascending sort so shared cache children overlay isolated
worktree parents at container start.
Co-authored-by: Claude <noreply@anthropic.com>
* feat(runtime): hook materialize_workspace between AgentState and Docker
Workspace mounts now flow Config -> Resolved -> Materialized before
reaching the docker run command, with parent-before-child ordering.
Co-authored-by: Claude <noreply@anthropic.com>
* feat(isolation): force_cleanup_isolated removes worktree + branch + record
Best-effort git invocations that tolerate missing host repo and
already-removed worktree. Used by purge and the finalizer's force-delete
branch.
Co-authored-by: Claude <noreply@anthropic.com>
* feat(isolation): finalizer skeleton + AttachOutcome shape
Decides Preserved when container still running, OOMed, or exited
non-zero. Clean-exit path stubbed - implemented in follow-ups.
Co-authored-by: Claude <noreply@anthropic.com>
* feat(isolation): safe-cleanup deletes branches with no commits
When the worktree is clean and HEAD equals the recorded base, the
scratch branch is removed automatically.
Co-authored-by: Claude <noreply@anthropic.com>
* feat(isolation): consult upstream when deciding safe cleanup
Pushed commits (reachable from upstream) are safe to delete; local-only
commits or no-upstream divergence preserve the worktree.
Co-authored-by: Claude <noreply@anthropic.com>
* test(isolation): cover interactive unsafe-cleanup prompt branches
Co-authored-by: Claude <noreply@anthropic.com>
* feat(runtime): finalize foreground session after attach in load + hardline
Both load and hardline now consult inspect_attach_outcome and dispatch
the shared finalizer. Return-to-agent retries safe cleanup once after
the operator returns.
Co-authored-by: Claude <noreply@anthropic.com>
* fix(purge): refuse to run on a live container
Closes a pre-existing gap where purge could delete state out from
under a running agent. Operator must eject first.
Co-authored-by: Claude <noreply@anthropic.com>
* feat(purge): remove isolated worktrees and scratch branches
Reads isolation.json and runs force_cleanup_isolated for each record
before deleting the per-container state directory.
Co-authored-by: Claude <noreply@anthropic.com>
* feat(cli): --mount-isolation DST=TYPE on workspace create/edit
Repeatable. Rejects clone before persistence with the canonical
"reserved but not implemented yet" message.
Co-authored-by: Claude <noreply@anthropic.com>
* feat(workspace): add Isolation column to workspace show
Renders canonical lowercase name for every mount so CLI output matches
TOML/CLI input verbatim.
Co-authored-by: Claude <noreply@anthropic.com>
* feat(load): add --force to acknowledge dirty host tree
Required for non-interactive isolated-mount materialization when the
host working tree is dirty.
Co-authored-by: Claude <noreply@anthropic.com>
* feat(workspace): detect source drift on edit affecting isolated mounts
Edits that change src for a mount with preserved isolated state are
rejected unless --delete-isolated-state is passed and no related
container is running.
Co-authored-by: Claude <noreply@anthropic.com>
* feat(cli): jackin cd opens a child shell in an isolated worktree
Single-mount-no-dst → uses it. Dst-provided → exact match. Multi-mount
no-dst → interactive picker on TTY, error on non-TTY. Sets JACKIN_*
env vars. Does not modify the parent shell.
Co-authored-by: Claude <noreply@anthropic.com>
* feat(console): show isolation badge per mount in editor + preview
Adds an Iso column to the workspace-manager mount table (editor and
list-pane sub-panel) and a `[shared|worktree|clone]` tag to the agent
preview's resolved-mounts lines. Per the per-mount-isolation spec the
badge renders the canonical spelling for every mount, including
`shared`, so operators always see which strategy applies.
Co-authored-by: Claude <noreply@anthropic.com>
* feat(console): I hotkey cycles isolation on the selected mount
Mirrors the existing R (readonly) toggle. Cycles Shared -> Worktree
-> Shared; Clone is reserved-but-rejected in V1 and is not entered
through this hotkey, but a saved Clone mount snaps back to Shared on
the first I press rather than getting stuck. The cycling rule lives in
EditorState::cycle_isolation_for_selected_mount so the input dispatch
arm stays trivial.
Also surfaces the new key in the Mounts-tab footer hint alongside the
existing R toggle so the affordance is discoverable.
Co-authored-by: Claude <noreply@anthropic.com>
* feat(console): source-drift confirm modal in workspace editor
Save flow runs the same drift detection as `jackin workspace edit`:
detect_workspace_edit_drift evaluates the prospective mount list
(post-collapse, post-upsert) against IsolationRecords on disk before
the on-disk write.
Running container drift -> ErrorPopup ("eject first"); save aborted.
Stopped container drift -> Confirm modal listing the affected
container names with a Yes/No prompt. On Yes the modal handler
re-stashes the plan with delete_isolated_acknowledged = true and the
second commit pass calls force_cleanup_isolated for each affected
record before writing.
Reduced scope vs the original three-button "Delete preserved state and
save / Cancel / Open mount details" dialog: the modal is the existing
two-button Confirm widget (Yes/No). The third "open mount details"
affordance is omitted — operators dismiss with N/Esc, find the
offending mount in the editor, and revert the src by hand. Adding it
would require either a custom widget or repurposing an existing
multi-choice one and threading mount-row focus through the modal
plumbing; the safety value is in the block-and-ack semantics, which
the two-button form covers.
Adds a commit_editor_save_with_runner test seam so the FakeRunner can
drive the drift branch without a real Docker daemon.
Co-authored-by: Claude <noreply@anthropic.com>
* docs(workspaces): add per-mount isolation section
Document the per-mount isolation feature in the workspaces guide:
the three modes (shared default, worktree, clone reserved-but-rejected),
validation preconditions, the isolated-source + shared-cache child
pattern with TOML, and pointers to --mount-isolation and jackin cd.
Co-authored-by: Claude <noreply@anthropic.com>
* docs(mounts): document mount isolation field
Add an "Mount isolation" section to the mounts guide covering the
shared/worktree values, the global-mount rejection at parse time,
and the isolated-source + shared-cache child composition pattern.
Co-authored-by: Claude <noreply@anthropic.com>
* docs(configuration): add MountConfig.isolation field
Document the new mounts[].isolation field in the configuration
reference: shared default, worktree opt-in, clone reserved-but-rejected,
and the global-mount parse-time rejection.
Co-authored-by: Claude <noreply@anthropic.com>
* docs(architecture): document materialization flow + isolation.json
Add a "Workspace materialization" section to the architecture reference
covering the WorkspaceConfig -> ResolvedWorkspace -> MaterializedWorkspace
shapes, the per-container isolation.json layout, and the post-attach
foreground finalizer's Preserved/Cleaned/ReturnToAgent decision matrix.
Co-authored-by: Claude <noreply@anthropic.com>
* docs(workspace): document --mount-isolation and Isolation column
Add --mount-isolation to workspace create/edit option tables (with the
clone "planned but not implemented" note), document the new
--delete-isolated-state flag for non-interactive source-drift edits,
and note the Isolation column on workspace show.
Co-authored-by: Claude <noreply@anthropic.com>
* docs(load): document --force dirty-host acknowledgement
Add --force to the option table and a dedicated section explaining
when it's required (non-interactive load with a worktree-isolated
mount + dirty host tree) and what it does NOT do (no stash, no
discard, no relaxation of other validation).
Co-authored-by: Claude <noreply@anthropic.com>
* docs(purge): document running-agent guard and isolated cleanup
Document purge's new behavior: refuses to run on a running container
(eject first), force-removes isolated worktrees + scratch branches
recorded in isolation.json, and tolerates a missing host repo on
best-effort cleanup.
Co-authored-by: Claude <noreply@anthropic.com>
* docs(cd): add jackin cd command reference
Create a reference page for jackin cd <container> [dst] covering
arguments, the mount-selection behavior matrix (zero/one/many isolated
records, with and without dst), the JACKIN_* env vars set in the
child shell, exit-code passthrough, and the no-parent-mutation
guarantee. Wire it into the Commands sidebar between console and
launch.
Co-authored-by: Claude <noreply@anthropic.com>
* docs(roadmap): mark per-mount isolation V1 implemented
Flip the per-mount-isolation roadmap status to "Implemented in V1"
and replace the duplicate-mounts-allowed line with the actual V1
rule: multiple isolated mounts are allowed (with branch-name
disambiguation), but nested isolated dst paths are rejected at
validation because the inner worktree's .git would land inside the
outer worktree's tree.
Co-authored-by: Claude <noreply@anthropic.com>
* docs(structure): add isolation module tree and cd command
Update PROJECT_STRUCTURE.md to document the new per-mount-isolation
work: isolation/ module row (mod/branch/materialize/state/finalize/
cleanup), cli/cd.rs entry on the cli/ row, --mount-isolation /
--delete-isolated-state / --force notes on the relevant CLI rows,
foreground-finalizer mention on the runtime row, the new
commands/cd.mdx in the docs map, and a code->docs cross-reference
row mapping src/isolation/** to all the doc pages it touches.
Co-authored-by: Claude <noreply@anthropic.com>
* test(isolation): end-to-end materialize -> clean-exit -> cleanup
Exercises the full lifecycle through public APIs with a small inline
scripted runner. No real git or docker.
Co-authored-by: Claude <noreply@anthropic.com>
* docs(isolation): note finalizer is local-only for hardline lockdown
Co-authored-by: Claude <noreply@anthropic.com>
* style(test): apply rustfmt to per-mount isolation e2e
Co-authored-by: Claude <noreply@anthropic.com>
* ci(docs): verify docs links in PRs and on the deployed site (#173)
* ci(docs): add link checking
* ci(docs): stabilize lychee checks
* ci(docs): validate edit links with lychee
* ci(docs): close link check gaps
* docs: add repo file link component
* docs: explain repo link source check
* ci(docs): allow manual dispatch of deployed link check
Two refinements from PR review:
- The check-deployed job now triggers on workflow_dispatch in addition
to schedule, so maintainers can manually verify the live deployed
docs without waiting for the daily cron or pushing to main. This
closes the gap against goal "manual workflow to verify both built
site and deployed documentation".
- Drop github.sha from the deploy job's lychee cache primary key so it
matches across runs (the SHA-keyed primary was guaranteed to miss,
forcing fallback to restore-keys). Now mirrors the cache key shape
used by check-deployed.
Co-authored-by: Claude <noreply@anthropic.com>
* ci(docs): harden link-check workflow and broaden source lint
Address review feedback on PR #173.
Workflow (.github/workflows/docs.yml):
- Split concurrency group by event_name so a scheduled or
workflow_dispatch run cannot cancel an in-flight push deploy. Cancel
in-progress is now scoped to pull_request only.
- Exclude main from workflow_dispatch on check-deployed. The deploy job
already verifies the just-deployed site, so running both in parallel
would race against the publish window. Manual verification of the live
site from main flows through deploy; from feature branches it flows
through check-deployed.
- Add the build cache as a fallback restore-key for check-deployed so
the daily cron and manual runs warm-start from the last build cache
when the lychee.toml fingerprint changes.
RepoFile component (docs/src/components/RepoFile.astro):
- Add target=_blank and rel=noopener noreferrer so GitHub source links
open in a new tab, matching the behavior rehype-external-links applies
to plain markdown external links in MDX.
Source lint (docs/scripts/check-repo-links.ts):
- Normalize leading slashes when checking RepoFile path existence so the
validator agrees with the component's own normalization.
- Cover top-level repo-specific files (Cargo.toml, Cargo.lock, Justfile,
build.rs, docker-bake.hcl, mise.toml, release.toml, renovate.json) so
a rename of any of those also breaks the docs CI gate, not only paths
under src/, docs/, docker/, .github/.
Content (docs/src/content/docs/developing/construct-image.mdx):
- Convert the remaining inline-code references to docker-bake.hcl and
Justfile to <RepoFile />. These were the references that motivated
extending the lint to top-level files.
Co-authored-by: Claude <noreply@anthropic.com>
* docs(roadmap): apply RepoFile lint to multi-runtime proposal
After merging main into codex/docs-link-checks, check-repo-links flagged
37 plain inline-code references to existing repo files in the new
multi-runtime-support.mdx (added in #174 before this PR's lint existed
on main). Convert each one to <RepoFile /> so renames or deletions of
those source files break the docs gate before merge, the same way the
rest of the roadmap is now protected.
No prose changes — every conversion is one-for-one (`src/foo.rs` →
<RepoFile path="src/foo.rs" />). The trailing-slash directory reference
to `docker/runtime/` is left as a code span, since the lint correctly
skips it (it's a directory, not a file).
Co-authored-by: Claude <noreply@anthropic.com>
* fix(roadmap): repair broken Amp CLI link in multi-runtime proposal
CI failed on this PR's last build because lychee found a 404 on
https://github.com/sourcegraph/amp in multi-runtime-support.mdx (added
in #174). That repo does not exist publicly — Amp's source is not on
GitHub.
Point the link at https://ampcode.com instead, which is already the
canonical Amp URL used elsewhere in the docs (getting-started/why.mdx).
Co-authored-by: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
(cherry picked from commit f3f3e5e)
* docs(roadmap): wrap src/cli/cd.rs in <RepoFile> on per-mount-isolation page
The check-repo-links script (added in #173) flags any inline-code
reference to a real repo file that isn't wrapped in <RepoFile />.
src/cli/cd.rs was created on this branch, so once #173's lint reaches
this branch via the previous cherry-pick, the bare reference fails
the Docs CI check.
Co-authored-by: Claude <noreply@anthropic.com>
* docs: switch cross-doc links to absolute URL form
The link-check job's lychee step (added on main in #173, hardened in
#176) verifies built-site links against the on-disk dist tree. Relative
`.mdx`-suffixed links break that check because lychee resolves them as
literal file paths under the rendered URL's directory — e.g.
`./workspaces.mdx` rendered from `/guides/mounts/` resolves to
`/guides/mounts/workspaces.mdx`, not `/guides/workspaces/`.
Switch the four cross-doc links added by the per-mount-isolation work
to the rendered-URL form (`/guides/workspaces/#per-mount-isolation`
etc.) — same convention as the existing `[mount collapse](/commands/workspace/#mount-collapse)`
link in the same file.
Co-authored-by: Claude <noreply@anthropic.com>
* feat(isolation): emit verbose debug-mode trace for worktree lifecycle
Operators sharing logs to debug worktree behavior had no visibility into
the lifecycle — only three error/warning sites fired output, and none of
them ran on the happy path. `--debug` toggled a display mode (preserve
scrollback, clear spinner) but was not a verbose-trace facility.
This adds a `debug_log!(category, fmt, ...)` macro in `src/tui/mod.rs`
that gates on the existing `DEBUG_MODE` atomic so disabled call sites
cost only an atomic load (formatting is deferred behind the gate).
Output uses a `[jackin debug <category>]` prefix so shared logs are
greppable.
Instrumented sites (all under `category = "isolation"`):
- `materialize_workspace`: per-call summary (workspace, container,
selector, mount counts, force/interactive flags).
- `materialize_one`: per-mount decision trail — drift detection,
worktree reuse, preflight, base-commit lookup, branch derivation
with selected suffix, and the `git worktree add` invocation itself.
- `ensure_worktree_config_enabled`: every state transition (already
enabled vs. bumping repositoryformatversion vs. flipping the flag)
with the host repo path.
- `state.rs`: write_records (count + path), upsert_record (insert vs.
replace), remove_record (drop vs. no-op).
- `cleanup.rs`: force_cleanup_isolated entry, the two git invocations,
the rm -rf fallback, and the host-repo-missing skip path.
purge_isolated_for_container per-container summary.
- `finalize.rs`: foreground-session entry with exit code/oom/interactive
flags, the early-return path for non-clean exits, per-record cleanup
assessment.
- `runtime/launch.rs`: load_agent's call into materialize_workspace.
Read paths (`read_records`, `read_record`) are intentionally NOT logged
— they fire on every invocation and would drown the log.
Manual verification (since binary-level stderr capture would need a new
test dependency):
cargo run --release --bin jackin -- --debug load <agent>
cargo run --release --bin jackin -- --debug workspace edit <ws> \
--mount-isolation /workspace/proj=worktree
cargo run --release --bin jackin -- --debug purge <container>
Each emits a chronologically ordered `[jackin debug isolation] ...`
trace covering every git invocation, isolation.json mutation, and
finalize decision — suitable for sharing in bug reports.
Co-authored-by: Claude <noreply@anthropic.com>
* fix(tui): rename Iso column to Isolation, count isolation flips as one change
Two related TUI papercuts surfaced together when an operator flipped a
mount's isolation from `shared` to `worktree` via the `I` hotkey:
1. The mount-table header read `Iso` — opaque on first sight. Replaced
with `Isolation` (the full word). Bumped the column-width constant
from 8 → 9 so the header label fits without disturbing data-row
alignment, and renamed `MOUNT_ISO_COL_WIDTH` →
`MOUNT_ISOLATION_COL_WIDTH` for consistency. Updated the
alignment-regression test that asserted on the old label.
2. Cycling isolation on an existing mount (same `dst`, same `src`)
reported "2 changes" in the save-row footer and rendered the
Confirm Save dialog with a `+`/`-` pair for the same path. Both
sites used `MountConfig::contains()` — full-struct equality — so
any isolation/readonly drift made the row appear as remove + add.
Extracted a `MountDiff` classifier in `console/manager/state.rs`
that keys on `dst` (the identity used by upsert/remove everywhere
else). Same-`dst` matches with structural drift are now reported
as a single `Modified`, counted as one change in `change_count`
and rendered as a `~ <new>` line with a dimmed `was: <old>` follow-up
in the Confirm Save summary so the operator sees exactly what
changed without parsing a remove + add pair.
Extended `mount_summary` to include the isolation tag so the
delta is visible in both the new and old lines:
`~/foo (rw, worktree, github · main)`.
Records a new shared rule in `RULES.md` ("TUI Labels") to prevent
future short-form labels in user-facing TUI surfaces — operators
read the TUI in passing and cannot afford to decode `Iso`/`Cfg`/`Env`/
`WD`-style abbreviations. Lists the established short forms that are
NOT considered abbreviations (`dst`, `src`, `git`, `op`).
Co-authored-by: Claude <noreply@anthropic.com>
* fix(isolation): make worktree mode actually work inside the container
V1's worktree mode shipped with a gap: the materialized worktree was
bind-mounted into the container at <dst>, but the worktree's `.git`
text file (a pointer back to <host_repo>/.git/worktrees/<n>/) referenced
an absolute host path that didn't exist inside the container. Every git
command — `git status`, `git log`, `git commit`, `git push` — failed
with "fatal: not a git repository". The agent could read source files
but could not commit work, defeating worktree mode's whole purpose.
Fix: wire up three additional bind mounts at docker-run time, plus two
jackin-owned override files written at materialization, so git's gitdir
relationship resolves consistently inside the container without
modifying any host-side files.
For each isolated worktree, the container now sees:
1. The worktree at <dst> (existing).
2. The host repo's `.git/` at /jackin-isolation/<container>-git/ rw,
so git can find objects, refs, and the per-worktree admin dir.
3. A jackin-owned `.git` text file at <dst>/.git overriding the
worktree's host-side pointer with one targeting the container path.
4. A jackin-owned back-pointer at /jackin-isolation/<container>-git/
worktrees/<n>/gitdir overriding git's verification check (host's
absolute path doesn't match <dst> inside the container).
Override files live under <data_dir>/jackin-<container>/.git-overrides/
and are written once at materialize time. Host files (worktree's `.git`
and the admin dir's `gitdir`) are NEVER modified — host-side
`git worktree list` continues to work identically.
Three layouts were considered (Docker Sandboxes-style `.jackin/` in the
host repo, indirect mount with override files, jackin-owned bare repo).
The chosen approach preserves jackin's dst-based mount model (operator
configures dst=/workspace/jackin → agent works at that exact path), keeps
the host repo clean (no `.jackin/` directory), and exposes only the
worktree to the agent (not the entire host main tree). Full design
rationale and the comparison with Docker Sandboxes, Conductor, and
clone mode (planned for V1.1) are in
docs/src/content/docs/reference/roadmap/per-mount-isolation.mdx
under "Design Decision: Worktree Materialization Layout" and
"Comparison with Other Tools".
Trust trade-off: the agent has rw access to the host repo's `.git/`
since refs/objects are inherently shared in git worktrees. Worktree
mode is appropriate for trusted agents on personal projects where
immediate ref visibility on the host is valuable. Operators who want
ref isolation should use clone mode (planned).
Tests:
- write_git_overrides_writes_both_files_with_correct_content asserts
override file content matches the design doc verbatim.
- write_git_overrides_is_idempotent confirms re-running on a reused
worktree (load → eject → load) doesn't drift.
- override_id_strips_slashes_and_trims pins the file-naming scheme.
- container_git_dir_path_namespaces_by_container_name pins the
hardcoded container-side path so two parallel agents don't collide.
- Extended per_mount_isolation_e2e to assert MaterializedMount carries
WorktreeAuxMounts on the worktree path and that override files land
on disk at the documented locations.
Manual verification recipe (add after running once):
cargo run --release --bin jackin -- --debug load <agent>
docker exec -ti <container> git status # was failing, now works
docker exec -ti <container> git log # works
docker exec -ti <container> git commit -m test --allow-empty
git -C <host-repo> branch -a # shows new branch
Co-authored-by: Claude <noreply@anthropic.com>
* refactor(isolation): /jackin/{host,admin}/<dst> mounts, container-name basename, :ro hardening
Three related changes that finalize the worktree-mode mount layout:
1. Container-side path scheme renamed and reorganized.
/jackin-isolation/<container>-git/... → /jackin/host/<dst-stripped>/.git
/jackin/admin/<dst-stripped>/{commondir,gitdir}
- Single top-level /jackin/ namespace for everything jackin contributes
to the agent's filesystem (room to grow with /jackin/cache/, etc.)
- host/ category mirrors host topology so docker inspect shows symmetric
Source/Destination paths both ending in `.git`
- admin/ category lives at a separate top level so the override files
(which sit on top of files inside the admin dir) do NOT visually nest
inside /jackin/host/.../.git/. Two top-level concerns, no overlap.
2. Host-side storage groups all git artifacts for one mount under
<state>/git/<dst-stripped>/, with override-file names matching their
docker mount destinations:
<state>/git/<dst-stripped>/
├── <container>/ (the worktree; basename = container name)
└── overrides/
├── .git
├── commondir
└── gitdir
Replaces the prior <state>/.git-overrides/ flat layout with underscored
slug filenames. New layout uses dst as a real directory tree (no slug)
and source filenames identical to destination filenames — the source/
destination relationship is obvious in `docker inspect`.
3. Worktree subdir basename = container name. `git worktree add` derives
the host-side admin entry name from the worktree path's basename (no
--name flag exists upstream). Using the container name (which jackin
guarantees is globally unique) makes admin entries in
<host_repo>/.git/worktrees/ globally unique per (host_repo, container)
— `git worktree list` on the host immediately shows which container
owns each worktree.
This required a new validation rule:
`workspace::validate_isolation_layout` now rejects two isolated mounts
that resolve to the same host repository within one workspace.
Allowing them would force the same container-name basename twice in
one host repo's .git/worktrees/ namespace; no real operator workflow
has surfaced for this case. Revisit if one does.
Removes the now-dead suffix logic from materialize.rs:
- `count_isolated_per_repo` (helper)
- `canonicalize_or_clone` (helper)
- `dst_to_branch_suffix` (in src/isolation/branch.rs — no callers left)
The `branch_name` function keeps its optional suffix parameter for
future clone-mode use; V1 worktree always passes None.
4. The three override files (replacement `.git` pointer, `commondir`,
`gitdir` back-pointer) are mounted `:ro` as defensive hardening. Git
only reads them during normal agent work, and a misbehaving agent
could otherwise rewrite the gitdir pointer to redirect operations at
a different repo entirely. The host `.git/` and admin mounts stay rw
because git writes refs/objects/HEAD/index/logs there.
Tests:
- workspace::tests::isolation_layout_rejects_two_worktree_mounts_on_same_repo
- workspace::tests::isolation_layout_allows_different_host_repos_in_one_workspace
- materialize::tests::worktree_path_uses_container_name_as_basename
- materialize::tests::container_host_git_path_mirrors_dst_under_jackin_host
- materialize::tests::container_admin_path_lives_under_jackin_admin
- materialize::tests::host_and_admin_paths_disambiguate_per_mount_in_one_container
- materialize::tests::write_git_overrides_writes_three_files_with_correct_content
- materialize::tests::write_git_overrides_is_idempotent
- launch::tests::build_workspace_mount_strings_marks_overrides_readonly
(asserts all 6 mounts in correct order with correct :ro placement)
- per_mount_isolation_e2e: updated for new path scheme + admin name
Removed:
- materialize::tests::two_isolated_mounts_same_repo_get_dst_suffixed_branches
(case is now rejected at the workspace-validation level)
Roadmap MDX (per-mount-isolation.mdx):
- Container-side mount layout section: 4 mounts → 6, new path scheme,
override-file storage layout
- Composition Rules: documents the new same-host-repo rejection
- Comparison table: bind mount count for jackin worktree updated 4 → 6
- V1 Scope: ship list updated with new layout and the new validation rule
Manual verification (after merge):
cargo run --release --bin jackin -- --debug load <agent> <workspace>
docker inspect <container> | jq '.[0].Mounts' # see /jackin/{host,admin}/...
docker exec -w <dst> <container> git status # works
git -C <host_repo> worktree list # admin name = <container>
Co-authored-by: Claude <noreply@anthropic.com>
* refactor(isolation): single /jackin/host/ root, no commondir override, Model B branch naming
Final V1 design after extended brainstorming with the operator. Drops
the `/jackin/admin/<dst>` namespace and the `commondir` override file:
the per-worktree admin entry now lives natively at
`worktrees/<container>/` inside the host `.git/` mount, so git's
on-disk default `commondir = ../..` resolves correctly without an
override.
Container-side topology, per isolated mount (4 binds total, down from 6):
- `<dst>` (rw) — the materialized worktree
- `/jackin/host/<dst-tree>/.git` (rw) — host repo's `.git/`
- `<dst>/.git` (`:ro`) — replacement gitdir pointer
- `/jackin/host/<dst-tree>/.git/worktrees/<container>/gitdir` (`:ro`)
— replacement back-pointer
Host-side layout under each per-container state dir:
- `git/worktree/repo/<dst-tree>/<container>/` — git's territory
- `git/overrides/<dst-tree>/{.git,gitdir}` — jackin-owned overrides
Branch naming follows Model B: `jackin/scratch/<container_name>`
verbatim. Admin entry name = container name (deterministic, globally
unique because container names are workspace-unique and
`validate_isolation_layout` rejects two isolated mounts on the same
host repo within one workspace — no auto-suffix or read-back needed).
Roadmap doc updated to reflect the final design.
Co-authored-by: Claude <noreply@anthropic.com>
* style(isolation): rustfmt assert_eq! width
Co-authored-by: Claude <noreply@anthropic.com>
* docs(isolation): align stale references with shipped V1 design
Sweep stale references across roadmap, guides, command and architecture
docs into alignment with what the code actually does. No content added —
the doc just told two contradictory stories before (Model B branch
naming alongside the old selector-key derivation; the new
git/worktree/repo/<dst>/<container>/ on-disk layout alongside the
proposed-but-never-implemented isolated/<slug>/ layout). Now there is
one consistent story end-to-end.
Touches:
- docs/src/content/docs/reference/roadmap/per-mount-isolation.mdx
- docs/src/content/docs/guides/workspaces.mdx
- docs/src/content/docs/commands/purge.mdx
- docs/src/content/docs/reference/architecture.mdx
Co-authored-by: Claude <noreply@anthropic.com>
* chore(cli): remove jackin cd command from V1
Operationally redundant with `git worktree list` + native shell `cd`.
For worktree mode, the host's `git worktree list` already enumerates
every isolated worktree by branch and absolute path, so a plain
`cd $(...)` reaches the same destination. The remaining edge cases
(preserved-dirty inspection, multi-mount picker) are rare enough that a
dedicated subcommand is net cost rather than net benefit.
Removes:
- src/cli/cd.rs (CdArgs + select_record + tests)
- Command::Cd enum variant in src/cli/mod.rs
- handle_cd dispatch in src/app/mod.rs
- docs/src/content/docs/commands/cd.mdx and its sidebar entry
Updates:
- src/isolation/finalize.rs preserved-state warning: drop "jackin cd ..."
hint, point operator to the printed worktree path instead
- src/isolation/materialize.rs source-drift error: same treatment
- guides/workspaces.mdx + reference/architecture.mdx: drop cd references
- roadmap entry: replace "Convenience navigation" paragraph with a
removed-from-V1 note explaining the rationale; add cd to the Defer
list so it's recoverable if a real workflow surfaces
isolation.json schema, the preserved-state machinery, and `hardline` /
`purge` flows are unaffected — only the inspection convenience layer
is gone.
Co-authored-by: Claude <noreply@anthropic.com>
* chore(isolation): drop clone from V1 enum/parser/CLI
Per principle: don't pre-add API for unimplemented features. The `clone`
keyword was previously parsed by TOML/CLI and then rejected at validation
with a "planned but not implemented yet" error. Operators got false
positives in linting tools and confusing late failures with no benefit —
nothing in V1 actually does anything useful with the value.
Removes from the runtime:
- MountIsolation::Clone enum variant (src/isolation/mod.rs)
- explicit Clone-rejection in parse_mount_isolation (src/cli/workspace.rs):
FromStr now produces "invalid isolation `clone`" naturally
- MountIsolation::Clone match arm in materialize_workspace
(src/isolation/materialize.rs)
- console comments referencing the reserved-but-rejected wording
Tests:
- New `rejects_clone_until_implemented` test on FromStr asserts the
standard "invalid isolation `clone`; expected one of: shared, worktree"
error so this stays locked down
- parse_mount_isolation_rejects_clone updated to assert the new error
shape
Docs:
- guides/workspaces.mdx, commands/workspace.mdx,
reference/configuration.mdx, reference/roadmap.mdx: drop the
"reserved keyword" wording, point at the V1.1 roadmap entry instead
- roadmap/per-mount-isolation.mdx: keep the `clone` design discussion,
rephrase the V1 vocabulary section to make it explicit that the keyword
is added back when clone mode ships, not pre-shipped now
apply_isolation_overrides already enforces "--mount-isolation must
reference an existing mount destination" (planner.rs); no change needed
for that requirement, just clarified in the roadmap CLI-behavior bullet.
Co-authored-by: Claude <noreply@anthropic.com>
* fix(workspace): always write mount isolation field explicitly on save
Old configs without the `isolation` field still deserialize to Shared
(the enum default). On save, drop the `skip_serializing_if = is_shared`
guard so every mount writes its isolation level explicitly — including
`shared`. Old TOMLs migrate to the new shape on first save instead of
silently retaining their pre-isolation form.
Rationale: when the operator opens the saved config, every mount should
name its isolation level. No "field is missing therefore implicitly
shared" — the value is always present and the source of truth in the
file matches the source of truth at runtime.
Touches:
- src/workspace/mod.rs MountConfig.isolation: drop skip_serializing_if
- mount_config_omits_isolation_field_when_shared_on_serialize → renamed
to mount_config_writes_isolation_field_even_when_shared_on_serialize,
asserts the field IS written
- guides/mounts.mdx + reference/configuration.mdx: update wording
Co-authored-by: Claude <noreply@anthropic.com>
* docs: remove stale per-mount-isolation design spec
The brainstorming spec at docs/superpowers/specs/ predated the V1 design
iterations and no longer reflects what shipped (it still describes the
old `/jackin-isolation/` mount layout, the selector-key-based branch
naming, the reserved `clone` enum value, and `jackin cd`). The roadmap
entry at docs/src/content/docs/reference/roadmap/per-mount-isolation.mdx
is now the single source of truth.
Co-authored-by: Claude <noreply@anthropic.com>
* docs(roadmap): improve per-mount isolation entry — accurate Sandboxes
comparison, V1 overview, concrete clone-mode personas
Four targeted improvements to the per-mount-isolation roadmap doc:
1. Add a "How V1 worktree mode works (TL;DR)" overview at the top of
Host-Side Materialization. 5-step walkthrough of the materialize +
mount + commit flow before the deep-dive subsections, so readers
land on the entry can understand the shipped V1 in 90 seconds
without scrolling through the layout/lifecycle/etc.
2. Rewrite the Docker Sandboxes comparison for accuracy. The old
description got it wrong on multiple points:
- Sandboxes use a microVM with hypervisor isolation, not Docker
containers (Linux namespaces) like jackin.
- The host filesystem is exposed via filesystem passthrough at the
SAME absolute path as the host, not a bind mount of the entire
repo at "the same relative paths".
- Worktree path is `<host_repo>/.sbx/<sandbox-name>-worktrees/<branch>/`
(sandbox name is in the path), not `<host_repo>/.sbx/<branch>/`.
- Sandboxes do NOT expose the host main working tree to the agent
— only the worktree subdir and the parent `.git/`. Our table
previously said "✓ (entire host repo mounted)".
The architectural insight is now explicit: Sandboxes' absolute-path
equivalence makes git's on-disk absolute pointers resolve natively,
which is why they don't need override files. We pay that cost
because Docker containers translate host paths to operator-chosen
`dst` values, breaking absolute-path equivalence.
3. Concrete clone-mode operator personas. The previous description
was abstract ("complementary mode for ref isolation"). Replaced
with four named situations clone mode targets: untrusted/experimental
agents, parallel-fan-out scratch-branch noise, editor watcher
churn, and teams whose workflow is push-to-share anyway.
4. New "Sandbox runtime" and "Host file exposure" rows in the
comparison table to make the underlying architectural choice
immediately visible. Cross-referenced from the Sandboxes prose.
Net: ~80 lines changed/added, primarily replacement of the wrong
Docker Sandboxes facts and addition of the V1-overview block.
Co-authored-by: Claude <noreply@anthropic.com>
* fix(isolation): close four data-loss windows in finalize/cleanup paths
Four merge-blocking issues surfaced by deep code review of PR #177.
Each was a silent-failure window that could destroy operator data on
the unhappy path while the happy-path manual smoke test stayed green.
1. assess_cleanup now treats every git capture failure as
PreservedUnpushed (was: unwrap_or_default → empty string → could
land in SafeToDelete). Without this, a transient `git rev-list`
failure (corrupted pack mid-traversal, broken pipe under load,
index.lock from a backgrounded git GC) would auto-delete the
worktree and scratch branch, garbage-collecting unpushed commits.
Each capture site now uses an explicit `match` that returns
PreservedUnpushed with debug_log of the underlying error, plus a
defense-in-depth empty-HEAD guard.
2. finalize_clean_exit now collects ALL preserved records and prompts
per-record (was: needs_prompt.get_or_insert reached only the first
one). On a multi-mount workspace where the operator chooses
force-delete on the first prompt, the second preserved worktree was
silently orphaned and the container torn down anyway — the only
reconnection path (jackin hardline) was lost. Now each preserved
record gets its own prompt; "return to agent" short-circuits the
loop; "preserve" propagates as Preserved and skips container
teardown.
3. inspect_attach_outcome now returns still_running() on docker capture
failure (was: stopped(0) → entered finalize_clean_exit → could
compound with #1 to delete worktrees of containers that may still
be alive). The conservative direction is "preserve when we don't
know" — `jackin hardline` recovers from there.
4. force_cleanup_isolated now verifies cleanup actually completed
before removing the isolation.json record (was: let _ on git ops +
unconditional remove_record → orphan worktree admin entries on the
host repo and orphan branches with no jackin reference). Tolerates
the idempotent paths (already-removed worktree, already-deleted
branch verified absent via `git branch --list`); bails on real
failures with a clear "record retained, re-run jackin purge after
resolving the issue" message.
Test coverage:
- 5 new tests in isolation/finalize.rs pinning the assess_cleanup
capture-failure → PreservedUnpushed contract for each git command
in the assessment chain, plus the empty-HEAD guard.
- 3 new tests pinning the multi-record finalize path (force-delete-all,
mixed force/preserve, non-interactive multi-mount warning).
- 1 new test for inspect_attach_outcome capture-failure fallback.
- 3 new cleanup tests (branch-already-deleted tolerance, real-failure
retention, error-message contract).
- 1 new test for validate_workspace_config integration (catches the
validate_isolation_layout call site if anyone refactors it away).
- 1 new test for build_workspace_mount_strings on a multi-mount
isolated workspace (8 distinct binds, no path collisions, :ro
hardening on every override file).
- 2 new drift-detection tests (dst-removed flagged; isolation-mode
flips not-flagged with explanatory note for future improvement).
Net: 1170 → 1186 tests, 16 additions, all passing.
Co-authored-by: Claude <noreply@anthropic.com>
* fix(isolation): close P1/P2/P3 — follow-on bugs from second review
Second deep code review of PR #177 surfaced three more issues after
the first round of merge-blocker fixes shipped:
P1. `force_cleanup_isolated` failure mid-loop in finalize_clean_exit
propagated as Err via `?`, leaving the operator with a raw cleanup
error from deep in finalize, no Preserved signal to the caller, the
container left running without explicit teardown decision, and
subsequent records in the loop never prompted. This regression was
introduced by the round-1 multi-record loop fix (the single-record
path always succeeded). Now caught per-record, eprintln'd as a
warning, and treated as `any_preserved_after_prompt = true` so the
loop continues and the caller gets `Preserved`.
P2. `inspect_attach_outcome` only treated `status == "running"` as
still-alive. `paused | restarting | removing | created | dead` all
fell through to `stopped(0)` → entered `finalize_clean_exit` →
could auto-delete worktrees of containers that may resume any
moment. Concrete: `docker pause jackin-x` while jackin re-attaches
→ status="paused" → SafeToDelete on a clean tree → operator
unpauses to find the worktree gone. Replaced if-cascade with an
explicit `match status` that only routes `exited` through stopped()
and treats unknown status strings conservatively as still_running.
P3. `purge_isolated_for_container` swallowed per-record errors with
eprintln warnings and returned `Ok(())`. Exacerbated by the
round-1 fix #4 (force_cleanup_isolated now bails more often on
real failures). Operator runs `jackin purge`, sees a warning
scroll past, gets exit-code-0 prompt back, may believe purge
completed. Now collects failures and surfaces an aggregate Err
with the failed mount list so the exit code reflects reality.
Test coverage for these fixes:
- 2 new tests in finalize.rs: ReturnToAgent on the 2nd-of-3 prompt
(early-return short-circuits), and force_cleanup_isolated failing
mid-loop (loop continues, returns Preserved).
- 8 new tests in launch.rs covering every status code path:
exited(0/non-zero/oom), running, paused, transient (restarting/
removing/created), dead, unknown.
- 2 new tests in cleanup.rs: purge bails on partial failure,
branch_still_present returning None proceeds (pins the doc-comment
contract against future refactors to `unwrap_or(true)`).
Net: 1186 → 1198 tests, +12 additions, all passing. fmt/clippy clean.
Co-authored-by: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
Co-authored-by: Codex <codex@openai.com>
donbeave
added a commit
that referenced
this pull request
May 7, 2026
- Move 9 TODO items from monolithic TODO.md into separate files in todo/ - Each file is a self-contained design doc with problem, options, and related source files for easy agent handoff - Mark resolved security findings (#3, #4, #6, #7) in SECURITY_REVIEW_FINDINGS.md - Update PROJECT_STRUCTURE.md with todo/ section and TESTING.md entry - TODO.md becomes an index pointing to todo/ files Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Co-authored-by: Codex <codex@openai.com>
donbeave
added a commit
that referenced
this pull request
May 7, 2026
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Co-authored-by: Codex <codex@openai.com>
donbeave
added a commit
that referenced
this pull request
May 7, 2026
* feat: implement trust-on-first-use model for third-party agent sources Add a trust gate that prompts operators before building untrusted third-party agents. Built-in agents are always trusted. New namespaced agents default to untrusted and require explicit confirmation on first use. The trusted flag is persisted in config.toml so subsequent runs proceed without prompts. Non-interactive sessions bail with a clear error for untrusted sources. Also syncs the roadmap with TODO items: adds missing completed item (JACKIN_DIND_HOSTNAME), adds missing planned items (Bollard migration), and marks the agent source trust security finding as resolved. https://claude.ai/code/session_01SkHoAne5Q5EpUybYk6ghUT * improve trust prompt with clear risk details and mark finding #9 resolved Rewrite the trust confirmation message to clearly explain what trusting an agent means: Dockerfile execution, arbitrary commands on the host, and workspace file access. The non-interactive error now tells the user how to pre-trust via config.toml. The decline message explains how to retry. Also mark security finding #9 (mount policy guardrails) as resolved since sensitive mount warnings were already implemented. https://claude.ai/code/session_01SkHoAne5Q5EpUybYk6ghUT * fix: address PR review — docs, stale comment, save clarity, test, serde Review fixes for the agent source trust model: - Fix duplicate doc comment on trust_agent() (copy-paste from sync_builtin_agents) - Replace confusing `!source.trusted` save condition with explicit `newly_trusted` flag for readability - Add unit test for non-interactive trust gate (verifies the error includes agent name and git URL) - Skip serializing `trusted = false` to keep config.toml clean (only `trusted = true` appears) - Document trust in load.mdx (step 3 in "What happens", warning callout for CI) and security-model.mdx (new section 4 with config.toml example, threat model row, best practices update) https://claude.ai/code/session_01SkHoAne5Q5EpUybYk6ghUT * fix: broken docs anchor link and misleading test name - Fix cross-reference link in load.mdx: use #4-agent-source-trust (matching the numbered heading in security-model.mdx) - Rename test to `load_trusted_namespaced_agent_builds_and_runs` since it no longer exercises auto-registration (that's covered by the config test `resolve_agent_source_adds_owner_repo_on_first_use`) https://claude.ai/code/session_01SkHoAne5Q5EpUybYk6ghUT * feat: add jackin trust/untrust CLI, testable trust gate, revocation Address all follow-up items from the maintainer review: - Extract load_agent_with() with injected trust callback, following the resolve_agent_repo_with() pattern. Tests can now exercise accept and decline paths without mocking dialoguer. - Restore auto-registration test coverage: the accept test uses load_agent_with(auto_trust) so resolve_agent_source's is_new path is exercised again. Add decline test verifying no config persistence and no Docker commands when trust is refused. - Add `jackin trust <selector>` and `jackin untrust <selector>` CLI commands so operators can manage trust without editing config.toml. - Add untrust_agent() to AppConfig with tests. - Document trust/untrust CLI in security-model.mdx and load.mdx. - Document that trust is keyed by selector (not URL) with a note about remote-mismatch protection. https://claude.ai/code/session_01SkHoAne5Q5EpUybYk6ghUT * refactor: consolidate trust/untrust into single CLI command Follow mise's pattern: `jackin trust` is one command with flags instead of separate trust/untrust subcommands. jackin trust <selector> # grant trust jackin trust <selector> --untrust # revoke trust jackin trust <selector> --show # check status The --show and --untrust flags conflict with each other (enforced by clap). https://claude.ai/code/session_01SkHoAne5Q5EpUybYk6ghUT * refactor: move trust CLI under config with grant/revoke/list subcommands Follow the same pattern as `jackin config mount {add,remove,list}`: jackin config trust grant chainargos/the-architect jackin config trust revoke chainargos/the-architect jackin config trust list This keeps all config mutations under `jackin config` and uses the subcommand pattern consistently throughout the CLI. The `list` subcommand shows all currently trusted agents. https://claude.ai/code/session_01SkHoAne5Q5EpUybYk6ghUT * fix: refuse to revoke trust on builtins, add CLI parsing tests - is_builtin_agent() check prevents revoking trust on built-in agents (agent-smith, the-architect) which would be silently re-granted by sync_builtin_agents on next load - Add 3 CLI parsing tests for config trust {grant, revoke, list}, matching the existing coverage for config mount {add, remove, list} - Tighten TrustCommand::List doc comment to "List all currently trusted agent sources" https://claude.ai/code/session_01SkHoAne5Q5EpUybYk6ghUT --------- Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Co-authored-by: Codex <codex@openai.com>
donbeave
added a commit
that referenced
this pull request
May 7, 2026
…183) * docs(roadmap): iteration 13 — AI code verifiability framing, config/types.rs full spec Primary goal shift: codebase must be verifiable for AI-generated code. - §0: replace generic description with explicit verifiability rationale (module contracts, localised concerns, types/behaviour separation) - §4 intro: add "Why structure matters for AI-generated code" section with audit-units table mapping each post-split file to one reviewable question - §4 4a: expand config/types.rs from description to full execution spec — exact type list, post-split mod.rs content, zero-change submodule guarantee (verified: agents.rs/persist.rs/workspaces.rs use super::T which resolves through mod.rs re-exports unchanged), impl-extension pattern already in use documented * docs(roadmap): iteration 14 — editor method map, app helpers, //! queue - §4 4c: config/editor.rs split is now execution-ready — complete 6-file method-to-file table with private helper placement verified (validate_candidate→io.rs, table_path_mut→mod.rs pub(super), auth_forward_str→agent_ops.rs, create_workspace delegates to AppConfig) - §4 4e: app/mod.rs split complete — all private helpers mapped (parse_auth_forward_mode_from_cli→config_cmd.rs, workspace_env_scope→workspace_cmd.rs, print_env_table note, remove_data_dir_if_exists→dispatch.rs) - §10 step 5: add //! priority queue — 10 files with draft content, prioritised by cold-landing impact and AI audit risk; selector.rs and instance/mod.rs explicitly document the /→__ invariant * docs(roadmap): iteration 15 — dep graph fix, trust.rs safety, OQ1 closed - §4 4d: correct operator_env dependency graph — layers.rs imports both mod.rs (OpRunner) AND client.rs (OpCli for non-injectable resolve_operator_env wrapper at line 797); still a valid DAG - §4 4f: verify trust.rs split safety — FnOnce injection pattern means launch_pipeline.rs has zero dependency on trust.rs; import chain documented; trust bypass audit now requires reading only ~60L - §9 OQ1 closed: op_cache.rs read in full — 4-level structure, per-level invalidation, no TTL/expiry (expiry handled at OpCli subprocess level), DEFAULT_ACCOUNT_KEY sentinel documented * docs(roadmap): iteration 16 — CommandRunner Rule 3, render/editor split, 4a/4c independence - Fix duplicate Rule 3 section introduced by previous edit; add docker.rs co-location note as third edge case (three edge cases, not two) - Add render/editor.rs as new Rule 5 violator: 1666L post-PR #171 (was listed as 782L); propose 6-file tab-by-tab split with auditability note on the security-adjacent Secrets tab - Add §10 execution-order note: 4a and 4c are independent — editor.rs imports AppConfig via crate::config re-exports regardless of 4a order - Append iteration 16 log entry with confidence table and weakest sections * docs(roadmap): iteration 17 — instance/auth.rs audit, state.rs split, line count corrections - Add instance/auth.rs to //! priority queue at #4: four security invariants (0o600 perms, symlink rejection, TOCTOU-safe writes, macOS Keychain) documented in draft //! content - Add state.rs as new Rule 5 violator: 992L/628L production; 26+ types mixed with impl blocks; propose 5-file types/behavior split - Correct stale line counts: render/list.rs 1122→1989 (PR #171 added render_environments_subpanel); state.rs 865→992; priorities upgraded - Fix §7.9 snapshot function line refs: sentinel_description_pane 306→332, mounts_subpanel 408→433, render_tab_strip 180→269, test ref 720→944 - Renumber //! priority queue to 11 entries (was 10) * docs(roadmap): iteration 18 — agent_allow OQ2 closed, render/list.rs split proposal - Close OQ2: agent_allow.rs read in full — 55L, correct //! doc, design sound; serves as model for //! priority queue pattern - Add render/list.rs as new Rule 5 violator: 668L production (PR #171 added render_environments_subpanel); propose 3-file split (mod.rs, details.rs, subpanels.rs); note import-path change for agents_block_agent_count - Update §1 module map: agent_allow.rs entry corrected with size/API * docs(roadmap): iteration 19 — input/editor.rs critical correction, split proposal - Correct input/editor.rs: 2349L total (was 1304L), 1141L production (was 547L) — PR #171 added Secrets-tab handlers; pub(super) fn handle_editor_modal at line 618 was invisible to previous grep pattern; now the largest production file in the codebase; priority → Critical - Correct input/save.rs: 1472L total, 661L production (was 567L) - Add 5-file split proposal for input/editor.rs: mod.rs (two dispatchers), secrets.rs (~500L AI-generated Secrets-tab), agents.rs, mounts.rs, general.rs - Update key insight paragraph naming input/editor.rs as largest production file * docs(roadmap): iteration 20 — console splits in §10, MSRV evidence, animation.rs verdict - Add console/manager/ as §10 Step 4f group with 5 sub-steps in priority order; rename existing 4f (launch.rs) → 4g; add circular-import risk note for ManagerStage/EditorState split sequencing - Analyze tui/animation.rs: 582L all-production, no split needed (banner_grid is a tightly-coupled rendering loop); section comments compensate for missing //! - Partially close OQ3: u64::is_multiple_of (stabilized 1.86) found in animation.rs; within declared MSRV 1.94; full cargo +1.94.0 check deferred (toolchain unavailable) * docs(roadmap): iteration 21 — input/save.rs split, //! queue fix, save.rs corrections - Add input/save.rs split proposal: 4 pub(super) fns discovered; 3-file split (mod.rs + flow.rs + preview.rs); no cross-dependency between flow and preview groups; §10 4f-v updated from Optional to concrete plan - Fix //! queue preamble: "first 10 files" → "first 11 files" - Correct save.rs module map (1418→1472L, correct key exports) and hot-spot table note (begin_editor_save ~280L → ~118L; commit_editor_save is the Phase 2 partner at ~149L) * docs(roadmap): iteration 22 — input/list.rs and mount_info.rs analysis - Analyze input/list.rs: 214L production (tests at 215); has //! doc; two focused pub(super) fns; no split needed; Low priority; correct module map - Add mount_info.rs to hot-spot table: 277L production; Low priority; has //! doc; correct module map with 3 public enums + inspect fn - Fix stale §2 diagnosis note: docs/internal/roadmap/ now exists * docs(roadmap): iteration 23 — audit units table +5 console rows, input/mod.rs corrected - Expand audit units table from 8 to 13 entries: add state/types.rs, state/editor.rs, input/editor/secrets.rs, render/list/subpanels.rs, input/save/preview.rs — all targeting PR #171 AI-generated console code - Add PR #171 context note linking 5 new entries to AI-generated code concern - Correct input/mod.rs module map: 369L, add InputOutcome enum to exports - Verify rust-toolchain.toml absence; §7.7 and §2 concept 25 already correct * docs(roadmap): iteration 24 — render/mod.rs analysis, //! exemplars table, EditorTab confirmed - Add §4 Rule 7 positive exemplars table: 7 files with //! docs graded 1-element (render/mod.rs), 2-element (input/save.rs etc), 3-element (env_model.rs, agent_allow.rs); PR #171 docs-discipline pattern noted - Correct render/mod.rs module map: 421L; FooterItem + palette constants + render_header + centered_rect_fixed added to key exports - Confirm EditorTab variants: General, Mounts, Agents, Secrets (Rust enum) vs "Secrets / Environments" (UI label); /stub qualifier already removed * docs(roadmap): iteration 25 — too_many_lines recount, FooterItem PR, MountConfig caveat - Correct too_many_lines count: 13 across 8 → 16 across 11 files (PR #171 added 5 suppressions in console/manager); add full breakdown table; update all 3 occurrences in roadmap - Fix FooterItem PR reference: #165 → #166 (confirmed by git log --follow) - Add MountConfig → MountSpec rename caveat to §7.5 snapshot test description * docs(roadmap): iteration 26 — console/mod.rs and op_picker/render.rs analyzed - Add console/mod.rs to hot-spot table: 406L/307L production (Low); correct module map from ~200 → 406L; note missing //! doc with ConsoleStage design block comment worth promoting - Add op_picker/render.rs to hot-spot table: 865L/545L production (Medium); PR #171 AI-generated; 14 functions in two logical groups (entry/helpers vs level renderers); split into levels.rs proposed - Correct 3 stale ~200L estimates for console/mod.rs across roadmap * docs(roadmap): iteration 27 — op_picker/mod.rs discovery, render split, operator_env correction - Add op_picker/mod.rs to hot-spot table: 1712L/775L production (High); PR #171 AI-generated; OpPickerState types+behavior split opportunity; has 7-line //! doc; module map split into two rows (mod.rs + render.rs) - Add op_picker/render.rs 2-file split proposal: render.rs (coordinator) + render_pane.rs (pane/level renderers); no cross-dependency confirmed - Correct operator_env.rs total: 1569→2130L (880L production); update 4 occurrences across hot-spot table, ASCII tree, §4 analysis * docs(roadmap): iteration 28 — op_picker/mod.rs 3-file split, count corrections - Add op_picker/mod.rs formal 3-file split: loading.rs (async load family ~120L) + keys.rs (4 level key handlers ~315L) + mod.rs (types/constructors) - Correct "24 files" → "28+" for 500L threshold count - Update total LOC: ~40,664 → ~43,587 (2 occurrences, with provenance note) * docs(roadmap): iteration 29 — op_picker execution order + file_browser analysis - §10 Step 4f: expand from 5 to 7 sub-steps; add 4f-vi (op_picker/mod.rs → mod.rs + loading.rs + keys.rs) and 4f-vii (op_picker/render.rs → render.rs + pane.rs); document impl-extension and import-path caveats - §4 //! exemplars: add file_browser/ subsystem analysis — all 5 files have //! docs, no file exceeds ~350L production; classified as exemplar (not a split candidate); document git_prompt.rs coupling-density justification and input.rs as 28-file false positive (144L production) - §1 module map: expand single file_browser/ row to 5 individual rows with production LOC and dominant concern per file * docs(roadmap): iteration 30 — challenge split-first thesis, fresh LOC corrections - §4: Add "Alternative thesis: documentation-first verification" — challenges the two core assumptions behind file splitting (files-as-audit-unit and file-size-as-context-constraint); adds 7-criterion comparison table vs structure-first approach; introduces phased combined recommendation: Phase 1 = doc sprint (//! contracts + specs/ for 3 subsystems, 2-3 PRs, zero structural change); Phase 2 = splits only for >600L production files (reduces scope from 14+ to 4 files); Phase 3 = workspace if LOC > 150K - Fix stale LOC: app/mod.rs 951→979, config/editor.rs 1467→1548 (7 and 8 locations respectively; verified by fresh find|xargs wc -l scan) - §1 module map: add agent_picker.rs (436L), scope_picker.rs (201L), source_picker.rs (244L) — all PR #171 additions with //! docs * docs(roadmap): iteration 31 — fix 600L→800L threshold error, correct LOC - §4 alternative thesis: correct ">600L production → 4 files" claim introduced in iteration 30; re-verified all 9 candidate files via #[cfg(test)] line position; threshold must be >800L to get exactly 4 files (9 exceed 600L); add verification table with test-start lines - Production LOC corrections (5+ locations each): launch.rs 1085→~1077, operator_env.rs 810→~880, app/mod.rs 928→~957, config/editor.rs 503→~584 - §2 OpPicker row: replace vague "no entry yet" with confirmed gap: PROJECT_STRUCTURE.md line 53 still lists pre-PR#171 widget set (10 named); omits op_picker/, agent_picker.rs, scope_picker.rs, source_picker.rs and pre-dates the manager/ sub-structure split * docs(roadmap): iteration 32 — two-tier spec arch, behavioral spec template - §8.1: Add two-tier spec architecture table distinguishing feature specs (public Starlight MDX, user-facing) from behavioral specs (internal docs/internal/specs/, for AI code verification) — resolves contradiction between §4 (which said docs/internal/specs/) and §8.1 (which said "no longer needed; specs are public") - §8.1: Add concrete behavioral spec template for op_picker/ with state machine table and 3 INV invariant entries each with a grep-executable "Verify by:" command; template directly usable for the 3 Phase 1 specs - §8.1: Remove erroneous "docs/internal/specs/ no longer needed" claim - Confirmed render/editor.rs ~736L and render/list.rs ~668L production (no interspersed production code — all test blocks follow consecutively) * docs(roadmap): iteration 33 — executive summary, §0 correctness - §0: Add executive summary (~300 words) with core problem, 3-phase recommendation, key counter-argument, and navigation table pointing to §2/§4/§7/§8/§10 by question — resolves the meta-irony of a readability roadmap with no entry-point orientation - §0 item 2: "1569-line monolith" → "2130-line monolith" (operator_env.rs current verified size; stale reference was in the first section readers see) - §0 item 3: Add "(selective)" qualifier and explicit note that standard Rust co-locates struct+impl — impl-extension pattern is justified only for files >800L production, not as a universal rule * docs(roadmap): iteration 34 — spec priority reorder, §10 Phase 1 track - §0 + §4 Phase 1: Prioritize runtime/launch.rs behavioral spec (no //! doc, ~1077L production, critical path — all jackin load failures trace here); drop config/editor.rs from Phase 1 (its 963L test suite already serves as behavioral spec — tests are behavioral examples); reduce Phase 1 from 3 specs to 2 specs; add reasoning for the priority ordering - §10 Step 2: Split into two parallel tracks — Track A (cc-sdd tooling setup) + Track B (Phase 1 behavioral spec authoring); Track B includes specific INV invariants to capture for runtime/launch.rs grounded in reading the actual function structure (step comment positions); adds sequencing rationale: spec must precede structural splits * docs(roadmap): iteration 35 — verified INV entries for runtime/launch.rs Read load_agent_with lines 553-892 in full. Replaced 3 draft INVs from iteration 34 (inferred from step comment positions) with 5 verified INVs citing exact line numbers: - INV-1: trust gate (line 594) precedes image build (line 736) - INV-2: container name claimed (line 754) between image build and network - INV-3: token verified (line 763) before network creation (line 827) - INV-4: render_exit called at lines 886 AND 890 (all exit paths) - INV-5: cleanup disarm semantics — Running→disarm, clean exit→cleanup, crash→disarm (explains jackin hardline compatibility) Corrected wrong line number: claim_container_name call is at 754, not 918 (918 is the function definition). Each INV has a grep-executable Verify by. * docs(roadmap): iteration 36 — CI gate for PROJECT_STRUCTURE.md freshness §3: Add "Preventing future PROJECT_STRUCTURE.md staleness" subsection with three concrete options: - Option A: CONTRIBUTING.md rule (necessary but insufficient) - Option B: ci.yml git-diff-scoped shell check (recommended) — only checks files added in the current PR so it doesn't require fixing existing stale entries before merging; greps for module directory name in prose - Option C: Structured TOML module registry (over-engineered for scale) Includes concrete YAML snippet for Option B grounded in the check:repo-links.ts pattern already established in docs/scripts/ * docs(roadmap): iterations 36-37 — CI gate + greenfield workspace architecture Iteration 36: - §3: Add "Preventing future PROJECT_STRUCTURE.md staleness" subsection with 3 options (CONTRIBUTING.md rule / ci.yml git-diff check / TOML registry); recommend Option B (git-diff-scoped YAML step) with concrete snippet grounded in existing check:repo-links.ts pattern from docs/scripts/ Iteration 37 (operator directive: greenfield Rust structure): - §4: Add "Greenfield architecture — ideal structure for a growing project" section based on verified cross-module dependency graph (grep iteration 37) - Confirms dependency tiers: workspace/manifest/docker/paths/selector = Tier 0; config/tui/instance = Tier 1; operator_env/runtime/repo = Tier 2; console = Tier 3 - Key finding: workspace/ is LOWER-level than config/ (config re-exports workspace types at lines 5-6); ideal naming inverted in greenfield (jackin-core > jackin-config) - Documents ideal 6-crate workspace: jackin-core, jackin-config, jackin-tui, jackin-runtime, jackin-console, jackin-shell + thin binary - Notes console/ has NO runtime/ import — cleanest pre-existing crate boundary - Bridge: incremental splits (4a, 4d, 4g) are pre-work toward workspace migration * docs(roadmap): iteration 38 — Rust workspace standards, community evidence Ground workspace recommendation in real-world project research: - ripgrep (9 crates), gitui (5 crates) went workspace due to library consumers - starship and fd-find stay single-crate at 1M+ LOC — no library use case - jackin (43K LOC, no external consumers) maps to starship/fd pattern → single-crate is community-standard; "stay single-crate" recommendation confirmed Update greenfield workspace structure to follow matklad's pattern: - Virtual manifest at root (no [package] in root Cargo.toml) - Flat crates/ directory (not nested); crate names match folder names - version = "0.0.0" for unpublished internal crates - Add inline dep comments to each crate in the ASCII structure Add research notes: ripgrep/starship/gitui/fd-find Cargo.toml findings + Cargo workspaces reference + matklad "Large Rust Workspaces" (2021-08-22) * docs(roadmap): revise §7.9 + §3 — adopt per-directory README.md §7.9: Reverse previous "reject" recommendation to "adopt" per-directory README.md for major src/ module directories. Rationale: README.md is AI-native — Claude Code, Copilot, Cursor load it automatically on directory entry, giving AI agents orientation before they decide which file to open. PROJECT_STRUCTURE.md being confirmed stale removes the main argument for the "single root file" approach. Add three-layer documentation model table: - README.md: directory orientation (AI + human, on entry) - AGENTS.md: agent workflow rules (root, session start) - CLAUDE.md: @AGENTS.md pointer only — NEVER add content here - //! docs: file-level contracts (when reading/editing) Add specific README.md content targets for 7 directories (src/, src/runtime/, src/console/, src/console/manager/, src/console/widgets/, docs/, docs/internal/). §3 target document shape: Add per-directory README.md to proposed hierarchy; add docs/internal/specs/ explicitly; note CLAUDE.md design principle (single-line @AGENTS.md — never duplicate content). * docs(roadmap): internal docs are browsable — unified Starlight site Operator directive: internal docs (architecture, specs, ADRs, roadmap) should be browsable, not hidden filesystem files. They are a different TYPE of docs focused on implementation details and vision, published as a "Developer Reference" section of the Starlight site. §3 target document shape: - docs/internal/ moves into docs/src/content/docs/internal/ (Starlight pages) - Browsable at jackin.tailrocks.com/internal/ - Sidebar: "Developer Reference" group (collapsed by default) with sub-sections for architecture, code-tour, contributing, testing, decisions, specs, roadmap - Include astro.config.ts sidebar config snippet §8.1 two-tier spec distinction eliminated: - Feature specs and behavioral specs both live at docs/src/content/docs/internal/specs/ - Type expressed via spec_type: behavioral | feature frontmatter, not filesystem location - Both browsable and searchable via Starlight; AI agents can be pointed to URLs §8.3 + §4: - All docs/internal/specs/ paths → docs/src/content/docs/internal/specs/ - ADRs: docs/internal/decisions/ → docs/src/content/docs/internal/decisions/ (browsable) - README.md pointer for src/runtime/ updated to URL reference * docs(roadmap): §11 — modern Rust docs platform (future project) Add §11 capturing the vision for a modern docs.rs alternative with: - rustdoc JSON ingestion → Astro Starlight presentation - MCP server for AI agent queries (Context7 alternative for Rust) - Rust-specific query types: rust_get_context(), rust_find_impls(), rust_search_types() — things Context7 cannot provide - Comparison table vs Context7 - Architecture diagram (ingestion → processing → Starlight + MCP) - Name candidates: rustlight, ferrodoc, cargo-starlight / starlight.rs - Note that jackin's §7.15 gen-rust-api.ts pipeline is the intentional prototype for the platform's processing and presentation layers * docs(roadmap): iteration 39 — update §0, fix stale internal/ paths §0 executive summary: rewrite to reflect decisions from iterations 30-38: - browsable internal docs (jackin.tailrocks.com/internal/) - per-directory README.md adoption (§7.9 reversed) - CLAUDE.md = @AGENTS.md single-line pointer only - greenfield workspace architecture (matklad's virtual manifest pattern) - §11 future project: modern Rust docs platform / Context7-for-Rust - document size 1800+ → 2200+ Fix stale docs/internal/ bare paths not caught by iteration 38 sweep: - Mermaid diagram: INTERNAL_ROADMAP, INTERNAL_CODE_TOUR → Starlight paths - §7.10 ADRs: docs/internal/decisions/NNN-title.md → .mdx Starlight path - §10 Track B item 2: op-picker spec path → Starlight MDX * docs(roadmap): iteration 40 — §7.15 pipeline + Rule 4 pub audit §7.15 (new): rustdoc JSON → Astro Starlight API documentation pipeline - Three options: rustdoc HTML publish / rustdoc JSON + bun script (recommended) / rustdoc-json crate as Rust binary - Option B recommended: matches existing docs/scripts/ pattern, nightly isolated to separate CI step, zero effect on stable build - Key design: URL at /internal/api/, cross-links to behavioral specs, Starlight unified search, prototype for §11 future project - Pub(crate) note: gen-rust-api.ts can feed Rule 4 visibility audit - Recommend: adopt after Phase 1 //! sprint (value ∝ coverage) §4 Rule 4 pub discipline: replace estimated "50-100 items" guess with verified numbers from iteration 40 grep: - 257 bare pub items, 21 pub(crate), 61 pub(super) across 94 files - 0 uses of unreachable_pub lint — no enforcement gate - Top violators: operator_env.rs (17), tui/output.rs (13), planner.rs (8) - Add concrete Cargo.toml [lints.rust] snippet: unreachable_pub = "warn" - Revised scope: ~150-200 mechanical conversions (excludes entry points) * docs(roadmap): split research into 19 actionable items Delete _research_notes.md (no longer needed). Replace 2343L READABILITY_AND_MODERNIZATION.md with: - README.md: index of all 19 items with phase, ordering notes, links - READABILITY_AND_MODERNIZATION.md: lightweight research summary (63L) - items/ITEM-001 through ITEM-019: individual actionable items Items by phase: Phase 1 (low risk, no confirmation): ITEM-001..004, 006..011 Phase 1 (needs confirmation): ITEM-005, 016, 018 Phase 2 (structural splits, confirmation required): ITEM-012..015 Phase 3 (deferred): ITEM-017 (rustdoc pipeline), ITEM-019 (workspace) Each item has: summary, key files with line numbers, steps, what needs confirmation, and relevant research backing from the 40-iteration analysis loop. * docs(roadmap): migrate 19 items to Starlight reference/roadmap section Move all codebase health roadmap items from docs/internal/roadmap/items/ (plain Markdown, not browsable) to docs/src/content/docs/reference/roadmap/ (MDX pages, browsable at jackin.tailrocks.com/reference/roadmap/). Adds a new "Codebase health" sidebar group (Phase 1 → Phase 3) to astro.config.ts. Deletes the old items/ directory. Updates the internal README to redirect to the new location. Also adds codebase-readability.mdx — a new overview item that captures the overall readability/restructuring program with a recommended execution order: file splits first, then greenfield workspace, then per-directory README+AGENTS.md, then docs and specs. * docs(roadmap): remove premature internal/roadmap/README.md The internal/ structure doesn't exist yet — it will be created as part of the roadmap items themselves. No need for a redirect stub now. * docs(roadmap): remove READABILITY_AND_MODERNIZATION.md research archive All content has been distilled into the individual Starlight roadmap pages. The full 2343L research is preserved in git history at commit b7e9fc2. * docs(roadmap): fix check:repo-links errors + remove iteration log - Replace plain code spans with <RepoFile> for validate.rs, mise.toml, Cargo.toml, and op_picker/mod.rs - Remove deleted READABILITY_AND_MODERNIZATION.md reference from codebase-readability.mdx - Delete _iteration_log.md (git history is the archive) * docs(roadmap): fix lychee false-positive link in move-contributing-testing The example redirect text contained a markdown hyperlink to a proposed future file path that doesn't exist yet. Changed to a code span. --------- Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Co-authored-by: Claude <noreply@anthropic.com>
donbeave
added a commit
that referenced
this pull request
May 7, 2026
* docs(spec): per-mount isolation V1 implementation spec
Captures roadmap → executable design: module layout under
src/isolation/, MountIsolation enum + MountConfig.isolation field,
materialization runtime hook, foreground finalizer with
safe/preserved/force cleanup, source-drift detection, jackin cd
command, TUI integration. Test list and docs touchpoints enumerated
for the implementation plan.
* feat(isolation): introduce MountIsolation enum
* feat(workspace): add isolation field to MountConfig
Defaults to Shared; serde skips emitting the field when Shared so
existing TOMLs round-trip unchanged.
* feat(workspace): reject nested isolated mounts
Two worktree-isolated mounts whose dsts nest have no safe on-disk
layout. Sibling isolated mounts and isolated-parent-with-shared-child
remain allowed.
* feat(config): reject isolation field on global mounts
Adds a strict GlobalMountConfig wire-format struct that mirrors
MountConfig minus the isolation field, with deny_unknown_fields
so operators get a clear parse error if they try to set isolation
on a global mount. Isolation remains a workspace-mount concept.
* refactor(config): drop dead code and tighten global-mount API
- Delete unused From<MountConfig> for GlobalMountConfig (silently
dropped isolation; no callers).
- Delete unused get_mut and remove on DockerMounts along with their
#[allow(dead_code)] annotations.
- Tighten AppConfig::add_mount: debug_assert that incoming
MountConfig has Shared isolation, and construct GlobalMountConfig
explicitly from src/dst/readonly rather than via the (now-deleted)
From impl. Keeps the public signature stable so callers in CLI,
resolve.rs, and preview.rs don't need to change (Issue 2 option B).
- Add wire-path rejection test that goes through MountEntry's
untagged enum and asserts on the actual serde error
("data did not match any variant of untagged enum MountEntry").
- Soften GlobalMountConfig's doc comment to reflect the actual
serde error shape at the wire path.
* feat(isolation): IsolationRecord + isolation.json IO
Atomic write via tmp+rename. Version-1 envelope leaves room for schema
evolution. Read/upsert/remove keyed by mount destination.
* test(isolation): drop redundant clones in state tests
Bring the warning baseline back to 87 after Task 2.1 introduced
two clippy::style hits (cloned_ref_to_slice_refs, redundant_clone).
* feat(isolation): list_records_for_workspace walks data dir
Used by workspace-edit drift detection to find which containers have
preserved isolated state for a given workspace.
* feat(isolation): branch_name renderer with namespace + suffix support
Suffix is appended to the final selector segment so namespaced agents
keep their selector shape and the disambiguator goes on the leaf name.
* feat(isolation): MaterializedWorkspace types
Third workspace shape (Config -> Resolved -> Materialized) used as the
runtime handoff into Docker launch.
* feat(isolation): worktree_path_for derives on-disk path from mount dst
Uses dst verbatim (leading/trailing slashes stripped) under
isolated/, so the layout mirrors the container path.
* feat(isolation): ensure_worktree_config_enabled
One-shot enabler for extensions.worktreeConfig on the host repo.
Bumps core.repositoryformatversion to 1 when needed.
* feat(isolation): preflight checks for worktree materialization
Sensitive-mount, readonly, repo-root, and mid-operation guards.
Errors cite the mount destination and the worktree mode.
* feat(isolation): dirty-host preflight gate with --force opt-out
Non-interactive load without --force rejects a dirty host tree.
Interactive contexts are expected to obtain ack upstream.
* feat(isolation): materialize_workspace orchestrator
Per-mount worktree materialization with idempotent reuse, source-drift
guard, and branch-name disambiguation when multiple isolated mounts
target the same host repo.
* test(isolation): cover branch disambiguation for same-repo mounts
* feat(isolation): order Docker mounts parent-before-child
Length-ascending sort so shared cache children overlay isolated
worktree parents at container start.
* feat(runtime): hook materialize_workspace between AgentState and Docker
Workspace mounts now flow Config -> Resolved -> Materialized before
reaching the docker run command, with parent-before-child ordering.
* feat(isolation): force_cleanup_isolated removes worktree + branch + record
Best-effort git invocations that tolerate missing host repo and
already-removed worktree. Used by purge and the finalizer's force-delete
branch.
* feat(isolation): finalizer skeleton + AttachOutcome shape
Decides Preserved when container still running, OOMed, or exited
non-zero. Clean-exit path stubbed - implemented in follow-ups.
* feat(isolation): safe-cleanup deletes branches with no commits
When the worktree is clean and HEAD equals the recorded base, the
scratch branch is removed automatically.
* feat(isolation): consult upstream when deciding safe cleanup
Pushed commits (reachable from upstream) are safe to delete; local-only
commits or no-upstream divergence preserve the worktree.
* test(isolation): cover interactive unsafe-cleanup prompt branches
* feat(runtime): finalize foreground session after attach in load + hardline
Both load and hardline now consult inspect_attach_outcome and dispatch
the shared finalizer. Return-to-agent retries safe cleanup once after
the operator returns.
* fix(purge): refuse to run on a live container
Closes a pre-existing gap where purge could delete state out from
under a running agent. Operator must eject first.
* feat(purge): remove isolated worktrees and scratch branches
Reads isolation.json and runs force_cleanup_isolated for each record
before deleting the per-container state directory.
* feat(cli): --mount-isolation DST=TYPE on workspace create/edit
Repeatable. Rejects clone before persistence with the canonical
"reserved but not implemented yet" message.
* feat(workspace): add Isolation column to workspace show
Renders canonical lowercase name for every mount so CLI output matches
TOML/CLI input verbatim.
* feat(load): add --force to acknowledge dirty host tree
Required for non-interactive isolated-mount materialization when the
host working tree is dirty.
* feat(workspace): detect source drift on edit affecting isolated mounts
Edits that change src for a mount with preserved isolated state are
rejected unless --delete-isolated-state is passed and no related
container is running.
* feat(cli): jackin cd opens a child shell in an isolated worktree
Single-mount-no-dst → uses it. Dst-provided → exact match. Multi-mount
no-dst → interactive picker on TTY, error on non-TTY. Sets JACKIN_*
env vars. Does not modify the parent shell.
* feat(console): show isolation badge per mount in editor + preview
Adds an Iso column to the workspace-manager mount table (editor and
list-pane sub-panel) and a `[shared|worktree|clone]` tag to the agent
preview's resolved-mounts lines. Per the per-mount-isolation spec the
badge renders the canonical spelling for every mount, including
`shared`, so operators always see which strategy applies.
* feat(console): I hotkey cycles isolation on the selected mount
Mirrors the existing R (readonly) toggle. Cycles Shared -> Worktree
-> Shared; Clone is reserved-but-rejected in V1 and is not entered
through this hotkey, but a saved Clone mount snaps back to Shared on
the first I press rather than getting stuck. The cycling rule lives in
EditorState::cycle_isolation_for_selected_mount so the input dispatch
arm stays trivial.
Also surfaces the new key in the Mounts-tab footer hint alongside the
existing R toggle so the affordance is discoverable.
* feat(console): source-drift confirm modal in workspace editor
Save flow runs the same drift detection as `jackin workspace edit`:
detect_workspace_edit_drift evaluates the prospective mount list
(post-collapse, post-upsert) against IsolationRecords on disk before
the on-disk write.
Running container drift -> ErrorPopup ("eject first"); save aborted.
Stopped container drift -> Confirm modal listing the affected
container names with a Yes/No prompt. On Yes the modal handler
re-stashes the plan with delete_isolated_acknowledged = true and the
second commit pass calls force_cleanup_isolated for each affected
record before writing.
Reduced scope vs the original three-button "Delete preserved state and
save / Cancel / Open mount details" dialog: the modal is the existing
two-button Confirm widget (Yes/No). The third "open mount details"
affordance is omitted — operators dismiss with N/Esc, find the
offending mount in the editor, and revert the src by hand. Adding it
would require either a custom widget or repurposing an existing
multi-choice one and threading mount-row focus through the modal
plumbing; the safety value is in the block-and-ack semantics, which
the two-button form covers.
Adds a commit_editor_save_with_runner test seam so the FakeRunner can
drive the drift branch without a real Docker daemon.
* docs(workspaces): add per-mount isolation section
Document the per-mount isolation feature in the workspaces guide:
the three modes (shared default, worktree, clone reserved-but-rejected),
validation preconditions, the isolated-source + shared-cache child
pattern with TOML, and pointers to --mount-isolation and jackin cd.
* docs(mounts): document mount isolation field
Add an "Mount isolation" section to the mounts guide covering the
shared/worktree values, the global-mount rejection at parse time,
and the isolated-source + shared-cache child composition pattern.
* docs(configuration): add MountConfig.isolation field
Document the new mounts[].isolation field in the configuration
reference: shared default, worktree opt-in, clone reserved-but-rejected,
and the global-mount parse-time rejection.
* docs(architecture): document materialization flow + isolation.json
Add a "Workspace materialization" section to the architecture reference
covering the WorkspaceConfig -> ResolvedWorkspace -> MaterializedWorkspace
shapes, the per-container isolation.json layout, and the post-attach
foreground finalizer's Preserved/Cleaned/ReturnToAgent decision matrix.
* docs(workspace): document --mount-isolation and Isolation column
Add --mount-isolation to workspace create/edit option tables (with the
clone "planned but not implemented" note), document the new
--delete-isolated-state flag for non-interactive source-drift edits,
and note the Isolation column on workspace show.
* docs(load): document --force dirty-host acknowledgement
Add --force to the option table and a dedicated section explaining
when it's required (non-interactive load with a worktree-isolated
mount + dirty host tree) and what it does NOT do (no stash, no
discard, no relaxation of other validation).
* docs(purge): document running-agent guard and isolated cleanup
Document purge's new behavior: refuses to run on a running container
(eject first), force-removes isolated worktrees + scratch branches
recorded in isolation.json, and tolerates a missing host repo on
best-effort cleanup.
* docs(cd): add jackin cd command reference
Create a reference page for jackin cd <container> [dst] covering
arguments, the mount-selection behavior matrix (zero/one/many isolated
records, with and without dst), the JACKIN_* env vars set in the
child shell, exit-code passthrough, and the no-parent-mutation
guarantee. Wire it into the Commands sidebar between console and
launch.
* docs(roadmap): mark per-mount isolation V1 implemented
Flip the per-mount-isolation roadmap status to "Implemented in V1"
and replace the duplicate-mounts-allowed line with the actual V1
rule: multiple isolated mounts are allowed (with branch-name
disambiguation), but nested isolated dst paths are rejected at
validation because the inner worktree's .git would land inside the
outer worktree's tree.
* docs(structure): add isolation module tree and cd command
Update PROJECT_STRUCTURE.md to document the new per-mount-isolation
work: isolation/ module row (mod/branch/materialize/state/finalize/
cleanup), cli/cd.rs entry on the cli/ row, --mount-isolation /
--delete-isolated-state / --force notes on the relevant CLI rows,
foreground-finalizer mention on the runtime row, the new
commands/cd.mdx in the docs map, and a code->docs cross-reference
row mapping src/isolation/** to all the doc pages it touches.
* test(isolation): end-to-end materialize -> clean-exit -> cleanup
Exercises the full lifecycle through public APIs with a small inline
scripted runner. No real git or docker.
* docs(isolation): note finalizer is local-only for hardline lockdown
* style(test): apply rustfmt to per-mount isolation e2e
* ci(docs): verify docs links in PRs and on the deployed site (#173)
* ci(docs): add link checking
* ci(docs): stabilize lychee checks
* ci(docs): validate edit links with lychee
* ci(docs): close link check gaps
* docs: add repo file link component
* docs: explain repo link source check
* ci(docs): allow manual dispatch of deployed link check
Two refinements from PR review:
- The check-deployed job now triggers on workflow_dispatch in addition
to schedule, so maintainers can manually verify the live deployed
docs without waiting for the daily cron or pushing to main. This
closes the gap against goal "manual workflow to verify both built
site and deployed documentation".
- Drop github.sha from the deploy job's lychee cache primary key so it
matches across runs (the SHA-keyed primary was guaranteed to miss,
forcing fallback to restore-keys). Now mirrors the cache key shape
used by check-deployed.
* ci(docs): harden link-check workflow and broaden source lint
Address review feedback on PR #173.
Workflow (.github/workflows/docs.yml):
- Split concurrency group by event_name so a scheduled or
workflow_dispatch run cannot cancel an in-flight push deploy. Cancel
in-progress is now scoped to pull_request only.
- Exclude main from workflow_dispatch on check-deployed. The deploy job
already verifies the just-deployed site, so running both in parallel
would race against the publish window. Manual verification of the live
site from main flows through deploy; from feature branches it flows
through check-deployed.
- Add the build cache as a fallback restore-key for check-deployed so
the daily cron and manual runs warm-start from the last build cache
when the lychee.toml fingerprint changes.
RepoFile component (docs/src/components/RepoFile.astro):
- Add target=_blank and rel=noopener noreferrer so GitHub source links
open in a new tab, matching the behavior rehype-external-links applies
to plain markdown external links in MDX.
Source lint (docs/scripts/check-repo-links.ts):
- Normalize leading slashes when checking RepoFile path existence so the
validator agrees with the component's own normalization.
- Cover top-level repo-specific files (Cargo.toml, Cargo.lock, Justfile,
build.rs, docker-bake.hcl, mise.toml, release.toml, renovate.json) so
a rename of any of those also breaks the docs CI gate, not only paths
under src/, docs/, docker/, .github/.
Content (docs/src/content/docs/developing/construct-image.mdx):
- Convert the remaining inline-code references to docker-bake.hcl and
Justfile to <RepoFile />. These were the references that motivated
extending the lint to top-level files.
* docs(roadmap): apply RepoFile lint to multi-runtime proposal
After merging main into codex/docs-link-checks, check-repo-links flagged
37 plain inline-code references to existing repo files in the new
multi-runtime-support.mdx (added in #174 before this PR's lint existed
on main). Convert each one to <RepoFile /> so renames or deletions of
those source files break the docs gate before merge, the same way the
rest of the roadmap is now protected.
No prose changes — every conversion is one-for-one (`src/foo.rs` →
<RepoFile path="src/foo.rs" />). The trailing-slash directory reference
to `docker/runtime/` is left as a code span, since the lint correctly
skips it (it's a directory, not a file).
* fix(roadmap): repair broken Amp CLI link in multi-runtime proposal
CI failed on this PR's last build because lychee found a 404 on
https://github.com/sourcegraph/amp in multi-runtime-support.mdx (added
in #174). That repo does not exist publicly — Amp's source is not on
GitHub.
Point the link at https://ampcode.com instead, which is already the
canonical Amp URL used elsewhere in the docs (getting-started/why.mdx).
---------
(cherry picked from commit f3f3e5e)
* docs(roadmap): wrap src/cli/cd.rs in <RepoFile> on per-mount-isolation page
The check-repo-links script (added in #173) flags any inline-code
reference to a real repo file that isn't wrapped in <RepoFile />.
src/cli/cd.rs was created on this branch, so once #173's lint reaches
this branch via the previous cherry-pick, the bare reference fails
the Docs CI check.
* docs: switch cross-doc links to absolute URL form
The link-check job's lychee step (added on main in #173, hardened in
#176) verifies built-site links against the on-disk dist tree. Relative
`.mdx`-suffixed links break that check because lychee resolves them as
literal file paths under the rendered URL's directory — e.g.
`./workspaces.mdx` rendered from `/guides/mounts/` resolves to
`/guides/mounts/workspaces.mdx`, not `/guides/workspaces/`.
Switch the four cross-doc links added by the per-mount-isolation work
to the rendered-URL form (`/guides/workspaces/#per-mount-isolation`
etc.) — same convention as the existing `[mount collapse](/commands/workspace/#mount-collapse)`
link in the same file.
* feat(isolation): emit verbose debug-mode trace for worktree lifecycle
Operators sharing logs to debug worktree behavior had no visibility into
the lifecycle — only three error/warning sites fired output, and none of
them ran on the happy path. `--debug` toggled a display mode (preserve
scrollback, clear spinner) but was not a verbose-trace facility.
This adds a `debug_log!(category, fmt, ...)` macro in `src/tui/mod.rs`
that gates on the existing `DEBUG_MODE` atomic so disabled call sites
cost only an atomic load (formatting is deferred behind the gate).
Output uses a `[jackin debug <category>]` prefix so shared logs are
greppable.
Instrumented sites (all under `category = "isolation"`):
- `materialize_workspace`: per-call summary (workspace, container,
selector, mount counts, force/interactive flags).
- `materialize_one`: per-mount decision trail — drift detection,
worktree reuse, preflight, base-commit lookup, branch derivation
with selected suffix, and the `git worktree add` invocation itself.
- `ensure_worktree_config_enabled`: every state transition (already
enabled vs. bumping repositoryformatversion vs. flipping the flag)
with the host repo path.
- `state.rs`: write_records (count + path), upsert_record (insert vs.
replace), remove_record (drop vs. no-op).
- `cleanup.rs`: force_cleanup_isolated entry, the two git invocations,
the rm -rf fallback, and the host-repo-missing skip path.
purge_isolated_for_container per-container summary.
- `finalize.rs`: foreground-session entry with exit code/oom/interactive
flags, the early-return path for non-clean exits, per-record cleanup
assessment.
- `runtime/launch.rs`: load_agent's call into materialize_workspace.
Read paths (`read_records`, `read_record`) are intentionally NOT logged
— they fire on every invocation and would drown the log.
Manual verification (since binary-level stderr capture would need a new
test dependency):
cargo run --release --bin jackin -- --debug load <agent>
cargo run --release --bin jackin -- --debug workspace edit <ws> \
--mount-isolation /workspace/proj=worktree
cargo run --release --bin jackin -- --debug purge <container>
Each emits a chronologically ordered `[jackin debug isolation] ...`
trace covering every git invocation, isolation.json mutation, and
finalize decision — suitable for sharing in bug reports.
* fix(tui): rename Iso column to Isolation, count isolation flips as one change
Two related TUI papercuts surfaced together when an operator flipped a
mount's isolation from `shared` to `worktree` via the `I` hotkey:
1. The mount-table header read `Iso` — opaque on first sight. Replaced
with `Isolation` (the full word). Bumped the column-width constant
from 8 → 9 so the header label fits without disturbing data-row
alignment, and renamed `MOUNT_ISO_COL_WIDTH` →
`MOUNT_ISOLATION_COL_WIDTH` for consistency. Updated the
alignment-regression test that asserted on the old label.
2. Cycling isolation on an existing mount (same `dst`, same `src`)
reported "2 changes" in the save-row footer and rendered the
Confirm Save dialog with a `+`/`-` pair for the same path. Both
sites used `MountConfig::contains()` — full-struct equality — so
any isolation/readonly drift made the row appear as remove + add.
Extracted a `MountDiff` classifier in `console/manager/state.rs`
that keys on `dst` (the identity used by upsert/remove everywhere
else). Same-`dst` matches with structural drift are now reported
as a single `Modified`, counted as one change in `change_count`
and rendered as a `~ <new>` line with a dimmed `was: <old>` follow-up
in the Confirm Save summary so the operator sees exactly what
changed without parsing a remove + add pair.
Extended `mount_summary` to include the isolation tag so the
delta is visible in both the new and old lines:
`~/foo (rw, worktree, github · main)`.
Records a new shared rule in `RULES.md` ("TUI Labels") to prevent
future short-form labels in user-facing TUI surfaces — operators
read the TUI in passing and cannot afford to decode `Iso`/`Cfg`/`Env`/
`WD`-style abbreviations. Lists the established short forms that are
NOT considered abbreviations (`dst`, `src`, `git`, `op`).
* fix(isolation): make worktree mode actually work inside the container
V1's worktree mode shipped with a gap: the materialized worktree was
bind-mounted into the container at <dst>, but the worktree's `.git`
text file (a pointer back to <host_repo>/.git/worktrees/<n>/) referenced
an absolute host path that didn't exist inside the container. Every git
command — `git status`, `git log`, `git commit`, `git push` — failed
with "fatal: not a git repository". The agent could read source files
but could not commit work, defeating worktree mode's whole purpose.
Fix: wire up three additional bind mounts at docker-run time, plus two
jackin-owned override files written at materialization, so git's gitdir
relationship resolves consistently inside the container without
modifying any host-side files.
For each isolated worktree, the container now sees:
1. The worktree at <dst> (existing).
2. The host repo's `.git/` at /jackin-isolation/<container>-git/ rw,
so git can find objects, refs, and the per-worktree admin dir.
3. A jackin-owned `.git` text file at <dst>/.git overriding the
worktree's host-side pointer with one targeting the container path.
4. A jackin-owned back-pointer at /jackin-isolation/<container>-git/
worktrees/<n>/gitdir overriding git's verification check (host's
absolute path doesn't match <dst> inside the container).
Override files live under <data_dir>/jackin-<container>/.git-overrides/
and are written once at materialize time. Host files (worktree's `.git`
and the admin dir's `gitdir`) are NEVER modified — host-side
`git worktree list` continues to work identically.
Three layouts were considered (Docker Sandboxes-style `.jackin/` in the
host repo, indirect mount with override files, jackin-owned bare repo).
The chosen approach preserves jackin's dst-based mount model (operator
configures dst=/workspace/jackin → agent works at that exact path), keeps
the host repo clean (no `.jackin/` directory), and exposes only the
worktree to the agent (not the entire host main tree). Full design
rationale and the comparison with Docker Sandboxes, Conductor, and
clone mode (planned for V1.1) are in
docs/src/content/docs/reference/roadmap/per-mount-isolation.mdx
under "Design Decision: Worktree Materialization Layout" and
"Comparison with Other Tools".
Trust trade-off: the agent has rw access to the host repo's `.git/`
since refs/objects are inherently shared in git worktrees. Worktree
mode is appropriate for trusted agents on personal projects where
immediate ref visibility on the host is valuable. Operators who want
ref isolation should use clone mode (planned).
Tests:
- write_git_overrides_writes_both_files_with_correct_content asserts
override file content matches the design doc verbatim.
- write_git_overrides_is_idempotent confirms re-running on a reused
worktree (load → eject → load) doesn't drift.
- override_id_strips_slashes_and_trims pins the file-naming scheme.
- container_git_dir_path_namespaces_by_container_name pins the
hardcoded container-side path so two parallel agents don't collide.
- Extended per_mount_isolation_e2e to assert MaterializedMount carries
WorktreeAuxMounts on the worktree path and that override files land
on disk at the documented locations.
Manual verification recipe (add after running once):
cargo run --release --bin jackin -- --debug load <agent>
docker exec -ti <container> git status # was failing, now works
docker exec -ti <container> git log # works
docker exec -ti <container> git commit -m test --allow-empty
git -C <host-repo> branch -a # shows new branch
* refactor(isolation): /jackin/{host,admin}/<dst> mounts, container-name basename, :ro hardening
Three related changes that finalize the worktree-mode mount layout:
1. Container-side path scheme renamed and reorganized.
/jackin-isolation/<container>-git/... → /jackin/host/<dst-stripped>/.git
/jackin/admin/<dst-stripped>/{commondir,gitdir}
- Single top-level /jackin/ namespace for everything jackin contributes
to the agent's filesystem (room to grow with /jackin/cache/, etc.)
- host/ category mirrors host topology so docker inspect shows symmetric
Source/Destination paths both ending in `.git`
- admin/ category lives at a separate top level so the override files
(which sit on top of files inside the admin dir) do NOT visually nest
inside /jackin/host/.../.git/. Two top-level concerns, no overlap.
2. Host-side storage groups all git artifacts for one mount under
<state>/git/<dst-stripped>/, with override-file names matching their
docker mount destinations:
<state>/git/<dst-stripped>/
├── <container>/ (the worktree; basename = container name)
└── overrides/
├── .git
├── commondir
└── gitdir
Replaces the prior <state>/.git-overrides/ flat layout with underscored
slug filenames. New layout uses dst as a real directory tree (no slug)
and source filenames identical to destination filenames — the source/
destination relationship is obvious in `docker inspect`.
3. Worktree subdir basename = container name. `git worktree add` derives
the host-side admin entry name from the worktree path's basename (no
--name flag exists upstream). Using the container name (which jackin
guarantees is globally unique) makes admin entries in
<host_repo>/.git/worktrees/ globally unique per (host_repo, container)
— `git worktree list` on the host immediately shows which container
owns each worktree.
This required a new validation rule:
`workspace::validate_isolation_layout` now rejects two isolated mounts
that resolve to the same host repository within one workspace.
Allowing them would force the same container-name basename twice in
one host repo's .git/worktrees/ namespace; no real operator workflow
has surfaced for this case. Revisit if one does.
Removes the now-dead suffix logic from materialize.rs:
- `count_isolated_per_repo` (helper)
- `canonicalize_or_clone` (helper)
- `dst_to_branch_suffix` (in src/isolation/branch.rs — no callers left)
The `branch_name` function keeps its optional suffix parameter for
future clone-mode use; V1 worktree always passes None.
4. The three override files (replacement `.git` pointer, `commondir`,
`gitdir` back-pointer) are mounted `:ro` as defensive hardening. Git
only reads them during normal agent work, and a misbehaving agent
could otherwise rewrite the gitdir pointer to redirect operations at
a different repo entirely. The host `.git/` and admin mounts stay rw
because git writes refs/objects/HEAD/index/logs there.
Tests:
- workspace::tests::isolation_layout_rejects_two_worktree_mounts_on_same_repo
- workspace::tests::isolation_layout_allows_different_host_repos_in_one_workspace
- materialize::tests::worktree_path_uses_container_name_as_basename
- materialize::tests::container_host_git_path_mirrors_dst_under_jackin_host
- materialize::tests::container_admin_path_lives_under_jackin_admin
- materialize::tests::host_and_admin_paths_disambiguate_per_mount_in_one_container
- materialize::tests::write_git_overrides_writes_three_files_with_correct_content
- materialize::tests::write_git_overrides_is_idempotent
- launch::tests::build_workspace_mount_strings_marks_overrides_readonly
(asserts all 6 mounts in correct order with correct :ro placement)
- per_mount_isolation_e2e: updated for new path scheme + admin name
Removed:
- materialize::tests::two_isolated_mounts_same_repo_get_dst_suffixed_branches
(case is now rejected at the workspace-validation level)
Roadmap MDX (per-mount-isolation.mdx):
- Container-side mount layout section: 4 mounts → 6, new path scheme,
override-file storage layout
- Composition Rules: documents the new same-host-repo rejection
- Comparison table: bind mount count for jackin worktree updated 4 → 6
- V1 Scope: ship list updated with new layout and the new validation rule
Manual verification (after merge):
cargo run --release --bin jackin -- --debug load <agent> <workspace>
docker inspect <container> | jq '.[0].Mounts' # see /jackin/{host,admin}/...
docker exec -w <dst> <container> git status # works
git -C <host_repo> worktree list # admin name = <container>
* refactor(isolation): single /jackin/host/ root, no commondir override, Model B branch naming
Final V1 design after extended brainstorming with the operator. Drops
the `/jackin/admin/<dst>` namespace and the `commondir` override file:
the per-worktree admin entry now lives natively at
`worktrees/<container>/` inside the host `.git/` mount, so git's
on-disk default `commondir = ../..` resolves correctly without an
override.
Container-side topology, per isolated mount (4 binds total, down from 6):
- `<dst>` (rw) — the materialized worktree
- `/jackin/host/<dst-tree>/.git` (rw) — host repo's `.git/`
- `<dst>/.git` (`:ro`) — replacement gitdir pointer
- `/jackin/host/<dst-tree>/.git/worktrees/<container>/gitdir` (`:ro`)
— replacement back-pointer
Host-side layout under each per-container state dir:
- `git/worktree/repo/<dst-tree>/<container>/` — git's territory
- `git/overrides/<dst-tree>/{.git,gitdir}` — jackin-owned overrides
Branch naming follows Model B: `jackin/scratch/<container_name>`
verbatim. Admin entry name = container name (deterministic, globally
unique because container names are workspace-unique and
`validate_isolation_layout` rejects two isolated mounts on the same
host repo within one workspace — no auto-suffix or read-back needed).
Roadmap doc updated to reflect the final design.
* style(isolation): rustfmt assert_eq! width
* docs(isolation): align stale references with shipped V1 design
Sweep stale references across roadmap, guides, command and architecture
docs into alignment with what the code actually does. No content added —
the doc just told two contradictory stories before (Model B branch
naming alongside the old selector-key derivation; the new
git/worktree/repo/<dst>/<container>/ on-disk layout alongside the
proposed-but-never-implemented isolated/<slug>/ layout). Now there is
one consistent story end-to-end.
Touches:
- docs/src/content/docs/reference/roadmap/per-mount-isolation.mdx
- docs/src/content/docs/guides/workspaces.mdx
- docs/src/content/docs/commands/purge.mdx
- docs/src/content/docs/reference/architecture.mdx
* chore(cli): remove jackin cd command from V1
Operationally redundant with `git worktree list` + native shell `cd`.
For worktree mode, the host's `git worktree list` already enumerates
every isolated worktree by branch and absolute path, so a plain
`cd $(...)` reaches the same destination. The remaining edge cases
(preserved-dirty inspection, multi-mount picker) are rare enough that a
dedicated subcommand is net cost rather than net benefit.
Removes:
- src/cli/cd.rs (CdArgs + select_record + tests)
- Command::Cd enum variant in src/cli/mod.rs
- handle_cd dispatch in src/app/mod.rs
- docs/src/content/docs/commands/cd.mdx and its sidebar entry
Updates:
- src/isolation/finalize.rs preserved-state warning: drop "jackin cd ..."
hint, point operator to the printed worktree path instead
- src/isolation/materialize.rs source-drift error: same treatment
- guides/workspaces.mdx + reference/architecture.mdx: drop cd references
- roadmap entry: replace "Convenience navigation" paragraph with a
removed-from-V1 note explaining the rationale; add cd to the Defer
list so it's recoverable if a real workflow surfaces
isolation.json schema, the preserved-state machinery, and `hardline` /
`purge` flows are unaffected — only the inspection convenience layer
is gone.
* chore(isolation): drop clone from V1 enum/parser/CLI
Per principle: don't pre-add API for unimplemented features. The `clone`
keyword was previously parsed by TOML/CLI and then rejected at validation
with a "planned but not implemented yet" error. Operators got false
positives in linting tools and confusing late failures with no benefit —
nothing in V1 actually does anything useful with the value.
Removes from the runtime:
- MountIsolation::Clone enum variant (src/isolation/mod.rs)
- explicit Clone-rejection in parse_mount_isolation (src/cli/workspace.rs):
FromStr now produces "invalid isolation `clone`" naturally
- MountIsolation::Clone match arm in materialize_workspace
(src/isolation/materialize.rs)
- console comments referencing the reserved-but-rejected wording
Tests:
- New `rejects_clone_until_implemented` test on FromStr asserts the
standard "invalid isolation `clone`; expected one of: shared, worktree"
error so this stays locked down
- parse_mount_isolation_rejects_clone updated to assert the new error
shape
Docs:
- guides/workspaces.mdx, commands/workspace.mdx,
reference/configuration.mdx, reference/roadmap.mdx: drop the
"reserved keyword" wording, point at the V1.1 roadmap entry instead
- roadmap/per-mount-isolation.mdx: keep the `clone` design discussion,
rephrase the V1 vocabulary section to make it explicit that the keyword
is added back when clone mode ships, not pre-shipped now
apply_isolation_overrides already enforces "--mount-isolation must
reference an existing mount destination" (planner.rs); no change needed
for that requirement, just clarified in the roadmap CLI-behavior bullet.
* fix(workspace): always write mount isolation field explicitly on save
Old configs without the `isolation` field still deserialize to Shared
(the enum default). On save, drop the `skip_serializing_if = is_shared`
guard so every mount writes its isolation level explicitly — including
`shared`. Old TOMLs migrate to the new shape on first save instead of
silently retaining their pre-isolation form.
Rationale: when the operator opens the saved config, every mount should
name its isolation level. No "field is missing therefore implicitly
shared" — the value is always present and the source of truth in the
file matches the source of truth at runtime.
Touches:
- src/workspace/mod.rs MountConfig.isolation: drop skip_serializing_if
- mount_config_omits_isolation_field_when_shared_on_serialize → renamed
to mount_config_writes_isolation_field_even_when_shared_on_serialize,
asserts the field IS written
- guides/mounts.mdx + reference/configuration.mdx: update wording
* docs: remove stale per-mount-isolation design spec
The brainstorming spec at docs/superpowers/specs/ predated the V1 design
iterations and no longer reflects what shipped (it still describes the
old `/jackin-isolation/` mount layout, the selector-key-based branch
naming, the reserved `clone` enum value, and `jackin cd`). The roadmap
entry at docs/src/content/docs/reference/roadmap/per-mount-isolation.mdx
is now the single source of truth.
* docs(roadmap): improve per-mount isolation entry — accurate Sandboxes
comparison, V1 overview, concrete clone-mode personas
Four targeted improvements to the per-mount-isolation roadmap doc:
1. Add a "How V1 worktree mode works (TL;DR)" overview at the top of
Host-Side Materialization. 5-step walkthrough of the materialize +
mount + commit flow before the deep-dive subsections, so readers
land on the entry can understand the shipped V1 in 90 seconds
without scrolling through the layout/lifecycle/etc.
2. Rewrite the Docker Sandboxes comparison for accuracy. The old
description got it wrong on multiple points:
- Sandboxes use a microVM with hypervisor isolation, not Docker
containers (Linux namespaces) like jackin.
- The host filesystem is exposed via filesystem passthrough at the
SAME absolute path as the host, not a bind mount of the entire
repo at "the same relative paths".
- Worktree path is `<host_repo>/.sbx/<sandbox-name>-worktrees/<branch>/`
(sandbox name is in the path), not `<host_repo>/.sbx/<branch>/`.
- Sandboxes do NOT expose the host main working tree to the agent
— only the worktree subdir and the parent `.git/`. Our table
previously said "✓ (entire host repo mounted)".
The architectural insight is now explicit: Sandboxes' absolute-path
equivalence makes git's on-disk absolute pointers resolve natively,
which is why they don't need override files. We pay that cost
because Docker containers translate host paths to operator-chosen
`dst` values, breaking absolute-path equivalence.
3. Concrete clone-mode operator personas. The previous description
was abstract ("complementary mode for ref isolation"). Replaced
with four named situations clone mode targets: untrusted/experimental
agents, parallel-fan-out scratch-branch noise, editor watcher
churn, and teams whose workflow is push-to-share anyway.
4. New "Sandbox runtime" and "Host file exposure" rows in the
comparison table to make the underlying architectural choice
immediately visible. Cross-referenced from the Sandboxes prose.
Net: ~80 lines changed/added, primarily replacement of the wrong
Docker Sandboxes facts and addition of the V1-overview block.
* fix(isolation): close four data-loss windows in finalize/cleanup paths
Four merge-blocking issues surfaced by deep code review of PR #177.
Each was a silent-failure window that could destroy operator data on
the unhappy path while the happy-path manual smoke test stayed green.
1. assess_cleanup now treats every git capture failure as
PreservedUnpushed (was: unwrap_or_default → empty string → could
land in SafeToDelete). Without this, a transient `git rev-list`
failure (corrupted pack mid-traversal, broken pipe under load,
index.lock from a backgrounded git GC) would auto-delete the
worktree and scratch branch, garbage-collecting unpushed commits.
Each capture site now uses an explicit `match` that returns
PreservedUnpushed with debug_log of the underlying error, plus a
defense-in-depth empty-HEAD guard.
2. finalize_clean_exit now collects ALL preserved records and prompts
per-record (was: needs_prompt.get_or_insert reached only the first
one). On a multi-mount workspace where the operator chooses
force-delete on the first prompt, the second preserved worktree was
silently orphaned and the container torn down anyway — the only
reconnection path (jackin hardline) was lost. Now each preserved
record gets its own prompt; "return to agent" short-circuits the
loop; "preserve" propagates as Preserved and skips container
teardown.
3. inspect_attach_outcome now returns still_running() on docker capture
failure (was: stopped(0) → entered finalize_clean_exit → could
compound with #1 to delete worktrees of containers that may still
be alive). The conservative direction is "preserve when we don't
know" — `jackin hardline` recovers from there.
4. force_cleanup_isolated now verifies cleanup actually completed
before removing the isolation.json record (was: let _ on git ops +
unconditional remove_record → orphan worktree admin entries on the
host repo and orphan branches with no jackin reference). Tolerates
the idempotent paths (already-removed worktree, already-deleted
branch verified absent via `git branch --list`); bails on real
failures with a clear "record retained, re-run jackin purge after
resolving the issue" message.
Test coverage:
- 5 new tests in isolation/finalize.rs pinning the assess_cleanup
capture-failure → PreservedUnpushed contract for each git command
in the assessment chain, plus the empty-HEAD guard.
- 3 new tests pinning the multi-record finalize path (force-delete-all,
mixed force/preserve, non-interactive multi-mount warning).
- 1 new test for inspect_attach_outcome capture-failure fallback.
- 3 new cleanup tests (branch-already-deleted tolerance, real-failure
retention, error-message contract).
- 1 new test for validate_workspace_config integration (catches the
validate_isolation_layout call site if anyone refactors it away).
- 1 new test for build_workspace_mount_strings on a multi-mount
isolated workspace (8 distinct binds, no path collisions, :ro
hardening on every override file).
- 2 new drift-detection tests (dst-removed flagged; isolation-mode
flips not-flagged with explanatory note for future improvement).
Net: 1170 → 1186 tests, 16 additions, all passing.
* fix(isolation): close P1/P2/P3 — follow-on bugs from second review
Second deep code review of PR #177 surfaced three more issues after
the first round of merge-blocker fixes shipped:
P1. `force_cleanup_isolated` failure mid-loop in finalize_clean_exit
propagated as Err via `?`, leaving the operator with a raw cleanup
error from deep in finalize, no Preserved signal to the caller, the
container left running without explicit teardown decision, and
subsequent records in the loop never prompted. This regression was
introduced by the round-1 multi-record loop fix (the single-record
path always succeeded). Now caught per-record, eprintln'd as a
warning, and treated as `any_preserved_after_prompt = true` so the
loop continues and the caller gets `Preserved`.
P2. `inspect_attach_outcome` only treated `status == "running"` as
still-alive. `paused | restarting | removing | created | dead` all
fell through to `stopped(0)` → entered `finalize_clean_exit` →
could auto-delete worktrees of containers that may resume any
moment. Concrete: `docker pause jackin-x` while jackin re-attaches
→ status="paused" → SafeToDelete on a clean tree → operator
unpauses to find the worktree gone. Replaced if-cascade with an
explicit `match status` that only routes `exited` through stopped()
and treats unknown status strings conservatively as still_running.
P3. `purge_isolated_for_container` swallowed per-record errors with
eprintln warnings and returned `Ok(())`. Exacerbated by the
round-1 fix #4 (force_cleanup_isolated now bails more often on
real failures). Operator runs `jackin purge`, sees a warning
scroll past, gets exit-code-0 prompt back, may believe purge
completed. Now collects failures and surfaces an aggregate Err
with the failed mount list so the exit code reflects reality.
Test coverage for these fixes:
- 2 new tests in finalize.rs: ReturnToAgent on the 2nd-of-3 prompt
(early-return short-circuits), and force_cleanup_isolated failing
mid-loop (loop continues, returns Preserved).
- 8 new tests in launch.rs covering every status code path:
exited(0/non-zero/oom), running, paused, transient (restarting/
removing/created), dead, unknown.
- 2 new tests in cleanup.rs: purge bails on partial failure,
branch_still_present returning None proceeds (pins the doc-comment
contract against future refactors to `unwrap_or(true)`).
Net: 1186 → 1198 tests, +12 additions, all passing. fmt/clippy clean.
---------
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Codex <codex@openai.com>
donbeave
added a commit
that referenced
this pull request
May 7, 2026
- Move 9 TODO items from monolithic TODO.md into separate files in todo/ - Each file is a self-contained design doc with problem, options, and related source files for easy agent handoff - Mark resolved security findings (#3, #4, #6, #7) in SECURITY_REVIEW_FINDINGS.md - Update PROJECT_STRUCTURE.md with todo/ section and TESTING.md entry - TODO.md becomes an index pointing to todo/ files Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Co-authored-by: Codex <codex@openai.com>
donbeave
added a commit
that referenced
this pull request
May 7, 2026
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Co-authored-by: Claude <noreply@anthropic.com>
donbeave
added a commit
that referenced
this pull request
May 7, 2026
* feat: implement trust-on-first-use model for third-party agent sources Add a trust gate that prompts operators before building untrusted third-party agents. Built-in agents are always trusted. New namespaced agents default to untrusted and require explicit confirmation on first use. The trusted flag is persisted in config.toml so subsequent runs proceed without prompts. Non-interactive sessions bail with a clear error for untrusted sources. Also syncs the roadmap with TODO items: adds missing completed item (JACKIN_DIND_HOSTNAME), adds missing planned items (Bollard migration), and marks the agent source trust security finding as resolved. https://claude.ai/code/session_01SkHoAne5Q5EpUybYk6ghUT * improve trust prompt with clear risk details and mark finding #9 resolved Rewrite the trust confirmation message to clearly explain what trusting an agent means: Dockerfile execution, arbitrary commands on the host, and workspace file access. The non-interactive error now tells the user how to pre-trust via config.toml. The decline message explains how to retry. Also mark security finding #9 (mount policy guardrails) as resolved since sensitive mount warnings were already implemented. https://claude.ai/code/session_01SkHoAne5Q5EpUybYk6ghUT * fix: address PR review — docs, stale comment, save clarity, test, serde Review fixes for the agent source trust model: - Fix duplicate doc comment on trust_agent() (copy-paste from sync_builtin_agents) - Replace confusing `!source.trusted` save condition with explicit `newly_trusted` flag for readability - Add unit test for non-interactive trust gate (verifies the error includes agent name and git URL) - Skip serializing `trusted = false` to keep config.toml clean (only `trusted = true` appears) - Document trust in load.mdx (step 3 in "What happens", warning callout for CI) and security-model.mdx (new section 4 with config.toml example, threat model row, best practices update) https://claude.ai/code/session_01SkHoAne5Q5EpUybYk6ghUT * fix: broken docs anchor link and misleading test name - Fix cross-reference link in load.mdx: use #4-agent-source-trust (matching the numbered heading in security-model.mdx) - Rename test to `load_trusted_namespaced_agent_builds_and_runs` since it no longer exercises auto-registration (that's covered by the config test `resolve_agent_source_adds_owner_repo_on_first_use`) https://claude.ai/code/session_01SkHoAne5Q5EpUybYk6ghUT * feat: add jackin trust/untrust CLI, testable trust gate, revocation Address all follow-up items from the maintainer review: - Extract load_agent_with() with injected trust callback, following the resolve_agent_repo_with() pattern. Tests can now exercise accept and decline paths without mocking dialoguer. - Restore auto-registration test coverage: the accept test uses load_agent_with(auto_trust) so resolve_agent_source's is_new path is exercised again. Add decline test verifying no config persistence and no Docker commands when trust is refused. - Add `jackin trust <selector>` and `jackin untrust <selector>` CLI commands so operators can manage trust without editing config.toml. - Add untrust_agent() to AppConfig with tests. - Document trust/untrust CLI in security-model.mdx and load.mdx. - Document that trust is keyed by selector (not URL) with a note about remote-mismatch protection. https://claude.ai/code/session_01SkHoAne5Q5EpUybYk6ghUT * refactor: consolidate trust/untrust into single CLI command Follow mise's pattern: `jackin trust` is one command with flags instead of separate trust/untrust subcommands. jackin trust <selector> # grant trust jackin trust <selector> --untrust # revoke trust jackin trust <selector> --show # check status The --show and --untrust flags conflict with each other (enforced by clap). https://claude.ai/code/session_01SkHoAne5Q5EpUybYk6ghUT * refactor: move trust CLI under config with grant/revoke/list subcommands Follow the same pattern as `jackin config mount {add,remove,list}`: jackin config trust grant chainargos/the-architect jackin config trust revoke chainargos/the-architect jackin config trust list This keeps all config mutations under `jackin config` and uses the subcommand pattern consistently throughout the CLI. The `list` subcommand shows all currently trusted agents. https://claude.ai/code/session_01SkHoAne5Q5EpUybYk6ghUT * fix: refuse to revoke trust on builtins, add CLI parsing tests - is_builtin_agent() check prevents revoking trust on built-in agents (agent-smith, the-architect) which would be silently re-granted by sync_builtin_agents on next load - Add 3 CLI parsing tests for config trust {grant, revoke, list}, matching the existing coverage for config mount {add, remove, list} - Tighten TrustCommand::List doc comment to "List all currently trusted agent sources" https://claude.ai/code/session_01SkHoAne5Q5EpUybYk6ghUT --------- Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Co-authored-by: Codex <codex@openai.com>
donbeave
added a commit
that referenced
this pull request
May 7, 2026
…183) * docs(roadmap): iteration 13 — AI code verifiability framing, config/types.rs full spec Primary goal shift: codebase must be verifiable for AI-generated code. - §0: replace generic description with explicit verifiability rationale (module contracts, localised concerns, types/behaviour separation) - §4 intro: add "Why structure matters for AI-generated code" section with audit-units table mapping each post-split file to one reviewable question - §4 4a: expand config/types.rs from description to full execution spec — exact type list, post-split mod.rs content, zero-change submodule guarantee (verified: agents.rs/persist.rs/workspaces.rs use super::T which resolves through mod.rs re-exports unchanged), impl-extension pattern already in use documented * docs(roadmap): iteration 14 — editor method map, app helpers, //! queue - §4 4c: config/editor.rs split is now execution-ready — complete 6-file method-to-file table with private helper placement verified (validate_candidate→io.rs, table_path_mut→mod.rs pub(super), auth_forward_str→agent_ops.rs, create_workspace delegates to AppConfig) - §4 4e: app/mod.rs split complete — all private helpers mapped (parse_auth_forward_mode_from_cli→config_cmd.rs, workspace_env_scope→workspace_cmd.rs, print_env_table note, remove_data_dir_if_exists→dispatch.rs) - §10 step 5: add //! priority queue — 10 files with draft content, prioritised by cold-landing impact and AI audit risk; selector.rs and instance/mod.rs explicitly document the /→__ invariant * docs(roadmap): iteration 15 — dep graph fix, trust.rs safety, OQ1 closed - §4 4d: correct operator_env dependency graph — layers.rs imports both mod.rs (OpRunner) AND client.rs (OpCli for non-injectable resolve_operator_env wrapper at line 797); still a valid DAG - §4 4f: verify trust.rs split safety — FnOnce injection pattern means launch_pipeline.rs has zero dependency on trust.rs; import chain documented; trust bypass audit now requires reading only ~60L - §9 OQ1 closed: op_cache.rs read in full — 4-level structure, per-level invalidation, no TTL/expiry (expiry handled at OpCli subprocess level), DEFAULT_ACCOUNT_KEY sentinel documented * docs(roadmap): iteration 16 — CommandRunner Rule 3, render/editor split, 4a/4c independence - Fix duplicate Rule 3 section introduced by previous edit; add docker.rs co-location note as third edge case (three edge cases, not two) - Add render/editor.rs as new Rule 5 violator: 1666L post-PR #171 (was listed as 782L); propose 6-file tab-by-tab split with auditability note on the security-adjacent Secrets tab - Add §10 execution-order note: 4a and 4c are independent — editor.rs imports AppConfig via crate::config re-exports regardless of 4a order - Append iteration 16 log entry with confidence table and weakest sections * docs(roadmap): iteration 17 — instance/auth.rs audit, state.rs split, line count corrections - Add instance/auth.rs to //! priority queue at #4: four security invariants (0o600 perms, symlink rejection, TOCTOU-safe writes, macOS Keychain) documented in draft //! content - Add state.rs as new Rule 5 violator: 992L/628L production; 26+ types mixed with impl blocks; propose 5-file types/behavior split - Correct stale line counts: render/list.rs 1122→1989 (PR #171 added render_environments_subpanel); state.rs 865→992; priorities upgraded - Fix §7.9 snapshot function line refs: sentinel_description_pane 306→332, mounts_subpanel 408→433, render_tab_strip 180→269, test ref 720→944 - Renumber //! priority queue to 11 entries (was 10) * docs(roadmap): iteration 18 — agent_allow OQ2 closed, render/list.rs split proposal - Close OQ2: agent_allow.rs read in full — 55L, correct //! doc, design sound; serves as model for //! priority queue pattern - Add render/list.rs as new Rule 5 violator: 668L production (PR #171 added render_environments_subpanel); propose 3-file split (mod.rs, details.rs, subpanels.rs); note import-path change for agents_block_agent_count - Update §1 module map: agent_allow.rs entry corrected with size/API * docs(roadmap): iteration 19 — input/editor.rs critical correction, split proposal - Correct input/editor.rs: 2349L total (was 1304L), 1141L production (was 547L) — PR #171 added Secrets-tab handlers; pub(super) fn handle_editor_modal at line 618 was invisible to previous grep pattern; now the largest production file in the codebase; priority → Critical - Correct input/save.rs: 1472L total, 661L production (was 567L) - Add 5-file split proposal for input/editor.rs: mod.rs (two dispatchers), secrets.rs (~500L AI-generated Secrets-tab), agents.rs, mounts.rs, general.rs - Update key insight paragraph naming input/editor.rs as largest production file * docs(roadmap): iteration 20 — console splits in §10, MSRV evidence, animation.rs verdict - Add console/manager/ as §10 Step 4f group with 5 sub-steps in priority order; rename existing 4f (launch.rs) → 4g; add circular-import risk note for ManagerStage/EditorState split sequencing - Analyze tui/animation.rs: 582L all-production, no split needed (banner_grid is a tightly-coupled rendering loop); section comments compensate for missing //! - Partially close OQ3: u64::is_multiple_of (stabilized 1.86) found in animation.rs; within declared MSRV 1.94; full cargo +1.94.0 check deferred (toolchain unavailable) * docs(roadmap): iteration 21 — input/save.rs split, //! queue fix, save.rs corrections - Add input/save.rs split proposal: 4 pub(super) fns discovered; 3-file split (mod.rs + flow.rs + preview.rs); no cross-dependency between flow and preview groups; §10 4f-v updated from Optional to concrete plan - Fix //! queue preamble: "first 10 files" → "first 11 files" - Correct save.rs module map (1418→1472L, correct key exports) and hot-spot table note (begin_editor_save ~280L → ~118L; commit_editor_save is the Phase 2 partner at ~149L) * docs(roadmap): iteration 22 — input/list.rs and mount_info.rs analysis - Analyze input/list.rs: 214L production (tests at 215); has //! doc; two focused pub(super) fns; no split needed; Low priority; correct module map - Add mount_info.rs to hot-spot table: 277L production; Low priority; has //! doc; correct module map with 3 public enums + inspect fn - Fix stale §2 diagnosis note: docs/internal/roadmap/ now exists * docs(roadmap): iteration 23 — audit units table +5 console rows, input/mod.rs corrected - Expand audit units table from 8 to 13 entries: add state/types.rs, state/editor.rs, input/editor/secrets.rs, render/list/subpanels.rs, input/save/preview.rs — all targeting PR #171 AI-generated console code - Add PR #171 context note linking 5 new entries to AI-generated code concern - Correct input/mod.rs module map: 369L, add InputOutcome enum to exports - Verify rust-toolchain.toml absence; §7.7 and §2 concept 25 already correct * docs(roadmap): iteration 24 — render/mod.rs analysis, //! exemplars table, EditorTab confirmed - Add §4 Rule 7 positive exemplars table: 7 files with //! docs graded 1-element (render/mod.rs), 2-element (input/save.rs etc), 3-element (env_model.rs, agent_allow.rs); PR #171 docs-discipline pattern noted - Correct render/mod.rs module map: 421L; FooterItem + palette constants + render_header + centered_rect_fixed added to key exports - Confirm EditorTab variants: General, Mounts, Agents, Secrets (Rust enum) vs "Secrets / Environments" (UI label); /stub qualifier already removed * docs(roadmap): iteration 25 — too_many_lines recount, FooterItem PR, MountConfig caveat - Correct too_many_lines count: 13 across 8 → 16 across 11 files (PR #171 added 5 suppressions in console/manager); add full breakdown table; update all 3 occurrences in roadmap - Fix FooterItem PR reference: #165 → #166 (confirmed by git log --follow) - Add MountConfig → MountSpec rename caveat to §7.5 snapshot test description * docs(roadmap): iteration 26 — console/mod.rs and op_picker/render.rs analyzed - Add console/mod.rs to hot-spot table: 406L/307L production (Low); correct module map from ~200 → 406L; note missing //! doc with ConsoleStage design block comment worth promoting - Add op_picker/render.rs to hot-spot table: 865L/545L production (Medium); PR #171 AI-generated; 14 functions in two logical groups (entry/helpers vs level renderers); split into levels.rs proposed - Correct 3 stale ~200L estimates for console/mod.rs across roadmap * docs(roadmap): iteration 27 — op_picker/mod.rs discovery, render split, operator_env correction - Add op_picker/mod.rs to hot-spot table: 1712L/775L production (High); PR #171 AI-generated; OpPickerState types+behavior split opportunity; has 7-line //! doc; module map split into two rows (mod.rs + render.rs) - Add op_picker/render.rs 2-file split proposal: render.rs (coordinator) + render_pane.rs (pane/level renderers); no cross-dependency confirmed - Correct operator_env.rs total: 1569→2130L (880L production); update 4 occurrences across hot-spot table, ASCII tree, §4 analysis * docs(roadmap): iteration 28 — op_picker/mod.rs 3-file split, count corrections - Add op_picker/mod.rs formal 3-file split: loading.rs (async load family ~120L) + keys.rs (4 level key handlers ~315L) + mod.rs (types/constructors) - Correct "24 files" → "28+" for 500L threshold count - Update total LOC: ~40,664 → ~43,587 (2 occurrences, with provenance note) * docs(roadmap): iteration 29 — op_picker execution order + file_browser analysis - §10 Step 4f: expand from 5 to 7 sub-steps; add 4f-vi (op_picker/mod.rs → mod.rs + loading.rs + keys.rs) and 4f-vii (op_picker/render.rs → render.rs + pane.rs); document impl-extension and import-path caveats - §4 //! exemplars: add file_browser/ subsystem analysis — all 5 files have //! docs, no file exceeds ~350L production; classified as exemplar (not a split candidate); document git_prompt.rs coupling-density justification and input.rs as 28-file false positive (144L production) - §1 module map: expand single file_browser/ row to 5 individual rows with production LOC and dominant concern per file * docs(roadmap): iteration 30 — challenge split-first thesis, fresh LOC corrections - §4: Add "Alternative thesis: documentation-first verification" — challenges the two core assumptions behind file splitting (files-as-audit-unit and file-size-as-context-constraint); adds 7-criterion comparison table vs structure-first approach; introduces phased combined recommendation: Phase 1 = doc sprint (//! contracts + specs/ for 3 subsystems, 2-3 PRs, zero structural change); Phase 2 = splits only for >600L production files (reduces scope from 14+ to 4 files); Phase 3 = workspace if LOC > 150K - Fix stale LOC: app/mod.rs 951→979, config/editor.rs 1467→1548 (7 and 8 locations respectively; verified by fresh find|xargs wc -l scan) - §1 module map: add agent_picker.rs (436L), scope_picker.rs (201L), source_picker.rs (244L) — all PR #171 additions with //! docs * docs(roadmap): iteration 31 — fix 600L→800L threshold error, correct LOC - §4 alternative thesis: correct ">600L production → 4 files" claim introduced in iteration 30; re-verified all 9 candidate files via #[cfg(test)] line position; threshold must be >800L to get exactly 4 files (9 exceed 600L); add verification table with test-start lines - Production LOC corrections (5+ locations each): launch.rs 1085→~1077, operator_env.rs 810→~880, app/mod.rs 928→~957, config/editor.rs 503→~584 - §2 OpPicker row: replace vague "no entry yet" with confirmed gap: PROJECT_STRUCTURE.md line 53 still lists pre-PR#171 widget set (10 named); omits op_picker/, agent_picker.rs, scope_picker.rs, source_picker.rs and pre-dates the manager/ sub-structure split * docs(roadmap): iteration 32 — two-tier spec arch, behavioral spec template - §8.1: Add two-tier spec architecture table distinguishing feature specs (public Starlight MDX, user-facing) from behavioral specs (internal docs/internal/specs/, for AI code verification) — resolves contradiction between §4 (which said docs/internal/specs/) and §8.1 (which said "no longer needed; specs are public") - §8.1: Add concrete behavioral spec template for op_picker/ with state machine table and 3 INV invariant entries each with a grep-executable "Verify by:" command; template directly usable for the 3 Phase 1 specs - §8.1: Remove erroneous "docs/internal/specs/ no longer needed" claim - Confirmed render/editor.rs ~736L and render/list.rs ~668L production (no interspersed production code — all test blocks follow consecutively) * docs(roadmap): iteration 33 — executive summary, §0 correctness - §0: Add executive summary (~300 words) with core problem, 3-phase recommendation, key counter-argument, and navigation table pointing to §2/§4/§7/§8/§10 by question — resolves the meta-irony of a readability roadmap with no entry-point orientation - §0 item 2: "1569-line monolith" → "2130-line monolith" (operator_env.rs current verified size; stale reference was in the first section readers see) - §0 item 3: Add "(selective)" qualifier and explicit note that standard Rust co-locates struct+impl — impl-extension pattern is justified only for files >800L production, not as a universal rule * docs(roadmap): iteration 34 — spec priority reorder, §10 Phase 1 track - §0 + §4 Phase 1: Prioritize runtime/launch.rs behavioral spec (no //! doc, ~1077L production, critical path — all jackin load failures trace here); drop config/editor.rs from Phase 1 (its 963L test suite already serves as behavioral spec — tests are behavioral examples); reduce Phase 1 from 3 specs to 2 specs; add reasoning for the priority ordering - §10 Step 2: Split into two parallel tracks — Track A (cc-sdd tooling setup) + Track B (Phase 1 behavioral spec authoring); Track B includes specific INV invariants to capture for runtime/launch.rs grounded in reading the actual function structure (step comment positions); adds sequencing rationale: spec must precede structural splits * docs(roadmap): iteration 35 — verified INV entries for runtime/launch.rs Read load_agent_with lines 553-892 in full. Replaced 3 draft INVs from iteration 34 (inferred from step comment positions) with 5 verified INVs citing exact line numbers: - INV-1: trust gate (line 594) precedes image build (line 736) - INV-2: container name claimed (line 754) between image build and network - INV-3: token verified (line 763) before network creation (line 827) - INV-4: render_exit called at lines 886 AND 890 (all exit paths) - INV-5: cleanup disarm semantics — Running→disarm, clean exit→cleanup, crash→disarm (explains jackin hardline compatibility) Corrected wrong line number: claim_container_name call is at 754, not 918 (918 is the function definition). Each INV has a grep-executable Verify by. * docs(roadmap): iteration 36 — CI gate for PROJECT_STRUCTURE.md freshness §3: Add "Preventing future PROJECT_STRUCTURE.md staleness" subsection with three concrete options: - Option A: CONTRIBUTING.md rule (necessary but insufficient) - Option B: ci.yml git-diff-scoped shell check (recommended) — only checks files added in the current PR so it doesn't require fixing existing stale entries before merging; greps for module directory name in prose - Option C: Structured TOML module registry (over-engineered for scale) Includes concrete YAML snippet for Option B grounded in the check:repo-links.ts pattern already established in docs/scripts/ * docs(roadmap): iterations 36-37 — CI gate + greenfield workspace architecture Iteration 36: - §3: Add "Preventing future PROJECT_STRUCTURE.md staleness" subsection with 3 options (CONTRIBUTING.md rule / ci.yml git-diff check / TOML registry); recommend Option B (git-diff-scoped YAML step) with concrete snippet grounded in existing check:repo-links.ts pattern from docs/scripts/ Iteration 37 (operator directive: greenfield Rust structure): - §4: Add "Greenfield architecture — ideal structure for a growing project" section based on verified cross-module dependency graph (grep iteration 37) - Confirms dependency tiers: workspace/manifest/docker/paths/selector = Tier 0; config/tui/instance = Tier 1; operator_env/runtime/repo = Tier 2; console = Tier 3 - Key finding: workspace/ is LOWER-level than config/ (config re-exports workspace types at lines 5-6); ideal naming inverted in greenfield (jackin-core > jackin-config) - Documents ideal 6-crate workspace: jackin-core, jackin-config, jackin-tui, jackin-runtime, jackin-console, jackin-shell + thin binary - Notes console/ has NO runtime/ import — cleanest pre-existing crate boundary - Bridge: incremental splits (4a, 4d, 4g) are pre-work toward workspace migration * docs(roadmap): iteration 38 — Rust workspace standards, community evidence Ground workspace recommendation in real-world project research: - ripgrep (9 crates), gitui (5 crates) went workspace due to library consumers - starship and fd-find stay single-crate at 1M+ LOC — no library use case - jackin (43K LOC, no external consumers) maps to starship/fd pattern → single-crate is community-standard; "stay single-crate" recommendation confirmed Update greenfield workspace structure to follow matklad's pattern: - Virtual manifest at root (no [package] in root Cargo.toml) - Flat crates/ directory (not nested); crate names match folder names - version = "0.0.0" for unpublished internal crates - Add inline dep comments to each crate in the ASCII structure Add research notes: ripgrep/starship/gitui/fd-find Cargo.toml findings + Cargo workspaces reference + matklad "Large Rust Workspaces" (2021-08-22) * docs(roadmap): revise §7.9 + §3 — adopt per-directory README.md §7.9: Reverse previous "reject" recommendation to "adopt" per-directory README.md for major src/ module directories. Rationale: README.md is AI-native — Claude Code, Copilot, Cursor load it automatically on directory entry, giving AI agents orientation before they decide which file to open. PROJECT_STRUCTURE.md being confirmed stale removes the main argument for the "single root file" approach. Add three-layer documentation model table: - README.md: directory orientation (AI + human, on entry) - AGENTS.md: agent workflow rules (root, session start) - CLAUDE.md: @AGENTS.md pointer only — NEVER add content here - //! docs: file-level contracts (when reading/editing) Add specific README.md content targets for 7 directories (src/, src/runtime/, src/console/, src/console/manager/, src/console/widgets/, docs/, docs/internal/). §3 target document shape: Add per-directory README.md to proposed hierarchy; add docs/internal/specs/ explicitly; note CLAUDE.md design principle (single-line @AGENTS.md — never duplicate content). * docs(roadmap): internal docs are browsable — unified Starlight site Operator directive: internal docs (architecture, specs, ADRs, roadmap) should be browsable, not hidden filesystem files. They are a different TYPE of docs focused on implementation details and vision, published as a "Developer Reference" section of the Starlight site. §3 target document shape: - docs/internal/ moves into docs/src/content/docs/internal/ (Starlight pages) - Browsable at jackin.tailrocks.com/internal/ - Sidebar: "Developer Reference" group (collapsed by default) with sub-sections for architecture, code-tour, contributing, testing, decisions, specs, roadmap - Include astro.config.ts sidebar config snippet §8.1 two-tier spec distinction eliminated: - Feature specs and behavioral specs both live at docs/src/content/docs/internal/specs/ - Type expressed via spec_type: behavioral | feature frontmatter, not filesystem location - Both browsable and searchable via Starlight; AI agents can be pointed to URLs §8.3 + §4: - All docs/internal/specs/ paths → docs/src/content/docs/internal/specs/ - ADRs: docs/internal/decisions/ → docs/src/content/docs/internal/decisions/ (browsable) - README.md pointer for src/runtime/ updated to URL reference * docs(roadmap): §11 — modern Rust docs platform (future project) Add §11 capturing the vision for a modern docs.rs alternative with: - rustdoc JSON ingestion → Astro Starlight presentation - MCP server for AI agent queries (Context7 alternative for Rust) - Rust-specific query types: rust_get_context(), rust_find_impls(), rust_search_types() — things Context7 cannot provide - Comparison table vs Context7 - Architecture diagram (ingestion → processing → Starlight + MCP) - Name candidates: rustlight, ferrodoc, cargo-starlight / starlight.rs - Note that jackin's §7.15 gen-rust-api.ts pipeline is the intentional prototype for the platform's processing and presentation layers * docs(roadmap): iteration 39 — update §0, fix stale internal/ paths §0 executive summary: rewrite to reflect decisions from iterations 30-38: - browsable internal docs (jackin.tailrocks.com/internal/) - per-directory README.md adoption (§7.9 reversed) - CLAUDE.md = @AGENTS.md single-line pointer only - greenfield workspace architecture (matklad's virtual manifest pattern) - §11 future project: modern Rust docs platform / Context7-for-Rust - document size 1800+ → 2200+ Fix stale docs/internal/ bare paths not caught by iteration 38 sweep: - Mermaid diagram: INTERNAL_ROADMAP, INTERNAL_CODE_TOUR → Starlight paths - §7.10 ADRs: docs/internal/decisions/NNN-title.md → .mdx Starlight path - §10 Track B item 2: op-picker spec path → Starlight MDX * docs(roadmap): iteration 40 — §7.15 pipeline + Rule 4 pub audit §7.15 (new): rustdoc JSON → Astro Starlight API documentation pipeline - Three options: rustdoc HTML publish / rustdoc JSON + bun script (recommended) / rustdoc-json crate as Rust binary - Option B recommended: matches existing docs/scripts/ pattern, nightly isolated to separate CI step, zero effect on stable build - Key design: URL at /internal/api/, cross-links to behavioral specs, Starlight unified search, prototype for §11 future project - Pub(crate) note: gen-rust-api.ts can feed Rule 4 visibility audit - Recommend: adopt after Phase 1 //! sprint (value ∝ coverage) §4 Rule 4 pub discipline: replace estimated "50-100 items" guess with verified numbers from iteration 40 grep: - 257 bare pub items, 21 pub(crate), 61 pub(super) across 94 files - 0 uses of unreachable_pub lint — no enforcement gate - Top violators: operator_env.rs (17), tui/output.rs (13), planner.rs (8) - Add concrete Cargo.toml [lints.rust] snippet: unreachable_pub = "warn" - Revised scope: ~150-200 mechanical conversions (excludes entry points) * docs(roadmap): split research into 19 actionable items Delete _research_notes.md (no longer needed). Replace 2343L READABILITY_AND_MODERNIZATION.md with: - README.md: index of all 19 items with phase, ordering notes, links - READABILITY_AND_MODERNIZATION.md: lightweight research summary (63L) - items/ITEM-001 through ITEM-019: individual actionable items Items by phase: Phase 1 (low risk, no confirmation): ITEM-001..004, 006..011 Phase 1 (needs confirmation): ITEM-005, 016, 018 Phase 2 (structural splits, confirmation required): ITEM-012..015 Phase 3 (deferred): ITEM-017 (rustdoc pipeline), ITEM-019 (workspace) Each item has: summary, key files with line numbers, steps, what needs confirmation, and relevant research backing from the 40-iteration analysis loop. * docs(roadmap): migrate 19 items to Starlight reference/roadmap section Move all codebase health roadmap items from docs/internal/roadmap/items/ (plain Markdown, not browsable) to docs/src/content/docs/reference/roadmap/ (MDX pages, browsable at jackin.tailrocks.com/reference/roadmap/). Adds a new "Codebase health" sidebar group (Phase 1 → Phase 3) to astro.config.ts. Deletes the old items/ directory. Updates the internal README to redirect to the new location. Also adds codebase-readability.mdx — a new overview item that captures the overall readability/restructuring program with a recommended execution order: file splits first, then greenfield workspace, then per-directory README+AGENTS.md, then docs and specs. * docs(roadmap): remove premature internal/roadmap/README.md The internal/ structure doesn't exist yet — it will be created as part of the roadmap items themselves. No need for a redirect stub now. * docs(roadmap): remove READABILITY_AND_MODERNIZATION.md research archive All content has been distilled into the individual Starlight roadmap pages. The full 2343L research is preserved in git history at commit b7e9fc2. * docs(roadmap): fix check:repo-links errors + remove iteration log - Replace plain code spans with <RepoFile> for validate.rs, mise.toml, Cargo.toml, and op_picker/mod.rs - Remove deleted READABILITY_AND_MODERNIZATION.md reference from codebase-readability.mdx - Delete _iteration_log.md (git history is the archive) * docs(roadmap): fix lychee false-positive link in move-contributing-testing The example redirect text contained a markdown hyperlink to a proposed future file path that doesn't exist yet. Changed to a code span. --------- Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Co-authored-by: Claude <noreply@anthropic.com>
donbeave
added a commit
that referenced
this pull request
May 7, 2026
* docs(spec): per-mount isolation V1 implementation spec
Captures roadmap → executable design: module layout under
src/isolation/, MountIsolation enum + MountConfig.isolation field,
materialization runtime hook, foreground finalizer with
safe/preserved/force cleanup, source-drift detection, jackin cd
command, TUI integration. Test list and docs touchpoints enumerated
for the implementation plan.
* feat(isolation): introduce MountIsolation enum
* feat(workspace): add isolation field to MountConfig
Defaults to Shared; serde skips emitting the field when Shared so
existing TOMLs round-trip unchanged.
* feat(workspace): reject nested isolated mounts
Two worktree-isolated mounts whose dsts nest have no safe on-disk
layout. Sibling isolated mounts and isolated-parent-with-shared-child
remain allowed.
* feat(config): reject isolation field on global mounts
Adds a strict GlobalMountConfig wire-format struct that mirrors
MountConfig minus the isolation field, with deny_unknown_fields
so operators get a clear parse error if they try to set isolation
on a global mount. Isolation remains a workspace-mount concept.
* refactor(config): drop dead code and tighten global-mount API
- Delete unused From<MountConfig> for GlobalMountConfig (silently
dropped isolation; no callers).
- Delete unused get_mut and remove on DockerMounts along with their
#[allow(dead_code)] annotations.
- Tighten AppConfig::add_mount: debug_assert that incoming
MountConfig has Shared isolation, and construct GlobalMountConfig
explicitly from src/dst/readonly rather than via the (now-deleted)
From impl. Keeps the public signature stable so callers in CLI,
resolve.rs, and preview.rs don't need to change (Issue 2 option B).
- Add wire-path rejection test that goes through MountEntry's
untagged enum and asserts on the actual serde error
("data did not match any variant of untagged enum MountEntry").
- Soften GlobalMountConfig's doc comment to reflect the actual
serde error shape at the wire path.
* feat(isolation): IsolationRecord + isolation.json IO
Atomic write via tmp+rename. Version-1 envelope leaves room for schema
evolution. Read/upsert/remove keyed by mount destination.
* test(isolation): drop redundant clones in state tests
Bring the warning baseline back to 87 after Task 2.1 introduced
two clippy::style hits (cloned_ref_to_slice_refs, redundant_clone).
* feat(isolation): list_records_for_workspace walks data dir
Used by workspace-edit drift detection to find which containers have
preserved isolated state for a given workspace.
* feat(isolation): branch_name renderer with namespace + suffix support
Suffix is appended to the final selector segment so namespaced agents
keep their selector shape and the disambiguator goes on the leaf name.
* feat(isolation): MaterializedWorkspace types
Third workspace shape (Config -> Resolved -> Materialized) used as the
runtime handoff into Docker launch.
* feat(isolation): worktree_path_for derives on-disk path from mount dst
Uses dst verbatim (leading/trailing slashes stripped) under
isolated/, so the layout mirrors the container path.
* feat(isolation): ensure_worktree_config_enabled
One-shot enabler for extensions.worktreeConfig on the host repo.
Bumps core.repositoryformatversion to 1 when needed.
* feat(isolation): preflight checks for worktree materialization
Sensitive-mount, readonly, repo-root, and mid-operation guards.
Errors cite the mount destination and the worktree mode.
* feat(isolation): dirty-host preflight gate with --force opt-out
Non-interactive load without --force rejects a dirty host tree.
Interactive contexts are expected to obtain ack upstream.
* feat(isolation): materialize_workspace orchestrator
Per-mount worktree materialization with idempotent reuse, source-drift
guard, and branch-name disambiguation when multiple isolated mounts
target the same host repo.
* test(isolation): cover branch disambiguation for same-repo mounts
* feat(isolation): order Docker mounts parent-before-child
Length-ascending sort so shared cache children overlay isolated
worktree parents at container start.
* feat(runtime): hook materialize_workspace between AgentState and Docker
Workspace mounts now flow Config -> Resolved -> Materialized before
reaching the docker run command, with parent-before-child ordering.
* feat(isolation): force_cleanup_isolated removes worktree + branch + record
Best-effort git invocations that tolerate missing host repo and
already-removed worktree. Used by purge and the finalizer's force-delete
branch.
* feat(isolation): finalizer skeleton + AttachOutcome shape
Decides Preserved when container still running, OOMed, or exited
non-zero. Clean-exit path stubbed - implemented in follow-ups.
* feat(isolation): safe-cleanup deletes branches with no commits
When the worktree is clean and HEAD equals the recorded base, the
scratch branch is removed automatically.
* feat(isolation): consult upstream when deciding safe cleanup
Pushed commits (reachable from upstream) are safe to delete; local-only
commits or no-upstream divergence preserve the worktree.
* test(isolation): cover interactive unsafe-cleanup prompt branches
* feat(runtime): finalize foreground session after attach in load + hardline
Both load and hardline now consult inspect_attach_outcome and dispatch
the shared finalizer. Return-to-agent retries safe cleanup once after
the operator returns.
* fix(purge): refuse to run on a live container
Closes a pre-existing gap where purge could delete state out from
under a running agent. Operator must eject first.
* feat(purge): remove isolated worktrees and scratch branches
Reads isolation.json and runs force_cleanup_isolated for each record
before deleting the per-container state directory.
* feat(cli): --mount-isolation DST=TYPE on workspace create/edit
Repeatable. Rejects clone before persistence with the canonical
"reserved but not implemented yet" message.
* feat(workspace): add Isolation column to workspace show
Renders canonical lowercase name for every mount so CLI output matches
TOML/CLI input verbatim.
* feat(load): add --force to acknowledge dirty host tree
Required for non-interactive isolated-mount materialization when the
host working tree is dirty.
* feat(workspace): detect source drift on edit affecting isolated mounts
Edits that change src for a mount with preserved isolated state are
rejected unless --delete-isolated-state is passed and no related
container is running.
* feat(cli): jackin cd opens a child shell in an isolated worktree
Single-mount-no-dst → uses it. Dst-provided → exact match. Multi-mount
no-dst → interactive picker on TTY, error on non-TTY. Sets JACKIN_*
env vars. Does not modify the parent shell.
* feat(console): show isolation badge per mount in editor + preview
Adds an Iso column to the workspace-manager mount table (editor and
list-pane sub-panel) and a `[shared|worktree|clone]` tag to the agent
preview's resolved-mounts lines. Per the per-mount-isolation spec the
badge renders the canonical spelling for every mount, including
`shared`, so operators always see which strategy applies.
* feat(console): I hotkey cycles isolation on the selected mount
Mirrors the existing R (readonly) toggle. Cycles Shared -> Worktree
-> Shared; Clone is reserved-but-rejected in V1 and is not entered
through this hotkey, but a saved Clone mount snaps back to Shared on
the first I press rather than getting stuck. The cycling rule lives in
EditorState::cycle_isolation_for_selected_mount so the input dispatch
arm stays trivial.
Also surfaces the new key in the Mounts-tab footer hint alongside the
existing R toggle so the affordance is discoverable.
* feat(console): source-drift confirm modal in workspace editor
Save flow runs the same drift detection as `jackin workspace edit`:
detect_workspace_edit_drift evaluates the prospective mount list
(post-collapse, post-upsert) against IsolationRecords on disk before
the on-disk write.
Running container drift -> ErrorPopup ("eject first"); save aborted.
Stopped container drift -> Confirm modal listing the affected
container names with a Yes/No prompt. On Yes the modal handler
re-stashes the plan with delete_isolated_acknowledged = true and the
second commit pass calls force_cleanup_isolated for each affected
record before writing.
Reduced scope vs the original three-button "Delete preserved state and
save / Cancel / Open mount details" dialog: the modal is the existing
two-button Confirm widget (Yes/No). The third "open mount details"
affordance is omitted — operators dismiss with N/Esc, find the
offending mount in the editor, and revert the src by hand. Adding it
would require either a custom widget or repurposing an existing
multi-choice one and threading mount-row focus through the modal
plumbing; the safety value is in the block-and-ack semantics, which
the two-button form covers.
Adds a commit_editor_save_with_runner test seam so the FakeRunner can
drive the drift branch without a real Docker daemon.
* docs(workspaces): add per-mount isolation section
Document the per-mount isolation feature in the workspaces guide:
the three modes (shared default, worktree, clone reserved-but-rejected),
validation preconditions, the isolated-source + shared-cache child
pattern with TOML, and pointers to --mount-isolation and jackin cd.
* docs(mounts): document mount isolation field
Add an "Mount isolation" section to the mounts guide covering the
shared/worktree values, the global-mount rejection at parse time,
and the isolated-source + shared-cache child composition pattern.
* docs(configuration): add MountConfig.isolation field
Document the new mounts[].isolation field in the configuration
reference: shared default, worktree opt-in, clone reserved-but-rejected,
and the global-mount parse-time rejection.
* docs(architecture): document materialization flow + isolation.json
Add a "Workspace materialization" section to the architecture reference
covering the WorkspaceConfig -> ResolvedWorkspace -> MaterializedWorkspace
shapes, the per-container isolation.json layout, and the post-attach
foreground finalizer's Preserved/Cleaned/ReturnToAgent decision matrix.
* docs(workspace): document --mount-isolation and Isolation column
Add --mount-isolation to workspace create/edit option tables (with the
clone "planned but not implemented" note), document the new
--delete-isolated-state flag for non-interactive source-drift edits,
and note the Isolation column on workspace show.
* docs(load): document --force dirty-host acknowledgement
Add --force to the option table and a dedicated section explaining
when it's required (non-interactive load with a worktree-isolated
mount + dirty host tree) and what it does NOT do (no stash, no
discard, no relaxation of other validation).
* docs(purge): document running-agent guard and isolated cleanup
Document purge's new behavior: refuses to run on a running container
(eject first), force-removes isolated worktrees + scratch branches
recorded in isolation.json, and tolerates a missing host repo on
best-effort cleanup.
* docs(cd): add jackin cd command reference
Create a reference page for jackin cd <container> [dst] covering
arguments, the mount-selection behavior matrix (zero/one/many isolated
records, with and without dst), the JACKIN_* env vars set in the
child shell, exit-code passthrough, and the no-parent-mutation
guarantee. Wire it into the Commands sidebar between console and
launch.
* docs(roadmap): mark per-mount isolation V1 implemented
Flip the per-mount-isolation roadmap status to "Implemented in V1"
and replace the duplicate-mounts-allowed line with the actual V1
rule: multiple isolated mounts are allowed (with branch-name
disambiguation), but nested isolated dst paths are rejected at
validation because the inner worktree's .git would land inside the
outer worktree's tree.
* docs(structure): add isolation module tree and cd command
Update PROJECT_STRUCTURE.md to document the new per-mount-isolation
work: isolation/ module row (mod/branch/materialize/state/finalize/
cleanup), cli/cd.rs entry on the cli/ row, --mount-isolation /
--delete-isolated-state / --force notes on the relevant CLI rows,
foreground-finalizer mention on the runtime row, the new
commands/cd.mdx in the docs map, and a code->docs cross-reference
row mapping src/isolation/** to all the doc pages it touches.
* test(isolation): end-to-end materialize -> clean-exit -> cleanup
Exercises the full lifecycle through public APIs with a small inline
scripted runner. No real git or docker.
* docs(isolation): note finalizer is local-only for hardline lockdown
* style(test): apply rustfmt to per-mount isolation e2e
* ci(docs): verify docs links in PRs and on the deployed site (#173)
* ci(docs): add link checking
* ci(docs): stabilize lychee checks
* ci(docs): validate edit links with lychee
* ci(docs): close link check gaps
* docs: add repo file link component
* docs: explain repo link source check
* ci(docs): allow manual dispatch of deployed link check
Two refinements from PR review:
- The check-deployed job now triggers on workflow_dispatch in addition
to schedule, so maintainers can manually verify the live deployed
docs without waiting for the daily cron or pushing to main. This
closes the gap against goal "manual workflow to verify both built
site and deployed documentation".
- Drop github.sha from the deploy job's lychee cache primary key so it
matches across runs (the SHA-keyed primary was guaranteed to miss,
forcing fallback to restore-keys). Now mirrors the cache key shape
used by check-deployed.
* ci(docs): harden link-check workflow and broaden source lint
Address review feedback on PR #173.
Workflow (.github/workflows/docs.yml):
- Split concurrency group by event_name so a scheduled or
workflow_dispatch run cannot cancel an in-flight push deploy. Cancel
in-progress is now scoped to pull_request only.
- Exclude main from workflow_dispatch on check-deployed. The deploy job
already verifies the just-deployed site, so running both in parallel
would race against the publish window. Manual verification of the live
site from main flows through deploy; from feature branches it flows
through check-deployed.
- Add the build cache as a fallback restore-key for check-deployed so
the daily cron and manual runs warm-start from the last build cache
when the lychee.toml fingerprint changes.
RepoFile component (docs/src/components/RepoFile.astro):
- Add target=_blank and rel=noopener noreferrer so GitHub source links
open in a new tab, matching the behavior rehype-external-links applies
to plain markdown external links in MDX.
Source lint (docs/scripts/check-repo-links.ts):
- Normalize leading slashes when checking RepoFile path existence so the
validator agrees with the component's own normalization.
- Cover top-level repo-specific files (Cargo.toml, Cargo.lock, Justfile,
build.rs, docker-bake.hcl, mise.toml, release.toml, renovate.json) so
a rename of any of those also breaks the docs CI gate, not only paths
under src/, docs/, docker/, .github/.
Content (docs/src/content/docs/developing/construct-image.mdx):
- Convert the remaining inline-code references to docker-bake.hcl and
Justfile to <RepoFile />. These were the references that motivated
extending the lint to top-level files.
* docs(roadmap): apply RepoFile lint to multi-runtime proposal
After merging main into codex/docs-link-checks, check-repo-links flagged
37 plain inline-code references to existing repo files in the new
multi-runtime-support.mdx (added in #174 before this PR's lint existed
on main). Convert each one to <RepoFile /> so renames or deletions of
those source files break the docs gate before merge, the same way the
rest of the roadmap is now protected.
No prose changes — every conversion is one-for-one (`src/foo.rs` →
<RepoFile path="src/foo.rs" />). The trailing-slash directory reference
to `docker/runtime/` is left as a code span, since the lint correctly
skips it (it's a directory, not a file).
* fix(roadmap): repair broken Amp CLI link in multi-runtime proposal
CI failed on this PR's last build because lychee found a 404 on
https://github.com/sourcegraph/amp in multi-runtime-support.mdx (added
in #174). That repo does not exist publicly — Amp's source is not on
GitHub.
Point the link at https://ampcode.com instead, which is already the
canonical Amp URL used elsewhere in the docs (getting-started/why.mdx).
---------
(cherry picked from commit f3f3e5e)
* docs(roadmap): wrap src/cli/cd.rs in <RepoFile> on per-mount-isolation page
The check-repo-links script (added in #173) flags any inline-code
reference to a real repo file that isn't wrapped in <RepoFile />.
src/cli/cd.rs was created on this branch, so once #173's lint reaches
this branch via the previous cherry-pick, the bare reference fails
the Docs CI check.
* docs: switch cross-doc links to absolute URL form
The link-check job's lychee step (added on main in #173, hardened in
#176) verifies built-site links against the on-disk dist tree. Relative
`.mdx`-suffixed links break that check because lychee resolves them as
literal file paths under the rendered URL's directory — e.g.
`./workspaces.mdx` rendered from `/guides/mounts/` resolves to
`/guides/mounts/workspaces.mdx`, not `/guides/workspaces/`.
Switch the four cross-doc links added by the per-mount-isolation work
to the rendered-URL form (`/guides/workspaces/#per-mount-isolation`
etc.) — same convention as the existing `[mount collapse](/commands/workspace/#mount-collapse)`
link in the same file.
* feat(isolation): emit verbose debug-mode trace for worktree lifecycle
Operators sharing logs to debug worktree behavior had no visibility into
the lifecycle — only three error/warning sites fired output, and none of
them ran on the happy path. `--debug` toggled a display mode (preserve
scrollback, clear spinner) but was not a verbose-trace facility.
This adds a `debug_log!(category, fmt, ...)` macro in `src/tui/mod.rs`
that gates on the existing `DEBUG_MODE` atomic so disabled call sites
cost only an atomic load (formatting is deferred behind the gate).
Output uses a `[jackin debug <category>]` prefix so shared logs are
greppable.
Instrumented sites (all under `category = "isolation"`):
- `materialize_workspace`: per-call summary (workspace, container,
selector, mount counts, force/interactive flags).
- `materialize_one`: per-mount decision trail — drift detection,
worktree reuse, preflight, base-commit lookup, branch derivation
with selected suffix, and the `git worktree add` invocation itself.
- `ensure_worktree_config_enabled`: every state transition (already
enabled vs. bumping repositoryformatversion vs. flipping the flag)
with the host repo path.
- `state.rs`: write_records (count + path), upsert_record (insert vs.
replace), remove_record (drop vs. no-op).
- `cleanup.rs`: force_cleanup_isolated entry, the two git invocations,
the rm -rf fallback, and the host-repo-missing skip path.
purge_isolated_for_container per-container summary.
- `finalize.rs`: foreground-session entry with exit code/oom/interactive
flags, the early-return path for non-clean exits, per-record cleanup
assessment.
- `runtime/launch.rs`: load_agent's call into materialize_workspace.
Read paths (`read_records`, `read_record`) are intentionally NOT logged
— they fire on every invocation and would drown the log.
Manual verification (since binary-level stderr capture would need a new
test dependency):
cargo run --release --bin jackin -- --debug load <agent>
cargo run --release --bin jackin -- --debug workspace edit <ws> \
--mount-isolation /workspace/proj=worktree
cargo run --release --bin jackin -- --debug purge <container>
Each emits a chronologically ordered `[jackin debug isolation] ...`
trace covering every git invocation, isolation.json mutation, and
finalize decision — suitable for sharing in bug reports.
* fix(tui): rename Iso column to Isolation, count isolation flips as one change
Two related TUI papercuts surfaced together when an operator flipped a
mount's isolation from `shared` to `worktree` via the `I` hotkey:
1. The mount-table header read `Iso` — opaque on first sight. Replaced
with `Isolation` (the full word). Bumped the column-width constant
from 8 → 9 so the header label fits without disturbing data-row
alignment, and renamed `MOUNT_ISO_COL_WIDTH` →
`MOUNT_ISOLATION_COL_WIDTH` for consistency. Updated the
alignment-regression test that asserted on the old label.
2. Cycling isolation on an existing mount (same `dst`, same `src`)
reported "2 changes" in the save-row footer and rendered the
Confirm Save dialog with a `+`/`-` pair for the same path. Both
sites used `MountConfig::contains()` — full-struct equality — so
any isolation/readonly drift made the row appear as remove + add.
Extracted a `MountDiff` classifier in `console/manager/state.rs`
that keys on `dst` (the identity used by upsert/remove everywhere
else). Same-`dst` matches with structural drift are now reported
as a single `Modified`, counted as one change in `change_count`
and rendered as a `~ <new>` line with a dimmed `was: <old>` follow-up
in the Confirm Save summary so the operator sees exactly what
changed without parsing a remove + add pair.
Extended `mount_summary` to include the isolation tag so the
delta is visible in both the new and old lines:
`~/foo (rw, worktree, github · main)`.
Records a new shared rule in `RULES.md` ("TUI Labels") to prevent
future short-form labels in user-facing TUI surfaces — operators
read the TUI in passing and cannot afford to decode `Iso`/`Cfg`/`Env`/
`WD`-style abbreviations. Lists the established short forms that are
NOT considered abbreviations (`dst`, `src`, `git`, `op`).
* fix(isolation): make worktree mode actually work inside the container
V1's worktree mode shipped with a gap: the materialized worktree was
bind-mounted into the container at <dst>, but the worktree's `.git`
text file (a pointer back to <host_repo>/.git/worktrees/<n>/) referenced
an absolute host path that didn't exist inside the container. Every git
command — `git status`, `git log`, `git commit`, `git push` — failed
with "fatal: not a git repository". The agent could read source files
but could not commit work, defeating worktree mode's whole purpose.
Fix: wire up three additional bind mounts at docker-run time, plus two
jackin-owned override files written at materialization, so git's gitdir
relationship resolves consistently inside the container without
modifying any host-side files.
For each isolated worktree, the container now sees:
1. The worktree at <dst> (existing).
2. The host repo's `.git/` at /jackin-isolation/<container>-git/ rw,
so git can find objects, refs, and the per-worktree admin dir.
3. A jackin-owned `.git` text file at <dst>/.git overriding the
worktree's host-side pointer with one targeting the container path.
4. A jackin-owned back-pointer at /jackin-isolation/<container>-git/
worktrees/<n>/gitdir overriding git's verification check (host's
absolute path doesn't match <dst> inside the container).
Override files live under <data_dir>/jackin-<container>/.git-overrides/
and are written once at materialize time. Host files (worktree's `.git`
and the admin dir's `gitdir`) are NEVER modified — host-side
`git worktree list` continues to work identically.
Three layouts were considered (Docker Sandboxes-style `.jackin/` in the
host repo, indirect mount with override files, jackin-owned bare repo).
The chosen approach preserves jackin's dst-based mount model (operator
configures dst=/workspace/jackin → agent works at that exact path), keeps
the host repo clean (no `.jackin/` directory), and exposes only the
worktree to the agent (not the entire host main tree). Full design
rationale and the comparison with Docker Sandboxes, Conductor, and
clone mode (planned for V1.1) are in
docs/src/content/docs/reference/roadmap/per-mount-isolation.mdx
under "Design Decision: Worktree Materialization Layout" and
"Comparison with Other Tools".
Trust trade-off: the agent has rw access to the host repo's `.git/`
since refs/objects are inherently shared in git worktrees. Worktree
mode is appropriate for trusted agents on personal projects where
immediate ref visibility on the host is valuable. Operators who want
ref isolation should use clone mode (planned).
Tests:
- write_git_overrides_writes_both_files_with_correct_content asserts
override file content matches the design doc verbatim.
- write_git_overrides_is_idempotent confirms re-running on a reused
worktree (load → eject → load) doesn't drift.
- override_id_strips_slashes_and_trims pins the file-naming scheme.
- container_git_dir_path_namespaces_by_container_name pins the
hardcoded container-side path so two parallel agents don't collide.
- Extended per_mount_isolation_e2e to assert MaterializedMount carries
WorktreeAuxMounts on the worktree path and that override files land
on disk at the documented locations.
Manual verification recipe (add after running once):
cargo run --release --bin jackin -- --debug load <agent>
docker exec -ti <container> git status # was failing, now works
docker exec -ti <container> git log # works
docker exec -ti <container> git commit -m test --allow-empty
git -C <host-repo> branch -a # shows new branch
* refactor(isolation): /jackin/{host,admin}/<dst> mounts, container-name basename, :ro hardening
Three related changes that finalize the worktree-mode mount layout:
1. Container-side path scheme renamed and reorganized.
/jackin-isolation/<container>-git/... → /jackin/host/<dst-stripped>/.git
/jackin/admin/<dst-stripped>/{commondir,gitdir}
- Single top-level /jackin/ namespace for everything jackin contributes
to the agent's filesystem (room to grow with /jackin/cache/, etc.)
- host/ category mirrors host topology so docker inspect shows symmetric
Source/Destination paths both ending in `.git`
- admin/ category lives at a separate top level so the override files
(which sit on top of files inside the admin dir) do NOT visually nest
inside /jackin/host/.../.git/. Two top-level concerns, no overlap.
2. Host-side storage groups all git artifacts for one mount under
<state>/git/<dst-stripped>/, with override-file names matching their
docker mount destinations:
<state>/git/<dst-stripped>/
├── <container>/ (the worktree; basename = container name)
└── overrides/
├── .git
├── commondir
└── gitdir
Replaces the prior <state>/.git-overrides/ flat layout with underscored
slug filenames. New layout uses dst as a real directory tree (no slug)
and source filenames identical to destination filenames — the source/
destination relationship is obvious in `docker inspect`.
3. Worktree subdir basename = container name. `git worktree add` derives
the host-side admin entry name from the worktree path's basename (no
--name flag exists upstream). Using the container name (which jackin
guarantees is globally unique) makes admin entries in
<host_repo>/.git/worktrees/ globally unique per (host_repo, container)
— `git worktree list` on the host immediately shows which container
owns each worktree.
This required a new validation rule:
`workspace::validate_isolation_layout` now rejects two isolated mounts
that resolve to the same host repository within one workspace.
Allowing them would force the same container-name basename twice in
one host repo's .git/worktrees/ namespace; no real operator workflow
has surfaced for this case. Revisit if one does.
Removes the now-dead suffix logic from materialize.rs:
- `count_isolated_per_repo` (helper)
- `canonicalize_or_clone` (helper)
- `dst_to_branch_suffix` (in src/isolation/branch.rs — no callers left)
The `branch_name` function keeps its optional suffix parameter for
future clone-mode use; V1 worktree always passes None.
4. The three override files (replacement `.git` pointer, `commondir`,
`gitdir` back-pointer) are mounted `:ro` as defensive hardening. Git
only reads them during normal agent work, and a misbehaving agent
could otherwise rewrite the gitdir pointer to redirect operations at
a different repo entirely. The host `.git/` and admin mounts stay rw
because git writes refs/objects/HEAD/index/logs there.
Tests:
- workspace::tests::isolation_layout_rejects_two_worktree_mounts_on_same_repo
- workspace::tests::isolation_layout_allows_different_host_repos_in_one_workspace
- materialize::tests::worktree_path_uses_container_name_as_basename
- materialize::tests::container_host_git_path_mirrors_dst_under_jackin_host
- materialize::tests::container_admin_path_lives_under_jackin_admin
- materialize::tests::host_and_admin_paths_disambiguate_per_mount_in_one_container
- materialize::tests::write_git_overrides_writes_three_files_with_correct_content
- materialize::tests::write_git_overrides_is_idempotent
- launch::tests::build_workspace_mount_strings_marks_overrides_readonly
(asserts all 6 mounts in correct order with correct :ro placement)
- per_mount_isolation_e2e: updated for new path scheme + admin name
Removed:
- materialize::tests::two_isolated_mounts_same_repo_get_dst_suffixed_branches
(case is now rejected at the workspace-validation level)
Roadmap MDX (per-mount-isolation.mdx):
- Container-side mount layout section: 4 mounts → 6, new path scheme,
override-file storage layout
- Composition Rules: documents the new same-host-repo rejection
- Comparison table: bind mount count for jackin worktree updated 4 → 6
- V1 Scope: ship list updated with new layout and the new validation rule
Manual verification (after merge):
cargo run --release --bin jackin -- --debug load <agent> <workspace>
docker inspect <container> | jq '.[0].Mounts' # see /jackin/{host,admin}/...
docker exec -w <dst> <container> git status # works
git -C <host_repo> worktree list # admin name = <container>
* refactor(isolation): single /jackin/host/ root, no commondir override, Model B branch naming
Final V1 design after extended brainstorming with the operator. Drops
the `/jackin/admin/<dst>` namespace and the `commondir` override file:
the per-worktree admin entry now lives natively at
`worktrees/<container>/` inside the host `.git/` mount, so git's
on-disk default `commondir = ../..` resolves correctly without an
override.
Container-side topology, per isolated mount (4 binds total, down from 6):
- `<dst>` (rw) — the materialized worktree
- `/jackin/host/<dst-tree>/.git` (rw) — host repo's `.git/`
- `<dst>/.git` (`:ro`) — replacement gitdir pointer
- `/jackin/host/<dst-tree>/.git/worktrees/<container>/gitdir` (`:ro`)
— replacement back-pointer
Host-side layout under each per-container state dir:
- `git/worktree/repo/<dst-tree>/<container>/` — git's territory
- `git/overrides/<dst-tree>/{.git,gitdir}` — jackin-owned overrides
Branch naming follows Model B: `jackin/scratch/<container_name>`
verbatim. Admin entry name = container name (deterministic, globally
unique because container names are workspace-unique and
`validate_isolation_layout` rejects two isolated mounts on the same
host repo within one workspace — no auto-suffix or read-back needed).
Roadmap doc updated to reflect the final design.
* style(isolation): rustfmt assert_eq! width
* docs(isolation): align stale references with shipped V1 design
Sweep stale references across roadmap, guides, command and architecture
docs into alignment with what the code actually does. No content added —
the doc just told two contradictory stories before (Model B branch
naming alongside the old selector-key derivation; the new
git/worktree/repo/<dst>/<container>/ on-disk layout alongside the
proposed-but-never-implemented isolated/<slug>/ layout). Now there is
one consistent story end-to-end.
Touches:
- docs/src/content/docs/reference/roadmap/per-mount-isolation.mdx
- docs/src/content/docs/guides/workspaces.mdx
- docs/src/content/docs/commands/purge.mdx
- docs/src/content/docs/reference/architecture.mdx
* chore(cli): remove jackin cd command from V1
Operationally redundant with `git worktree list` + native shell `cd`.
For worktree mode, the host's `git worktree list` already enumerates
every isolated worktree by branch and absolute path, so a plain
`cd $(...)` reaches the same destination. The remaining edge cases
(preserved-dirty inspection, multi-mount picker) are rare enough that a
dedicated subcommand is net cost rather than net benefit.
Removes:
- src/cli/cd.rs (CdArgs + select_record + tests)
- Command::Cd enum variant in src/cli/mod.rs
- handle_cd dispatch in src/app/mod.rs
- docs/src/content/docs/commands/cd.mdx and its sidebar entry
Updates:
- src/isolation/finalize.rs preserved-state warning: drop "jackin cd ..."
hint, point operator to the printed worktree path instead
- src/isolation/materialize.rs source-drift error: same treatment
- guides/workspaces.mdx + reference/architecture.mdx: drop cd references
- roadmap entry: replace "Convenience navigation" paragraph with a
removed-from-V1 note explaining the rationale; add cd to the Defer
list so it's recoverable if a real workflow surfaces
isolation.json schema, the preserved-state machinery, and `hardline` /
`purge` flows are unaffected — only the inspection convenience layer
is gone.
* chore(isolation): drop clone from V1 enum/parser/CLI
Per principle: don't pre-add API for unimplemented features. The `clone`
keyword was previously parsed by TOML/CLI and then rejected at validation
with a "planned but not implemented yet" error. Operators got false
positives in linting tools and confusing late failures with no benefit —
nothing in V1 actually does anything useful with the value.
Removes from the runtime:
- MountIsolation::Clone enum variant (src/isolation/mod.rs)
- explicit Clone-rejection in parse_mount_isolation (src/cli/workspace.rs):
FromStr now produces "invalid isolation `clone`" naturally
- MountIsolation::Clone match arm in materialize_workspace
(src/isolation/materialize.rs)
- console comments referencing the reserved-but-rejected wording
Tests:
- New `rejects_clone_until_implemented` test on FromStr asserts the
standard "invalid isolation `clone`; expected one of: shared, worktree"
error so this stays locked down
- parse_mount_isolation_rejects_clone updated to assert the new error
shape
Docs:
- guides/workspaces.mdx, commands/workspace.mdx,
reference/configuration.mdx, reference/roadmap.mdx: drop the
"reserved keyword" wording, point at the V1.1 roadmap entry instead
- roadmap/per-mount-isolation.mdx: keep the `clone` design discussion,
rephrase the V1 vocabulary section to make it explicit that the keyword
is added back when clone mode ships, not pre-shipped now
apply_isolation_overrides already enforces "--mount-isolation must
reference an existing mount destination" (planner.rs); no change needed
for that requirement, just clarified in the roadmap CLI-behavior bullet.
* fix(workspace): always write mount isolation field explicitly on save
Old configs without the `isolation` field still deserialize to Shared
(the enum default). On save, drop the `skip_serializing_if = is_shared`
guard so every mount writes its isolation level explicitly — including
`shared`. Old TOMLs migrate to the new shape on first save instead of
silently retaining their pre-isolation form.
Rationale: when the operator opens the saved config, every mount should
name its isolation level. No "field is missing therefore implicitly
shared" — the value is always present and the source of truth in the
file matches the source of truth at runtime.
Touches:
- src/workspace/mod.rs MountConfig.isolation: drop skip_serializing_if
- mount_config_omits_isolation_field_when_shared_on_serialize → renamed
to mount_config_writes_isolation_field_even_when_shared_on_serialize,
asserts the field IS written
- guides/mounts.mdx + reference/configuration.mdx: update wording
* docs: remove stale per-mount-isolation design spec
The brainstorming spec at docs/superpowers/specs/ predated the V1 design
iterations and no longer reflects what shipped (it still describes the
old `/jackin-isolation/` mount layout, the selector-key-based branch
naming, the reserved `clone` enum value, and `jackin cd`). The roadmap
entry at docs/src/content/docs/reference/roadmap/per-mount-isolation.mdx
is now the single source of truth.
* docs(roadmap): improve per-mount isolation entry — accurate Sandboxes
comparison, V1 overview, concrete clone-mode personas
Four targeted improvements to the per-mount-isolation roadmap doc:
1. Add a "How V1 worktree mode works (TL;DR)" overview at the top of
Host-Side Materialization. 5-step walkthrough of the materialize +
mount + commit flow before the deep-dive subsections, so readers
land on the entry can understand the shipped V1 in 90 seconds
without scrolling through the layout/lifecycle/etc.
2. Rewrite the Docker Sandboxes comparison for accuracy. The old
description got it wrong on multiple points:
- Sandboxes use a microVM with hypervisor isolation, not Docker
containers (Linux namespaces) like jackin.
- The host filesystem is exposed via filesystem passthrough at the
SAME absolute path as the host, not a bind mount of the entire
repo at "the same relative paths".
- Worktree path is `<host_repo>/.sbx/<sandbox-name>-worktrees/<branch>/`
(sandbox name is in the path), not `<host_repo>/.sbx/<branch>/`.
- Sandboxes do NOT expose the host main working tree to the agent
— only the worktree subdir and the parent `.git/`. Our table
previously said "✓ (entire host repo mounted)".
The architectural insight is now explicit: Sandboxes' absolute-path
equivalence makes git's on-disk absolute pointers resolve natively,
which is why they don't need override files. We pay that cost
because Docker containers translate host paths to operator-chosen
`dst` values, breaking absolute-path equivalence.
3. Concrete clone-mode operator personas. The previous description
was abstract ("complementary mode for ref isolation"). Replaced
with four named situations clone mode targets: untrusted/experimental
agents, parallel-fan-out scratch-branch noise, editor watcher
churn, and teams whose workflow is push-to-share anyway.
4. New "Sandbox runtime" and "Host file exposure" rows in the
comparison table to make the underlying architectural choice
immediately visible. Cross-referenced from the Sandboxes prose.
Net: ~80 lines changed/added, primarily replacement of the wrong
Docker Sandboxes facts and addition of the V1-overview block.
* fix(isolation): close four data-loss windows in finalize/cleanup paths
Four merge-blocking issues surfaced by deep code review of PR #177.
Each was a silent-failure window that could destroy operator data on
the unhappy path while the happy-path manual smoke test stayed green.
1. assess_cleanup now treats every git capture failure as
PreservedUnpushed (was: unwrap_or_default → empty string → could
land in SafeToDelete). Without this, a transient `git rev-list`
failure (corrupted pack mid-traversal, broken pipe under load,
index.lock from a backgrounded git GC) would auto-delete the
worktree and scratch branch, garbage-collecting unpushed commits.
Each capture site now uses an explicit `match` that returns
PreservedUnpushed with debug_log of the underlying error, plus a
defense-in-depth empty-HEAD guard.
2. finalize_clean_exit now collects ALL preserved records and prompts
per-record (was: needs_prompt.get_or_insert reached only the first
one). On a multi-mount workspace where the operator chooses
force-delete on the first prompt, the second preserved worktree was
silently orphaned and the container torn down anyway — the only
reconnection path (jackin hardline) was lost. Now each preserved
record gets its own prompt; "return to agent" short-circuits the
loop; "preserve" propagates as Preserved and skips container
teardown.
3. inspect_attach_outcome now returns still_running() on docker capture
failure (was: stopped(0) → entered finalize_clean_exit → could
compound with #1 to delete worktrees of containers that may still
be alive). The conservative direction is "preserve when we don't
know" — `jackin hardline` recovers from there.
4. force_cleanup_isolated now verifies cleanup actually completed
before removing the isolation.json record (was: let _ on git ops +
unconditional remove_record → orphan worktree admin entries on the
host repo and orphan branches with no jackin reference). Tolerates
the idempotent paths (already-removed worktree, already-deleted
branch verified absent via `git branch --list`); bails on real
failures with a clear "record retained, re-run jackin purge after
resolving the issue" message.
Test coverage:
- 5 new tests in isolation/finalize.rs pinning the assess_cleanup
capture-failure → PreservedUnpushed contract for each git command
in the assessment chain, plus the empty-HEAD guard.
- 3 new tests pinning the multi-record finalize path (force-delete-all,
mixed force/preserve, non-interactive multi-mount warning).
- 1 new test for inspect_attach_outcome capture-failure fallback.
- 3 new cleanup tests (branch-already-deleted tolerance, real-failure
retention, error-message contract).
- 1 new test for validate_workspace_config integration (catches the
validate_isolation_layout call site if anyone refactors it away).
- 1 new test for build_workspace_mount_strings on a multi-mount
isolated workspace (8 distinct binds, no path collisions, :ro
hardening on every override file).
- 2 new drift-detection tests (dst-removed flagged; isolation-mode
flips not-flagged with explanatory note for future improvement).
Net: 1170 → 1186 tests, 16 additions, all passing.
* fix(isolation): close P1/P2/P3 — follow-on bugs from second review
Second deep code review of PR #177 surfaced three more issues after
the first round of merge-blocker fixes shipped:
P1. `force_cleanup_isolated` failure mid-loop in finalize_clean_exit
propagated as Err via `?`, leaving the operator with a raw cleanup
error from deep in finalize, no Preserved signal to the caller, the
container left running without explicit teardown decision, and
subsequent records in the loop never prompted. This regression was
introduced by the round-1 multi-record loop fix (the single-record
path always succeeded). Now caught per-record, eprintln'd as a
warning, and treated as `any_preserved_after_prompt = true` so the
loop continues and the caller gets `Preserved`.
P2. `inspect_attach_outcome` only treated `status == "running"` as
still-alive. `paused | restarting | removing | created | dead` all
fell through to `stopped(0)` → entered `finalize_clean_exit` →
could auto-delete worktrees of containers that may resume any
moment. Concrete: `docker pause jackin-x` while jackin re-attaches
→ status="paused" → SafeToDelete on a clean tree → operator
unpauses to find the worktree gone. Replaced if-cascade with an
explicit `match status` that only routes `exited` through stopped()
and treats unknown status strings conservatively as still_running.
P3. `purge_isolated_for_container` swallowed per-record errors with
eprintln warnings and returned `Ok(())`. Exacerbated by the
round-1 fix #4 (force_cleanup_isolated now bails more often on
real failures). Operator runs `jackin purge`, sees a warning
scroll past, gets exit-code-0 prompt back, may believe purge
completed. Now collects failures and surfaces an aggregate Err
with the failed mount list so the exit code reflects reality.
Test coverage for these fixes:
- 2 new tests in finalize.rs: ReturnToAgent on the 2nd-of-3 prompt
(early-return short-circuits), and force_cleanup_isolated failing
mid-loop (loop continues, returns Preserved).
- 8 new tests in launch.rs covering every status code path:
exited(0/non-zero/oom), running, paused, transient (restarting/
removing/created), dead, unknown.
- 2 new tests in cleanup.rs: purge bails on partial failure,
branch_still_present returning None proceeds (pins the doc-comment
contract against future refactors to `unwrap_or(true)`).
Net: 1186 → 1198 tests, +12 additions, all passing. fmt/clippy clean.
---------
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Codex <codex@openai.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
crosstermfrom 0.28 to 0.29ratatuifrom 0.29 to 0.30tomlfrom 0.8 to 1.1All 142 tests pass and clippy is clean.
🤖 Generated with Claude Code