@compozy/skeeper 0.1.0 → 0.2.0

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 +183 -211
  2. package/package.json +16 -16
package/README.md CHANGED
@@ -1,6 +1,7 @@
1
1
  <div align="center">
2
- <h1>skeeper</h1>
3
- <p><strong>Version your spec artifacts PRDs, tech specs, ADRs, AI plans — in a sidecar Git repository, without polluting your main PRs.</strong></p>
2
+ <p>
3
+ <img src="docs/assets/skeeper-readme-hero.png" alt="skeeper hero showing AI specs syncing into a sidecar Git repository" width="100%">
4
+ </p>
4
5
  <p>
5
6
  <a href="https://github.com/compozy/skeeper/actions/workflows/ci.yml">
6
7
  <img src="https://github.com/compozy/skeeper/actions/workflows/ci.yml/badge.svg" alt="CI">
@@ -8,9 +9,6 @@
8
9
  <a href="https://pkg.go.dev/github.com/compozy/skeeper">
9
10
  <img src="https://pkg.go.dev/badge/github.com/compozy/skeeper.svg" alt="Go Reference">
10
11
  </a>
11
- <a href="https://goreportcard.com/report/github.com/compozy/skeeper">
12
- <img src="https://goreportcard.com/badge/github.com/compozy/skeeper" alt="Go Report Card">
13
- </a>
14
12
  <a href="LICENSE">
15
13
  <img src="https://img.shields.io/badge/License-MIT-blue.svg" alt="License: MIT">
16
14
  </a>
@@ -20,331 +18,305 @@
20
18
  </p>
21
19
  </div>
22
20
 
