Skip to content

Commit 16c2a5e

Browse files
feat: scaffold adapter interfaces and module boundaries (issue #16)
* feat(rust): introduce domain-adapter-infra module boundaries (issue #16) * test(docs): add module boundary contract checks and architecture notes (issue #16)
1 parent b80071c commit 16c2a5e

27 files changed

+956
-450
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,19 @@ Tauri 2 + React/TypeScript desktop foundation for managing MCP and Skills across
2626
- `src/`: React frontend application.
2727
- `src/backend/`: Typed frontend contracts and Tauri invoke client.
2828
- `src-tauri/`: Rust backend and Tauri application shell.
29+
- `src-tauri/src/domain/`: Domain layer (adapter interfaces, client profiles, capabilities).
30+
- `src-tauri/src/adapters/`: Client-specific adapter implementations.
31+
- `src-tauri/src/infra/`: Infrastructure composition (adapter registry).
32+
- `src-tauri/src/application/`: Use-case service layer consumed by commands.
2933
- `docs/spec/`: MVP requirements and contracts.
34+
- `docs/architecture/`: Module boundary and design documentation.
3035
- `schemas/`: JSON schema contracts for specs.
3136
- `tests/`: Node.js unit tests for spec and project contracts.
3237

3338
## Notes
3439

3540
- The frontend command runner calls typed placeholder backend commands: `detect_clients`, `list_resources`, and `mutate_resource`.
3641
- Command responses use a shared envelope (`ok`, `data`, `error`, `meta`) with lifecycle and operation metadata.
42+
- Backend commands are thin boundaries and delegate to `AdapterService` through `AdapterRegistry`.
3743
- `src-tauri/tauri.conf.json` is configured to use `pnpm` for both dev and build hooks.
3844
- `scripts/ensure-tauri-icon.mjs` generates the required Tauri icon in clean environments.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Module Boundaries (Issue #16)
2+
3+
This project uses explicit backend layers to keep client-specific behavior isolated.
4+
5+
## Layer Responsibilities
6+
7+
- `domain/`
8+
- Owns adapter interface (`ClientAdapter`) and client profile/capability definitions.
9+
- Contains types that describe adapter behavior without binding to concrete client implementations.
10+
- `adapters/`
11+
- Contains concrete adapter implementations for each supported client.
12+
- Current scaffold adapters: Claude Code, Codex CLI, Cursor, Codex App.
13+
- `infra/`
14+
- Provides infrastructure wiring (`AdapterRegistry`) that composes adapters.
15+
- Exposes registry lookup/iteration used by application services.
16+
- `application/`
17+
- Coordinates use-cases (`AdapterService`) and maps adapter outputs to command contracts.
18+
- `commands/`
19+
- Thin Tauri command boundary; handles envelope metadata and delegates business flow.
20+
21+
## Extension Rule
22+
23+
To add a new client adapter, implement `ClientAdapter` in a new file under `adapters/` and register it in `infra/adapter_registry.rs`. Command and UI layers should remain unchanged.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
use crate::{
2+
contracts::{common::ResourceKind, detect::ClientDetection, mutate::MutationAction},
3+
domain::{
4+
AdapterListResult, AdapterMutationResult, CLAUDE_CODE_PROFILE, ClientAdapter, ClientProfile,
5+
},
6+
};
7+
8+
use super::placeholder::{detect_placeholder, list_placeholder, mutate_placeholder};
9+
10+
pub struct ClaudeCodeAdapter;
11+
12+
impl ClaudeCodeAdapter {
13+
pub fn new() -> Self {
14+
Self
15+
}
16+
}
17+
18+
impl ClientAdapter for ClaudeCodeAdapter {
19+
fn profile(&self) -> &'static ClientProfile {
20+
&CLAUDE_CODE_PROFILE
21+
}
22+
23+
fn detect(&self, include_versions: bool) -> ClientDetection {
24+
detect_placeholder(self.profile(), include_versions)
25+
}
26+
27+
fn list_resources(&self, resource_kind: ResourceKind) -> AdapterListResult {
28+
list_placeholder(self.profile(), resource_kind)
29+
}
30+
31+
fn mutate_resource(&self, action: MutationAction, target_id: &str) -> AdapterMutationResult {
32+
mutate_placeholder(self.profile(), action, target_id)
33+
}
34+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
use crate::{
2+
contracts::{common::ResourceKind, detect::ClientDetection, mutate::MutationAction},
3+
domain::{
4+
AdapterListResult, AdapterMutationResult, CODEX_APP_PROFILE, ClientAdapter, ClientProfile,
5+
},
6+
};
7+
8+
use super::placeholder::{detect_placeholder, list_placeholder, mutate_placeholder};
9+
10+
pub struct CodexAppAdapter;
11+
12+
impl CodexAppAdapter {
13+
pub fn new() -> Self {
14+
Self
15+
}
16+
}
17+
18+
impl ClientAdapter for CodexAppAdapter {
19+
fn profile(&self) -> &'static ClientProfile {
20+
&CODEX_APP_PROFILE
21+
}
22+
23+
fn detect(&self, include_versions: bool) -> ClientDetection {
24+
detect_placeholder(self.profile(), include_versions)
25+
}
26+
27+
fn list_resources(&self, resource_kind: ResourceKind) -> AdapterListResult {
28+
list_placeholder(self.profile(), resource_kind)
29+
}
30+
31+
fn mutate_resource(&self, action: MutationAction, target_id: &str) -> AdapterMutationResult {
32+
mutate_placeholder(self.profile(), action, target_id)
33+
}
34+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
use crate::{
2+
contracts::{common::ResourceKind, detect::ClientDetection, mutate::MutationAction},
3+
domain::{
4+
AdapterListResult, AdapterMutationResult, CODEX_CLI_PROFILE, ClientAdapter, ClientProfile,
5+
},
6+
};
7+
8+
use super::placeholder::{detect_placeholder, list_placeholder, mutate_placeholder};
9+
10+
pub struct CodexCliAdapter;
11+
12+
impl CodexCliAdapter {
13+
pub fn new() -> Self {
14+
Self
15+
}
16+
}
17+
18+
impl ClientAdapter for CodexCliAdapter {
19+
fn profile(&self) -> &'static ClientProfile {
20+
&CODEX_CLI_PROFILE
21+
}
22+
23+
fn detect(&self, include_versions: bool) -> ClientDetection {
24+
detect_placeholder(self.profile(), include_versions)
25+
}
26+
27+
fn list_resources(&self, resource_kind: ResourceKind) -> AdapterListResult {
28+
list_placeholder(self.profile(), resource_kind)
29+
}
30+
31+
fn mutate_resource(&self, action: MutationAction, target_id: &str) -> AdapterMutationResult {
32+
mutate_placeholder(self.profile(), action, target_id)
33+
}
34+
}

src-tauri/src/adapters/cursor.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
use crate::{
2+
contracts::{common::ResourceKind, detect::ClientDetection, mutate::MutationAction},
3+
domain::{
4+
AdapterListResult, AdapterMutationResult, CURSOR_PROFILE, ClientAdapter, ClientProfile,
5+
},
6+
};
7+
8+
use super::placeholder::{detect_placeholder, list_placeholder, mutate_placeholder};
9+
10+
pub struct CursorAdapter;
11+
12+
impl CursorAdapter {
13+
pub fn new() -> Self {
14+
Self
15+
}
16+
}
17+
18+
impl ClientAdapter for CursorAdapter {
19+
fn profile(&self) -> &'static ClientProfile {
20+
&CURSOR_PROFILE
21+
}
22+
23+
fn detect(&self, include_versions: bool) -> ClientDetection {
24+
detect_placeholder(self.profile(), include_versions)
25+
}
26+
27+
fn list_resources(&self, resource_kind: ResourceKind) -> AdapterListResult {
28+
list_placeholder(self.profile(), resource_kind)
29+
}
30+
31+
fn mutate_resource(&self, action: MutationAction, target_id: &str) -> AdapterMutationResult {
32+
mutate_placeholder(self.profile(), action, target_id)
33+
}
34+
}

src-tauri/src/adapters/mod.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
mod claude_code;
2+
mod codex_app;
3+
mod codex_cli;
4+
mod cursor;
5+
mod placeholder;
6+
7+
pub use claude_code::ClaudeCodeAdapter;
8+
pub use codex_app::CodexAppAdapter;
9+
pub use codex_cli::CodexCliAdapter;
10+
pub use cursor::CursorAdapter;
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
use crate::{
2+
contracts::{
3+
common::ResourceKind,
4+
detect::{ClientDetection, DetectionEvidence, DetectionStatus},
5+
mutate::MutationAction,
6+
},
7+
domain::{AdapterListResult, AdapterMutationResult, ClientProfile},
8+
};
9+
10+
pub fn detect_placeholder(
11+
profile: &'static ClientProfile,
12+
include_versions: bool,
13+
) -> ClientDetection {
14+
ClientDetection {
15+
client: profile.kind,
16+
status: DetectionStatus::Absent,
17+
evidence: DetectionEvidence {
18+
binary_path: None,
19+
config_path: None,
20+
version: include_versions.then_some("not_collected".to_string()),
21+
},
22+
note: format!(
23+
"{} adapter scaffold is ready. Detection implementation will be added in issue #19/#20.",
24+
profile.display_name
25+
),
26+
}
27+
}
28+
29+
pub fn list_placeholder(
30+
profile: &'static ClientProfile,
31+
resource_kind: ResourceKind,
32+
) -> AdapterListResult {
33+
AdapterListResult {
34+
items: Vec::new(),
35+
warning: Some(format!(
36+
"{} listing for '{}' is not implemented yet.",
37+
resource_kind.as_str(),
38+
profile.key
39+
)),
40+
}
41+
}
42+
43+
pub fn mutate_placeholder(
44+
profile: &'static ClientProfile,
45+
action: MutationAction,
46+
target_id: &str,
47+
) -> AdapterMutationResult {
48+
AdapterMutationResult {
49+
accepted: false,
50+
message: format!(
51+
"Mutation '{}' for '{}' on '{}' is not implemented yet.",
52+
action.as_str(),
53+
target_id,
54+
profile.key
55+
),
56+
}
57+
}

0 commit comments

Comments
 (0)