Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions docs/advanced.md
Original file line number Diff line number Diff line change
Expand Up @@ -531,8 +531,10 @@ Hosted review mode is enabled with `--hosted-review`,
Coven Code does not load user-scope memory by default. The prompt records that
hosted review mode is active and lists the AGENTS.md scopes that were loaded.
Durable hosted memory and transcript namespaces are separated under a hosted
review path and require tenant scope plus a canonical repository identity before
they can be resolved for persistence.
review path and require tenant scope, GitHub App installation id, stable repo
id, and canonical repository identity before they can be resolved for
persistence. Hosted namespaces include a domain component so default branch,
branch, pull-request, release, and security-private memory stay separated.

Hosted review mode also disables write/execute-capable tools, configured MCP
servers, plugins, user memory, and managed rules by default. Trusted hosted
Expand Down
7 changes: 5 additions & 2 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,11 @@ Or in `settings.json`:
When hosted review mode is active, Coven Code skips user-scope memory
(`~/.coven-code/AGENTS.md` and `~/.coven-code/CLAUDE.md`) by default, marks
new session artifacts as hosted review artifacts, and requires a tenant plus
canonical repository identity before resolving hosted durable memory paths.
Local-personal mode remains the default and continues to load user memory.
GitHub App installation id, stable repository id, and canonical repository
identity before resolving hosted durable memory paths. Hosted memory and
transcripts are also split by memory domain, such as default branch, named
branch, pull request, release, or security-private review. Local-personal mode
remains the default and continues to load user memory.

Hosted review also disables write/execute-capable tools, configured MCP
servers, and plugins by default. A trusted hosted policy can opt individual
Expand Down
44 changes: 44 additions & 0 deletions docs/shared/hosted-review-phase-2-pr.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Hosted Review Phase 2 PR Notes

## Linked issues

Fixes #98.
Fixes #99.
Fixes #104.
Fixes #110.

## Summary

- Expands hosted review scope to include tenant id, GitHub App installation id, stable repo id, repo full name, canonical repo identity, and memory domain.
- Moves hosted memory and transcript paths from local-path identity to tenant/installation/repo/domain namespaces.
- Adds canonical GitHub repo identity parsing for HTTPS and SSH remotes plus deterministic local project ids.
- Adds hosted-derived settings sync project keys and hosted team-memory repo keys so hosted callers do not pass arbitrary project ids.
- Splits hosted memory domains for default branch, branch, release, pull request, and security-private review contexts.
- Hardens Windows test isolation for home-derived paths and gates Unix-socket daemon tests to Unix.
- Corrects Windows TUI system-root test fixtures to use drive-qualified absolute paths.

## Test evidence

- `cargo fmt --all`
- `cargo check --workspace`
- `cargo clippy --workspace --all-targets -- -D warnings`
- `cargo test -p claurst-core --lib hosted -- --nocapture`
- `cargo test -p claurst-core --lib local_project_id -- --nocapture`
- `cargo test -p claurst-core --lib sync_keys -- --nocapture`
- `cargo test -p claurst-core --lib team_memory_key -- --nocapture`
- `cargo test -p claurst-commands --lib named_commands::tests::test_agents_reset_removes_saved_roster_state -- --nocapture`
- `cargo test -p claurst-core --lib roster_reset -- --nocapture`
- `cargo test -p claurst-core --lib build_import_preview_maps_settings_and_doc -- --nocapture`
- `cargo test -p claurst-core --lib test_imported_anthropic_cli_token_resolves_without_coven_oauth_client -- --nocapture`
- `cargo test -p claurst-tui --lib windows_system_root --quiet`
- `cargo test --workspace --quiet`

## Full-suite status

- `cargo test --workspace --quiet` passes on Windows with `CARGO_TARGET_DIR=C:\dev-cargo-target\coven-code` after Smart App Control was disabled locally.

## Risk notes

