@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.
- package/.claude-plugin/plugin.json +1 -1
- package/hooks/hooks.json +4 -1
- package/package.json +1 -1
- package/skills/build/SKILL.md +64 -0
- package/skills/dock/SKILL.md +25 -0
- package/skills/dock/references/hardening.md +104 -0
- package/skills/dock/references/pipeline-templates.md +139 -212
- package/skills/dock/tests/evals.json +10 -0
- package/skills/handover/SKILL.md +164 -0
- package/skills/handover/references/handover-template.md +89 -0
- package/skills/handover/scripts/handover.sh +71 -0
- package/skills/handover/tests/evals.json +35 -0
- package/skills/hoist/SKILL.md +89 -0
- package/skills/hoist/references/hardening.md +62 -0
- package/skills/hoist/references/interview-guide.md +28 -0
- package/skills/hoist/references/release-templates.md +136 -0
- package/skills/hoist/references/setup-guides.md +30 -0
- package/skills/hoist/tests/evals.json +36 -0
- package/skills/manifest.json +24 -4
|
@@ -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
|
-
|
|
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:
|
|
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:
|
|
37
|
-
cancel-in-progress:
|
|
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
|
-
|
|
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
|
-
-
|
|
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
|
|
83
|
-
|
|
84
|
-
|
|
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:
|
|
97
|
-
tags:
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
-
|
|
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:
|
|
112
|
-
|
|
113
|
-
|
|
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:
|
|
106
|
+
- name: Resolve digest reference
|
|
107
|
+
id: digest
|
|
117
108
|
run: |
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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
|
-
|
|
136
|
-
|
|
137
|
-
|
|
120
|
+
permissions:
|
|
121
|
+
contents: read
|
|
122
|
+
id-token: write
|
|
138
123
|
steps:
|
|
139
|
-
- uses:
|
|
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
|
-
|
|
155
|
-
|
|
156
|
-
--
|
|
157
|
-
"${{
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
-
|
|
138
|
+
permissions:
|
|
139
|
+
contents: read
|
|
140
|
+
id-token: write
|
|
175
141
|
steps:
|
|
176
|
-
- uses:
|
|
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
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
230
|
-
pool:
|
|
231
|
-
vmImage: $(vmImageName)
|
|
182
|
+
- job: BuildSignPush
|
|
183
|
+
pool: { vmImage: ubuntu-latest }
|
|
232
184
|
steps:
|
|
233
|
-
- task:
|
|
234
|
-
displayName:
|
|
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
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
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
|
-
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
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.
|