Managed project cloning
Canonical Quirl project documentation synced from docs/decisions/2026-09-06_192326220_managed-project-cloning.md.
Accepted · 2026-09-06
Status: Accepted (interactive Unix and explicit CLI scope). Date: 2026-09-06 (derived from the commit that introduced this entry; the source record carried no date).
Decision
Extend Quirl's existing Projects feature with quirl projects clone <repository> [--root path] and quirl projects root [--root path]. Use the GHQ-compatible
<root>/<host>/<repository-path> layout, retaining nested namespaces. Each
checkout is an independent Git repository. Resolve the root from an explicit
option, GHQ_ROOT, Git's ghq.root configuration, then ~/Projects, in that
order. With multiple ghq.root values, reuse a matching checkout in any configured
root; otherwise create the checkout under the first root. quirl projects root
prints that first root. No GHQ executable or short command alias is required.
In rich interactive Normal mode, recognize only a standalone, literal git clone URL without options, redirections, a destination, or additional commands. Before
execution, offer the concrete managed destination. The original command remains
the default; changing its destination requires an explicit selection. Users can
choose a managed location once, opt in to using managed locations for future
eligible clones, or dismiss future suggestions. Store this preference separately
from executable Lua configuration. quirl projects policy [ask|managed|off]
prints the saved setting or changes it. Missing state defaults to ask.
Choosing the original command, managing once, or disabling suggestions saves
off; cancelling leaves ask. managed requires explicit opt-in. The preference
lives in $XDG_STATE_HOME/quirl/clone-policy.json, falling back to
~/.local/state/quirl/clone-policy.json. Scripts and explicit destinations preserve
ordinary Git semantics.
The CLI owns orchestration, preference persistence, and the existing project
index. Git remains responsible for cloning and authentication, using the normal
foreground execution path. Successful clones and validated existing checkouts
become immediately available to the project picker. Opening a project is a
separate explicit Alt-Q u action that preserves unfinished input; a subprocess
cannot change its parent's directory.
Failure model and invariants
Remote text, environment, Git configuration, persisted preferences, filesystem entries, and subprocess results are untrusted inputs. A destination can already exist or change between inspection and cloning. Git can fail after creating partial data. Index publication and preference persistence can fail independently of a successful clone. SIGINT or SIGTERM may arrive during a metadata probe or while the chooser owns terminal input. Cancellation or terminal failure must retain ordinary foreground child ownership and restore the editor.
- Parse supported HTTP(S), SSH, and scp-style remotes into a validated host and
complete repository path. Strip only the conventional final
.gitsuffix. Nondefault ports use ahost__port_NNNsuffix; input host underscores are rejected so they cannot collide with this suffix. SSH aliases retain their supplied identity rather than consulting SSH configuration. Reject traversal, empty components, controls, percent encodings, query strings, IPv6 literals, embedded HTTP credentials, unsupported transports and local paths before any filesystem mutation. Remote text is never shell syntax and is never evaluated a second time. - Resolve roots with bounded configuration reads. Canonicalize the selected existing root, or its nearest existing ancestor when creating a new root, as the user's trusted location. Reject symlinks in managed descendants. Git's pathname-based subprocess boundary assumes trusted ancestors remain unchanged while Git runs; it cannot prevent arbitrary same-user ancestor replacement. A configured root is a parent directory, never a request to combine independent projects into one Git repository.
- Never overwrite an existing destination. Reuse a checkout only after verifying its Git marker and matching origin identity. A conflicting checkout, directory, or file returns an actionable error. Reuse never pulls, resets, or checks out a branch. Reserve a new final directory with atomic creation before starting Git. Creation races remain errors; no error path recursively removes data.
- Run Git through the existing supervised process boundary. Preserve its exit status and authentication interaction. Report partial clone data without silently retrying or deleting it.
- A scoped signal guard owns SIGINT/SIGTERM cancellation across metadata probes and the chooser. Handlers only record cancellation; the supervised execution boundary terminates and reaps probe children. The chooser checks the same token every bounded 16 ms turn. A cancelled probe cannot fall back to executing the original clone, and cancellation cannot accept a managed destination.
- Publish cancellation as visible status 130 and retain the last user command's
status across prompt turns. Before executing user source, the CLI explicitly
seeds the native executor with the bounded shell status. Internal metadata
probes never publish their status into the interactive shell's
$?. - Publish a project only after validating a successful checkout. A cache failure must not turn a completed clone into an invitation to overwrite or reclone it.
- The recognizer excludes expansions, aliases, wrappers, compound commands,
options, redirections, and explicit destinations. Explicit executable paths and
forced external commands such as
/usr/bin/git cloneand^git clonealso execute unchanged. Only baregitand canonical literalquirl projectscommands enter the corresponding interactive workflows. Only interactive rich Normal-mode execution offers suggestions. - Persist only a strict, versioned preference of at most 4 KiB, with bounded reads, private atomic replacement, and conflict detection against concurrent updates. Invalid state cannot enable automatic managed cloning. Cancelling an offer must not opt in or execute a different command.
Resource sketch
Remote and root strings are at most 4,096 bytes. Each remote component is at most 255 bytes, with at most 32 remote components and 32 absolute-root components. Git root configuration admits at most 16 values. Canonical roots are revalidated against the same byte and depth limits; the combined destination is at most 4,096 bytes. All configuration and checkout probes for one plan share one absolute 2-second deadline. Reservation revalidation and post-clone verification each have a separate 2-second budget. Each probe captures at most 16 KiB per output stream. Preference state is at most 4 KiB. The chooser admits at most one input event per 16 ms turn and observes cancellation each turn. No repository scan or remote network lookup runs per keystroke. The existing project cache owns bounded publication and background discovery. Interactive Git execution has the same foreground lifecycle and cancellation behavior as other terminal programs, without an overall clone wall deadline.
Evidence required
Unit tests cover supported remote transports and nested namespaces, invalid and oversized paths, root precedence, existing matching and conflicting checkouts, symlink boundaries, malformed preferences, and recognition exclusions. Process fixtures prove clone failure and cancellation leave existing data intact. An isolated signal fixture sends SIGINT only after a metadata child starts, then requires a cancelled result, child cleanup, and no clone execution. Chooser tests require already-cancelled input to acquire no terminal and never accept a destination. Interactive journeys prove the default preserves the original command, opting in changes only eligible clones, successful cloning reaches the picker immediately, and opening restores unfinished shell input correctly. Catalog tests keep the CLI, help, completion, and generated command reference aligned.
Embedded foreground terminals
Canonical Quirl project documentation synced from docs/decisions/2026-09-06_192326212_embedded-foreground-terminals.md.
Complete like Zsh and ask Zsh for arguments
Canonical Quirl project documentation synced from docs/decisions/2026-09-30_002231168_complete-like-zsh-and-ask-zsh-for-arguments.md.