@compozy/skeeper 0.1.1 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +185 -211
  2. package/package.json +16 -16
package/README.md CHANGED
@@ -9,9 +9,6 @@
9
9
  <a href="https://pkg.go.dev/github.com/compozy/skeeper">
10
10
  <img src="https://pkg.go.dev/badge/github.com/compozy/skeeper.svg" alt="Go Reference">
11
11
  </a>
12
- <a href="https://goreportcard.com/report/github.com/compozy/skeeper">
13
- <img src="https://goreportcard.com/badge/github.com/compozy/skeeper" alt="Go Report Card">
14
- </a>
15
12
  <a href="LICENSE">
16
13
  <img src="https://img.shields.io/badge/License-MIT-blue.svg" alt="License: MIT">
17
14
  </a>
@@ -21,341 +18,318 @@
21
18
  </p>
22
19
  </div>
23
20
 
24
- Specs and code want to live together. Spec files (`SPEC.md`, `docs/specs/*`, `.claude/plans/*`, ADRs, RFCs) belong next to the code they describe — but committing them to your main repo bloats every PR with documentation noise, and ignoring them loses history. `skeeper` runs a sidecar Git repository that mirrors matched spec files on every commit. You edit specs at their natural paths, your main PRs stay focused on code, and a separate Git history keeps full `git log`, `git blame`, and branch-aware versioning of every spec change. One `skeeper init` and the post-commit hook does the rest — without ever blocking your `git commit`.
25
-
26
- ## ✨ Highlights
21
+ Spec docs drift from code, or they bloat every PR. Skeeper picks neither.
27
22
 
28
- - **One sidecar repo, full Git history.** Specs version normally `git log`, `git blame`, branches, PRs without touching your main repo's diff.
29
- - **Shared sidecars without collisions.** Named namespaces isolate stored paths and pushed branches inside one sidecar remote.
30
- - **Edit specs where they belong.** Spec files stay next to the code they describe. `skeeper` mirrors them into `.skeeper/` for you.
31
- - **A post-commit hook that never breaks your commit.** 750 ms foreground budget per namespace; on failure, the sync queues locally and retries on the next manual `skeeper sync`.
32
- - **Branch-aware mirroring.** Sidecar branches track main-tree branches, so feature work and `main` stay isolated.
33
- - **Fresh-clone hydration.** `skeeper hydrate` restores matched specs into a new clone so teammates start with full context.
34
- - **Glob-based pattern matching.** Doublestar globs (`**/SPEC.md`, `docs/specs/**`, `.claude/plans/**`) — match specs the way you actually organize them.
35
- - **Shells out to `git` and `gh`.** Reuses your existing GitHub auth. Every operation is debuggable with the same Git commands you already know.
36
- - **Single static binary, zero runtime deps.** Linux, macOS, Windows on amd64/arm64. CGO disabled.
37
-
38
- ## 📦 Installation
23
+ It mirrors `SPEC.md`, ADRs, RFCs, and AI plan files into a sidecar Git repository and commits a tiny `skeeper.lock` to your main repo that pins every commit to exact sidecar commits. PR diffs stay focused on code, spec history stays auditable, and nothing silently drifts because the managed Git hooks fail the commit if the sidecar state cannot be proven.
39
24
 
40
- #### Homebrew
25
+ ## ✨ Highlights
41
26
 
42
- ```bash
43
- brew tap compozy/compozy
44
- brew install --cask skeeper
45
- ```
27
+ - **Lockfile-backed reliability.** `skeeper.lock` records sidecar URL, source branch, namespace branch, sidecar commit, per-namespace digest, file count, and byte count.
28
+ - **Strict managed hooks.** The managed `pre-commit` and `pre-merge-commit` hooks sync staged content, push the sidecar, write and stage `skeeper.lock`, and fail closed. The managed `pre-push` hook verifies the lock against the sidecar remote.
29
+ - **Specs stay local to their code.** Edit `SPEC.md`, `docs/specs/**`, `.claude/plans/**`, ADRs, RFCs, or custom globs where they naturally belong.
30
+ - **Shared sidecars without collisions.** Namespaces isolate stored paths and sidecar branches inside one sidecar remote.
31
+ - **Branch-aware history.** Namespace branches use `<namespace>/__branches__/<source-branch>`.
32
+ - **Fresh-clone hydration.** `skeeper hydrate` restores files from the locked sidecar commits, not a best-effort latest branch.
33
+ - **Safe reconciliation.** `hydrate`, `fsck`, `diff`, and `reconcile` classify per-path drift before any local managed document is overwritten or moved.
34
+ - **Agent-friendly commands.** `status`, `sync`, `verify`, `fsck`, `diff`, `reconcile`, `update`, `hooks check`, `repair status`, `rescue`, `pattern`, `adopt`, and `untrack` all support deterministic output where needed.
35
+ - **Skill for AI agents.** A bundled skill at [`.agents/skills/skeeper/SKILL.md`](.agents/skills/skeeper/SKILL.md) teaches coding agents the strict-sync workflow, namespaces, and recovery commands.
46
36
 
