LeslieWylie / dsh-checkout-guard

목록에 있음

Pre-flight guard for git working copies: is the checkout you are about to write to the one you think it is? Branch identity, drift from the remote and how stale that figure is, the identity your next commit will carry, work already in the index, and duplicate clones. Read-only, offline by default.

main기타 소스 보기

설치

npm install dsh-checkout-guard

이 명령은 GitHub 저장소 주소에서 생성됩니다. 실행 전에 업스트림 README와 소스를 검토하고 재현성이 필요하면 release 또는 commit을 고정하세요.

README

유지 관리자가 작성한 문서 스냅샷입니다.

GitHub에서 보기 ↗
커밋 82fe5f0동기화 2026. 8. 18.

dsh-checkout-guard

Is the working copy you are about to write to the one you think it is?

A DSH plugin that answers that before you edit, commit, or push — not after. It reads a git checkout and tells you where it sits relative to its remote, how stale that answer is, who your next commit will be attributed to, what work is already sitting in the index, and whether another clone of the same repository on this machine has moved ahead of the one you are in.

Read-only. Offline unless you ask for a fetch. Never takes the index lock.


Why

Every check here exists because a specific mistake actually happened, usually to an agent working across many repositories at once:

What goes wrongWhat you see afterwards
You edit a checkout that is 8 commits behindYour "fix" reverts work, or your push is rejected after the fact
Two clones of one repository, you are in the stale oneChanges vanish; the repo on the forge never shows them
A concurrent process moved HEAD to another branchYour commit lands on a branch you have never heard of
Something was already staged before you ran git addYour commit packages somebody else's work in progress
No repository-local user.emailA work address is permanently attached to a public commit
Detached HEADThe commits exist, then don't, and nothing warned you

None of these throw. Git does exactly what you asked; you asked in a state you had misread. That is the entire problem space this plugin covers.

Install

npm install dsh-checkout-guard

Then append to your profile's cordis.yml:

- id: checkout-guard
  name: 'dsh-checkout-guard'

Node 22.19+ or 24+. The only runtime requirement is git on PATH.

Tools

checkout_guard — one working copy, in depth

{ "path": "/abs/path/to/repo", "expectBranch": "main" }
{
  "branch":  { "current": "main", "detached": false, "upstream": "origin/main", "linkedWorktree": false },
  "sync":    { "ahead": 1, "behind": 1, "fetchAgeSeconds": 255600, "fetchedNow": false },
  "remote":  { "normalized": "github.com/acme/widget", "host": "github.com", "publicForge": true },
  "identity":{ "email": "you@example.com", "source": "global", "repositoryOverride": false },
  "workingTree": { "staged": [], "unstaged": ["package.json"], "untracked": [], "clean": false },
  "duplicateCheckouts": [ { "path": "/elsewhere/widget", "branch": "main", "ahead": 3, "aheadOfThisOne": true } ],
  "blockers": [
    "diverged from origin/main by 1 commit(s) (and 1 ahead); you are reading code the remote has already moved past, and a push will be rejected",
    "another checkout of the same remote is ahead of this one: /elsewhere/widget (main, 3 ahead vs 1); you may be editing the stale copy"
  ],
  "warnings": [
    "remote refs were last updated 71h ago; ahead/behind is computed from that snapshot, not from the remote as it is now (re-run with fetch: true)"
  ],
  "verdict": "blocked",
  "safeToWrite": false
}

safeToWrite is the one field a caller needs. blockers are states where proceeding loses or misplaces work; warnings are things worth knowing that are not automatically wrong.

ParameterEffect
path (required)Absolute path to the working copy, or any directory inside it
fetchContact the remote first so ahead/behind is current. The only networked option, default false
expectBranchThe branch you believe you are on. A mismatch is a blocker
expectIdentityThe email you expect to author with. A mismatch is a blocker
remoteRemote to compare against (default origin)
staleAfterHoursWhen remote refs count as stale (default 24)
findDuplicates / duplicateRoots / maxDepth / maxReposControl the search for other clones

expectBranch and expectIdentity are how you turn a report into an assertion. Without them the plugin describes; with them it refuses.

checkout_guard_scan — every working copy under a root

{ "roots": ["/abs/path/to/projects"], "maxDepth": 3 }

Returns one row per checkout — branch, upstream, ahead/behind, uncommitted counts, fetch age — plus:

  • needsAttention: the ones that are behind, diverged, detached, or have no upstream
  • duplicateRemotes: paths grouped by remote, so two clones of one repository are visible at a glance

Run this first to find out which checkouts deserve a closer look, then hand those paths to checkout_guard.

What it deliberately does not do

  • It does not tell you a remote is archived. That needs the forge's API, and this plugin does not make authenticated HTTP calls. remote.normalized gives you host/owner/repo to check yourself.
  • It does not fix anything. No pull, no rebase, no stash, no checkout. The recovery for "behind" and the recovery for "diverged" are different, and picking between them is not a decision a guard should make for you.
  • It does not judge your identity. It reports which config your author line comes from, and warns only when a public forge is involved and the repository has no local override. If you pass expectIdentity, it compares against exactly what you named — it never guesses what your address should be.

Safety properties

  • No writes to any repository. The only command that can modify anything is git fetch --dry-run, which runs only when fetch: true and touches nothing but FETCH_HEAD.
  • GIT_OPTIONAL_LOCKS=0 on every call. Ordinary git status refreshes and rewrites the index, taking .git/index.lock. A guard whose whole premise is "another agent may be in this checkout right now" must not be able to block that agent.
  • GIT_TERMINAL_PROMPT=0, empty GIT_ASKPASS. A fetch against a private remote fails fast instead of parking on a credential prompt nobody can see.
  • Credentials are stripped during URL normalization, before a remote can reach a result object or a log line.
  • Path allowlist. Both sides are realpath'd, so a symlink planted inside an allowed root cannot read outside it.

Configuration

- id: checkout-guard
  name: 'dsh-checkout-guard'
  config:
    roots:
      - /Users/you/projects

roots bounds everything the plugin will look at. Unset, it defaults to your home directory.

Development

npm install
npm test          # unit tests against real git repos in a temp dir
npm run test:boot # loads the plugin into a real cordis Context

There is no build step. src/index.js is the published entry point, byte for byte. Plugins that compile to lib/ and commit the output have to keep the two in sync forever, and every test that imports src/ passes while the artifact an installer runs is stale. Removing the build removes the whole class.

The unit tests create real repositories — real remotes, real pushes, real divergence — rather than stubbing git. A hand-written double answers the way its author expects, and the author's expectation about repository state is precisely what is under test.

tests/boot.test.mjs exits 0 with a SKIP from a bare clone so contributors are not blocked, and exits 1 under DSH_BOOT_STRICT=1, which CI sets. A skipped integration test reported as green is worse than no test at all.

中文 · MIT

프로젝트 파일 및 신호

표시된 항목은 디렉터리 스냅샷에서 감지된 공개 저장소 신호입니다.

테스트감지됨

저장소 정보

언어
JavaScript
라이선스
MIT
마지막 업데이트
2026. 8. 18. AM 3:26

신중하게 설치하기

소스 코드, 권한, 수명 주기 스크립트, 의존성 및 네트워크 접근을 검토하고 신뢰하지 않는 플러그인은 격리 환경에서 테스트하세요.