- Local mode pathing remains available through existing local APIs.
- Hosted durable state now requires explicit scope to avoid path-derived cross-tenant collisions.
- Security-private domains are represented and are excluded from public review loading unless explicitly allowed by policy.
13 changes: 10 additions & 3 deletions src-rust/crates/commands/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10363,6 +10363,7 @@ pub(crate) mod test_env {

pub(crate) struct CommandEnvGuard {
old_home: Option<String>,
old_test_home: Option<String>,
old_userprofile: Option<String>,
old_coven_home: Option<String>,
old_user: Option<String>,
Expand All @@ -10377,6 +10378,7 @@ pub(crate) mod test_env {
let lock = ENV_LOCK.lock().unwrap_or_else(|err| err.into_inner());
let guard = Self {
old_home: std::env::var("HOME").ok(),
old_test_home: std::env::var("COVEN_CODE_TEST_HOME").ok(),
old_userprofile: std::env::var("USERPROFILE").ok(),
old_coven_home: std::env::var("COVEN_HOME").ok(),
old_user: std::env::var("USER").ok(),
Expand All @@ -10386,6 +10388,7 @@ pub(crate) mod test_env {
_lock: lock,
};
std::env::set_var("HOME", home);
std::env::set_var("COVEN_CODE_TEST_HOME", home);
std::env::set_var("USERPROFILE", home);
std::env::set_var("COVEN_HOME", coven_home);
match user {
Expand Down Expand Up @@ -10418,14 +10421,18 @@ pub(crate) mod test_env {
Some(value) => std::env::set_var("HOME", value),
None => std::env::remove_var("HOME"),
}
match &self.old_coven_home {
Some(value) => std::env::set_var("COVEN_HOME", value),
None => std::env::remove_var("COVEN_HOME"),
match &self.old_test_home {
Some(value) => std::env::set_var("COVEN_CODE_TEST_HOME", value),
None => std::env::remove_var("COVEN_CODE_TEST_HOME"),
}
match &self.old_userprofile {
Some(value) => std::env::set_var("USERPROFILE", value),
None => std::env::remove_var("USERPROFILE"),
}
match &self.old_coven_home {
Some(value) => std::env::set_var("COVEN_HOME", value),
None => std::env::remove_var("COVEN_HOME"),
}
match &self.old_user {
Some(value) => std::env::set_var("USER", value),
None => std::env::remove_var("USER"),
Expand Down
40 changes: 40 additions & 0 deletions src-rust/crates/core/src/git_utils.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
//! Git utilities for Coven Code.
//! Mirrors src/utils/git.ts (926 lines) and src/utils/git/ subdirectory.

use crate::hosted_review::CanonicalRepoIdentity;
use sha2::{Digest, Sha256};
use std::path::{Path, PathBuf};
use std::process::Command;

Expand Down Expand Up @@ -49,6 +51,28 @@ pub fn get_current_branch(repo_root: &Path) -> String {
}
}

pub fn get_origin_remote_url(repo_root: &Path) -> Option<String> {
let remote = git_output(repo_root, &["remote", "get-url", "origin"]);
(!remote.is_empty()).then_some(remote)
}

pub fn canonical_repo_identity_from_origin(repo_root: &Path) -> Option<CanonicalRepoIdentity> {
let remote = get_origin_remote_url(repo_root)?;
CanonicalRepoIdentity::from_git_remote_url(&remote)
}

pub fn local_project_id_from_identity(identity: &CanonicalRepoIdentity) -> String {
let mut hasher = Sha256::new();
hasher.update(identity.canonical_string().as_bytes());
let digest = hex::encode(hasher.finalize());
format!("local-git-{}", &digest[..16])
}

pub fn local_project_id_from_origin(repo_root: &Path) -> Option<String> {
canonical_repo_identity_from_origin(repo_root)
.map(|identity| local_project_id_from_identity(&identity))
}

/// Return list of files modified (staged or unstaged).
pub fn list_modified_files(repo_root: &Path) -> Vec<PathBuf> {
let output = git_output(repo_root, &["diff", "--name-only", "HEAD"]);
Expand Down Expand Up @@ -216,4 +240,20 @@ mod tests {
let commits = get_commit_history(Path::new("."), 0);
assert!(commits.is_empty());
}

#[test]
fn local_project_id_normalizes_equivalent_https_and_ssh_remotes() {
let https = CanonicalRepoIdentity::from_git_remote_url(
"https://github.com/OpenCoven/coven-code.git",
)
.unwrap();
let ssh =
CanonicalRepoIdentity::from_git_remote_url("git@github.com:OpenCoven/coven-code.git")
.unwrap();

assert_eq!(
local_project_id_from_identity(&https),
local_project_id_from_identity(&ssh)
);
}
}
Loading