23
- 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`.
24
-
25
- ## ✨ Highlights
21
+ Spec docs drift from code, or they bloat every PR. Skeeper picks neither.
26
22
 
27
- - **One sidecar repo, full Git history.** Specs version normally `git log`, `git blame`, branches, PRs without touching your main repo's diff.
28
- - **Shared sidecars without collisions.** `directory` namespaces each source repo inside one sidecar remote, including both stored paths and pushed branches.
29
- - **Edit specs where they belong.** Spec files stay next to the code they describe. `skeeper` mirrors them into `.skeeper/` for you.
30
- - **A post-commit hook that never breaks your commit.** 750 ms foreground budget; on failure, the sync queues locally and retries on the next manual `skeeper sync`.
31
- - **Branch-aware mirroring.** Sidecar branches track main-tree branches, so feature work and `main` stay isolated.
32
- - **Fresh-clone hydration.** `skeeper hydrate` restores matched specs into a new clone so teammates start with full context.
33
- - **Glob-based pattern matching.** Doublestar globs (`**/SPEC.md`, `docs/specs/**`, `.claude/plans/**`) — match specs the way you actually organize them.
34
- - **Shells out to `git` and `gh`.** Reuses your existing GitHub auth. Every operation is debuggable with the same Git commands you already know.
35
- - **Single static binary, zero runtime deps.** Linux, macOS, Windows on amd64/arm64. CGO disabled.
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.
36
24
 
37
- ## 📦 Installation
25
+ ## Highlights
38
26
 
39
- #### Homebrew
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
+ - **Agent-friendly commands.** `status`, `sync`, `verify`, `fsck`, `hooks check`, `repair status`, `pattern`, `adopt`, and `untrack` all support deterministic output where needed.
34
+ - **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.
40
35
 
41
- ```bash
42
- brew tap compozy/compozy
43
- brew install --cask skeeper
44
- ```
36
+ ## 🎯 Who Is This For
45
37
 
46
- #### NPM
38
+ - Teams using AI coding agents that produce `SPEC.md`, PRD, TechSpec, and plan markdown next to code.
39
+ - Engineering organizations running ADRs, RFCs, and design docs in-repo without making every PR a docs+code review.
40
+ - Solo developers who want full spec history (`git log`, `git blame`, branches, PRs) without polluting their main repository's diff.
47
41
 
48
- ```bash
49
- npm install -g @compozy/skeeper
50
- ```
51
-
52
- #### Go
42
+ ## 📦 Installation
53
43
 
54
44
  ```bash
55
45
  go install github.com/compozy/skeeper/cmd/skeeper@latest
56
46
  ```
57
47
 
58
- #### From Source
48
+ Other release channels are available through GitHub Releases, Homebrew, NPM, and the distroless Docker image.
59
49
 
60
- ```bash
61
- git clone git@github.com:compozy/skeeper.git
62
- cd skeeper && make verify && go build -o bin/skeeper ./cmd/skeeper
63
- ```
64
-
65
- #### Docker
66
-
67
- ```bash
68
- git clone git@github.com:compozy/skeeper.git
69
- cd skeeper && make docker-build # builds skeeper:dev (distroless, nonroot)
70
- docker run --rm -v "$PWD:/workspace" -w /workspace skeeper:dev status
71
- ```
72
-
73
- #### Prerequisites
50
+ Prerequisites:
74
51
 
75
52
  - `git` on `PATH`
76
- - `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`.
53
+ - `gh` only when `skeeper init` creates a new GitHub sidecar repo
77
54
 
78
55
  ## 🔄 How It Works
79
56
 
80
- Spec files live at their natural paths next to code. Your main repo's `.gitignore` lists those patterns plus `.skeeper/`, so neither the specs nor the sidecar clone ever appear in a main-repo diff.
81
-
82
- On every `git commit`, the managed post-commit hook runs `skeeper sync --hook` with a 750 ms foreground budget. `skeeper` matches files against your patterns, copies them into `.skeeper/`, commits with a reference to the main commit SHA, and pushes to the sidecar remote.
83
-
84
- When `directory` is configured, files are stored under that namespace in the sidecar and branches are pushed as `<directory>/__branches__/<source-branch>`. For example, `directory: skeeper` on source branch `main` stores `src/auth/SPEC.md` as `skeeper/src/auth/SPEC.md` and pushes sidecar branch `skeeper/__branches__/main`.
57
+ 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>`.
85
58
 
86
- If anything fails network, auth, push rejection, timeout `skeeper` writes a retry record to `.git/skeeper/queue.json`, 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.
59
+ 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.
87
60
 
88
61
  ```mermaid
89
- flowchart LR
90
- A[Developer<br/>git commit] --> B[post-commit hook<br/>skeeper sync --hook]
91
- B --> C{Sync within<br/>750 ms?}
92
- C -- yes --> D[Copy matched specs<br/>into .skeeper/]
93
- D --> E[git commit<br/>in sidecar]
94
- E --> F[git push<br/>to sidecar remote]
95
- C -- no / error --> G[Write retry record<br/>.git/skeeper/queue.json]
96
- G --> H[Hook exits 0<br/>main commit succeeds]
97
- H -. later .-> I[skeeper sync<br/>drains queue]
98
- I --> D
62
+ flowchart TD
63
+ Start([👤 git commit]):::user --> UserHook[🪝 Existing user hook content]:::user
64
+ UserHook --> Block
65
+
66
+ subgraph Block [📦 Skeeper pre-commit block]
67
+ direction TB
68
+ S1[🧮 Reconcile staged specs<br/>+ ownership] --> S2[🔄 Fetch &amp; rebase<br/>sidecar branch]
69
+ S2 --> S3[🪞 Mirror namespace files<br/>into .skeeper/]
70
+ S3 --> S4[📤 Commit &amp; push sidecar]
71
+ S4 --> S5[🔒 Write &amp; stage<br/>skeeper.lock]
72
+ end
73
+
74
+ Block --> Commit[✅ Main commit proceeds]:::ok
75
+ Commit --> Push([🚀 git push]):::user
76
+ Push --> Verify[🔍 Skeeper pre-push verify]:::skeeper
77
+ Verify --> Done([🎉 Sidecar verified]):::ok
78
+
79
+ classDef user fill:#dbeafe,stroke:#1d4ed8,color:#0c1e3e
80
+ classDef skeeper fill:#fef3c7,stroke:#b45309,color:#3b2c00
81
+ classDef ok fill:#dcfce7,stroke:#15803d,color:#052e16
82
+ class S1,S2,S3,S4,S5 skeeper
99
83
  ```
100
84
 
85
+ 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.
86
+
101
87
  ## ⚙️ Configuration
102
88
 
103
- `skeeper init` writes `.skeeper.yml` at the repo root. Commit it — your teammates need it for `skeeper hydrate`.
89
+ `skeeper init` writes `.skeeper.yml` at the repository root. Commit it.
104
90
 
105
91
  ```yaml
106
- # Required: sidecar repository URL
107
92
  sidecar: git@github.com:user/myproject-specs.git
108
93
 
109
- # Recommended: namespace for this source repo inside a shared sidecar
110
- directory: myproject
111
-
112
- # Required: doublestar globs that select spec files
113
- patterns:
114
- - "**/SPEC.md"
115
- - "docs/specs/**"
116
- - ".claude/plans/**"
117
- - "**/*.spec.md"
94
+ namespaces:
95
+ - name: project
96
+ patterns:
97
+ - "**/SPEC.md"
98
+ - "docs/specs/**"
99
+ - ".claude/plans/**"
100
+ - "**/*.spec.md"
101
+ exclude:
102
+ - "docs/specs/private/**"
118
103
 