47
- #### NPM
37
+ ## 🎯 Who Is This For
48
38
 
49
- ```bash
50
- npm install -g @compozy/skeeper
51
- ```
39
+ - Teams using AI coding agents that produce `SPEC.md`, PRD, TechSpec, and plan markdown next to code.
40
+ - Engineering organizations running ADRs, RFCs, and design docs in-repo without making every PR a docs+code review.
41
+ - Solo developers who want full spec history (`git log`, `git blame`, branches, PRs) without polluting their main repository's diff.
52
42
 
53
- #### Go
43
+ ## 📦 Installation
54
44
 
55
45
  ```bash
56
46
  go install github.com/compozy/skeeper/cmd/skeeper@latest
57
47
  ```
58
48
 
59
- #### From Source
60
-
61
- ```bash
62
- git clone git@github.com:compozy/skeeper.git
63
- cd skeeper && make verify && go build -o bin/skeeper ./cmd/skeeper
64
- ```
65
-
66
- #### Docker
67
-
68
- ```bash
69
- git clone git@github.com:compozy/skeeper.git
70
- cd skeeper && make docker-build # builds skeeper:dev (distroless, nonroot)
71
- docker run --rm -v "$PWD:/workspace" -w /workspace skeeper:dev status
72
- ```
49
+ Other release channels are available through GitHub Releases, Homebrew, NPM, and the distroless Docker image.
73
50
 
74
- #### Prerequisites
51
+ Prerequisites:
75
52
 
76
53
  - `git` on `PATH`
77
- - `gh` (GitHub CLI) **only when `skeeper init` creates a new sidecar** — existing sidecars can be reused with `--sidecar`. Day-to-day commands need only `git`.
54
+ - `gh` only when `skeeper init` creates a new GitHub sidecar repo
78
55
 
79
56
  ## 🔄 How It Works
80
57
 
81
- Spec files live at their natural paths next to code. Your main repo's `.gitignore` lists the effective namespace patterns plus `.skeeper/`, so neither owned specs nor the sidecar clone ever appear in a main-repo diff.
58
+ Spec files live in the main worktree but are ignored by the main repository through a managed `.gitignore` block. The sidecar repository stores mirrored files under `<namespace>/<path>` and pushes them to `<namespace>/__branches__/<source-branch>`.
82
59
 
83
- On every `git commit`, the managed post-commit hook runs `skeeper sync --hook` with a 750 ms foreground budget per namespace. `skeeper` matches files against namespace patterns, copies them into `.skeeper/`, commits with a reference to the main commit SHA, and pushes to the sidecar remote.
84
-
85
- Each namespace stores files under `<namespace>/<path>` in the sidecar and pushes branch `<namespace>/__branches__/<source-branch>`. For example, namespace `skills` on source branch `main` stores `skills/review.md` as `skills/skills/review.md` and pushes sidecar branch `skills/__branches__/main`.
86
-
87
- If anything fails — network, auth, push rejection, timeout — `skeeper` writes a retry record to `.git/skeeper/queue.json` with the failing namespace when one is known, appends a one-line audit entry to `.git/skeeper/sync.log`, and exits 0 so your `git commit` always succeeds. Run `skeeper sync` later to drain the queue.
60
+ On commit, the managed `pre-commit` block runs last. On automatic merge commits, the managed `pre-merge-commit` block runs the same strict sync path because Git does not run `pre-commit` for merge commits. Both hooks build a plan from the staged index plus explicitly owned ignored/untracked spec paths, fetch and rebase sidecar branches, mirror content into `.skeeper/`, commit and push the sidecar, write `skeeper.lock`, and stage that lock before Git creates the main commit.
88
61
 
89
62
  ```mermaid
90
- flowchart LR
91
- A[Developer<br/>git commit] --> B[post-commit hook<br/>skeeper sync --hook]
92
- B --> C{Namespace sync within<br/>750 ms?}
93
- C -- yes --> D[Copy matched specs<br/>into .skeeper/]
94
- D --> E[git commit<br/>in sidecar]
95
- E --> F[git push<br/>to sidecar remote]
96
- C -- no / error --> G[Write namespace retry record<br/>.git/skeeper/queue.json]
97
- G --> H[Hook exits 0<br/>main commit succeeds]
98
- H -. later .-> I[skeeper sync<br/>drains queue]
99
- I --> D
63
+ flowchart TD
64
+ Start([👤 git commit]):::user --> UserHook[🪝 Existing user hook content]:::user
65
+ UserHook --> Block
66
+
67
+ subgraph Block [📦 Skeeper pre-commit block]
68
+ direction TB
69
+ S1[🧮 Reconcile staged specs<br/>+ ownership] --> S2[🔄 Fetch &amp; rebase<br/>sidecar branch]
70
+ S2 --> S3[🪞 Mirror namespace files<br/>into .skeeper/]
71
+ S3 --> S4[📤 Commit &amp; push sidecar]
72
+ S4 --> S5[🔒 Write &amp; stage<br/>skeeper.lock]
73
+ end
74
+
75
+ Block --> Commit[✅ Main commit proceeds]:::ok
76
+ Commit --> Push([🚀 git push]):::user
77
+ Push --> Verify[🔍 Skeeper pre-push verify]:::skeeper
78
+ Verify --> Done([🎉 Sidecar verified]):::ok
79
+
80
+ classDef user fill:#dbeafe,stroke:#1d4ed8,color:#0c1e3e
81
+ classDef skeeper fill:#fef3c7,stroke:#b45309,color:#3b2c00
82
+ classDef ok fill:#dcfce7,stroke:#15803d,color:#052e16
83
+ class S1,S2,S3,S4,S5 skeeper
100
84
  ```
101
85
 
86
+ If sync fails, the commit fails. This is intentional: a committed main change should not silently drift from the sidecar. The audited bypass is `SKEEPER_SKIP=1`; it records `.git/skeeper/bypass.json`, prints a warning, and `pre-push`, `status`, `fsck`, and `verify` continue to surface stale-lock diagnostics until `skeeper sync` repairs the state. `git commit --no-verify` is unsupported because Git skips all hook code and cannot record an audit trail.
87
+
102
88
  ## ⚙️ Configuration
103
89
 
104
- `skeeper init` writes `.skeeper.yml` at the repo root. Commit it — your teammates need it for `skeeper hydrate`.
90
+ `skeeper init` writes `.skeeper.yml` at the repository root. Commit it.
105
91
 
106
92
  ```yaml
107
- # Required: sidecar repository URL
108
93
  sidecar: git@github.com:user/myproject-specs.git
109
94
 
110
- # Required: namespaces route files into sidecar paths and branches
111
95
  namespaces:
112
- - name: skills
113
- patterns:
114
- - "skills/*.md"
115
-
116
- - name: myproject
96
+ - name: project
117
97
  patterns:
118
98
  - "**/SPEC.md"
119
99
  - "docs/specs/**"
120
100
  - ".claude/plans/**"
121
101
  - "**/*.spec.md"
122
102
  exclude:
123
- - "skills/*.md"
124
-
125
- # Optional: install one-liner shown to teammates after `skeeper hydrate`
126
- bootstrap: brew tap compozy/compozy && brew install --cask skeeper
103
+ - "docs/specs/private/**"
127
104
  ```
128
105
 
129
- Unknown keys are rejected — config errors fail loud, not silently.
130
-
131
- Every namespace needs a `name` and at least one `patterns` glob. `exclude` removes ownership from that namespace; if another namespace owns the excluded files, they stay tracked there. A file owned by more than one namespace is a configuration error because hidden precedence would make deletes unsafe.
106
+ Advanced operational defaults are optional:
132
107
 
