@slamb2k/mad-skills 2.0.55 → 2.0.57

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.
@@ -19,12 +19,14 @@ Template variables:
19
19
 
20
20
  ## GitHub Actions
21
21
 
22
- ### Build & Deploy Workflow
22
+ ### Build & Deploy Workflow (hardened)
23
23
 
24
- File: `.github/workflows/deploy.yml`
24
+ Placeholders: `{REGISTRY}` (e.g. `ghcr.io/OWNER/REPO`), `{IMAGE_NAME}`.
25
+ GHCR uses the ephemeral `GITHUB_TOKEN`; cloud registries use OIDC (see the
26
+ commented block). Deployment is by digest and gated on signature verification.
25
27
 
26
28
  ```yaml
27
- name: Build & Deploy
29
+ name: Release
28
30
 
29
31
  on:
30
32
  pull_request:
@@ -33,8 +35,8 @@ on:
33
35
  tags: ["v*"]
34
36
 
35
37
  concurrency:
36
- group: deploy-${{ github.ref }}
37
- cancel-in-progress: ${{ github.event_name == 'pull_request' }}
38
+ group: ${{ github.workflow }}-${{ github.ref }}
39
+ cancel-in-progress: true
38
40
 
39
41
  env:
40
42
  REGISTRY: {REGISTRY}
@@ -42,155 +44,112 @@ env:
42
44
 
43
45
  permissions:
44
46
  contents: read
45
- packages: write
47
+ packages: write # GHCR push
48
+ id-token: write # OIDC: cloud login + keyless cosign
46
49
 
47
50
  jobs:
48
- # ── Build & Test ──────────────────────────────────────────────
49
51
  build:
50
52
  runs-on: ubuntu-latest
51
- # Skip for release tags — we retag, not rebuild
52
- if: ${{ !startsWith(github.ref, 'refs/tags/v') }}
53
53
  outputs:
54
- image_tag: ${{ steps.meta.outputs.tags }}
55
- sha_short: ${{ steps.vars.outputs.sha_short }}
54
+ image: ${{ steps.digest.outputs.ref }} # REGISTRY/IMAGE@sha256:...
56
55
  steps:
57
56
  - uses: actions/checkout@v4
58
57
 
59
- - name: Set variables
60
- id: vars
61
- run: echo "sha_short=$(git rev-parse --short HEAD)" >> "$GITHUB_OUTPUT"
62
-
63
- - name: Docker meta
64
- id: meta
65
- uses: docker/metadata-action@v5
66
- with:
67
- images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
68
- tags: |
69
- type=sha,prefix=
70
- type=ref,event=pr,prefix=pr-
71
- type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', '{DEFAULT_BRANCH}') }}
72
-
73
58
  - uses: docker/setup-buildx-action@v3
74
59
 
75
- - name: Login to registry
60
+ - uses: imjasonh/setup-crane@v0.4
61
+
62
+ # Idempotency guard: skip build if this commit's image already exists.
63
+ - name: Check existing image
64
+ id: guard
65
+ run: |
66
+ TAG="${REGISTRY}/${IMAGE_NAME}:${GITHUB_SHA}"
67
+ if crane manifest "$TAG" >/dev/null 2>&1; then
68
+ echo "exists=true" >> "$GITHUB_OUTPUT"
69
+ echo "::notice::$TAG already present — reusing digest."
70
+ else
71
+ echo "exists=false" >> "$GITHUB_OUTPUT"
72
+ fi
73
+
74
+ # GHCR login (ephemeral token). For cloud registries, replace with OIDC:
75
+ # AWS ECR: aws-actions/configure-aws-credentials@v4 (role-to-assume) + aws ecr get-login-password
76
+ # Azure ACR: azure/login@v2 (WIF client-id/tenant-id/subscription-id) + az acr login
77
+ # GAR: google-github-actions/auth@v2 (workload_identity_provider + service_account) + gcloud auth configure-docker
78
+ - name: Login to GHCR
79
+ if: steps.guard.outputs.exists == 'false' && github.event_name == 'push'
76
80
  uses: docker/login-action@v3
77
81
  with:
78
82
  registry: ${{ env.REGISTRY }}
79
83
  username: ${{ github.actor }}
80
84
  password: ${{ secrets.GITHUB_TOKEN }}
81
85
 
82
- - name: Build and test
83
- uses: docker/build-push-action@v6
84
- with:
85
- context: .
86
- target: test
87
- cache-from: type=gha
88
- cache-to: type=gha,mode=max
89
-
90
- - name: Build and push production image
91
- if: github.event_name == 'push'
86
+ - name: Build and push
87
+ id: build
88
+ if: steps.guard.outputs.exists == 'false'
92
89
  uses: docker/build-push-action@v6
93
90
  with:
94
91
  context: .
95
92
  target: production
96
- push: true
97
- tags: ${{ steps.meta.outputs.tags }}
98
- labels: ${{ steps.meta.outputs.labels }}
99
- cache-from: type=gha
100
- cache-to: type=gha,mode=max
93
+ push: ${{ github.event_name == 'push' }}
94
+ tags: |
95
+ ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
96
+ ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
97
+ provenance: true
98
+ sbom: true
101
99
 
102
- # ── Deploy to Dev ─────────────────────────────────────────────
103
- deploy-dev:
104
- needs: build
105
- if: github.ref == 'refs/heads/{DEFAULT_BRANCH}'
106
- runs-on: ubuntu-latest
107
- environment: dev
108
- steps:
109
- - uses: actions/checkout@v4
100
+ - uses: sigstore/cosign-installer@v3
110
101
 
111
- - name: Deploy to dev
112
- run: |
113
- # {DEV_DEPLOY_COMMANDS}
114
- echo "Deploying ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.build.outputs.sha_short }} to dev"
102
+ - name: Sign image (keyless)
103
+ if: steps.guard.outputs.exists == 'false' && github.event_name == 'push'
104
+ run: cosign sign --yes "${REGISTRY}/${IMAGE_NAME}@${{ steps.build.outputs.digest }}"
115
105
 
116
- - name: Smoke test
106
+ - name: Resolve digest reference
107
+ id: digest
117
108
  run: |
118
- # Poll the health endpoint until ready (bounded deadline so a
119
- # genuinely broken deploy still fails fast). Do not use a blind
120
- # `sleep N && curl` — it flakes on slow cold starts and wastes
121
- # time on fast ones.
122
- deadline=$((SECONDS + 120))
123
- until curl -fs "$DEV_URL/healthz" >/dev/null; do
124
- if [ $SECONDS -ge $deadline ]; then
125
- echo "Health check failed to pass within 120s" >&2
126
- exit 1
127
- fi
128
- sleep 2
129
- done
130
-
131
- # ── Promote to Staging (on release tag) ───────────────────────
132
- promote-staging:
133
- if: startsWith(github.ref, 'refs/tags/v')
109
+ if [ "${{ steps.guard.outputs.exists }}" = "true" ]; then
110
+ DIG=$(crane digest "${REGISTRY}/${IMAGE_NAME}:${GITHUB_SHA}")
111
+ else
112
+ DIG="${{ steps.build.outputs.digest }}"
113
+ fi
114
+ echo "ref=${REGISTRY}/${IMAGE_NAME}@${DIG}" >> "$GITHUB_OUTPUT"
115
+
116
+ deploy-dev:
117
+ needs: build
118
+ if: github.event_name == 'push' && github.ref == 'refs/heads/{DEFAULT_BRANCH}'
134
119
  runs-on: ubuntu-latest
135
- environment: staging
136
- outputs:
137
- version: ${{ steps.version.outputs.tag }}
120
+ permissions:
121
+ contents: read
122
+ id-token: write
138
123
  steps:
139
- - uses: actions/checkout@v4
140
-
141
- - name: Extract version
142
- id: version
143
- run: echo "tag=${GITHUB_REF#refs/tags/}" >> "$GITHUB_OUTPUT"
144
-
145
- - name: Login to registry
146
- uses: docker/login-action@v3
147
- with:
148
- registry: ${{ env.REGISTRY }}
149
- username: ${{ github.actor }}
150
- password: ${{ secrets.GITHUB_TOKEN }}
151
-
152
- - name: Retag image (no rebuild)
124
+ - uses: sigstore/cosign-installer@v3
125
+ - name: Verify signature before deploy
153
126
  run: |
154
- SHA=$(git rev-parse --short HEAD)
155
- docker buildx imagetools create \
156
- --tag "${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.tag }}" \
157
- "${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${SHA}"
158
-
159
- - name: Deploy to staging
160
- run: |
161
- # {STAGING_DEPLOY_COMMANDS}
162
- echo "Deploying ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.tag }} to staging"
163
-
164
- - name: Run staging tests
165
- run: |
166
- # Integration and e2e tests against staging
167
- echo "Running staging gate tests..."
168
-
169
- # ── Promote to Production ─────────────────────────────────────
170
- promote-prod:
171
- needs: promote-staging
127
+ cosign verify \
128
+ --certificate-identity-regexp "https://github.com/${{ github.repository }}/.*" \
129
+ --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
130
+ "${{ needs.build.outputs.image }}"
131
+ - name: Deploy to dev (by digest — never rebuild)
132
+ run: echo "Deploy ${{ needs.build.outputs.image }} to dev"
133
+
134
+ promote:
135
+ needs: build
172
136
  if: startsWith(github.ref, 'refs/tags/v')
173
137
  runs-on: ubuntu-latest
174
- environment: production
138
+ permissions:
139
+ contents: read
140
+ id-token: write
175
141
  steps:
176
- - uses: actions/checkout@v4
177
-
178
- - name: Deploy to production
179
- run: |
180
- # {PROD_DEPLOY_COMMANDS}
181
- echo "Deploying ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.promote-staging.outputs.version }} to production"
182
-
183
- - name: Post-deploy smoke test
142
+ - uses: sigstore/cosign-installer@v3
143
+ - name: Verify signature
184
144
  run: |
185
- # Poll until healthy, bounded deadline. Never `sleep N && curl`.
186
- deadline=$((SECONDS + 120))
187
- until curl -fs "$PROD_URL/healthz" >/dev/null; do
188
- if [ $SECONDS -ge $deadline ]; then
189
- echo "Production health check failed to pass within 120s" >&2
190
- exit 1
191
- fi
192
- sleep 2
193
- done
145
+ cosign verify \
146
+ --certificate-identity-regexp "https://github.com/${{ github.repository }}/.*" \
147
+ --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
148
+ "${{ needs.build.outputs.image }}"
149
+ - name: Promote to staging (by digest)
150
+ run: echo "Deploy ${{ needs.build.outputs.image }} to staging"
151
+ - name: Promote to prod (by digest)
152
+ run: echo "Deploy ${{ needs.build.outputs.image }} to prod"
194
153
  ```
