Skip to content

feat(workspace): add delete primitive (workspace-delete ability + CLI) #64

@chubes4

Description

@chubes4

Gap

workspace has read, write, edit, clone, plus the git and worktree op groups, but no file/directory delete primitive. Agents that need to remove tracked files have to drop out of the workspace API and run git rm or rm -rf directly on disk, which defeats the point of the workspace abstraction (containment checks, sensitive-path filter, allowlist enforcement, primary-mutation gating).

This came up in a homeboy scope-reduction PR (Extra-Chill/homeboy, branch scope-reduction-cut-synthesis) that needs to delete five files and one directory tree (~5,500 LOC of synthesis code). Per the host site's three-step rule, fixing the upstream gap before papering over it.

Proposed shape

New ability datamachine/workspace-delete mirroring workspace-write conventions, plus a wp datamachine-code workspace delete <repo> <path> CLI subcommand.

Ability input schema

{
  repo: string,                     // <repo> | <repo>@<branch-slug>
  path: string,                     // relative path within the repo (file OR directory)
  recursive?: bool = false,         // required when target is a directory
  allow_primary_mutation?: bool = false
}

Behaviour

  1. Resolve repo path via Workspace::resolve_repo_path.
  2. Reject path traversal (has_traversal), sensitive paths (is_sensitive_path), and allowlist violations (get_repo_allowed_paths / is_path_allowed) — same checks as git_add.
  3. Gate primary-mutation via Workspace::ensure_primary_mutation_allowed (consistent with git_add/git_commit).
  4. If the path is tracked by git: git rm [-r] -- <path> so the deletion is staged. Use --cached? No — full removal from working tree + index, matching what callers actually want.
  5. If the path is untracked: filesystem unlink (file) or recursive rmdir (directory) — only when recursive=true for directories.
  6. Containment check after the operation: confirm realpath(parent) is still inside repo_path (defensive against symlink shenanigans, even though git rm shouldn't create new paths).

Ability output schema

{
  success: bool,
  path: string,
  deleted: string[],     // for recursive deletes: every removed path (relative)
  was_tracked: bool      // git rm vs filesystem path
}

CLI

wp datamachine-code workspace delete <repo> <path> [--recursive] [--allow-primary-mutation]

Same argument parsing pattern as workspace edit / workspace write.

Why a single primitive (not two)

Splitting into delete-file + delete-directory doubles the surface for no gain. recursive flag covers the directory case and makes accidental recursive deletes opt-in (rejecting directories without recursive=true matches POSIX rm semantics).

Out of scope

  • No glob/pattern support — agents enumerate paths themselves. Consistent with git_add taking explicit paths.
  • No "soft delete" / trash semantics — git history is the undo path.
  • No batch ability workspace-delete-many — agents can call once per path. If batch becomes a real need, add later.

Testing

  • Unit-equivalent smoke tests via wp datamachine-code workspace delete against a throwaway worktree (the Playwright-style "real workspace, real disk" approach DMC already uses).
  • Verify primary-mutation gate: delete <repo> (bare, no @) without --allow-primary-mutation returns the same error as git_add does today.
  • Verify allowlist enforcement: configure datamachine_workspace_allowed_paths and confirm out-of-allowlist deletes return path_not_allowed.
  • Verify sensitive-path rejection: deleting .env / wp-config.php returns sensitive_path.
  • Verify tracked vs untracked paths produce correct was_tracked output.

AI assistance

  • AI assistance: Yes
  • Tool(s): Claude Code (Sonnet 4.5)
  • Used for: Drafting the issue from a homeboy scope-reduction call-graph audit. Chris reviewed the gap and authorized the upstream-first fix.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions