Skip to main content
Status: accepted · Version 1.0 · Filed 2026-04-18

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

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 stategithub_repoModeAction
exists + has .gitanyregisterbootstrap_project(strategy=“register” if Prism-structured else “bind”)
exists + no .gitanyERRORrefuse — “directory exists but is not a git repo; won’t auto-init”
not existspresentclonehost git clone <repo> <project_dir> → bootstrap_project(strategy=“register” if Prism-structured else “bind”)
not existsabsentERRORrefuse — “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

{
  "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.
Last modified on April 22, 2026