119
- # Optional: install one-liner shown to teammates after `skeeper hydrate`
120
104
  bootstrap: brew tap compozy/compozy && brew install --cask skeeper
121
105
  ```
122
106
 
123
- Unknown keys are rejected — config errors fail loud, not silently.
107
+ Advanced operational defaults are optional:
124
108
 
125
- `directory` is strongly recommended for every new project and is written by `skeeper init` by default. It lets multiple source repos safely use one sidecar repository: each repo writes to its own sidecar subdirectory and its own namespaced sidecar branches. If `directory` is omitted, `skeeper` uses the legacy behavior: files are mirrored at the sidecar root and the sidecar branch is exactly the source branch. That legacy mode is useful for dedicated sidecars, but it is unsafe for shared sidecars because different repos can delete each other's matched files and compete for the same branch.
126
-
127
- There is no automatic migration from root storage to a new `directory`. Adding `directory` starts using a new path namespace and branch namespace.
128
-
129
- Local-only state lives under `.git/skeeper/` (already gitignored by Git's hooks directory):
130
-
131
- | File | Purpose |
132
- | ------------ | ------------------------------------------------------ |
133
- | `queue.json` | Pending retries from failed hook runs |
134
- | `sync.log` | Append-only audit log of sync attempts and error codes |
109
+ ```yaml
110
+ settings:
111
+ guardrails:
112
+ max_files: 100
113
+ max_bytes: 10485760
114
+ hooks:
115
+ pre_push_timeout: 30s
116
+ allow_skip_env: SKEEPER_SKIP
117
+
118
+ namespaces:
119
+ - name: generated
120
+ patterns:
121
+ - "generated/specs/**"
122
+ respect_gitignore: false
123
+ ```
135
124
 
136
- ## 🚀 Quick Start
125
+ Rules:
137
126
 
138
- ### 1. Install
127
+ - Unknown keys are rejected.
128
+ - Every namespace needs a `name` and at least one glob in `patterns`.
129
+ - `exclude` is the only public exclusion mechanism. Negative globs in `patterns` are rejected.
130
+ - Ownership must be unique. If two namespaces own the same file, the plan fails and asks for an `exclude` fix.
131
+ - `respect_gitignore: false` bypasses root `.gitignore`, nested `.gitignore`, `.git/info/exclude`, and global excludes for that namespace. `.git/` and `.skeeper/` are always excluded.
139
132
 
140
- ```bash
141
- go install github.com/compozy/skeeper/cmd/skeeper@latest
142
- ```
133
+ Local-only state lives under `.git/skeeper/`:
143
134
 
144
- ### 2. Initialize the sidecar
135
+ | File | Purpose |
136
+ | ------------------ | ---------------------------------------------- |
137
+ | `transaction.json` | Current resumable mutating operation and phase |
138
+ | `bypass.json` | Latest audited strict-hook bypass |
145
139
 
146
- In a Git repo where you want to track specs:
140
+ ## 🚀 Quick Start
147
141
 
148
142
  ```bash
149
143
  skeeper init
150
144
  ```
151
145
 
152
- Interactive by default — opens a terminal form for the sidecar mode, repository name or URL, `directory`, bootstrap command, and spec patterns. Or pass values as flags:
146
+ Interactive init asks for the sidecar mode, repository name or URL, namespace, bootstrap command, and optional extra context globs. With flags:
153
147
 
154
148
  ```bash