133
- `skeeper init` writes one namespace by default. Add more namespaces by editing `.skeeper.yml`.
134
-
135
- Local-only state lives under `.git/skeeper/` (already gitignored by Git's hooks directory):
108
+ ```yaml
109
+ settings:
110
+ guardrails:
111
+ max_files: 100
112
+ max_bytes: 10485760
113
+ hooks:
114
+ pre_push_timeout: 30s
115
+ allow_skip_env: SKEEPER_SKIP
136
116
 
137
- | File | Purpose |
138
- | ------------ | ------------------------------------------------------ |
139
- | `queue.json` | Pending retries from failed hook runs |
140
- | `sync.log` | Append-only audit log of sync attempts and error codes |
117
+ namespaces:
118
+ - name: generated
119
+ patterns:
120
+ - "generated/specs/**"
121
+ respect_gitignore: false
122
+ ```
141
123
 
142
- ## 🚀 Quick Start
124
+ Rules:
143
125
 
144
- ### 1. Install
126
+ - Unknown keys are rejected.
127
+ - Every namespace needs a `name` and at least one glob in `patterns`.
128
+ - `exclude` is the only public exclusion mechanism. Negative globs in `patterns` are rejected.
129
+ - Ownership must be unique. If two namespaces own the same file, the plan fails and asks for an `exclude` fix.
130
+ - `respect_gitignore: false` bypasses root `.gitignore`, nested `.gitignore`, `.git/info/exclude`, and global excludes for that namespace. `.git/` and `.skeeper/` are always excluded.
145
131
 
146
- ```bash
147
- go install github.com/compozy/skeeper/cmd/skeeper@latest
148
- ```
132
+ Local-only state lives under `.git/skeeper/`:
149
133
 
150
- ### 2. Initialize the sidecar
134
+ | File | Purpose |
135
+ | ------------------ | ---------------------------------------------- |
136
+ | `transaction.json` | Current resumable mutating operation and phase |
137
+ | `bypass.json` | Latest audited strict-hook bypass |
138
+ | `hydration.json` | Last locked sidecar blobs hydrated locally |
139
+ | `rescue/` | Local files moved aside before prune/overwrite |
151
140
 
152
- In a Git repo where you want to track specs:
141
+ ## 🚀 Quick Start
153
142
 
154
143
  ```bash
155
144
  skeeper init
156
145
  ```
157
146
 
158
- Interactive by default — opens a terminal form for the sidecar mode, repository name or URL, namespace, bootstrap command, and optional extra context globs. The interactive flow always includes the default spec globs (`**/SPEC.md`, `docs/specs/**`, `.claude/plans/**`, and `**/*.spec.md`) in the initial namespace; the extra context prompt starts empty and is only for additional folders or files you explicitly want in that namespace. Or pass values as flags:
147
+ Interactive init asks for the sidecar mode, repository name or URL, namespace, bootstrap command, and optional extra context globs. With flags:
159
148
 
160
149
  ```bash
161
150
  skeeper init \
162
151
  --sidecar-name myproject-specs \
163
152
  --visibility private \
164
- --namespace myproject \
153
+ --namespace project \
165
154
  --patterns "**/SPEC.md" \
166
- --patterns ".claude/plans/**"
155
+ --patterns "docs/specs/**"
167
156
  ```
168
157
 
169
- To reuse one shared sidecar remote across multiple source repos:
158
+ Use an existing shared sidecar:
170
159
 
171
160
  ```bash
172
161
  skeeper init \
173
162
  --sidecar git@github.com:user/shared-specs.git \
174
- --namespace myproject \
163
+ --namespace project \
175
164
  --patterns "**/SPEC.md"
176
165
  ```
177
166
 
178
- `skeeper init` creates the GitHub repo with `gh repo create` unless `--sidecar` points to an existing remote. It clones the sidecar into `.skeeper/`, writes `.skeeper.yml`, updates `.gitignore`, and installs the post-commit hook. New init defaults the namespace to the source repo name.
179
-
180
- When using flags, repeated `--patterns` values are the complete pattern set written to `.skeeper.yml`; they do not append to the interactive defaults.
181
-
182
- ### 3. Edit specs and commit normally
167
+ Then edit specs and commit normally:
183
168
 
184
169
  ```bash
185
170
  $EDITOR src/auth/SPEC.md
186
- git add .
171
+ git add src/auth/service.go src/auth/SPEC.md
187
172
  git commit -m "auth: design OAuth provider flow"
188
173
  ```
189
174
 
190
- The hook fires automatically. No extra step.
175
+ The `pre-commit` and `pre-merge-commit` hooks mirror specs and stage `skeeper.lock`. If a hook stages a new lock, review it and include it in the commit.
176
+
177
+ ## 🛟 Failed Sync Recovery
191
178
 
192
- ### 4. Inspect
179
+ Inspect local repair state:
193
180
 
194
181
  ```bash
195
- skeeper status # sidecar URL, branch mapping, last sync, pending count
196
- skeeper log src/auth/SPEC.md # sidecar Git history for one file
182
+ skeeper repair status
197
183
  ```
198
184
 
199
- ### 5. Onboard a teammate
185
+ Resume the recorded operation when network/auth/sidecar contention has been fixed:
200
186
 
201
187
  ```bash
202
- git clone git@github.com:user/myproject.git
203
- cd myproject
204
- skeeper hydrate
188
+ skeeper repair resume
205
189
  ```
206
190
 
207
- `hydrate` clones the sidecar into `.skeeper/`, restores matched specs into the working tree, and installs the hook.
208
-
209
- ### 6. Recover from a failed sync
210
-
211
- If the hook ever queued work (network blip, push rejection):
191
+ Abort only before the main index has been mutated:
212
192
 
213
193
  ```bash
214
- skeeper sync # drain queued retries, then run a fresh sync
215
- skeeper sync --pull # rebase the sidecar branch first — useful when teammates pushed
194
+ skeeper repair abort
216
195
  ```
217
196
 
218
- ## 🧰 How Sync Works
219
-
220
- The post-commit hook is a _managed block_ in `.git/hooks/post-commit`, installed idempotently. It runs `skeeper sync --hook` with a 750 ms foreground budget per namespace so your `git commit` stays bounded even on a slow network.
197
+ Run a fresh repair sync when a bypass or stale lock is reported:
221
198
 
222
- On the success path, `skeeper` matches files with doublestar globs, copies them into `.skeeper/`, then runs `git add`, `git commit`, and `git push` against the sidecar remote. Each namespace copies to `.skeeper/<namespace>/<path>` and pushes `<namespace>/__branches__/<source-branch>`. Sidecar commits reference the main-repo SHA so you can correlate spec changes back to the code change that triggered them.
223
-
224
- On the failure path — timeout, auth failure, network failure, or push rejection — `skeeper` writes a retry record to `.git/skeeper/queue.json` with the failing namespace when one is known, appends to `.git/skeeper/sync.log`, prints a one-line note, and the hook exits 0. The next `skeeper sync` drains the queue before running a normal sync. Use `skeeper sync --pull` when a teammate pushed sidecar updates between your commits; it fetches and rebases before pushing.
225
-
226
- This design has two consequences worth knowing:
227
-
228
- - **`git commit` never fails because of `skeeper`.** Worst case, you have queued work to drain.
229
- - **Conflicts surface as Git conflicts.** `skeeper sync --pull` stops if the rebase reports unresolved conflicts; resolve them in `.skeeper/` with normal Git tooling, then re-run.
199
+ ```bash
200
+ skeeper sync
201
+ skeeper verify
202
+ ```
230
203
 
231
204
  ## 📖 CLI Reference
232
205
 
233
- <details>
234
- <summary><code>skeeper init</code> — Create and connect a sidecar specs repository</summary>
235
-
236
206
  ```bash
237
207
  skeeper init [flags]
208
+ skeeper sync [--dry-run] [--json] [--commit --message <msg>] [--force]
209
+ skeeper adopt <path-or-glob>... [--dry-run] [--json] [--force] [--commit --message <msg>]
210
+ skeeper untrack <path-or-glob>... [--dry-run] [--json] [--force] [--commit --message <msg>]
211
+ skeeper pattern test <glob> [--namespace <name>] [--json]
212
+ skeeper pattern add <glob> [--namespace <name>] [--exclude <glob>]... [--adopt-existing] [--dry-run] [--json] [--force] [--commit --message <msg>]
213
+ skeeper hydrate [--dry-run] [--json] [--keep-local|--adopt-local|--prune-local|--merge] [--ours|--theirs]
214
+ skeeper reconcile [--dry-run] [--json] [--adopt-local|--prune-local|--merge] [--ours|--theirs]
215
+ skeeper diff [--json] [--namespace <name>] [--class <class>] [--extra] [--missing] [--modified]
216
+ skeeper rescue list [--json]
217
+ skeeper rescue restore <id> [path...] [--json] [--overwrite]
218
+ skeeper update [--json] [--no-git] [--reconcile <report|keep-local|adopt-local|prune-local|merge>] [--ours|--theirs]
219
+ skeeper status [--json]
220
+ skeeper log <path> [--latest]
221
+ skeeper fsck [--json] [--source-branch <branch>]
222
+ skeeper verify [--json] [--source-branch <branch>]
223
+ skeeper hooks install [--json]
224
+ skeeper hooks check [--json]
225
+ skeeper merge-driver [--json]
226
+ skeeper repair status|resume|abort [--json]
227
+ skeeper version
238
228
  ```
239
229
 
240
- | Flag | Default | Description |
241
- | ---------------- | --------- | ------------------------------------------------------------ |
242
- | `--sidecar` | | Existing sidecar repository URL |
243
- | `--sidecar-name` | | GitHub sidecar repository name or `OWNER/REPO` |
244
- | `--visibility` | `private` | GitHub visibility: `private`, `public`, or `internal` |
245
- | `--namespace` | repo slug | Initial sidecar namespace for this source repo |
246
- | `--bootstrap` | | Optional install command stored in `.skeeper.yml` |
247
- | `--patterns` | | Complete spec glob pattern set; repeat for multiple patterns |
230
+ Command notes:
248
231
 
249
- When run interactively, `init` opens a terminal form. It includes the default spec globs automatically, then asks whether to add extra context globs such as `AGENTS.md`, `CLAUDE.md`, or `.codex/plans/**`. It runs `gh repo create` for `--sidecar-name` or the create mode, but skips GitHub creation when `--sidecar` is provided. `--sidecar` and `--sidecar-name` are mutually exclusive.
232
+ - `sync` uses working-tree content and stages `skeeper.lock`. Hook mode uses staged index content.
233
+ - `adopt` and `untrack` push sidecar coverage before removing main-index tracking.
234
+ - `pattern add --adopt-existing` updates `.skeeper.yml`, updates the managed `.gitignore` block, then runs the same adoption transaction.
235
+ - `verify` checks `skeeper.lock` against the sidecar remote and does not require hooks.
236
+ - `fsck` compares current working-tree specs against locked sidecar content, reports exact drift paths, and does not mutate files or refs.
237
+ - `hydrate` restores from locked sidecar commits by default, but fails closed if local managed files would be overwritten or orphaned.
238
+ - `reconcile` is the explicit status/add/merge equivalent for managed documents. Use `--adopt-local` to publish local-only files, or `--prune-local` to move them to `.git/skeeper/rescue/<id>/` before restoring.
239
+ - `diff` lists per-path drift classes such as `local_only`, `missing_local`, `local_modified`, and `both_modified_conflict`.
240
+ - `update` is the high-level clone workflow for agents: safe fast-forward, verify, hydrate, fsck, and hook validation.
241
+ - `log --latest` fetches the namespace branch and reads its latest history instead of the locked commit.
242
+ - `hooks install` removes legacy Skeeper post-commit blocks, installs strict pre-commit/pre-merge-commit/pre-push blocks, writes `.gitattributes`, and configures the `skeeper.lock` merge driver.
250
243
 
251
- </details>
244
+ ## 🤖 CI Action
252
245
 
253
- <details>
254
- <summary><code>skeeper hydrate</code> — Restore spec files from the sidecar repository</summary>
246
+ Use the same-repository Action to verify `skeeper.lock` in CI:
255
247
 
256
- ```bash
257
- skeeper hydrate
248
+ ```yaml
249
+ name: skeeper
250
+
251
+ on:
252
+ pull_request:
253
+ push:
254
+ branches: [main]
255
+
256
+ jobs:
257
+ verify:
258
+ runs-on: ubuntu-latest
259
+ steps:
260
+ - uses: actions/checkout@v4
261
+ with:
262
+ fetch-depth: 0
263
+ - uses: compozy/skeeper@v0.1.1
264
+ with:
265
+ args: |
266
+ verify
267
+ --json
268
+ ssh-private-key: ${{ secrets.SKEEPER_SSH_PRIVATE_KEY }}
258
269
  ```
259
270
 
260
- Use after a fresh clone of the main repo. `hydrate` clones the sidecar into `.skeeper/`, copies matched spec files into the working tree, and installs the post-commit hook. No flags.
271
+ Credential precedence:
261
272
 
262
- </details>
273
+ 1. `ssh-private-key` writes a temp key and sets `GIT_SSH_COMMAND`.
274
+ 2. `token` configures HTTPS GitHub credentials.
275
+ 3. Existing runner Git/SSH credentials are used when neither input is provided.
263
276
 
264
- <details>
265
- <summary><code>skeeper sync</code> — Mirror spec files into the sidecar repository</summary>
277
+ Secrets are masked before configuration. The wrapper downloads the released Skeeper binary for the action ref/tag and delegates verification to the CLI.
266
278
 
267
- ```bash
268
- skeeper sync [flags]
269
- ```
279
+ ## 🩺 Troubleshooting
270
280
 
271
- | Flag | Default | Description |
272
- | -------- | ------- | ------------------------------------------------------------------------------------ |
273
- | `--pull` | `false` | Pull and rebase the sidecar branch before syncing |
274
- | `--hook` | `false` | Run in post-commit hook mode: 750 ms foreground budget per namespace, always exits 0 |
281
+ **`SKEEPER_SKIP=1` was used**
275
282
 
276
- Drains queued retries from `.git/skeeper/queue.json`, then mirrors spec files into `.skeeper/`, commits, and pushes. Use `--pull` when teammates may have pushed sidecar updates between your commits. `--hook` is what the installed post-commit hook calls — you rarely run it manually.
283
+ Run `skeeper status`, then `skeeper sync`, then `skeeper verify`. The bypass journal remains visible until sync clears it.
277
284
 
278
- </details>
285
+ **Sidecar push was rejected**
279
286
 
280
- <details>
281
- <summary><code>skeeper status</code> — Show sidecar sync status</summary>
282
-
283
- ```bash
284
- skeeper status
285
- ```
287
+ Run `skeeper repair status`. If the failure happened before main-index mutation, fix network/auth or sidecar contention and run `skeeper repair resume`. If the main index was already mutated, inspect the listed files manually.
286
288
 
287
- Prints the sidecar URL, current source branch, one status block per namespace, last sync commit and age, remote state, tracked file counts, and pending queued syncs. No flags.
289
+ **`skeeper.lock` conflicts during merge**
288
290
 
289
- </details>
291
+ Run `skeeper hooks install` to ensure the merge driver is configured, then rerun the merge. Manual editing of scalar sidecar SHAs is unsupported; regenerate the lock through `skeeper merge-driver` or `skeeper sync`.
290
292
 
291
- <details>
292
- <summary><code>skeeper log &lt;path&gt;</code> — Show sidecar history for a spec file</summary>
293
+ **`skeeper hydrate` is blocked by local managed files**
293
294
 
294
- ```bash
295
- skeeper log <path>
296
- ```
295
+ Run `skeeper diff` to inspect exact paths. Use `skeeper reconcile --adopt-local` when the local files should be published into the sidecar, or `skeeper reconcile --prune-local` when they should be moved to rescue before restoring the locked content. Use `skeeper rescue list` and `skeeper rescue restore <id>` to recover pruned files.
297
296
 
298
- Runs `git log` against the sidecar for one spec file. The path is relative to the main repo root, e.g. `skeeper log src/auth/SPEC.md`. `skeeper` resolves the current owning namespace and reads `<namespace>/<path>` inside that namespace branch.
297
+ **`verify` reports a lock mismatch**
299
298
 
300
- </details>
299
+ The main commit and sidecar remote disagree. Run `skeeper sync`, include the updated `skeeper.lock`, and rerun `skeeper verify`.
301
300
 
302
- <details>
303
- <summary><code>skeeper version</code> — Print build metadata</summary>
301
+ **A namespace overlaps another namespace**
304
302
 
305
- ```bash
306
- skeeper version
307
- ```
303
+ Move shared files into exactly one namespace by adding `exclude:` entries. Skeeper does not use order-based precedence.
308
304
 
309
- Prints `Version`, `Commit`, and `BuildDate` injected at build time via ldflags.
305
+ ## 🚫 When Skeeper Is the Wrong Tool
310
306
 
311
- </details>
307
+ - Repositories where specs already belong in the main diff and reviewers explicitly want them inline.
308
+ - Teams that need PR review on the spec content itself before merge — Skeeper mirrors after the main commit succeeds, by design.
309
+ - Repositories without a stable sidecar Git host: Skeeper fails the commit when the sidecar is unreachable (the audited `SKEEPER_SKIP=1` bypass exists, but it is not a substitute for a working remote).
310
+ - Storing build artifacts, generated code, or large binaries. Default guardrails cap mutating plans at 100 files and 10 MiB on purpose.
312
311
 
313
312
  ## 🛠️ Development
314
313
 
315
314
  ```bash
316
- mise install # provision Go 1.26.2, Bun 1.3.4, and CLI tools
315
+ mise install
317
316
  bun install
318
317
  make hooks-install
319
- make verify # fmt → lint → test → build (BLOCKING gate)
318
+ make verify
320
319
  ```
321
320
 
322
321
  Common targets:
323
322
 
324
323
  ```bash
325
- make fmt # gofmt every .go file
326
- make lint # golangci-lint v2 + gopls modernize (zero tolerance)
327
- make test # gotestsum + -race -parallel=4
328
- make build # bin/skeeper with version ldflags
329
- make cover # coverage.out + coverage.html
330
- ```
331
-
332
- Releases are prepared through release pull requests and published with [GoReleaser Pro](.goreleaser.yml). A push to `main` creates or updates a release PR with `pr-release`; the release PR runs a GoReleaser dry run, and merging the release commit publishes GitHub release artifacts, the Homebrew cask, and the NPM package.
333
-
334
- Release publishing requires these GitHub Actions secrets:
335
-
336
- | Secret | Purpose |
337
- | ---------------- | ------------------------------------------------------------- |
338
- | `RELEASE_TOKEN` | Create/update release PRs, push release tags, update Homebrew |
339
- | `GORELEASER_KEY` | Run GoReleaser Pro |
340
- | `NPM_TOKEN` | Publish `@compozy/skeeper` and authenticate npm in release CI |
341
-
342
- Release notes are generated by `pr-release`. Add pending human-authored notes under `.release-notes/`; the release PR writes the current release body to `RELEASE_BODY.md` and prepends it to `RELEASE_NOTES.md`. The production workflow passes `RELEASE_BODY.md` to GoReleaser with the Skeeper release header and footer templates.
343
-
344
- Local release checks:
345
-
346
- ```bash
347
- make release-snapshot # requires GoReleaser Pro in PATH, or GORELEASER_KEY for the installer
324
+ make fmt
325
+ make lint
326
+ make test
327
+ make build
328
+ make cover
329
+ make release-snapshot
348
330
  ```
349
331
 
350
- Contributor guidance, commit conventions, and the agent skill dispatch protocol live in [`CLAUDE.md`](CLAUDE.md) and [`AGENTS.md`](AGENTS.md).
351
-
352
- ## 🤖 Official Agent Skill
353
-
354
- `skeeper` ships its own first-party agent skill at [`skills/skeeper-project/SKILL.md`](skills/skeeper-project/SKILL.md). LLM agents working in this repository should load it before reading or modifying code — it captures the workflow, project rules, and verification gates that aren't obvious from the source alone, and points to `references/domain-map.md` and `references/repo-contract.md` for package boundaries and change rules.
355
-
356
- ## 🤝 Contributing
357
-
358
- Contributions are welcome. Open an issue to discuss larger changes, or send a pull request for fixes and small improvements. All commits follow [Conventional Commits](https://www.conventionalcommits.org/) (`build`, `chore`, `ci`, `docs`, `feat`, `fix`, `perf`, `refactor`, `test`), enforced by `commitlint`.
332
+ Contributor guidance, commit conventions, and agent instructions live in [`CLAUDE.md`](CLAUDE.md) and [`AGENTS.md`](AGENTS.md).
359
333
 
360
334
  ## 📄 License
361
335
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@compozy/skeeper",
3
3
  "type": "module",
4
- "version": "0.1.1",
4
+ "version": "0.2.1",
5
5
  "description": "Sidecar Git versioning for spec artifacts",
6
6
  "scripts": {
7
7
  "postinstall": "node install.js",
@@ -27,63 +27,63 @@
27
27
  },
28
28
  "archives": {
29
29
  "darwin-arm64": {
30
- "name": "skeeper_0.1.1_darwin_arm64.tar.gz",
31
- "url": "https://github.com/compozy/skeeper/releases/download/v0.1.1/skeeper_0.1.1_darwin_arm64.tar.gz",
30
+ "name": "skeeper_0.2.1_darwin_arm64.tar.gz",
31
+ "url": "https://github.com/compozy/skeeper/releases/download/v0.2.1/skeeper_0.2.1_darwin_arm64.tar.gz",
32
32
  "bins": [
33
33
  "skeeper"
34
34
  ],
35
35
  "format": "tar.gz",
36
36
  "checksum": {
37
37
  "algorithm": "sha256",
38
- "digest": "8b1f41d35c843099df08c3f43b503fc2163dd0ffb77846be14f3b997e0742d9c"
38
+ "digest": "0ce0b122891566136533442e176b05decc990bbe4e17bf01b290065276104392"
39
39
  }
40
40
  },
41
41
  "darwin-x64": {
42
- "name": "skeeper_0.1.1_darwin_x86_64.tar.gz",
43
- "url": "https://github.com/compozy/skeeper/releases/download/v0.1.1/skeeper_0.1.1_darwin_x86_64.tar.gz",
42
+ "name": "skeeper_0.2.1_darwin_x86_64.tar.gz",
43
+ "url": "https://github.com/compozy/skeeper/releases/download/v0.2.1/skeeper_0.2.1_darwin_x86_64.tar.gz",
44
44
  "bins": [
45
45
  "skeeper"
46
46
  ],
47
47
  "format": "tar.gz",
48
48
  "checksum": {
49
49
  "algorithm": "sha256",
50
- "digest": "f8e895b2a2206b15041dfd796d6c2df23e24f84a6272dae030ae929b62165a5a"
50
+ "digest": "5f7f2cf4ba83c3ed2f69eb388f04dce42b5e9f49009e307ede9b5200dcdc6621"
51
51
  }
52
52
  },
53
53
  "linux-arm64": {
54
- "name": "skeeper_0.1.1_linux_arm64.tar.gz",
55
- "url": "https://github.com/compozy/skeeper/releases/download/v0.1.1/skeeper_0.1.1_linux_arm64.tar.gz",
54
+ "name": "skeeper_0.2.1_linux_arm64.tar.gz",
55
+ "url": "https://github.com/compozy/skeeper/releases/download/v0.2.1/skeeper_0.2.1_linux_arm64.tar.gz",
56
56
  "bins": [
57
57
  "skeeper"
58
58
  ],
59
59
  "format": "tar.gz",
60
60
  "checksum": {
61
61
  "algorithm": "sha256",
62
- "digest": "711dbee658801c1c388d7c87d5b0ab1e39035c872266768d4ddef6edfa7e2912"
62
+ "digest": "b10431032496f3091e2227ca2dd3fa660425f6224f1526b6086cb00a2f006396"
63
63
  }
64
64
  },
65
65
  "linux-x64": {
66
- "name": "skeeper_0.1.1_linux_x86_64.tar.gz",
67
- "url": "https://github.com/compozy/skeeper/releases/download/v0.1.1/skeeper_0.1.1_linux_x86_64.tar.gz",
66
+ "name": "skeeper_0.2.1_linux_x86_64.tar.gz",
67
+ "url": "https://github.com/compozy/skeeper/releases/download/v0.2.1/skeeper_0.2.1_linux_x86_64.tar.gz",
68
68
  "bins": [
69
69
  "skeeper"
70
70
  ],
71
71
  "format": "tar.gz",
72
72
  "checksum": {
73
73
  "algorithm": "sha256",
74
- "digest": "76e72d20d8486e484b9789df69c19a327ad0307051554eaf7db078474a3cbbac"
74
+ "digest": "b913b1e145d0cb81f32c3b968e429810942237df0e3f93af2ebd10714409437d"
75
75
  }
76
76
  },
77
77
  "win32-x64": {
78
- "name": "skeeper_0.1.1_windows_x86_64.zip",
79
- "url": "https://github.com/compozy/skeeper/releases/download/v0.1.1/skeeper_0.1.1_windows_x86_64.zip",
78
+ "name": "skeeper_0.2.1_windows_x86_64.zip",
79
+ "url": "https://github.com/compozy/skeeper/releases/download/v0.2.1/skeeper_0.2.1_windows_x86_64.zip",
80
80
  "bins": [
81
81
  "skeeper.exe"
82
82
  ],
83
83
  "format": "zip",
84
84
  "checksum": {
85
85
  "algorithm": "sha256",
86
- "digest": "b445168f2811e1a2948f434cc03c91fbe2bff9c0ae1bd64677dfec393ed02559"
86
+ "digest": "0dc47392a8427bf29242ee92e72971cb8b4020ee78c03e6477ee0467523f8dd3"
87
87
  }
88
88
  }
89
89
  }