Skip to content
Merged
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
36 changes: 36 additions & 0 deletions docs/primary-mirror-architecture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Primary Mirror Architecture

## Problem

DMC primary checkouts currently serve three roles at once: default-branch reference, mutable working tree, and base for creating task worktrees. That makes stale reads and accidental default-branch mutations likely.

## Target Model

- A primary mirror is DMC-owned reference state for a remote default branch.
- Agents do feature, release, and repair work only in managed `repo@slug` worktrees.
- Mirror refresh is a narrow operation: fetch and fast-forward to the tracked remote ref.
- Dangerous operations on mirrors are blocked by default and require explicit operator approval.
- Read surfaces fail closed when a mirror is stale, diverged, detached, or otherwise unsafe unless the caller opts in with stale-read metadata.

## Migration Plan

1. Keep existing primary directories as the compatibility mirror path.
2. Enforce tactical safety first: separate primary refresh from dangerous primary mutation, and guard stale primary reads.
3. Add mirror metadata to each primary row: remote URL, tracked ref, last refresh result, freshness state, and whether local commits/dirty state block automated conversion.
4. For clean/current primaries, mark them `mirror_compatible` and allow DMC to refresh them as mirrors.
5. For dirty/diverged primaries, keep them read-protected and report a non-destructive remediation plan: preserve local commits in a worktree, push/open PR if needed, or archive before mirror conversion.
6. After migration, create release/default-branch work via explicit managed worktrees, not the mirror.

## Command Shape

- Investigation: `workspace show <repo>` then `workspace read <repo>@<slug> ...` or `workspace read <repo> ... --allow-stale-primary` when intentionally inspecting stale local state.
- Refresh: `workspace git pull <repo> --allow-primary-refresh`.
- Feature work: `workspace worktree add <repo> <branch> --from=origin/<base>`.
- Dangerous primary repair: `workspace git <commit|push|reset|rebase> <repo> --allow-dangerous-primary-mutation` with operator approval.

## Open Decisions

- Whether mirrors should become bare repositories or remain non-bare worktrees with stricter DMC-owned metadata.
- Whether stale primary reads should stay fail-closed permanently or return warnings for privileged UI callers.
- How long to keep compatibility for `--allow-primary-mutation` as a pull alias.
- Whether release checkouts need a distinct lifecycle state separate from normal task worktrees.
138 changes: 80 additions & 58 deletions inc/Abilities/WorkspaceAbilities.php

Large diffs are not rendered by default.