155
149
  skeeper init \
156
150
  --sidecar-name myproject-specs \
157
151
  --visibility private \
158
- --directory myproject \
152
+ --namespace project \
159
153
  --patterns "**/SPEC.md" \
160
- --patterns ".claude/plans/**"
154
+ --patterns "docs/specs/**"
161
155
  ```
162
156
 
163
- To reuse one shared sidecar remote across multiple source repos:
157
+ Use an existing shared sidecar:
164
158
 
165
159
  ```bash
166
160
  skeeper init \
167
161
  --sidecar git@github.com:user/shared-specs.git \
168
- --directory myproject \
162
+ --namespace project \
169
163
  --patterns "**/SPEC.md"
170
164
  ```
171
165
 
172
- `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 runs default `directory` to the source repo name; pass `--no-directory` only when you intentionally want legacy root behavior.
173
-
174
- ### 3. Edit specs and commit normally
166
+ Then edit specs and commit normally:
175
167
 
176
168
  ```bash
177
169
  $EDITOR src/auth/SPEC.md
178
- git add .
170
+ git add src/auth/service.go src/auth/SPEC.md
179
171
  git commit -m "auth: design OAuth provider flow"
180
172
  ```
181
173
 
182
- The hook fires automatically. No extra step.
174
+ 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.
183
175
 
184
- ### 4. Inspect
176
+ ## 🛟 Failed Sync Recovery
177
+
178
+ Inspect local repair state:
185
179
 
186
180
  ```bash
187
- skeeper status # sidecar URL, branch mapping, last sync, pending count
188
- skeeper log src/auth/SPEC.md # sidecar Git history for one file
181
+ skeeper repair status
189
182
  ```
190
183
 
191
- ### 5. Onboard a teammate
184
+ Resume the recorded operation when network/auth/sidecar contention has been fixed:
192
185
 
193
186
  ```bash
194
- git clone git@github.com:user/myproject.git
195
- cd myproject
196
- skeeper hydrate
187
+ skeeper repair resume
197
188
  ```
198
189
 
199
- `hydrate` clones the sidecar into `.skeeper/`, restores matched specs into the working tree, and installs the hook.
200
-
201
- ### 6. Recover from a failed sync
202
-
203
- If the hook ever queued work (network blip, push rejection):
190
+ Abort only before the main index has been mutated:
204
191
 
205
192
  ```bash
206
- skeeper sync # drain queued retries, then run a fresh sync
207
- skeeper sync --pull # rebase the sidecar branch first — useful when teammates pushed
193
+ skeeper repair abort
208
194
  ```
209
195
 
210
- ## 🧰 How Sync Works
211
-
212
- 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 so your `git commit` stays snappy even on a slow network.
213
-
214
- 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. With `directory`, the copy destination is `.skeeper/<directory>/<path>` and the push target is `<directory>/__branches__/<source-branch>`. Sidecar commits reference the main-repo SHA so you can correlate spec changes back to the code change that triggered them.
215
-
216
- On the failure path — timeout, auth failure, network failure, or push rejection — `skeeper` writes a retry record to `.git/skeeper/queue.json`, 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.
196
+ Run a fresh repair sync when a bypass or stale lock is reported:
217
197
 
218
- This design has two consequences worth knowing:
219
-
220
- - **`git commit` never fails because of `skeeper`.** Worst case, you have queued work to drain.
221
- - **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.
198
+ ```bash
199
+ skeeper sync
200
+ skeeper verify
201
+ ```
222
202
 
223
203
  ## 📖 CLI Reference
224
204
 