195
154
 
196
155
  ### Reusable deploy step patterns
@@ -201,112 +160,80 @@ deployment steps.
201
160
 
202
161
  ---
203
162
 
204
- ## Azure DevOps Pipelines
163
+ ## Azure DevOps Pipelines (hardened)
205
164
 
206
- File: `azure-pipelines.yml`
165
+ Uses a **Workload Identity Federation** service connection (`azureSubscription`)
166
+ for keyless ACR auth — no stored registry password. Images are signed and
167
+ deployed by digest.
207
168
 
208
169
  ```yaml
209
170
  trigger:
210
- branches:
211
- include: [{DEFAULT_BRANCH}]
212
- tags:
213
- include: ["v*"]
214
-
215
- pr:
216
- branches:
217
- include: [{DEFAULT_BRANCH}]
171
+ branches: { include: [main] }
172
+ tags: { include: ["v*"] }
218
173
 
219
174
  variables:
175
+ azureSubscription: "<WIF-service-connection-name>" # Workload Identity Federation
220
176
  registry: {REGISTRY}
221
177
  imageName: {IMAGE_NAME}
222
- vmImageName: ubuntu-latest
223
178
 
224
179
  stages:
225
- # ── Build & Test ──────────────────────────────────────────────
226
180
  - stage: Build
227
- condition: not(startsWith(variables['Build.SourceBranch'], 'refs/tags/v'))
228
181
  jobs:
229
- - job: BuildAndTest
230
- pool:
231
- vmImage: $(vmImageName)
182
+ - job: BuildSignPush
183
+ pool: { vmImage: ubuntu-latest }
232
184
  steps:
233
- - task: Docker@2
234
- displayName: Build test image
235
- inputs:
236
- command: build
237
- dockerfile: Dockerfile
238
- arguments: --target test -t $(imageName):test
239
-
240
- - task: Docker@2
241
- displayName: Build production image
185
+ - task: AzureCLI@2
186
+ displayName: ACR login (WorkloadIdentityFederation)
242
187
  inputs:
243
- command: build
244
- dockerfile: Dockerfile
245
- arguments: --target production -t $(registry)/$(imageName):$(Build.SourceVersion)
246
-
247
- - task: Docker@2
248
- displayName: Push to registry
249
- condition: eq(variables['Build.SourceBranch'], 'refs/heads/{DEFAULT_BRANCH}')
250
- inputs:
251
- command: push
252
- containerRegistry: $(dockerRegistryServiceConnection)
253
- repository: $(imageName)
254
- tags: |
255
- $(Build.SourceVersion)
256
- latest
257
-
258
- # ── Deploy to Dev ─────────────────────────────────────────────
259
- - stage: DeployDev
260
- condition: eq(variables['Build.SourceBranch'], 'refs/heads/{DEFAULT_BRANCH}')
188
+ azureSubscription: $(azureSubscription)
189
+ scriptType: bash
190
+ scriptLocation: inlineScript
191
+ inlineScript: az acr login --name $(echo $(registry) | cut -d. -f1)
192
+
193
+ - script: |
194
+ TAG="$(registry)/$(imageName):$(Build.SourceVersion)"
195
+ if docker manifest inspect "$TAG" >/dev/null 2>&1; then
196
+ echo "##vso[task.setvariable variable=exists]true"
197
+ else
198
+ echo "##vso[task.setvariable variable=exists]false"
199
+ fi
200
+ displayName: Idempotency guard
201
+
202
+ - script: |
203
+ docker buildx build --target production \
204
+ --provenance=true --sbom=true --push \
205
+ -t $(registry)/$(imageName):$(Build.SourceVersion) .
206
+ condition: ne(variables.exists, 'true')
207
+ displayName: Build, provenance, push
208
+
209
+ - script: |
210
+ DIG=$(docker buildx imagetools inspect $(registry)/$(imageName):$(Build.SourceVersion) --format '{{.Manifest.Digest}}')
211
+ echo "##vso[task.setvariable variable=digestRef;isOutput=true]$(registry)/$(imageName)@$DIG"
212
+ name: build
213
+ displayName: Resolve digest (both paths)
214
+
215
+ - script: cosign sign --yes $(build.digestRef)
216
+ condition: ne(variables.exists, 'true')
217
+ displayName: Sign image (keyless)
218
+
219
+ - stage: Promote
261
220
  dependsOn: Build
262
- jobs:
263
- - deployment: DeployDev
264
- environment: dev
265
- pool:
266
- vmImage: $(vmImageName)
267
- strategy:
268
- runOnce:
269
- deploy:
270
- steps:
271
- - script: |
272
- echo "Deploying $(registry)/$(imageName):$(Build.SourceVersion) to dev"
273
- # {DEV_DEPLOY_COMMANDS}
274
-
275
- # ── Promote to Staging ────────────────────────────────────────
276
- - stage: PromoteStaging
277
221
  condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v')
278
222
  jobs:
279
- - deployment: DeployStaging
280
- environment: staging
281
- pool:
282
- vmImage: $(vmImageName)
283
- strategy:
284
- runOnce:
285
- deploy:
286
- steps:
287
- - script: |
288
- TAG=${BUILD_SOURCEBRANCH#refs/tags/}
289
- echo "Retagging and deploying $TAG to staging"
290
- # Retag do NOT rebuild
291
- # {STAGING_DEPLOY_COMMANDS}
292
-
293
- # ── Promote to Production ─────────────────────────────────────
294
- - stage: PromoteProd
295
- condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v')
296
- dependsOn: PromoteStaging
297
- jobs:
298
- - deployment: DeployProd
299
- environment: production
300
- pool:
301
- vmImage: $(vmImageName)
302
- strategy:
303
- runOnce:
304
- deploy:
305
- steps:
306
- - script: |
307
- TAG=${BUILD_SOURCEBRANCH#refs/tags/}
308
- echo "Deploying $TAG to production"
309
- # {PROD_DEPLOY_COMMANDS}
223
+ - job: VerifyAndDeploy
224
+ pool: { vmImage: ubuntu-latest }
225
+ variables:
226
+ digestRef: $[ stageDependencies.Build.BuildSignPush.outputs['build.digestRef'] ]
227
+ steps:
228
+ # Set identity + issuer to your signing identity — see deploy/SETUP.md.
229
+ - script: |
230
+ cosign verify \
231
+ --certificate-identity-regexp "<CERT_IDENTITY_REGEXP>" \
232
+ --certificate-oidc-issuer "<OIDC_ISSUER>" \
233
+ $(digestRef)
234
+ displayName: Verify signature (by sha256 digest)
235
+ - script: echo "Deploy $(digestRef) to staging then prod (no rebuild)"
236
+ displayName: Promote by digest
310
237
  ```
