Back to home@Grove-ovo

dsh-stack

Enforced safety rails for stacked PRs in DeepSeek Harness — every sync, land, and cleanup is guarded, and "tests passed" is proven per commit with SHA-bound validation records.

Stars
0
Language
TypeScript
Created
Sep 10, 2026
Updated
Sep 10, 2026
GitHub repo

Introduction

dsh-stack

The Safety Rail & State Machine for GitHub Stacked PR Workflows in DeepSeek Harness.

License: MIT Cordis: Plugin Host: DeepSeek%20Harness

English | 中文

dsh-stack is a Cordis plugin for DeepSeek Harness that enforces safety policies for multi-PR dependency workflows (Stacked PRs) within the dsh stack commands. Every sync, land, and cleanup goes through guards that are enforced by default and admit only explicit, narrowly-scoped exemptions (see --self-approve); SHA-bound validation records make "tested once" provable per commit. This is defense in depth on top of — not a replacement for — GitHub branch-protection rules (required checks, stale-review dismissal), which remain the enforcement layer for other tools and the web UI.


📖 The 30-Second Elevator Pitch: Understanding Stacked PRs with a 3-Story House

Imagine constructing a 3-story building with a stacked PR workflow:

  • Floor 1 (PR #101): Database schema & migrations
  • Floor 2 (PR #102): Backend API endpoints (built upon Floor 1)
  • Floor 3 (PR #103): Frontend UI dashboard (built upon Floor 2)
Trunk (master)
└── PR #101 (feat/auth-db)
    └── PR #102 (feat/auth-api)
        └── PR #103 (feat/auth-ui)

The Real-World Disaster

When Floor 1 requires revisions during Code Review:

  1. Modifying Floor 1 changes the foundation commit.
  2. Running gh stack rebase / gh stack push then rewrites the affected upper commits and updates the remote branches.
  3. The Fatal Blindspot: Engineers see the green "Sync Success" notification and reflexively click Merge.
  4. The Disaster: Because commit SHAs changed, previous CI runs no longer attest the new HEAD (GitHub requires checks on the latest commit). Depending on repository rules (stale-review dismissal, "require approval of the most recent push"), existing review approvals may also lapse or require re-approval. If repository protection rules do not cover this gap, the new HEAD risks being merged into master without adequate re-validation.

How dsh-stack Saves You

GitHub's native gh stack provides the plumbing — gh stack rebase (cascade rebase after a base layer changes), gh stack push, and gh stack sync (fetch, reconcile, rebase if the trunk moved, and sync PR state). dsh-stack is the on-site safety inspector on top of that plumbing: it halts operations when the local working tree is dirty, re-runs local test suites on every layer touched by a rebase and binds the result to the exact commit tested, warns that approvals may need re-validation under your repository rules after SHA rewrites, polls GitHub for MERGED confirmation, and deletes branches only when the dependent count is verified to be zero — conditionally, so a branch that moved is retained.


⚡ Quick Start & Prerequisites

1. Prerequisites

dsh-stack works on top of the official GitHub CLI and gh-stack extension:

# 1. Ensure GitHub CLI is installed (>= 2.90.0 required)
gh --version

# 2. Install official GitHub gh-stack extension (>= 0.1.0 required)
gh extension install github/gh-stack

# 3. Authenticate with GitHub
gh auth login

[!NOTE] When running with a locally built DeepSeek Harness source checkout, ensure the monorepo has been built (pnpm run build) before running pnpm dsh web so that client web bundles are present.

2. Installing into DeepSeek Harness

From npm (recommended — prebuilt, no build permission needed):

dsh plugin --profile default add dsh-stack

From GitHub (installs sources and builds via prepare):

dsh plugin --profile default add github:Grove-ovo/dsh-stack

[!NOTE] pnpm ≥ 10 blocks a git dependency's build script until allowlisted. If the first add fails, copy the exact package key pnpm prints into allowBuilds under your profile's pnpm-workspace.yaml ($DSH_HOME/profiles/default/pnpm-workspace.yaml) and re-run. Pin a commit (github:Grove-ovo/dsh-stack#<sha>) for reproducible installs.

From a local checkout (development):

git clone https://github.com/Grove-ovo/dsh-stack.git
cd dsh-stack && pnpm install   # `prepare` builds lib/
dsh plugin --profile default add ./dsh-stack

Configuring validation (optional):

By default every layer is validated with pnpm test (falling back to npm test). Projects using another runner configure it in the profile's cordis.patch.yml ($DSH_HOME/profiles/default/cordis.patch.yml):

- replace:
    - id: stack
      config:
        validationCommand: ['pytest', '-q']     # executable + args as an array
        validationTimeoutMs: 600000             # default 120000

Records are fingerprinted by the command: changing validationCommand invalidates previous validation records, so a layer must be re-verified under the new command before it can land.

Any install path activates the bundle automatically (the dsh.bundle manifest joins the profile layer stack), and dsh plugin --profile default remove dsh-stack removes it. Verify the composed profile:

dsh --profile default --dump-config | grep stack

🛡️ The 8-Point Guardrail System

dsh-stack enforces eight guard assertions across the PR lifecycle — binding by default for every operation routed through the plugin's commands, with only explicit, narrowly-scoped exemptions:

┌─────────────────────────────────────────────────────────────────────────┐
│                        dsh-stack Guard System                           │
├───────────────┬─────────────────────────────────────────────────────────┤
│ 1. G-ENV      │ Environment & Auth Guard: gh >= 2.90.0, extension >= 0.1.0│
│ 2. G-TREE     │ Clean Worktree Guard: git status --porcelain pre-flight │
│ 3. G-TOPO     │ Topology DAG Guard: no cross-forks, cycles, or orphans  │
│ 4. G-TEST     │ Rewritten Layer Guard: mandatory local test execution   │
│ 5. G-VERIFY   │ Validation-Record Guard: SHA-bound proof per layer      │
│ 6. G-LAND     │ Landing Eligibility Guard: CI green & Review Approved    │
│ 7. G-POLL     │ Merge Confirmation Guard: active polling of MERGED state│
│ 8. G-DEL      │ Zero-Dependent Cleanup: strictly 0 open dependents      │
└───────────────┴─────────────────────────────────────────────────────────┘
  1. G-ENV (Environment & Auth Guard): Validates GitHub CLI ($\ge 2.90.0$), gh-stack extension ($\ge 0.1.0$), and active GraphQL authentication. Intercepts CLI recommendation prompts to prevent false-ready states.
  2. G-TREE (Clean Worktree Guard): Pre-flight check via git status --porcelain. Aborts synchronization before rebasing if uncommitted modifications exist.
  3. G-TOPO (Topology DAG Guard): Verifies the integrity of the dependency chain. Rejects cross-fork repositories, cyclic dependencies, and orphaned PR stacks.
  4. G-TEST (Rewritten Layer Guard): Upon cascade rebasing, runs local test suites on every layer (pnpm test, fallback npm test; custom command and timeout configurable). Outcomes are written as SHA-bound validation records — a record is only valid for the exact head commit it was proven for. Restoration is best-effort and never silent: if the workspace could not be restored after validation, the failure is reported.
  5. G-VERIFY (Validation-Record Guard): /stack-land refuses to merge any open layer without a passing record for its current head SHA. Missing, stale (head moved), or failed records each produce a distinct blocker; fix tests and re-run /stack-verify — no rebase or push needed.
  6. G-LAND (Landing Eligibility Guard): Collects all violations across all open layers (state, CI, approval) before deciding — a waived category (e.g. --self-approve for approvals) never hides violations of another category. Gracefully accommodates already-merged base PRs in the stack.
  7. G-POLL (Merge Confirmation Guard): Polls GitHub GraphQL to confirm target PRs transitioned to MERGED before branch deletion. Stacked merges can enter GitHub's merge queue, so observed states (e.g. QUEUED) are reported instead of flattening "not yet merged" into a failure.
  8. G-DEL (Zero-Dependent Cleanup Guard): Queries downstream open PRs. Deletes branches only when dependent count is strictly 0 — and every deletion is conditional on the branch still pointing at the merged PR head: remote deletes carry --force-with-lease=<ref>:<expectedSha> and local deletes use update-ref -d <ref> <expectedSha>, so a branch that received commits after the merge (or between the check and the delete) is retained, not destroyed. Fails safe (retaining branches with warnings) if queries fail or return non-integer values. /stack-cleanup re-runs this pass idempotently for partial lands.

💻 Slash Command Reference

CommandSyntaxRoleGuards EnforcedMachine Output
/stack-doctor/stack-doctor [--json]Health CheckG-ENVDoctorOutputJson
/stack/stack [pr...] [--json]Topology & LinkG-ENV, G-TOPOStackOutputJson
/stack-sync/stack-sync [--dry-run] [--json]Sync, Re-validate & RecordG-ENV, G-TREE, G-TESTSyncOutputJson
/stack-land/stack-land [--dry-run] [--limit <n>] [--self-approve] [--json]Guarded Ordered MergeG-ENV, G-VERIFY, G-LAND, G-POLL, G-DELLandOutputJson / LandPreviewJson
/stack-verify/stack-verify [--json]In-place Re-validationG-ENV, G-TOPO, G-TESTVerifyOutputJson
/stack-cleanup/stack-cleanup [--json]Idempotent Branch PruningG-ENV, G-TOPO, G-DELCleanupOutputJson

Detailed Usage Examples

1. /stack-doctor (Environment Diagnostic)

Runs an end-to-end audit of local tooling and network connectivity:

/stack-doctor

Output Example:

## Stack Doctor Diagnostic Report

| Component | Status | Details |
|---|---|---|
| GitHub CLI (gh) | ✅ Ready | `gh version 2.92.0 (2026-04-28)` |
| gh-stack Extension | ✅ Ready | `gh stack version 0.1.1` |
| Auth & API Status | ✅ Authenticated | User: `alice` |

2. /stack [pr...] (Stack Inspection & Linking)

Visualizes PR dependencies as an ASCII DAG. Accepts space- or comma-separated PR numbers with optional # prefixes:

# Auto-detect from current git branch
/stack

# Link explicit PR chain
/stack 101 102 103
/stack #101, #102, #103

Output Example:

### 📦 GitHub Stack Topology (Trunk: `master`)

master (Trunk)
  │
  ├─ #101 [feat/auth-db] (APPROVED | CI: SUCCESS)
  │    │
  │    └─ #102 [feat/auth-api] (APPROVED | CI: SUCCESS)
  │         │
  │         └─ #103 [feat/auth-ui] (CHANGES_REQUESTED | CI: PENDING)

3. /stack-sync (Sync, Re-validate & Record)

Syncs the stack via gh stack sync (fetch, reconcile, rebase if the trunk moved, push), then re-runs local test suites on every touched layer and records the SHA-bound result:

# Preview rebase without touching git state
/stack-sync --dry-run

# Sync the stack, re-validate every touched layer, record results
/stack-sync

Output Example on Test Failure:

### 🛑 Sync Halted: Validation Failure on Layer #102

Branch `feat/auth-api` failed tests after rebase.

AssertionError: Token validation expected 200, received 401


**Safety Guard Triggered**: Fix test errors on branch `feat/auth-api` before landing.

4. /stack-land (Topological Merge & Safe Branch Cleanup)

Merges PRs bottom-to-top, verifies MERGED status, and safely prunes branches with 0 dependents:

# Preview landing order (works even before approvals; guard findings are included in the preview)
/stack-land --dry-run

# Land only bottom 2 layers
/stack-land --limit 2

# Solo maintainer: waive the review-approval requirement explicitly (CI checks stay enforced)
/stack-land --self-approve

# Land complete stack
/stack-land

Output Example:

### 🚀 Official Stack Landed Successfully

Merged 2 PRs in order (limited from 3).

**Branch Cleanup Summary**:
- ✅ Deleted clean branch: `feat/auth-db`
- ⚠️ Retained branch: `feat/auth-api` (Branch "feat/auth-api" still has 1 open PR(s) depending on it.)

[!NOTE] --self-approve exists because GitHub rejects self-approvals on your own PRs. It waives only the review-approval guard (CI, topology, validation-record, and merge-state checks remain enforced), prints a solo-maintainer warning banner, and sets selfApproved: true in --json output. Team repositories should rely on real peer review and never use this flag.


🤖 Machine-Readable JSON Mode (--json)

Every command supports --json for integration into autonomous AI agents (such as DeepSeek Harness agents) or CI/CD pipelines:

/stack-doctor --json
/stack --json
/stack-sync --dry-run --json
/stack-land --limit 2 --json
/stack-verify --json
/stack-cleanup --json

Preview output is explicit about intent

/stack-land --dry-run --json never describes actions as performed. It returns a preview envelope with the verdict and the machine-coded reasons:

{
  "mode": "preview",
  "canLand": false,
  "plannedPrNumbers": [101, 102],
  "blockers": [{ "code": "GUARD_ERR_UNVERIFIED", "prNumber": 101, "message": "..." }],
  "cleanupCandidates": ["feat/auth-api"],
  "wouldRetain": ["feat/auth-db"]
}

Agents act on stable guard codes (GUARD_ERR_UNVERIFIED, GUARD_ERR_VALIDATION_STALE, GUARD_ERR_VALIDATION_FAILED, GUARD_ERR_CI_FAILED, GUARD_ERR_NOT_APPROVED, …) instead of parsing English error text.

✅ Acceptance Criteria

The behaviors this plugin is held to (each covered by the test suite):

  1. Parse-refusal: invalid arguments (--dryrun, --limit 0) produce zero link/sync/merge/delete calls.
  2. Waiver isolation: --self-approve waives only review approvals; with any CI failure or missing/stale validation record, merge calls stay at zero.
  3. Cross-command validation: after a sync halts on failing tests, /stack-land is blocked by the recorded failure; after /stack-verify passes, land proceeds. Merges never run without a passing record for the current head SHA.
  4. Post-rebase record binding: records are bound to the SHA after rebase — they are valid immediately after sync (never self-stale); landing still requires every other gate (CI, approvals, merge-state).
  5. Idempotent recovery: after a partial land, /stack-cleanup completes pruning without re-merging and reports observed merge states instead of ambiguous timeouts.

JSON Schema Sample (SyncOutputJson)

{
  "ok": true,
  "dryRun": false,
  "trunk": "master",
  "syncedLayers": [
    { "prNumber": 101, "headRef": "feat/auth-db", "newSha": "8a3f91c" },
    { "prNumber": 102, "headRef": "feat/auth-api", "newSha": "b4e120d" }
  ],
  "validationResults": [
    { "prNumber": 101, "headRef": "feat/auth-db", "passed": true, "output": "Tests passed" },
    { "prNumber": 102, "headRef": "feat/auth-api", "passed": true, "output": "Tests passed" }
  ]
}

🏛️ Architecture & Testing

┌─────────────────────────────────────────────────────────┐
│                      dsh UI / Agent                     │
│         Slash Commands: /stack, /stack-sync, ...        │
└────────────────────────────┬────────────────────────────┘
                             │ CommandInvocation
┌────────────────────────────▼────────────────────────────┐
│              dsh-stack Runtime Engine                   │
│   ├── GuardEngine (8 Guard Assertions / Remediation)    │
│   └── StackOrchestrator (DAG Parsing & State Machine)   │
└────────────────────────────┬────────────────────────────┘
                             │ depends on interface
┌────────────────────────────▼────────────────────────────┐
│                    GhClient Interface                   │
│         Decoupled abstraction for 100% testability      │
└──────────────┬───────────────────────────┬──────────────┘
               │ (Production)              │ (Test Fixture)
┌──────────────▼─────────────┐ ┌───────────▼──────────────┐
│  ExecGhClient (execFile)   │ │  MockGhClient (In-Memory)│
│  - child_process timeout   │ │  - full test suite       │
│  - AbortSignal propagation │ │  - 100% deterministic    │
│  - robust checkout & fetch │ │  - 3-5 layer fixtures    │
│  - Detached HEAD restore   │ │  - instant mock polling  │
│  - fail-safe cleanup pass  │ └──────────────────────────┘
└────────────────────────────┘

Running the Test Suite

Tests run on Node.js's native test runner; npm test builds lib/, runs a strict typecheck, then executes the full suite (tsdown and typescript are the only dev dependencies), so the package-entry assertions exercise the shipped artifact:

# Build lib/, typecheck, then run the full test suite
npm test
npm run test:node

# Run via Vitest (when working in parent monorepo context)
npm run test:vitest

🔒 Limitations & Security Model

What this plugin enforces — and, just as important, what it does not.

Trust model

  • Stack state comes from the GitHub API via gh (PRs, review decisions, check rollups). The plugin trusts these responses at query time; a state change between a check and a subsequent mutation is narrowed where practical (branch deletion is SHA-conditional) but not eliminated.
  • Local validation runs in your working tree against the branch head. It proves the tested commit passes your local suite; it does not replace GitHub required checks, which GitHub evaluates on its own infrastructure and policies.

Permissions & scope

  • Mutating operations (link, sync/push, merge, branch deletion) require write access to the repository and are executed with your gh credentials.
  • --self-approve is a scoped, auditable exemption for solo maintainers: it waives only the review-approval guard, is prominently flagged in output and JSON (selfApproved: true), and never waives CI, topology, or validation-record checks. Team repositories should rely on peer review.

Known boundaries

  • Merge queue: stacked merges may enter GitHub's merge queue; the plugin reports observed states and points to /stack-cleanup for post-settlement pruning, but it does not drive the queue itself.
  • Concurrent modifications: two people mutating the same stack simultaneously is not coordinated. Guards fail safe (retain branches, refuse ambiguous merges), but the last writer wins on the underlying gh operations.
  • Cross-fork stacks are not supported (GitHub native stacks are single-repository); the topology guard rejects them explicitly.
  • Approval invalidation is repository-dependent: SHA rewrites always invalidate CI attestation for the new HEAD; whether approvals lapse depends on your repository's stale-review / latest-push rules. The plugin surfaces the risk; GitHub enforces the rule.
  • Enterprise proxies, org policies, and self-hosted GitHub are untested.

Reporting bugs: please open an issue with the command, the --json output, and your gh / gh-stack versions.


📄 License

  • License: MIT
  • Agent Skill Definition: SKILL.md