225
- <details>
226
- <summary><code>skeeper init</code> — Create and connect a sidecar specs repository</summary>
227
-
228
205
  ```bash
229
206
  skeeper init [flags]
207
+ skeeper sync [--dry-run] [--json] [--commit --message <msg>] [--force]
208
+ skeeper adopt <path-or-glob>... [--dry-run] [--json] [--force] [--commit --message <msg>]
209
+ skeeper untrack <path-or-glob>... [--dry-run] [--json] [--force] [--commit --message <msg>]
210
+ skeeper pattern test <glob> [--namespace <name>] [--json]
211
+ skeeper pattern add <glob> [--namespace <name>] [--exclude <glob>]... [--adopt-existing] [--dry-run] [--json] [--force] [--commit --message <msg>]
212
+ skeeper hydrate
213
+ skeeper status [--json]
214
+ skeeper log <path> [--latest]
215
+ skeeper fsck [--json] [--source-branch <branch>]
216
+ skeeper verify [--json] [--source-branch <branch>]
217
+ skeeper hooks install [--json]
218
+ skeeper hooks check [--json]
219
+ skeeper merge-driver [--json]
220
+ skeeper repair status|resume|abort [--json]
221
+ skeeper version
230
222
  ```
231
223
 
232
- | Flag | Default | Description |
233
- | ---------------- | --------- | ----------------------------------------------------- |
234
- | `--sidecar` | | Existing sidecar repository URL |
235
- | `--sidecar-name` | | GitHub sidecar repository name or `OWNER/REPO` |
236
- | `--visibility` | `private` | GitHub visibility: `private`, `public`, or `internal` |
237
- | `--directory` | repo slug | Sidecar directory namespace for this source repo |
238
- | `--no-directory` | `false` | Omit namespace and use legacy root behavior |
239
- | `--bootstrap` | | Optional install command stored in `.skeeper.yml` |
240
- | `--patterns` | | Spec glob pattern; repeat for multiple patterns |
224
+ Command notes:
241
225
 
242
- When run interactively, `init` opens a terminal form. 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; `--directory` and `--no-directory` are mutually exclusive.
226
+ - `sync` uses working-tree content and stages `skeeper.lock`. Hook mode uses staged index content.
227
+ - `adopt` and `untrack` push sidecar coverage before removing main-index tracking.
228
+ - `pattern add --adopt-existing` updates `.skeeper.yml`, updates the managed `.gitignore` block, then runs the same adoption transaction.
229
+ - `verify` checks `skeeper.lock` against the sidecar remote and does not require hooks.
230
+ - `fsck` compares current working-tree specs against locked sidecar content and does not mutate files or refs.
231
+ - `hydrate` restores from locked sidecar commits by default.
232
+ - `log --latest` fetches the namespace branch and reads its latest history instead of the locked commit.
233
+ - `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.
243
234
 
244
- </details>
235
+ ## 🤖 CI Action
245
236
 
246
- <details>
247
- <summary><code>skeeper hydrate</code> — Restore spec files from the sidecar repository</summary>
237
+ Use the same-repository Action to verify `skeeper.lock` in CI:
248
238
 
249
- ```bash
250
- skeeper hydrate
239
+ ```yaml
240
+ name: skeeper
241
+
242
+ on:
243
+ pull_request:
244
+ push:
245
+ branches: [main]
246
+
247
+ jobs:
248
+ verify:
249
+ runs-on: ubuntu-latest
250
+ steps:
251
+ - uses: actions/checkout@v4
252
+ with:
253
+ fetch-depth: 0
254
+ - uses: compozy/skeeper@v0.1.1
255
+ with:
256
+ args: |
257
+ verify
258
+ --json
259
+ ssh-private-key: ${{ secrets.SKEEPER_SSH_PRIVATE_KEY }}
251
260
  ```
252
261
 
253
- 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.
254
-
255
- </details>
256
-
257
- <details>
258
- <summary><code>skeeper sync</code> — Mirror spec files into the sidecar repository</summary>
259
-
260
- ```bash
261
- skeeper sync [flags]
262
- ```
262
+ Credential precedence:
263
263
 
264
- | Flag | Default | Description |
265
- | -------- | ------- | ---------------------------------------------------------------------- |
266
- | `--pull` | `false` | Pull and rebase the sidecar branch before syncing |
267
- | `--hook` | `false` | Run in post-commit hook mode: 750 ms foreground budget, always exits 0 |
264
+ 1. `ssh-private-key` writes a temp key and sets `GIT_SSH_COMMAND`.
265
+ 2. `token` configures HTTPS GitHub credentials.
266
+ 3. Existing runner Git/SSH credentials are used when neither input is provided.
268
267
 