311
238
 
312
239
  ---
@@ -41,5 +41,15 @@
41
41
  { "type": "semantic", "value": "Suggests Azure Container Registry rather than ghcr.io as the default registry" },
42
42
  { "type": "semantic", "value": "References Azure Pipelines as the CI system for the generated workflow" }
43
43
  ]
44
+ },
45
+ {
46
+ "name": "hardened-pipeline",
47
+ "prompt": "/dock --skip-interview for a Node.js app deploying to AWS ECR",
48
+ "assertions": [
49
+ { "type": "semantic", "value": "Uses OIDC / keyless federated authentication to the container registry rather than stored long-lived credentials" },
50
+ { "type": "semantic", "value": "Signs the built image (cosign) and verifies the signature before promoting to an environment" },
51
+ { "type": "semantic", "value": "Promotes images by immutable sha256 digest rather than a mutable tag" },
52
+ { "type": "semantic", "value": "Generates a deploy/SETUP.md describing the cloud-side federated-identity setup required before first run" }
53
+ ]
44
54
  }
45
55
  ]
@@ -0,0 +1,164 @@
1
+ ---
2
+ name: handover
3
+ description: Persist a session handover document and signal the next fresh session to resume from it. Use when the user types /handover, says they want to hand off, checkpoint, wrap up, clear context, or start fresh while preserving state, or asks to carry work into a new session with clean optimised context. Captures everything a brand-new session needs — task, status, next steps, key files, decisions, gotchas, git state — writes it to disk, arms a one-shot signal, and tells the user to /clear so the next session auto-loads it.
4
+ argument-hint: repo, tmp, commit (optional target; default repo)
5
+ allowed-tools: Bash, Read, Write
6
+ ---
7
+
8
+ # Handover - Clean-Context Session Handoff
9
+
10
+ When this skill is invoked, IMMEDIATELY output the banner below before doing anything else.
11
+ Pick ONE tagline at random — vary your choice each time.
12
+ CRITICAL: Reproduce the banner EXACTLY character-for-character, preserving all leading spaces.
13
+
14
+ ```
15
+ {tagline}
16
+
17
+ ██╗ ██╗ █████╗ ███╗ ██╗██████╗ ██████╗ ██╗ ██╗███████╗██████╗
18
+ ██║ ██║██╔══██╗████╗ ██║██╔══██╗██╔═══██╗██║ ██║██╔════╝██╔══██╗
19
+ ███████║███████║██╔██╗ ██║██║ ██║██║ ██║██║ ██║█████╗ ██████╔╝
20
+ ██╔══██║██╔══██║██║╚██╗██║██║ ██║██║ ██║╚██╗ ██╔╝██╔══╝ ██╔══██╗
21
+ ██║ ██║██║ ██║██║ ╚████║██████╔╝╚██████╔╝ ╚████╔╝ ███████╗██║ ██║
22
+ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝╚═════╝ ╚═════╝ ╚═══╝ ╚══════╝╚═╝ ╚═╝
23
+ ```
24
+
25
+ Taglines:
26
+ - 🪂 Packing the parachute for a clean landing...
27
+ - 🧳 Bagging up the context, checking out...
28
+ - 🤝 Passing the baton to a fresh runner!
29
+ - 📦 Boxing up everything the next session needs!
30
+ - 🧹 Clearing the desk, leaving a tidy note!
31
+ - 🛟 Nobody loses the thread on my watch!
32
+ - 🌅 Fresh session, same mission!
33
+
34
+ ---
35
+
36
+ ## What this does
37
+
38
+ Capture the live state of this session into a handover document, arm a one-shot
39
+ signal, and hand the user off to a clean session that will automatically resume
40
+ from the document. The point is to **reset the context window without losing the
41
+ thread** — the next session starts lean but fully briefed.
42
+
43
+ Long sessions accumulate noise: dead ends, superseded plans, stale file reads. A
44
+ fresh session is faster and sharper, but naively starting over loses hard-won
45
+ context. `/handover` distills only what the *next* session needs, persists it, and
46
+ signals that session to pick it up — once. It will not re-read old handovers on
47
+ unrelated future sessions, because the signal is consumed on first read.
48
+
49
+ ## Usage
50
+
51
+ ```
52
+ /handover # default: HANDOVER.md in repo root, kept OUT of git
53
+ /handover repo # same as default (explicit)
54
+ /handover tmp # write under /tmp — never touches the repo at all
55
+ /handover commit # write docs/handovers/HANDOVER-<timestamp>.md, meant to be committed
56
+ ```
57
+
58
+ Read the argument from what the user typed. If none given, use `repo`.
59
+
60
+ ## Pre-flight
61
+
62
+ Before starting, check dependencies:
63
+
64
+ | Dependency | Type | Check | Required | Resolution | Detail |
65
+ |-----------|------|-------|----------|------------|--------|
66
+ | git | cli | `git rev-parse --show-toplevel` | no | fallback | Not in a git repo → use cwd for `repo` mode; skip `.git/info/exclude` step |
67
+ | jq | cli | `command -v jq` | no | fallback | Signal loader falls back to sed + raw-stdout injection; no behavior change |
68
+
69
+ Resolve the plugin root once for the signal command:
70
+
71
+ ```bash
72
+ PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$HOME/.claude/plugins/marketplaces/slamb2k}"
73
+ ```
74
+
75
+ ## What to do
76
+
77
+ Work through these steps in order. Don't skip the verification at the end.
78
+
79
+ ### 1. Resolve the target path
80
+
81
+ First get the repo root and a timestamp:
82
+
83
+ ```bash
84
+ git rev-parse --show-toplevel 2>/dev/null # repo root, or empty if not a git repo
85
+ date +%Y-%m-%d-%H%M
86
+ ```
87
+
88
+ Then pick the path based on the argument:
89
+
90
+ - **`repo`** (default): `<repo_root>/HANDOVER.md`. If not inside a git repo, use the
91
+ current working directory instead. After writing, keep it out of version control
92
+ by appending `HANDOVER.md` to `<repo_root>/.git/info/exclude` if it's a git repo
93
+ and not already listed there. This is a *local* ignore — it never modifies the
94
+ tracked `.gitignore`, so it won't show up in anyone else's checkout or your diffs.
95
+ - **`tmp`**: `/tmp/claude-handover/<key>/HANDOVER.md` where `<key>` is
96
+ `printf '%s' "$(pwd)" | cksum | cut -d' ' -f1`. Create the directory first.
97
+ Nothing is written inside the repo.
98
+ - **`commit`**: `<repo_root>/docs/handovers/HANDOVER-<timestamp>.md`. Create the
99
+ `docs/handovers/` directory if needed. Do **not** ignore it — this variant exists
100
+ precisely so the handover lands in git history as a durable checkpoint. Mention to
101
+ the user that they'll want to commit it.
102
+
103
+ ### 2. Write the handover document
104
+
105
+ This is the core of the skill — the document quality determines whether the next
106
+ session is productive or lost. Follow the structure in
107
+ `references/handover-template.md`. Read that file now if you haven't.
108
+
109
+ Fill it from the **actual conversation and repo state**, not generic boilerplate.
110
+ Be concrete: real file paths, real function names, real commands, real decisions.
111
+ Write for a competent engineer who has *zero* memory of this session — every
112
+ assumption in your head right now is invisible to them unless you write it down.
113
+
114
+ Gather git state to embed in the document:
115
+
116
+ ```bash
117
+ git -C <repo_root> branch --show-current
118
+ git -C <repo_root> status --short
119
+ git -C <repo_root> log --oneline -8
120
+ ```
121
+
122
+ Optimise for *their* context budget: include what unblocks action, link to files
123
+ rather than pasting large code, and cut the narrative of how you got here unless a
124
+ dead end is a genuine landmine worth a warning.
125
+
126
+ ### 3. Arm the one-shot signal
127
+
128
+ After the document is written, run (using the `PLUGIN_ROOT` resolved in pre-flight):
129
+
130
+ ```bash
131
+ bash "$PLUGIN_ROOT/skills/handover/scripts/handover.sh" signal "<absolute_path_to_handover>"
132
+ ```
133
+
134
+ This drops a signal keyed to the current project directory. The next session's
135
+ `SessionStart` hook — shipped with this plugin in `hooks/hooks.json` — detects it,
136
+ injects the handover as context, and deletes the signal so it fires exactly once.
137
+ Use the **absolute** path.
138
+
139
+ ### 4. Hand off to a fresh session
140
+
141
+ Tell the user plainly that the handover is ready and they should start the fresh
142
+ session themselves. You **cannot** trigger `/clear` programmatically — it is a
143
+ user-only command — so the final step is theirs. Say something like:
144
+
145
+ > Handover written to `<path>` and the next session is armed. Type **`/clear`**
146
+ > (or **`/new`**) now — the fresh session will automatically load this handover and
147
+ > pick up where we left off. Nothing else gets auto-loaded; the signal is consumed
148
+ > on first read.
149
+
150
+ Keep this final message short. The work is done; don't bury the one action they
151
+ need to take.
152
+
153
+ ## Important constraints
154
+
155
+ - **`/clear` is the user's to type.** No skill, hook, or command can clear the
156
+ context window automatically. Always end by asking them to do it.
157
+ - **The signal is one-shot and project-scoped.** It's keyed to the working
158
+ directory and deleted on first read, so handovers never leak into unrelated
159
+ sessions. A `HANDOVER.md` left sitting in a repo is inert unless re-armed.
160
+ - **Don't pollute git by default.** Only the `commit` variant is meant to be
161
+ tracked. `repo` mode relies on `.git/info/exclude`; `tmp` mode stays entirely
162
+ outside the repo.
163
+ - **If `/handover` is run again later**, overwrite the existing document and
164
+ re-arm the signal — the latest state wins.