84 changes: 57 additions & 27 deletions inc/Cli/Commands/WorkspaceCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -1910,11 +1910,14 @@ public function show( array $args, array $assoc_args ): void {
* default: 1048576
* ---
*
* [--offset=<line>]
* : Line number to start reading from (1-indexed).
*
* [--limit=<lines>]
* : Maximum number of lines to return.
* [--offset=<line>]
* : Line number to start reading from (1-indexed).
*
* [--limit=<lines>]
* : Maximum number of lines to return.
*
* [--allow-stale-primary]
* : Explicitly read from a stale, diverged, detached, or otherwise unsafe primary checkout.
*
* ## EXAMPLES
*
Expand Down Expand Up @@ -1958,6 +1961,10 @@ public function read( array $args, array $assoc_args ): void {
$input['limit'] = (int) $assoc_args['limit'];
}

if ( ! empty($assoc_args['allow-stale-primary']) ) {
$input['allow_stale_primary'] = true;
}

$result = $ability->execute($input);

if ( is_wp_error($result) ) {
Expand All @@ -1980,12 +1987,15 @@ public function read( array $args, array $assoc_args ): void {
* <repo>
* : Repository directory name.
*
* [<path>]
* : Relative directory path within the repo (defaults to root).
*
* [--format=<format>]
* : Output format.
* ---
* [<path>]
* : Relative directory path within the repo (defaults to root).
*
* [--allow-stale-primary]
* : Explicitly list a stale, diverged, detached, or otherwise unsafe primary checkout.
*
* [--format=<format>]
* : Output format.
* ---
* default: table
* options:
* - table
Expand Down Expand Up @@ -2025,6 +2035,10 @@ public function ls( array $args, array $assoc_args ): void {
$input['path'] = $args[1];
}

if ( ! empty($assoc_args['allow-stale-primary']) ) {
$input['allow_stale_primary'] = true;
}

$result = $ability->execute($input);

if ( is_wp_error($result) ) {
Expand Down Expand Up @@ -2079,15 +2093,18 @@ function ( $entry ) {
* default: 100
* ---
*
* [--context-lines=<count>]
* : Number of surrounding lines to include for each match.
* ---
* default: 0
* ---
*
* [--format=<format>]
* : Output format.
* ---
* [--context-lines=<count>]
* : Number of surrounding lines to include for each match.
* ---
* default: 0
* ---
*
* [--allow-stale-primary]
* : Explicitly grep a stale, diverged, detached, or otherwise unsafe primary checkout.
*
* [--format=<format>]
* : Output format.
* ---
* default: table
* options:
* - table
Expand Down Expand Up @@ -2135,6 +2152,10 @@ public function grep( array $args, array $assoc_args ): void {
$input['context_lines'] = (int) $assoc_args['context-lines'];
}

if ( ! empty($assoc_args['allow-stale-primary']) ) {
$input['allow_stale_primary'] = true;
}

$result = $ability->execute($input);
if ( is_wp_error($result) ) {
WP_CLI::error($result->get_error_message());
Expand Down Expand Up @@ -2601,11 +2622,17 @@ private function resolveAtFile( string $value ): string {
* : Relative path (repeatable) for add/diff operations. Named `--rel`
* to avoid colliding with WP-CLI's documented global `--path` flag.
*
* [--allow-dirty]
* : Allow pull with dirty working tree.
*
* [--allow-primary-mutation]
* : Permit mutating ops (pull/add/commit/push) on the primary checkout. Default-deny — use a worktree handle (`<repo>@<branch-slug>`) instead whenever possible.
* [--allow-dirty]
* : Allow pull with dirty working tree.
*
* [--allow-primary-refresh]
* : Permit safe primary refresh with `git pull --ff-only`.
*
* [--allow-primary-mutation]
* : Legacy alias for `--allow-primary-refresh` on `git pull`, and for non-dangerous primary file/index mutations. Does not permit primary commit, push, reset, or rebase.
*
* [--allow-dangerous-primary-mutation]
* : Permit primary commit, push, reset, or rebase. Use only for an explicitly approved primary mutation.
*
* [--remote=<remote>]
* : Remote name for pull/push (default: origin).
Expand Down Expand Up @@ -2687,8 +2714,11 @@ public function git( array $args, array $assoc_args ): void {

$input = array( 'name' => $repo );

// Mutating ops accept --allow-primary-mutation to operate on a primary checkout.
if ( in_array($operation, array( 'pull', 'add', 'commit', 'push', 'rebase', 'reset', 'pr-rebase' ), true) ) {
if ( 'pull' === $operation ) {
$input['allow_primary_refresh'] = ! empty($assoc_args['allow-primary-refresh']) || ! empty($assoc_args['allow-primary-mutation']);
} elseif ( in_array($operation, array( 'commit', 'push', 'rebase', 'reset', 'pr-rebase' ), true) ) {
$input['allow_dangerous_primary_mutation'] = ! empty($assoc_args['allow-dangerous-primary-mutation']);
} elseif ( in_array($operation, array( 'add' ), true) ) {
$input['allow_primary_mutation'] = ! empty($assoc_args['allow-primary-mutation']);
}

Expand Down
4 changes: 2 additions & 2 deletions inc/Runtime/AgentsMdSections.php
Original file line number Diff line number Diff line change
Expand Up @@ -144,8 +144,8 @@ private static function register_datamachine_section( string $wp ): void {
- **GitHub:** `{$wp} datamachine-code github {$github_subcmds}` — list/read GitHub state, manage issues and PRs, install review flows, and comment on reviews.
- **Editing inside a worktree:** any tool. Local agents on the same disk should use native file I/O and raw `git`; routing edits through workspace abilities is ceremony, not safety.
- **Workspace lifecycle:** use `workspace clone` for primary checkout adoption/cloning and `workspace worktree add` for isolated branches. Use the CLI `--help` output for current flags and subcommands.
- **Primary freshness:** before using a primary checkout for investigation or verification, inspect `workspace list|show|hygiene` freshness metadata. If the primary is stale, run `workspace git pull <repo> --allow-primary-mutation` or create the worktree from an explicit remote ref with `worktree add <repo> <branch> --from=origin/<base>`. Do not clone a second top-level primary for the same remote just to get fresh code.
- **Primary is read-only.** Never edit `<workspace>/<repo>` (no `@slug`). Mutating ops on bare `<repo>` handles via the CLI require `--allow-primary-mutation`. The primary tracks the deployed branch — operate on a worktree.
- **Primary freshness:** before using a primary checkout for investigation or verification, inspect `workspace list|show|hygiene` freshness metadata. If the primary is stale, run `workspace git pull <repo> --allow-primary-refresh` or create the worktree from an explicit remote ref with `worktree add <repo> <branch> --from=origin/<base>`. Stale primary reads require an explicit `--allow-stale-primary` opt-in. Do not clone a second top-level primary for the same remote just to get fresh code.
- **Primary is read-only.** Never edit `<workspace>/<repo>` (no `@slug`). Safe primary refresh uses `--allow-primary-refresh`; primary commit, push, reset, and rebase require the stronger `--allow-dangerous-primary-mutation` approval. The primary tracks the deployed branch — operate on a worktree.
- **Rule:** Never modify files under `wp-content/plugins/` or `wp-content/themes/` directly. Those paths are **read-only reference**. All code changes go through the workspace so they are tracked in git and reviewed via pull requests.

**System:** `{$wp} datamachine system health|prompts|run` — site health, prompt inspection, diagnostic runs.
Expand Down
Loading
Loading