269
- 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.
268
+ Secrets are masked before configuration. The wrapper downloads the released Skeeper binary for the action ref/tag and delegates verification to the CLI.
270
269
 
271
- </details>
270
+ ## 🩺 Troubleshooting
272
271
 
273
- <details>
274
- <summary><code>skeeper status</code> — Show sidecar sync status</summary>
272
+ **`SKEEPER_SKIP=1` was used**
275
273
 
276
- ```bash
277
- skeeper status
278
- ```
274
+ Run `skeeper status`, then `skeeper sync`, then `skeeper verify`. The bypass journal remains visible until sync clears it.
279
275
 
280
- Prints the sidecar URL, current source branch, directory namespace when configured, sidecar branch, last sync commit and age, remote URL, count of tracked spec files, and count of pending queued syncs. No flags.
276
+ **Sidecar push was rejected**
281
277
 
282
- </details>
278
+ 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.
283
279
 
284
- <details>
285
- <summary><code>skeeper log &lt;path&gt;</code> — Show sidecar history for a spec file</summary>
280
+ **`skeeper.lock` conflicts during merge**
286
281
 
287
- ```bash
288
- skeeper log <path>
289
- ```
282
+ 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
283
 
291
- 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`. When `directory` is configured, `skeeper` resolves that path to `<directory>/src/auth/SPEC.md` inside the sidecar.
284
+ **`verify` reports a lock mismatch**
292
285
 
293
- </details>
286
+ The main commit and sidecar remote disagree. Run `skeeper sync`, include the updated `skeeper.lock`, and rerun `skeeper verify`.
294
287
 
295
- <details>
296
- <summary><code>skeeper version</code> — Print build metadata</summary>
288
+ **A namespace overlaps another namespace**
297
289
 
298
- ```bash
299
- skeeper version
300
- ```
290
+ Move shared files into exactly one namespace by adding `exclude:` entries. Skeeper does not use order-based precedence.
301
291
 
302
- Prints `Version`, `Commit`, and `BuildDate` injected at build time via ldflags.
292
+ ## 🚫 When Skeeper Is the Wrong Tool
303
293
 
304
- </details>
294
+ - Repositories where specs already belong in the main diff and reviewers explicitly want them inline.
295
+ - Teams that need PR review on the spec content itself before merge — Skeeper mirrors after the main commit succeeds, by design.
296
+ - 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).
297
+ - Storing build artifacts, generated code, or large binaries. Default guardrails cap mutating plans at 100 files and 10 MiB on purpose.
305
298
 
306
299
  ## 🛠️ Development
307
300
 
308
301
  ```bash
309
- mise install # provision Go 1.26.2, Bun 1.3.4, and CLI tools
302
+ mise install
310
303
  bun install
311
304
  make hooks-install
312
- make verify # fmt → lint → test → build (BLOCKING gate)
305
+ make verify
313
306
  ```
314
307
 
315
308
  Common targets:
316
309
 
317
310
  ```bash
318
- make fmt # gofmt every .go file
319
- make lint # golangci-lint v2 + gopls modernize (zero tolerance)
320
- make test # gotestsum + -race -parallel=4
321
- make build # bin/skeeper with version ldflags
322
- make cover # coverage.out + coverage.html
323
- ```
324
-
325
- 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.
326
-
327
- Release publishing requires these GitHub Actions secrets:
328
-
329
- | Secret | Purpose |
330
- | ---------------- | ------------------------------------------------------------- |
331
- | `RELEASE_TOKEN` | Create/update release PRs, push release tags, update Homebrew |
332
- | `GORELEASER_KEY` | Run GoReleaser Pro |
333
- | `NPM_TOKEN` | Publish `@compozy/skeeper` and authenticate npm in release CI |
334
-
335
- 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.
336
-
337
- Local release checks:
338
-
339
- ```bash
340
- make release-snapshot # requires GoReleaser Pro in PATH, or GORELEASER_KEY for the installer
311
+ make fmt
312
+ make lint
313
+ make test
314
+ make build
315
+ make cover
316
+ make release-snapshot
341
317
  ```
342
318
 
343
- Contributor guidance, commit conventions, and the agent skill dispatch protocol live in [`CLAUDE.md`](CLAUDE.md) and [`AGENTS.md`](AGENTS.md).
344
-
345
- ## 🤝 Contributing
346
-
347
- 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`.
319
+ Contributor guidance, commit conventions, and agent instructions live in [`CLAUDE.md`](CLAUDE.md) and [`AGENTS.md`](AGENTS.md).
348
320
 
