omegon 0.6.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.
- package/.gitattributes +3 -0
- package/AGENTS.md +16 -0
- package/LICENSE +15 -0
- package/README.md +289 -0
- package/bin/pi.mjs +30 -0
- package/extensions/00-secrets/index.ts +1126 -0
- package/extensions/01-auth/auth.ts +401 -0
- package/extensions/01-auth/index.ts +289 -0
- package/extensions/auto-compact.ts +42 -0
- package/extensions/bootstrap/deps.ts +291 -0
- package/extensions/bootstrap/index.ts +811 -0
- package/extensions/chronos/chronos.sh +487 -0
- package/extensions/chronos/index.ts +148 -0
- package/extensions/cleave/assessment.ts +754 -0
- package/extensions/cleave/bridge.ts +31 -0
- package/extensions/cleave/conflicts.ts +250 -0
- package/extensions/cleave/dispatcher.ts +808 -0
- package/extensions/cleave/guardrails.ts +426 -0
- package/extensions/cleave/index.ts +3121 -0
- package/extensions/cleave/lifecycle-emitter.ts +20 -0
- package/extensions/cleave/openspec.ts +811 -0
- package/extensions/cleave/planner.ts +260 -0
- package/extensions/cleave/review.ts +579 -0
- package/extensions/cleave/skills.ts +355 -0
- package/extensions/cleave/types.ts +261 -0
- package/extensions/cleave/workspace.ts +861 -0
- package/extensions/cleave/worktree.ts +243 -0
- package/extensions/core-renderers.ts +253 -0
- package/extensions/dashboard/context-gauge.ts +58 -0
- package/extensions/dashboard/file-watch.ts +14 -0
- package/extensions/dashboard/footer.ts +1145 -0
- package/extensions/dashboard/git.ts +185 -0
- package/extensions/dashboard/index.ts +478 -0
- package/extensions/dashboard/memory-audit.ts +34 -0
- package/extensions/dashboard/overlay-data.ts +705 -0
- package/extensions/dashboard/overlay.ts +365 -0
- package/extensions/dashboard/render-utils.ts +54 -0
- package/extensions/dashboard/types.ts +191 -0
- package/extensions/dashboard/uri-helper.ts +45 -0
- package/extensions/debug.ts +69 -0
- package/extensions/defaults.ts +282 -0
- package/extensions/design-tree/dashboard-state.ts +161 -0
- package/extensions/design-tree/design-card.ts +362 -0
- package/extensions/design-tree/index.ts +2130 -0
- package/extensions/design-tree/lifecycle-emitter.ts +41 -0
- package/extensions/design-tree/tree.ts +1607 -0
- package/extensions/design-tree/types.ts +163 -0
- package/extensions/distill.ts +127 -0
- package/extensions/effort/index.ts +395 -0
- package/extensions/effort/tiers.ts +146 -0
- package/extensions/effort/types.ts +105 -0
- package/extensions/lib/git-state.ts +227 -0
- package/extensions/lib/local-models.ts +157 -0
- package/extensions/lib/model-preferences.ts +51 -0
- package/extensions/lib/model-routing.ts +720 -0
- package/extensions/lib/operator-fallback.ts +205 -0
- package/extensions/lib/operator-profile.ts +360 -0
- package/extensions/lib/slash-command-bridge.ts +253 -0
- package/extensions/lib/typebox-helpers.ts +16 -0
- package/extensions/local-inference/index.ts +727 -0
- package/extensions/mcp-bridge/README.md +220 -0
- package/extensions/mcp-bridge/index.ts +951 -0
- package/extensions/mcp-bridge/lib.ts +365 -0
- package/extensions/mcp-bridge/mcp.json +3 -0
- package/extensions/mcp-bridge/package.json +11 -0
- package/extensions/model-budget.ts +752 -0
- package/extensions/offline-driver.ts +403 -0
- package/extensions/openspec/archive-gate.ts +164 -0
- package/extensions/openspec/branch-cleanup.ts +64 -0
- package/extensions/openspec/dashboard-state.ts +50 -0
- package/extensions/openspec/index.ts +1917 -0
- package/extensions/openspec/lifecycle-emitter.ts +65 -0
- package/extensions/openspec/lifecycle-files.ts +70 -0
- package/extensions/openspec/lifecycle.ts +50 -0
- package/extensions/openspec/reconcile.ts +187 -0
- package/extensions/openspec/spec.ts +1385 -0
- package/extensions/openspec/types.ts +98 -0
- package/extensions/project-memory/DESIGN-global-mind.md +198 -0
- package/extensions/project-memory/README.md +202 -0
- package/extensions/project-memory/api-types.ts +382 -0
- package/extensions/project-memory/compaction-policy.ts +29 -0
- package/extensions/project-memory/core.ts +164 -0
- package/extensions/project-memory/embeddings.ts +230 -0
- package/extensions/project-memory/extraction-v2.ts +861 -0
- package/extensions/project-memory/factstore.ts +2177 -0
- package/extensions/project-memory/index.ts +3459 -0
- package/extensions/project-memory/injection-metrics.ts +91 -0
- package/extensions/project-memory/jsonl-io.ts +12 -0
- package/extensions/project-memory/lifecycle.ts +331 -0
- package/extensions/project-memory/migration.ts +293 -0
- package/extensions/project-memory/package.json +9 -0
- package/extensions/project-memory/sci-renderers.ts +7 -0
- package/extensions/project-memory/template.ts +103 -0
- package/extensions/project-memory/triggers.ts +52 -0
- package/extensions/project-memory/types.ts +102 -0
- package/extensions/render/composition/fonts/Inter-Bold.ttf +0 -0
- package/extensions/render/composition/fonts/Inter-Regular.ttf +0 -0
- package/extensions/render/composition/fonts/Tomorrow-Bold.ttf +0 -0
- package/extensions/render/composition/fonts/Tomorrow-Regular.ttf +0 -0
- package/extensions/render/composition/package-lock.json +534 -0
- package/extensions/render/composition/package.json +22 -0
- package/extensions/render/composition/render.mjs +246 -0
- package/extensions/render/composition/test-comp.tsx +87 -0
- package/extensions/render/composition/types.ts +24 -0
- package/extensions/render/excalidraw/UPSTREAM.md +81 -0
- package/extensions/render/excalidraw/elements.ts +764 -0
- package/extensions/render/excalidraw/index.ts +66 -0
- package/extensions/render/excalidraw/types.ts +223 -0
- package/extensions/render/excalidraw-renderer/pyproject.toml +8 -0
- package/extensions/render/excalidraw-renderer/render_excalidraw.py +182 -0
- package/extensions/render/excalidraw-renderer/render_template.html +59 -0
- package/extensions/render/index.ts +830 -0
- package/extensions/render/native-diagrams/index.ts +57 -0
- package/extensions/render/native-diagrams/motifs.ts +542 -0
- package/extensions/render/native-diagrams/raster.ts +8 -0
- package/extensions/render/native-diagrams/scene.ts +75 -0
- package/extensions/render/native-diagrams/spec.ts +204 -0
- package/extensions/render/native-diagrams/svg.ts +116 -0
- package/extensions/sci-ui.ts +304 -0
- package/extensions/session-log.ts +174 -0
- package/extensions/shared-state.ts +146 -0
- package/extensions/spinner-verbs.ts +91 -0
- package/extensions/style.ts +281 -0
- package/extensions/terminal-title.ts +191 -0
- package/extensions/tool-profile/index.ts +291 -0
- package/extensions/tool-profile/profiles.ts +290 -0
- package/extensions/types.d.ts +9 -0
- package/extensions/vault/index.ts +185 -0
- package/extensions/version-check.ts +90 -0
- package/extensions/view/index.ts +859 -0
- package/extensions/view/uri-resolver.ts +148 -0
- package/extensions/web-search/index.ts +182 -0
- package/extensions/web-search/providers.ts +121 -0
- package/extensions/web-ui/index.ts +110 -0
- package/extensions/web-ui/server.ts +265 -0
- package/extensions/web-ui/state.ts +462 -0
- package/extensions/web-ui/static/index.html +145 -0
- package/extensions/web-ui/types.ts +284 -0
- package/package.json +76 -0
- package/prompts/init.md +75 -0
- package/prompts/new-repo.md +54 -0
- package/prompts/oci-login.md +56 -0
- package/prompts/status.md +50 -0
- package/settings.json +4 -0
- package/skills/cleave/SKILL.md +218 -0
- package/skills/git/SKILL.md +209 -0
- package/skills/git/_reference/ci-validation.md +204 -0
- package/skills/oci/SKILL.md +338 -0
- package/skills/openspec/SKILL.md +346 -0
- package/skills/pi-extensions/SKILL.md +191 -0
- package/skills/pi-tui/SKILL.md +517 -0
- package/skills/python/SKILL.md +189 -0
- package/skills/rust/SKILL.md +268 -0
- package/skills/security/SKILL.md +206 -0
- package/skills/style/SKILL.md +264 -0
- package/skills/typescript/SKILL.md +225 -0
- package/skills/vault/SKILL.md +102 -0
- package/themes/alpharius-legacy.json +85 -0
- package/themes/alpharius.conf +59 -0
- package/themes/alpharius.json +88 -0
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: oci
|
|
3
|
+
description: OCI container and artifact best practices. Covers Containerfile authoring, multi-arch builds, registry authentication, image tagging, security scanning, and artifact management. Use when building, pushing, or managing container images and OCI artifacts.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# OCI Skill
|
|
7
|
+
|
|
8
|
+
Conventions for container images, OCI artifacts, and registry operations.
|
|
9
|
+
|
|
10
|
+
## Core Conventions
|
|
11
|
+
|
|
12
|
+
- **Containerfile** (not Dockerfile) as the canonical name
|
|
13
|
+
- **Podman** preferred, Docker compatible — commands are interchangeable
|
|
14
|
+
- **Multi-stage builds** to minimize final image size
|
|
15
|
+
- **Non-root** user in production images
|
|
16
|
+
- **Immutable tags** — never overwrite a published tag
|
|
17
|
+
|
|
18
|
+
## Registry Authentication
|
|
19
|
+
|
|
20
|
+
### GHCR (GitHub Container Registry)
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
echo "$GITHUB_TOKEN" | docker login ghcr.io -u USERNAME --password-stdin
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### AWS ECR
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
aws ecr get-login-password --region us-east-1 | \
|
|
30
|
+
docker login --username AWS --password-stdin ACCOUNT.dkr.ecr.us-east-1.amazonaws.com
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
ECR tokens expire after 12 hours. In CI, call before every push.
|
|
34
|
+
|
|
35
|
+
### Docker Hub
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
echo "$DOCKER_TOKEN" | docker login -u USERNAME --password-stdin
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Credential Helpers
|
|
42
|
+
|
|
43
|
+
| Registry | Helper |
|
|
44
|
+
|----------|--------|
|
|
45
|
+
| GHCR | `docker-credential-gh` (via `gh auth setup-docker`) |
|
|
46
|
+
| ECR | `docker-credential-ecr-login` (from `amazon-ecr-credential-helper`) |
|
|
47
|
+
| GCR/GAR | `docker-credential-gcloud` |
|
|
48
|
+
|
|
49
|
+
Configure in `~/.docker/config.json`:
|
|
50
|
+
```json
|
|
51
|
+
{
|
|
52
|
+
"credHelpers": {
|
|
53
|
+
"ghcr.io": "gh",
|
|
54
|
+
"ACCOUNT.dkr.ecr.REGION.amazonaws.com": "ecr-login"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Containerfile Conventions
|
|
60
|
+
|
|
61
|
+
### Structure
|
|
62
|
+
|
|
63
|
+
```dockerfile
|
|
64
|
+
# syntax=docker/dockerfile:1
|
|
65
|
+
FROM python:3.12-slim AS builder
|
|
66
|
+
|
|
67
|
+
WORKDIR /app
|
|
68
|
+
COPY requirements.txt .
|
|
69
|
+
RUN pip install --no-cache-dir -r requirements.txt
|
|
70
|
+
|
|
71
|
+
COPY src/ src/
|
|
72
|
+
|
|
73
|
+
FROM python:3.12-slim
|
|
74
|
+
WORKDIR /app
|
|
75
|
+
COPY --from=builder /app /app
|
|
76
|
+
|
|
77
|
+
RUN useradd -r -s /bin/false appuser
|
|
78
|
+
USER appuser
|
|
79
|
+
|
|
80
|
+
EXPOSE 8080
|
|
81
|
+
ENTRYPOINT ["python", "-m", "myapp"]
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Rules
|
|
85
|
+
|
|
86
|
+
| Rule | Rationale |
|
|
87
|
+
|------|-----------|
|
|
88
|
+
| Pin base image digests in CI | Reproducible builds |
|
|
89
|
+
| `--no-cache-dir` on pip install | Smaller layers |
|
|
90
|
+
| `COPY` specific paths, not `.` | Cache efficiency, avoid leaking secrets |
|
|
91
|
+
| `ARG` defaults for versions | Co-locate version with build definition |
|
|
92
|
+
| `LABEL org.opencontainers.image.*` | OCI metadata standard |
|
|
93
|
+
| One `RUN` per logical step | Readability over layer count (BuildKit caches well) |
|
|
94
|
+
|
|
95
|
+
### ARG Defaults — Not Build Scripts
|
|
96
|
+
|
|
97
|
+
Define version defaults in the Containerfile, not in justfiles or Makefiles:
|
|
98
|
+
|
|
99
|
+
```dockerfile
|
|
100
|
+
# Good — version lives with the build definition
|
|
101
|
+
ARG PYTHON_VERSION=3.12
|
|
102
|
+
FROM python:${PYTHON_VERSION}-slim
|
|
103
|
+
|
|
104
|
+
# Bad — hardcoded in justfile creates manual update obligation
|
|
105
|
+
# just build --build-arg PYTHON_VERSION=3.12
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Override only when needed: `docker build --build-arg PYTHON_VERSION=3.13 .`
|
|
109
|
+
|
|
110
|
+
### .dockerignore
|
|
111
|
+
|
|
112
|
+
Always include one. Minimum:
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
.git
|
|
116
|
+
.venv
|
|
117
|
+
__pycache__
|
|
118
|
+
*.pyc
|
|
119
|
+
node_modules
|
|
120
|
+
.env
|
|
121
|
+
*.secret
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Cross-Platform Builds
|
|
125
|
+
|
|
126
|
+
### Apple Silicon → amd64 Clusters
|
|
127
|
+
|
|
128
|
+
**Always specify `--platform` when the build host differs from the target.**
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
# Building on arm64 Mac for amd64 Kubernetes
|
|
132
|
+
docker build --platform linux/amd64 -t myimage:latest .
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Without `--platform`, the image is arm64 and **fails silently** on amd64 nodes.
|
|
136
|
+
|
|
137
|
+
### Multi-Arch with Buildx
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
docker buildx create --name multiarch --use
|
|
141
|
+
docker buildx build \
|
|
142
|
+
--platform linux/amd64,linux/arm64 \
|
|
143
|
+
--tag ghcr.io/org/image:v1.0.0 \
|
|
144
|
+
--push .
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Cache Busting
|
|
148
|
+
|
|
149
|
+
Combine `--no-cache` with `--platform` to prevent stale cross-arch layer caches:
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
docker build --platform linux/amd64 --no-cache -t myimage:latest .
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Stale pip caches can serve arm64 wheels even when targeting amd64.
|
|
156
|
+
|
|
157
|
+
## Tagging Strategy
|
|
158
|
+
|
|
159
|
+
### Convention
|
|
160
|
+
|
|
161
|
+
```
|
|
162
|
+
REGISTRY/ORG/IMAGE:TAG
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Tag Types
|
|
166
|
+
|
|
167
|
+
| Tag | When | Example |
|
|
168
|
+
|-----|------|---------|
|
|
169
|
+
| Semver | Release | `v1.2.3` |
|
|
170
|
+
| SHA | Every build | `sha-a1b2c3d` |
|
|
171
|
+
| Branch | Dev builds | `main`, `feature-x` |
|
|
172
|
+
| `latest` | **Avoid** | Ambiguous, not reproducible |
|
|
173
|
+
|
|
174
|
+
### Tagging Commands
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
# Tag with semver + SHA
|
|
178
|
+
IMAGE=ghcr.io/org/myapp
|
|
179
|
+
SHA=$(git rev-parse --short HEAD)
|
|
180
|
+
VERSION=v1.2.3
|
|
181
|
+
|
|
182
|
+
docker tag myapp:latest "$IMAGE:$VERSION"
|
|
183
|
+
docker tag myapp:latest "$IMAGE:sha-$SHA"
|
|
184
|
+
docker push "$IMAGE:$VERSION"
|
|
185
|
+
docker push "$IMAGE:sha-$SHA"
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## OCI Artifacts (Beyond Images)
|
|
189
|
+
|
|
190
|
+
OCI registries store more than container images:
|
|
191
|
+
|
|
192
|
+
| Artifact | Tool | Push Command |
|
|
193
|
+
|----------|------|-------------|
|
|
194
|
+
| Helm charts | `helm` | `helm push chart.tgz oci://ghcr.io/org/charts` |
|
|
195
|
+
| SBOMs | `cosign` | `cosign attach sbom --sbom sbom.spdx IMAGE` |
|
|
196
|
+
| Signatures | `cosign` | `cosign sign IMAGE` |
|
|
197
|
+
| Attestations | `cosign` | `cosign attest --predicate provenance.json IMAGE` |
|
|
198
|
+
| Arbitrary files | `oras` | `oras push ghcr.io/org/artifact:v1 ./file.txt` |
|
|
199
|
+
|
|
200
|
+
### Helm OCI Push
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
helm package ./chart
|
|
204
|
+
helm push chart-1.0.0.tgz oci://ghcr.io/org/charts
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Image Signing with Cosign
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
# Keyless signing (GitHub Actions OIDC)
|
|
211
|
+
cosign sign ghcr.io/org/image@sha256:DIGEST
|
|
212
|
+
|
|
213
|
+
# Key-based
|
|
214
|
+
cosign generate-key-pair
|
|
215
|
+
cosign sign --key cosign.key ghcr.io/org/image:v1.0.0
|
|
216
|
+
cosign verify --key cosign.pub ghcr.io/org/image:v1.0.0
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## Security Scanning
|
|
220
|
+
|
|
221
|
+
### Trivy
|
|
222
|
+
|
|
223
|
+
```bash
|
|
224
|
+
trivy image ghcr.io/org/image:v1.0.0 # Scan image
|
|
225
|
+
trivy fs . # Scan filesystem/deps
|
|
226
|
+
trivy image --severity HIGH,CRITICAL IMAGE # Filter severity
|
|
227
|
+
trivy image --exit-code 1 IMAGE # Fail CI on findings
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### Docker Scout (Docker Desktop)
|
|
231
|
+
|
|
232
|
+
```bash
|
|
233
|
+
docker scout cves IMAGE # CVE scan
|
|
234
|
+
docker scout quickview IMAGE # Summary
|
|
235
|
+
docker scout recommendations IMAGE # Base image suggestions
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### Grype
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
grype IMAGE # Vulnerability scan
|
|
242
|
+
grype dir:. # Scan local project
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
## Registry Management
|
|
246
|
+
|
|
247
|
+
### Lifecycle Policies (ECR)
|
|
248
|
+
|
|
249
|
+
```json
|
|
250
|
+
{
|
|
251
|
+
"rules": [{
|
|
252
|
+
"rulePriority": 1,
|
|
253
|
+
"description": "Expire untagged after 7 days",
|
|
254
|
+
"selection": {
|
|
255
|
+
"tagStatus": "untagged",
|
|
256
|
+
"countType": "sinceImagePushed",
|
|
257
|
+
"countUnit": "days",
|
|
258
|
+
"countNumber": 7
|
|
259
|
+
},
|
|
260
|
+
"action": { "type": "expire" }
|
|
261
|
+
}]
|
|
262
|
+
}
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
### GHCR Cleanup
|
|
266
|
+
|
|
267
|
+
```bash
|
|
268
|
+
# List package versions
|
|
269
|
+
gh api user/packages/container/IMAGE/versions | jq '.[].metadata.container.tags'
|
|
270
|
+
|
|
271
|
+
# Delete untagged
|
|
272
|
+
gh api --method DELETE user/packages/container/IMAGE/versions/VERSION_ID
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
### Repository Naming
|
|
276
|
+
|
|
277
|
+
```
|
|
278
|
+
ghcr.io/ORG/SERVICE # Application image
|
|
279
|
+
ghcr.io/ORG/charts/SERVICE # Helm chart
|
|
280
|
+
ghcr.io/ORG/base/RUNTIME # Shared base images
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
## CI/CD Integration
|
|
284
|
+
|
|
285
|
+
### GitHub Actions
|
|
286
|
+
|
|
287
|
+
```yaml
|
|
288
|
+
- uses: docker/login-action@v3
|
|
289
|
+
with:
|
|
290
|
+
registry: ghcr.io
|
|
291
|
+
username: ${{ github.actor }}
|
|
292
|
+
password: ${{ secrets.GITHUB_TOKEN }}
|
|
293
|
+
|
|
294
|
+
- uses: docker/build-push-action@v6
|
|
295
|
+
with:
|
|
296
|
+
context: .
|
|
297
|
+
push: true
|
|
298
|
+
platforms: linux/amd64,linux/arm64
|
|
299
|
+
tags: |
|
|
300
|
+
ghcr.io/${{ github.repository }}:${{ github.sha }}
|
|
301
|
+
ghcr.io/${{ github.repository }}:latest
|
|
302
|
+
cache-from: type=gha
|
|
303
|
+
cache-to: type=gha,mode=max
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### Build Cache (BuildKit)
|
|
307
|
+
|
|
308
|
+
| Cache Backend | Use Case |
|
|
309
|
+
|--------------|----------|
|
|
310
|
+
| `type=gha` | GitHub Actions (free, limited) |
|
|
311
|
+
| `type=registry` | Push cache layers to registry |
|
|
312
|
+
| `type=local` | Local directory (CI runners with persistent storage) |
|
|
313
|
+
|
|
314
|
+
## Debugging
|
|
315
|
+
|
|
316
|
+
```bash
|
|
317
|
+
docker history IMAGE # Layer sizes
|
|
318
|
+
docker inspect IMAGE # Full metadata
|
|
319
|
+
docker manifest inspect IMAGE # Multi-arch manifest
|
|
320
|
+
dive IMAGE # Interactive layer explorer
|
|
321
|
+
crane manifest IMAGE # OCI manifest (no Docker needed)
|
|
322
|
+
skopeo inspect docker://IMAGE # Remote inspection
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
## Common Gotchas
|
|
326
|
+
|
|
327
|
+
| Issue | Fix |
|
|
328
|
+
|-------|-----|
|
|
329
|
+
| Image works locally, fails in k8s | Missing `--platform linux/amd64` on Apple Silicon build |
|
|
330
|
+
| Push denied to GHCR | `gh auth token` scope needs `write:packages` |
|
|
331
|
+
| Stale layers after dep update | `--no-cache` or `--no-cache-filter=stage_name` |
|
|
332
|
+
| ECR login expired | Re-run `aws ecr get-login-password` (12h TTL) |
|
|
333
|
+
| `latest` tag not updating | Tag is a pointer, not auto-updated — push explicitly |
|
|
334
|
+
| Large image size | Multi-stage build, slim/distroless base, `.dockerignore` |
|
|
335
|
+
| Build context too large | Check `.dockerignore`, avoid `COPY . .` |
|
|
336
|
+
| Helm OCI push fails | `helm registry login` first, chart must be packaged `.tgz` |
|
|
337
|
+
| Runtime DB lost on restart | Mount persistent volume, or use config-file-backed state |
|
|
338
|
+
| `ARG` before `FROM` not visible after | Re-declare `ARG` after `FROM` to use in later stages |
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: openspec
|
|
3
|
+
description: OpenSpec lifecycle for spec-driven development. Use when proposing changes, writing Given/When/Then specs, generating tasks, verifying implementations, or archiving completed changes. Commands /opsx:propose, /opsx:spec, /opsx:ff, /opsx:status, /opsx:verify, /opsx:archive.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# OpenSpec — Spec-Driven Development Lifecycle
|
|
7
|
+
|
|
8
|
+
> **Load this skill** when working with OpenSpec changes, writing specs, generating tasks, or verifying implementations against specifications.
|
|
9
|
+
|
|
10
|
+
## Overview
|
|
11
|
+
|
|
12
|
+
OpenSpec is Omegon's specification layer for spec-and-test-driven development. It ensures that every non-trivial change follows the lifecycle:
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
propose → spec → plan → implement → verify → archive
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Specs define **what must be true** before code is written. They are the source of truth for correctness.
|
|
19
|
+
|
|
20
|
+
## Lifecycle Stages
|
|
21
|
+
|
|
22
|
+
| Stage | Artifacts | Next Action |
|
|
23
|
+
|-------|-----------|-------------|
|
|
24
|
+
| **proposed** | `proposal.md` | `/opsx:spec <change>` — write specs |
|
|
25
|
+
| **specified** | `specs/*.md` | `/opsx:ff <change>` — generate design + tasks |
|
|
26
|
+
| **planned** | `design.md`, `tasks.md` | `/cleave` — execute tasks |
|
|
27
|
+
| **implementing** | tasks in progress | continue work or `/cleave` |
|
|
28
|
+
| **verifying** | all tasks done | `/assess spec <change>` → `/opsx:archive` |
|
|
29
|
+
| **archived** | specs merged to baseline | complete |
|
|
30
|
+
|
|
31
|
+
## Lifecycle Reconciliation (required)
|
|
32
|
+
|
|
33
|
+
OpenSpec artifacts are not write-once planning docs. Treat them as runtime lifecycle state.
|
|
34
|
+
|
|
35
|
+
At these checkpoints, reconcile the artifacts to match reality:
|
|
36
|
+
|
|
37
|
+
1. **Implement / scaffold** — ensure the design-tree node is bound to the OpenSpec change and marked `implementing`
|
|
38
|
+
2. **Post-cleave** — ensure `tasks.md` reflects merged work, not just original intent
|
|
39
|
+
3. **Post-assess / post-fix** — after `/assess spec` or `/assess cleave`, reopen lifecycle state if review found remaining work, and append implementation-note deltas when fixes expanded file scope or constraints
|
|
40
|
+
4. **Pre-archive** — ensure the bound design-tree node and `tasks.md` are current before closing the change
|
|
41
|
+
|
|
42
|
+
Archive is expected to refuse obviously stale lifecycle state, especially:
|
|
43
|
+
- incomplete tasks in `tasks.md`
|
|
44
|
+
- no design-tree binding for the change
|
|
45
|
+
|
|
46
|
+
## Directory Structure
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
openspec/
|
|
50
|
+
├── changes/
|
|
51
|
+
│ └── <change-name>/
|
|
52
|
+
│ ├── proposal.md # Intent, scope, success criteria
|
|
53
|
+
│ ├── design.md # Architecture decisions, file changes
|
|
54
|
+
│ ├── tasks.md # Numbered task groups for /cleave
|
|
55
|
+
│ └── specs/
|
|
56
|
+
│ ├── <domain>.md # Delta specs with Given/When/Then
|
|
57
|
+
│ └── <domain>/
|
|
58
|
+
│ └── <sub>.md # Nested domain specs
|
|
59
|
+
├── baseline/ # Accumulated specs (post-archive)
|
|
60
|
+
│ └── <domain>.md
|
|
61
|
+
└── archive/ # Completed changes (timestamped)
|
|
62
|
+
└── YYYY-MM-DD-<name>/
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Spec File Format
|
|
66
|
+
|
|
67
|
+
Spec files use a **delta format** — they describe changes relative to the current baseline:
|
|
68
|
+
|
|
69
|
+
```markdown
|
|
70
|
+
# <domain> — Delta Spec
|
|
71
|
+
|
|
72
|
+
## ADDED Requirements
|
|
73
|
+
|
|
74
|
+
### Requirement: <title>
|
|
75
|
+
|
|
76
|
+
<description of what must be true>
|
|
77
|
+
|
|
78
|
+
#### Scenario: <scenario title>
|
|
79
|
+
Given <precondition>
|
|
80
|
+
When <action>
|
|
81
|
+
Then <expected outcome>
|
|
82
|
+
And <additional expectation>
|
|
83
|
+
|
|
84
|
+
## MODIFIED Requirements
|
|
85
|
+
|
|
86
|
+
### Requirement: <title>
|
|
87
|
+
|
|
88
|
+
<what changed and why>
|
|
89
|
+
|
|
90
|
+
#### Scenario: <updated scenario>
|
|
91
|
+
Given <new precondition>
|
|
92
|
+
When <action>
|
|
93
|
+
Then <updated expectation>
|
|
94
|
+
|
|
95
|
+
## REMOVED Requirements
|
|
96
|
+
|
|
97
|
+
### Requirement: <title>
|
|
98
|
+
|
|
99
|
+
<why this is being removed>
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Writing Good Scenarios
|
|
103
|
+
|
|
104
|
+
- **Given** establishes the starting state — be specific
|
|
105
|
+
- **When** is a single action — not a compound operation
|
|
106
|
+
- **Then** is the observable outcome — measurable and testable
|
|
107
|
+
- **And** adds additional assertions to Then
|
|
108
|
+
|
|
109
|
+
**Good:**
|
|
110
|
+
```
|
|
111
|
+
#### Scenario: Expired token rejected
|
|
112
|
+
Given a user has a JWT token that expired 5 minutes ago
|
|
113
|
+
When they make a GET request to /api/protected
|
|
114
|
+
Then the response status is 401
|
|
115
|
+
And the body contains {"error": "token_expired"}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
**Bad:**
|
|
119
|
+
```
|
|
120
|
+
#### Scenario: Auth works
|
|
121
|
+
Given the system is running
|
|
122
|
+
When a user authenticates
|
|
123
|
+
Then it works correctly
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Deriving API Contracts from Scenarios
|
|
127
|
+
|
|
128
|
+
When a change introduces or modifies a network API (HTTP, gRPC, WebSocket), **derive an OpenAPI 3.1 spec** (or AsyncAPI for event-driven APIs) from the scenarios during the Plan phase. Place it at `openspec/changes/<id>/api.yaml`.
|
|
129
|
+
|
|
130
|
+
**Mapping rules:**
|
|
131
|
+
|
|
132
|
+
| Scenario element | OpenAPI element |
|
|
133
|
+
|------------------|-----------------|
|
|
134
|
+
| `Given` preconditions (auth, existing data) | Security schemes, parameter constraints, `x-setup` |
|
|
135
|
+
| `When ... request to <path>` | `paths.<path>.<method>`, request body schema |
|
|
136
|
+
| `Then status is <code>` | `responses.<code>` |
|
|
137
|
+
| `Then body contains {...}` | Response schema (`application/json`) |
|
|
138
|
+
| `And header <name> is <value>` | Response headers |
|
|
139
|
+
| Error scenarios (`401`, `404`, `422`) | Error response schemas, problem detail types |
|
|
140
|
+
|
|
141
|
+
**Example — from scenario to contract:**
|
|
142
|
+
|
|
143
|
+
Scenario:
|
|
144
|
+
```
|
|
145
|
+
Given a user has a valid API key
|
|
146
|
+
When they POST to /api/widgets with {"name": "foo", "color": "blue"}
|
|
147
|
+
Then the response status is 201
|
|
148
|
+
And the body contains {"id": "<uuid>", "name": "foo", "color": "blue"}
|
|
149
|
+
And the Location header contains /api/widgets/<uuid>
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Derived OpenAPI fragment:
|
|
153
|
+
```yaml
|
|
154
|
+
paths:
|
|
155
|
+
/api/widgets:
|
|
156
|
+
post:
|
|
157
|
+
security:
|
|
158
|
+
- apiKey: []
|
|
159
|
+
requestBody:
|
|
160
|
+
required: true
|
|
161
|
+
content:
|
|
162
|
+
application/json:
|
|
163
|
+
schema:
|
|
164
|
+
type: object
|
|
165
|
+
required: [name, color]
|
|
166
|
+
properties:
|
|
167
|
+
name: { type: string }
|
|
168
|
+
color: { type: string }
|
|
169
|
+
responses:
|
|
170
|
+
'201':
|
|
171
|
+
description: Widget created
|
|
172
|
+
headers:
|
|
173
|
+
Location:
|
|
174
|
+
schema: { type: string, format: uri }
|
|
175
|
+
content:
|
|
176
|
+
application/json:
|
|
177
|
+
schema:
|
|
178
|
+
type: object
|
|
179
|
+
properties:
|
|
180
|
+
id: { type: string, format: uuid }
|
|
181
|
+
name: { type: string }
|
|
182
|
+
color: { type: string }
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
The contract is the **source of truth for API shape** — code implements the contract. If implementation diverges, fix the code or amend the spec with rationale.
|
|
186
|
+
|
|
187
|
+
## Commands
|
|
188
|
+
|
|
189
|
+
| Command | Description |
|
|
190
|
+
|---------|-------------|
|
|
191
|
+
| `/opsx:propose <name> <title>` | Create a new change with proposal.md |
|
|
192
|
+
| `/opsx:spec <change>` | Generate/add specs (triggers agent to write scenarios) |
|
|
193
|
+
| `/opsx:ff <change>` | Fast-forward: scaffold design.md + tasks.md from specs |
|
|
194
|
+
| `/opsx:status` | Show all active changes with lifecycle stage |
|
|
195
|
+
| `/opsx:verify <change>` | Delegates to `/assess spec` for spec verification |
|
|
196
|
+
| `/opsx:archive <change>` | Archive change, merge specs to baseline |
|
|
197
|
+
| `/opsx:apply <change>` | Continue implementing (delegates to `/cleave`) |
|
|
198
|
+
|
|
199
|
+
## Tool: `openspec_manage`
|
|
200
|
+
|
|
201
|
+
Agent-callable tool for programmatic lifecycle operations.
|
|
202
|
+
|
|
203
|
+
### Actions
|
|
204
|
+
|
|
205
|
+
| Action | Required Params | Description |
|
|
206
|
+
|--------|----------------|-------------|
|
|
207
|
+
| `status` | — | List all active changes |
|
|
208
|
+
| `get` | `change_name` | Get change details, stage, spec summary |
|
|
209
|
+
| `propose` | `name`, `title`, `intent` | Create new change |
|
|
210
|
+
| `add_spec` | `change_name`, `domain`, `spec_content` | Add raw spec markdown |
|
|
211
|
+
| `generate_spec` | `change_name`, `domain` | Generate spec scaffold from proposal |
|
|
212
|
+
| `fast_forward` | `change_name` | Generate design.md + tasks.md |
|
|
213
|
+
| `archive` | `change_name` | Archive completed change |
|
|
214
|
+
|
|
215
|
+
### `generate_spec` Optional Params
|
|
216
|
+
|
|
217
|
+
- `decisions`: Array of `{title, rationale}` — generates requirements per decision
|
|
218
|
+
- `open_questions`: Array of strings — generates MODIFIED Requirements placeholders
|
|
219
|
+
|
|
220
|
+
## Integration with Cleave
|
|
221
|
+
|
|
222
|
+
OpenSpec and cleave work together:
|
|
223
|
+
|
|
224
|
+
1. **`/opsx:ff`** generates `tasks.md` in the format cleave expects (numbered groups with checkboxes)
|
|
225
|
+
2. **`/cleave`** detects `openspec/changes/<name>/tasks.md` and uses it as the split plan
|
|
226
|
+
3. **`cleave_run`** with `openspec_change_path` updates task checkboxes on completion
|
|
227
|
+
4. **`/assess spec`** verifies implementation against spec scenarios
|
|
228
|
+
|
|
229
|
+
### Scenario-First Task Grouping
|
|
230
|
+
|
|
231
|
+
When generating `tasks.md`, group tasks by **spec domain** — not by file layer. Each group should own the end-to-end implementation of one or more spec files, including all file changes needed to satisfy those scenarios, even if that means multiple groups touch the same file.
|
|
232
|
+
|
|
233
|
+
**Do:**
|
|
234
|
+
```markdown
|
|
235
|
+
## 1. RBAC Enforcement
|
|
236
|
+
<!-- specs: relay/rbac -->
|
|
237
|
+
- [ ] Add relay.request and relay.accept capabilities to rbac.py
|
|
238
|
+
- [ ] Wire has_capability() check into create_session() in relay_service.py
|
|
239
|
+
- [ ] Return 403 when capability missing
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
**Don't** (splits enforcement across layers):
|
|
243
|
+
```markdown
|
|
244
|
+
## 1. Models
|
|
245
|
+
- [ ] Add capabilities to rbac.py
|
|
246
|
+
|
|
247
|
+
## 2. Service Logic
|
|
248
|
+
- [ ] Add session limits to relay_service.py
|
|
249
|
+
(RBAC enforcement falls between chairs — nobody wires has_capability)
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### Spec-Domain Annotations
|
|
253
|
+
|
|
254
|
+
Each task group header should include a `<!-- specs: domain/name -->` comment declaring which spec files the group owns. Multiple domains are comma-separated:
|
|
255
|
+
|
|
256
|
+
```markdown
|
|
257
|
+
## 2. Auth and Sessions
|
|
258
|
+
<!-- specs: relay/rbac, relay/session -->
|
|
259
|
+
- [ ] Implement auth checks
|
|
260
|
+
- [ ] Add session lifecycle
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
Cleave uses these annotations to deterministically map spec scenarios to child tasks as acceptance criteria. Groups without annotations fall back to heuristic matching.
|
|
264
|
+
|
|
265
|
+
### tasks.md Format
|
|
266
|
+
|
|
267
|
+
The full format that cleave parses:
|
|
268
|
+
```markdown
|
|
269
|
+
## 1. Group Title
|
|
270
|
+
<!-- specs: domain/name -->
|
|
271
|
+
|
|
272
|
+
- [ ] 1.1 Task description
|
|
273
|
+
- [ ] 1.2 Another task
|
|
274
|
+
- [x] 1.3 Completed task
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
## Integration with Design Tree
|
|
278
|
+
|
|
279
|
+
The design-tree `implement` action scaffolds OpenSpec change directories from design nodes:
|
|
280
|
+
|
|
281
|
+
- Design node **children** → task groups
|
|
282
|
+
- Design node **decisions** → additional task groups
|
|
283
|
+
- Design node **open questions** → noted in tasks
|
|
284
|
+
|
|
285
|
+
### ⚠️ The scaffolder produces a draft — always rewrite tasks.md immediately
|
|
286
|
+
|
|
287
|
+
The scaffolder reads **decisions only**. It does NOT read research sections, impl_notes file scope, or constraints. The generated `tasks.md` will contain one vague one-liner per decision title. This is expected scaffolding behaviour — it is not a usable task list.
|
|
288
|
+
|
|
289
|
+
**Immediately after every `implement` call**, you must:
|
|
290
|
+
|
|
291
|
+
1. Read the generated `tasks.md`
|
|
292
|
+
2. Read the design node's `impl_notes` (file scope + constraints) and research sections
|
|
293
|
+
3. Rewrite `tasks.md` completely — treat the generated file as a placeholder, not a draft to polish
|
|
294
|
+
|
|
295
|
+
**What a correct rewrite looks like:**
|
|
296
|
+
|
|
297
|
+
- One task group per file or coherent feature area (derived from impl_notes file scope)
|
|
298
|
+
- Each constraint maps to at least one concrete task item
|
|
299
|
+
- Research code examples (method signatures, class names) translate into numbered implementation tasks
|
|
300
|
+
- Rejected decisions are omitted entirely — never "implement" a rejected decision
|
|
301
|
+
- Dependencies between groups are stated explicitly at the top of the file
|
|
302
|
+
- If a scaffolded OpenSpec was created before a design decision was superseded, the tasks must reflect the current decision, not the old one
|
|
303
|
+
|
|
304
|
+
**Detecting a bad tasks.md:**
|
|
305
|
+
|
|
306
|
+
- Any task item whose text is a verbatim copy of a decision title → rewrite required
|
|
307
|
+
- Any task group labelled "Implement [rejected decision]" → immediately rewrite
|
|
308
|
+
- Fewer than 3 concrete numbered subtasks per group → likely too shallow
|
|
309
|
+
- No mention of specific method names, file paths, or test assertions → too abstract
|
|
310
|
+
|
|
311
|
+
This rewrite step is not optional polish — it is the primary authoring step for task content. The scaffolder provides structure; the agent provides substance.
|
|
312
|
+
|
|
313
|
+
## When to Use OpenSpec
|
|
314
|
+
|
|
315
|
+
**Always use** for:
|
|
316
|
+
- Multi-file changes (complexity ≥ 2.0 in cleave_assess)
|
|
317
|
+
- Any change affecting public APIs or data models
|
|
318
|
+
- Cross-cutting concerns (auth, logging, error handling)
|
|
319
|
+
- Changes that will be reviewed by others
|
|
320
|
+
|
|
321
|
+
**Skip for:**
|
|
322
|
+
- Single-file fixes, typos, config tweaks
|
|
323
|
+
- Changes with obvious correctness (renaming, formatting)
|
|
324
|
+
- Urgent hotfixes (document retroactively)
|
|
325
|
+
|
|
326
|
+
## Workflow Example
|
|
327
|
+
|
|
328
|
+
```
|
|
329
|
+
# 1. Propose
|
|
330
|
+
/opsx:propose jwt-auth "JWT Authentication"
|
|
331
|
+
|
|
332
|
+
# 2. Write specs (agent generates Given/When/Then)
|
|
333
|
+
/opsx:spec jwt-auth
|
|
334
|
+
|
|
335
|
+
# 3. Generate implementation plan
|
|
336
|
+
/opsx:ff jwt-auth
|
|
337
|
+
|
|
338
|
+
# 4. Execute in parallel
|
|
339
|
+
/cleave implement jwt-auth changes
|
|
340
|
+
|
|
341
|
+
# 5. Verify against specs
|
|
342
|
+
/assess spec jwt-auth
|
|
343
|
+
|
|
344
|
+
# 6. Archive
|
|
345
|
+
/opsx:archive jwt-auth
|
|
346
|
+
```
|