> ## Documentation Index
> Fetch the complete documentation index at: https://prism.ntecdev.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# SPEC-017 v1.0: prism_clone — cross-machine clone + register + sync (host-side MCP verb)

> prism_clone — cross-machine clone + register + sync (host-side MCP verb)

<Info>**Status:** `accepted` · **Version** `1.0` · Filed 2026-04-18</Info>

## SPEC-017 — prism\_clone

One MCP verb that takes a project name and optional github\_repo, auto-detects clone-vs-register-vs-bind, and terminates at the Plan #5 core invariant:

```
dir_exists($PROJECT_ROOT/<name>)            AND
(optional) git_repo($PROJECT_ROOT/<name>)    AND
prism_pid_exists(<name>, project_dir)        AND
mcp_allowlist_contains($PROJECT_ROOT/<name>) AND
bios_synced(<pid>)
```

### Runs host-side (not in the backend)

The backend runs in a Docker container in personal mode and cannot write to the host filesystem for cloning a NEW project outside the mounted repo. Therefore:

* git clone executes on the host (same process as the MCP server).
* Legacy-project detection probes the host filesystem.
* Prism-side registration (bootstrap\_project, register\_project\_paths, prism\_sync\_bios) is delegated to existing verbs.

This mirrors the `_execute_manifest` pattern from `bootstrap_project` — backend computes, host applies.

### Signature

```python theme={null}
prism_clone(
    name: str,
    github_repo: str | None = None,
    project_dir: str | None = None,   # defaults to $PROJECT_ROOT/<name>
    ptype: str = "application",
    branch: str | None = None,         # optional checkout after clone
    persona: str = "Lola",
) -> dict
```

### Mode detection

Evaluate `project_dir` on the host:

| Dir state         | github\_repo | Mode     | Action                                                                                                          |
| ----------------- | ------------ | -------- | --------------------------------------------------------------------------------------------------------------- |
| exists + has .git | any          | register | bootstrap\_project(strategy="register" if Prism-structured else "bind")                                         |
| exists + no .git  | any          | ERROR    | refuse — "directory exists but is not a git repo; won't auto-init"                                              |
| not exists        | present      | clone    | host `git clone <repo> <project_dir>` → bootstrap\_project(strategy="register" if Prism-structured else "bind") |
| not exists        | absent       | ERROR    | refuse — "no directory and no repo given; nothing to do"                                                        |

"Prism-structured" = has `PRISM.md` OR `context/_ACTIVE_CONTEXT.md` OR `plans/_TODO.md` at repo root.

### Terminal steps (all modes, unconditional on success)

1. `register_project_paths(project_dir)` — idempotent allowlist injection.
2. `prism_sync_bios(pid, files="all", dry_run=False, force=False)` — best-effort; if PRISM.md pre-flight blocks, surface in response but don't fail the whole call.
3. Legacy-project detection — returns `migration_candidate: bool` + `legacy_signatures: list[str]`.

### Return payload

```json theme={null}
{
  "pid": "PID-XYZ01",
  "project_dir": "/abs/path",
  "mode": "clone" | "register" | "bind",
  "git_result": { ... },
  "bootstrap_result": { ... },
  "paths_result": { ... },
  "sync_result": { "synced": [...], "skipped": [...], "errors": [...] },
  "migration_candidate": false,
  "legacy_signatures": []
}
```

### Legacy-project signature probes (Wave A stub — Wave B does full mapper)

Return `migration_candidate=True` if ANY of these are found at `project_dir`:

* `context/_ACTIVE_CONTEXT.md`
* `plans/_TODO.md`
* `plans/_DELTAS.md`
* `AGENTS.md` containing the regex `r"^# AGENTS\.md\s*$"` as a legacy AGENTS.md header signature
* A sibling Obsidian vault at `<project_dir>/../<name>Vault/` or `<project_dir>/.obsidian/`

Each probed path that hit is returned in `legacy_signatures`. Wave A does NOT read the content or produce a migration plan.

### Error shaping

* `DirExistsNotGit`: dir present, no `.git`. Agent should escalate to the user.
* `NothingToClone`: dir absent and github\_repo absent.
* `GitCloneFailed`: propagate git's stderr in the error message.
* `BackendError`: wrap any HTTPError from the backend with the call that failed.

### Smoke battery (Wave A exit gate)

1. `fresh_clone` — target dir absent, github\_repo given, small repo. Expect mode=clone, PID created, paths registered, bios synced, migration\_candidate=False.
2. `register_existing` — target dir is a Prism project already on disk. Expect mode=register, PID resolved (or created via bootstrap register), allowlist updated, bios re-synced.
3. `bind_non_prism` — target dir is a git repo with no Prism structure. Expect mode=bind, PID minted, scaffold manifest written, bios synced.
4. `error_exists_non_git` — target dir exists, no `.git`. Expect `DirExistsNotGit`. No side effects.

### Exit gate for Wave A

All 4 smoke cases green; verb callable end-to-end from Claude Desktop and Claude Code MCP surfaces; terminal-state invariant verified by post-call `prism_start(pid)` cold read.

### Frank-decisions NOT silently resolved

* FD-1 (Wave B): write path stays behind explicit user prompt, not auto-executed. (Affects Wave B migration integration — not Wave A.)
* FD-2/FD-3: n/a for Wave A.

### Dependencies

* Existing: `bootstrap_project`, `register_project_paths`, `prism_sync_bios` (all shipped).
* New: host-side git subprocess, legacy-project detector stub, mode-detection branching.
* Not new: no backend schema change, no new route, no alembic migration for Wave A.