349
321
  ## 📄 License
350
322
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@compozy/skeeper",
3
3
  "type": "module",
4
- "version": "0.1.0",
4
+ "version": "0.2.0",
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.0_darwin_arm64.tar.gz",
31
- "url": "https://github.com/compozy/skeeper/releases/download/v0.1.0/skeeper_0.1.0_darwin_arm64.tar.gz",
30
+ "name": "skeeper_0.2.0_darwin_arm64.tar.gz",
31
+ "url": "https://github.com/compozy/skeeper/releases/download/v0.2.0/skeeper_0.2.0_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": "3dffc1d6f8c06971d2ee8ba7bd6b3e0986f6a7d2f20ec11d9ba3d44ccc8a7e91"
38
+ "digest": "0b989d71f5cf6dc929bcf18d9f86fbe827ba74231b38b98fe268a299ba56d7da"
39
39
  }
40
40
  },
41
41
  "darwin-x64": {
42
- "name": "skeeper_0.1.0_darwin_x86_64.tar.gz",
43
- "url": "https://github.com/compozy/skeeper/releases/download/v0.1.0/skeeper_0.1.0_darwin_x86_64.tar.gz",
42
+ "name": "skeeper_0.2.0_darwin_x86_64.tar.gz",
43
+ "url": "https://github.com/compozy/skeeper/releases/download/v0.2.0/skeeper_0.2.0_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": "d1376a86f8660996be34293586b8361404bf4f59b0ff530fb5d45af31d0b6df6"
50
+ "digest": "fced98b16ffae5f72d843dfe532b989ee4f4c6df59973167f38a8e68fde1385c"
51
51
  }
52
52
  },
53
53
  "linux-arm64": {
54
- "name": "skeeper_0.1.0_linux_arm64.tar.gz",
55
- "url": "https://github.com/compozy/skeeper/releases/download/v0.1.0/skeeper_0.1.0_linux_arm64.tar.gz",
54
+ "name": "skeeper_0.2.0_linux_arm64.tar.gz",
55
+ "url": "https://github.com/compozy/skeeper/releases/download/v0.2.0/skeeper_0.2.0_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": "73ceebead4563597d5918f022371998a1756fb7ebd86cc9ecc8a571ee39c9298"
62
+ "digest": "04ab4382e4a01cb7a9165c055b8cf3b9675d03fbebe056ff67e2b04450c02a54"
63
63
  }
64
64
  },
65
65
  "linux-x64": {
66
- "name": "skeeper_0.1.0_linux_x86_64.tar.gz",
67
- "url": "https://github.com/compozy/skeeper/releases/download/v0.1.0/skeeper_0.1.0_linux_x86_64.tar.gz",
66
+ "name": "skeeper_0.2.0_linux_x86_64.tar.gz",
67
+ "url": "https://github.com/compozy/skeeper/releases/download/v0.2.0/skeeper_0.2.0_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": "3f3debdd7ab88395271cc1d0c904d16a07aaa881fc683fcbcbcf0324ca588caa"
74
+ "digest": "f1e0426e926fc654fccf23a6744cebc9313bf43cd5cc14576adef6cdf5612583"
75
75
  }
76
76
  },
77
77
  "win32-x64": {
78
- "name": "skeeper_0.1.0_windows_x86_64.zip",
79
- "url": "https://github.com/compozy/skeeper/releases/download/v0.1.0/skeeper_0.1.0_windows_x86_64.zip",
78
+ "name": "skeeper_0.2.0_windows_x86_64.zip",
79
+ "url": "https://github.com/compozy/skeeper/releases/download/v0.2.0/skeeper_0.2.0_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": "2e1f4162817f0b8d7673d3489d7152a10409a61f788273e09455befbcff9fad7"
86
+ "digest": "7dc1569a187eecaccc5e79e5f69be4da95acf050a6b58816bb790a38303e8733"
87
87
  }
88
88
  }
89
89
  }