Two bugs fixed:
1. redis.Nil not handled in GetProjectCache: When ACL GETUSER returns nil
(user doesn't exist), go-redis represents this as redis.Nil error. The
provisioner only checked for err.Contains("User") which didn't match,
causing spurious "get ACL user: redis: nil" errors on re-provision.
2. provisionRedis returns 409 even when REDIS_URL not in credential store:
If the Redis ACL user exists but REDIS_URL was never stored (e.g., due
to a failed previous run or lost state), the service would permanently
refuse to provision, leaving the project without usable Redis credentials.
Now checks the credential store: if REDIS_URL exists → true 409 duplicate;
if REDIS_URL missing → re-provision (CreateProjectCache resets the password).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
AddComponent (single-component path) already calls ensureProjectJWTSecret,
but AddComponentBatch has its own parallel implementation that bypassed it.
Components added via the /batch endpoint never had JWT_SECRET provisioned,
causing CrashLoopBackOff on startup ("JWT_SECRET must be set").
Add the call before the createInitialComponentDeployment loop so the secret
is stored in the credential store before K8s Secrets are created.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Without MustInit(), viper.AutomaticEnv() is never called, so Viper
cannot read environment variables injected by K8s secrets (envFrom).
This caused DATABASE_URL to always appear empty in deployed services,
forcing them into standalone/in-memory mode even when a database was
provisioned.
os.Getenv() calls like JWT_SECRET worked fine (direct syscall).
Viper-backed reads like DATABASE_URL did not (require AutomaticEnv).
Added pkgconfig.MustInit() call at the top of main() in both the
service component template and the full-monorepo example-api.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The usePersonaGeneration hook was created on disk but never committed
to git, so rendered projects had a broken import in index.ts causing
TypeScript build failures in CI.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
RC-1: Workers now get a Kubernetes Deployment on component creation.
NeedsPort() (port assignment) was incorrectly used to gate Deployment
creation - workers have no HTTP port but still need a Deployment so
CI `kubectl set image` can succeed. Added NeedsDeployment() returning
true for service/worker/app-react/app-astro/app-nextjs. AddIngressPath
is now guarded by port > 0 so workers don't attempt HTTP routing.
RC-2: JWT_SECRET is now auto-provisioned per-project when the first
code component is added. The skeleton service template fatally requires
JWT_SECRET at startup; previously fetchProjectCredentials() never fetched
it. ensureProjectJWTSecret() generates a cryptographically random 32-byte
secret, stores it as "{projectID}:JWT_SECRET", and JWT_SECRET is now
included in projectScopedKeys so it's injected into every deployment.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add ErrAnchorRequired sentinel to pkg/album — replaces fragile string equality check
used for 422 detection; callers now use errors.Is()
- Extract parseShotIndex() helper in album handler — replaces fmt.Sscanf which silently
accepted partial parses like "12abc"; strconv.Atoi requires the full string to be numeric
- Restructure caption saves in album/handler — captions now written outside the
len(img.Data) > 0 gate, so URL-only providers (no bytes returned) still get captions
- Add storage.FetchURL() shared utility — removes fetchBytes/downloadURL duplication
across album and generation packages; callers control timeout via their http.Client
- Add video captions to VideoHandler — same caption sidecar pattern applied to videos
- Add persona generation event types to realtime package — persona_spec_*, persona_image_*,
persona_video_* events added to EventType union and usePersonaGeneration hook exported
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
After each successful image upload to storage, a sidecar `.caption`
file is uploaded at the same path with `.caption` extension containing
the exact prompt used to generate the image.
Coverage:
- generation/handlers.go: ImageHandler → media/{userID}/images/{jobID}_{i}.caption
- album/handler.go: AnchorHandler → albums/{userID}/{albumID}/anchor.caption
- album/handler.go: ShotHandler → albums/{userID}/{albumID}/shots/{shotIndex}.caption
- personagen/service.go: generatePosition → personas/{specID}/images/{pos:02d}.caption
Caption failures are logged at warn level and never abort the job.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- specgen: extend dnaLLMResponse with heritage fields; conditionally extend
Stage 4 prompt for EthnicityMixed to ask LLM for primary_heritage,
secondary_heritage, and mix_percentage; populate IdentityDNA fields from
response so mixed personas get a real heritage breakdown
- imagegen: buildIdentitySection() produces "East Asian and Latina/Hispanic
heritage" description for mixed personas instead of generic "mixed-race"
- videogen: add genderPronouns() helper; replace hardcoded she/her with
pronoun set across all 4 video prompts; generateVideo() returns raw bytes
so caller can upload to storage
- service: GenerateVideo() uploads video to storage and sets VideoSpec.URL;
anchor ordering ensures position 1 is generated first; emit
persona_video_failed SSE event on non-fatal video failures; replace manual
fold helpers with strings.ToLower + strings.Contains
- worker/main: register persona_generate handler when both AI managers ready
- docs: add persona_video_failed to SSE events reference in personagen.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously SendOTP silently dropped requests for unknown emails, so new
users had no passwordless path in. Now:
- SendOTP: if REGISTRATION_ENABLED and email unknown, generates and
sends the code anyway (UserID nil until verify)
- VerifyOTP: if email unknown after valid code, auto-registers the user
(emailVerified=true — OTP delivery proves ownership, name defaults to
email local-part) then creates a session
REGISTRATION_ENABLED=false continues to block unknown emails at SendOTP,
preserving invite-only / closed-beta behaviour.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
In-memory auth codes are ephemeral — they're wiped on server restart.
Previously, codes were only visible via email delivery. If the server
restarted between OTP send and OTP verify, the code would be lost.
Now memory.AuthCodeRepository.Create() always logs the code to stdout
with a [DEV] prefix. This gives developers a reliable fallback regardless
of whether NOTIFY_URL is set. Updated CLAUDE.md to document this behavior
and the DEV_USER_EMAIL env var.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
In standalone mode (no DATABASE_URL), the in-memory user store only had
hardcoded demo accounts. Any real email the developer used was lost on every
server restart, causing OTP requests to silently fail with "unknown email".
NewUserRepository now accepts devEmail + devPassword. If DEV_USER_EMAIL is
set, that account is seeded on every startup alongside the demo users. The
developer's email is always registered, OTPs route to notify (or log to
console), and re-renders/restarts no longer break the auth flow.
New config fields: DevUserEmail (DEV_USER_EMAIL) / DevUserPassword
(DEV_USER_PASSWORD, default: "DevPassword1").
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Every project generated from the skeleton now ships with styled,
production-ready transactional emails out of the box.
New pkg/email package:
- Renderer: loads templates from caller-provided embed.FS, inlines CSS via
douceur at startup, derives plain text via goquery for multipart delivery
- DevHandler: live browser preview at GET /dev/emails and /dev/emails/{purpose}
(development only, never mounted in production)
- CSSInlineErr field on RenderedEmail so callers can log degraded renders
New service component templates:
- internal/email/embed.go.tmpl — embeds template FS (uses all: prefix for _*.html)
- internal/email/renderer_test.go.tmpl — 9 tests covering all purposes + brand injection
- internal/email/templates/ — 5 HTML email types (login_otp, email_verify,
magic_link, password_reset, welcome) + 5 shared partials (_layout, _header,
_footer, _button, _code_box)
Updated service component templates:
- config.go.tmpl — brand fields: AppName, AppURL, SupportEmail, LogoURL, BrandColor
- main.go.tmpl — wires renderer at startup, logs template count
- routes.go.tmpl — mounts /dev/emails in development; EmailRenderer in Dependencies
- notify.go.tmpl — renders HTML before sending; warns on CSS inlining failure
- go.mod.tmpl — adds douceur, goquery, gorilla/css, andybalholm/cascadia
Deleted: internal/adapter/email/helpers.go.tmpl (replaced by meta.yaml + renderer)
Fix: template directory named email_verify (matching domain.PurposeEmailVerify)
rather than verify_email — the mismatch caused all verification emails to fail
with "unknown email purpose" at send time while tests passed (tests called
Render directly with the wrong name).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix no-op RequireProjectAccess middleware to enforce project_ids
- Apply project access middleware to all project-scoped routes
- Filter GET /projects by allowed project IDs for restricted keys
- Add GET /me endpoint with key identity, scopes, and project access info
- Add PATCH /keys/{id} for partial key updates (name, scopes, project_ids, allowed_ips, expires_in)
- Add GET/POST/DELETE /projects/{id}/access for project-centric access management
- Auto-grant creating key access when using POST /project/create-and-build
- Accept grant_to_key_ids in create-and-build to grant multiple keys on project creation
- Move newProvisionerWithDeps test helper from production code to test file
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add NotifyProvisioner (port + adapter) using real notify admin API
- Create notify account + send key + host grant per project
- Inject NOTIFY_API_KEY/HOST/FROM into component deployments
- Store NOTIFY_URL, NOTIFY_ADMIN_KEY, RESEND_API_KEY in credential store
- Add setup-notify.sh for one-time host/provider/domain setup
- Add NOTIFY_ADMIN_KEY constant to domain/credential.go
- Wire provisioner in main.go with connection test guard
- Add .claude/guides/services/notify.md and CLAUDE.md entry
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
These components use useState/useRef hooks but lacked the Next.js 'use client'
directive, causing the Next.js app build to fail with Server Component errors.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Two distinct fixes:
1. Database terminology: Make it crystal clear that generated projects use
CockroachDB in production and PostgreSQL for local dev, while the rdev
platform itself uses PostgreSQL. Updated 15 files across skeleton agents,
component templates, cookbook trees, and platform docs.
2. Video storage: VideoHandler was ignoring vid.Data bytes (already downloaded
by the Gemini adapter with auth) and re-downloading from the provider URL
with a plain GET — which fails because Gemini URLs require API key auth.
Now uses vid.Data first, falls back to downloadURL only for public URLs.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds complete media storage pipeline with GCS presigned uploads, AI image/video/text generation
via queue-based workers, realtime SSE event streaming, and comprehensive skeleton packages
(storage, mediagen, textgen, generation, realtime, persona, routing, ai-client). Includes
security fixes for media delete authorization, nil pointer guards in handlers, video persistence
via download-then-upload, consistent signed URLs, and Image→ImageIcon rename to avoid DOM collision.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Dispatches builds to fix empty state dead-end and invisible Kanban
columns in Foundary Studio projects.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add TimeoutAgentExecution (22m) to handlers for synchronous SDLC
execution, and TimeoutAgent{Default,Medium,Heavy} (12/22/47m) to
workers for tiered agent task execution. Aligns with SDLC action
complexity tiers and prevents inline duration literals.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
The architect API returned "failed to start conversation" because
projectRepo.Get() failed — the in-memory K8s repo watches the rdev
namespace but projects deploy to the projects namespace. Made project
lookup non-fatal with fallback to default pod. Added error logging to
all architect handler methods (were silently swallowing errors).
Also adds setup-hooks, commit-after-qa, and pre-merge-validate steps
to the foundary cookbook tree for git hooks and code quality gates.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The architect service was missing pod_name/namespace in AgentRequest
metadata, causing Claude Code adapter to reject all requests. Added
ArchitectServiceConfig with pod resolution (project PodName → default
claudebox-0). Removed silent JSON fallback in extractSpecFromMessages
that masked errors.
Rewrote foundary cookbook from 90-step SDLC flow to focused 25-step
cookbook using natural language build prompts instead of /slash-commands
that claudebox cannot execute. Added "no fallbacks" rule to CLAUDE.md.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The HTTP claudebox client's ExecuteStream method used a bare
bufio.NewScanner with the default 64KB max token size. When Claude Code
produces tool results > 64KB (e.g., reading large files), the SSE event
exceeds the scanner limit and fails with "token too long".
Every other scanner in the codebase (claudecode adapter, claudebox
executor, kubernetes executor) already uses scanner.Buffer(buf, 1MB).
This was the only one missed.
Fixes: "agent execution failed: read stream: bufio.Scanner: token too long"
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Woodpecker's K8s backend creates a PVC per pipeline for workspace sharing.
If the agent misses cleanup, stale PVCs cause "already exists" errors that
mark pipelines as failed despite all steps succeeding.
Two-part fix:
1. Scale woodpecker-agent from 2 to 1 replica (eliminates PVC name race
between agents processing the same repo)
2. Add CronJob that garbage-collects wp-* PVCs older than 30 minutes
every 5 minutes (handles crash/restart edge cases)
Includes dedicated ServiceAccount and least-privilege RBAC (PVC list/delete
only in threesix namespace).
Ref: https://github.com/woodpecker-ci/woodpecker/issues/1594
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The fatal push+retry logic added in the git flow hardening broke tests
that use local-only git repos without an origin remote. Check for the
origin remote before attempting to push, preserving the fatal behavior
in production (where origin always exists) while allowing local/test
contexts to proceed without a remote.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
5 fixes from stress test analysis:
1. CRITICAL: Add pull-before-push to claudebox GitOperations.CommitAndPush,
matching the fix already in PodGitOperations (prevents push rejections
when concurrent builds advance the remote).
2. HIGH: Extract ResetToMain into PodGitOperations as a shared public method.
Wire into BuildExecutor after CloneRepo and update SDLCTaskExecutor to
use the shared method. Prevents builds from running on wrong branch when
worker pods are reused across tasks.
3. HIGH: Make branch create push failure fatal with retry+rollback in
cmd/sdlc/cmd_branch.go. Prevents orphaned .sdlc/ state that causes
merge failures after completing all 10 SDLC phases.
4. MEDIUM: Shell-escape token in credential helpers (both PodGitOperations
and claudebox GitOperations) to prevent shell injection via tokens
containing special characters.
5. MEDIUM: Add GitResetToMain to claudebox sidecar (git.go implementation,
server.go endpoint, client.go HTTP method) and wire into
HTTPSDLCTaskExecutor for the HTTP sidecar path.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The foundary cookbook was using template: "default" which seeds a flat
CI pipeline without the COMPONENT_STEPS_BELOW marker. When components
were added via batch API, updateWoodpeckerYml couldn't find the marker
and silently returned the file unchanged — component build/deploy steps
were never inserted. This caused component images to never be built,
leaving pods at 0 replicas indefinitely.
The skeleton template has the correct DAG-mode pipeline with markers
for component step insertion and build-complete dependency wiring.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Auth errors like "OAuth token has expired" were lost because Claude writes
them to stdout, not stderr. The error message only showed kubectl's generic
"command terminated with exit code 1". Now includes both stdout and stderr
in the error, making failures immediately diagnosable.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Components are scaffolded before CI builds their images. Previously deployments
started with 1 replica, causing ImagePullBackOff until the first build completed.
Now deployments start at 0 replicas; CI deploy steps scale to 1 after verifying
the image exists in the registry.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Registry was full (29.4GB/30GB) causing DIGEST_INVALID on all image pushes.
Cleaned old test project repos, expanded PVC from 30Gi to 80Gi, re-enabled cache.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Corrupt cache blobs in rdev/claudebox/cache cause DIGEST_INVALID on
push. Disable cache temporarily to force a clean build. Can re-enable
once a good image is pushed and the cache is rebuilt.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add --connect-timeout 10 and --max-time 15 to all verify step curl
calls to prevent hanging on registry health checks
- Fix cli template: depends_on [deps] -> [preflight] for consistency
- Add cross-reference comment to service template about verify logic
being replicated across all 5 component templates
- Document component CI step rules in composable-monorepo.md
- Compile regexes at package level instead of per-call in
component_updates.go
- Add component_updates_test.go
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The CI deploy step runs `kubectl set image statefulset/claudebox` but
the woodpecker-deployer Role only included `deployments`. Add
`statefulsets` to the allowed resources.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add UndeployAll() using label selectors to clean up monorepo components
on project deletion (replaces name-based Undeploy in DeleteProject and
the direct undeploy handler)
- Add ResourceGC background worker that periodically finds K8s resources
whose project label has no matching DB record, deletes after 1h safety
window
- Widen deployer client type from *kubernetes.Clientset to
kubernetes.Interface for testability
- UndeployAll accumulates errors via errors.Join instead of failing fast
- Add checkout/checkin sidecar dev flow: temporary git tokens, branch
checkout, review on checkin with cleanup workers
- Add interactive sessions: pod binding, command execution, SSE streaming,
ephemeral preview URLs with session cleanup workers
- Add GET /workers/pool endpoint for aggregate capacity and queue depth
- Add sessions:read and sessions:execute auth scopes
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Site verification may fail when component images haven't built yet.
The SDLC lifecycle completes regardless of site availability.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The sdlc merge command already transitions features to released
internally. The cookbook's transition step was running after archive,
which moved the feature and caused "feature not found". Fixed by:
- Reordering: transition before archive
- Adding on_error: continue to both (merge handles transition)
- Simplifying verification (no longer depends on transition outputs)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Both main and the feature branch modify .sdlc/state.yaml during the
SDLC lifecycle. Use -X theirs to auto-resolve conflicts in favor of the
feature branch, whose state is more current.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
After resetToMain in the executor, only remote refs exist for feature
branches. The merge command now checks if the local branch exists and
falls back to origin/<branch> when it doesn't.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The logging middleware's responseWriter wrapped http.ResponseWriter but
only implemented WriteHeader, Write, and Unwrap. The missing Flush()
method caused w.(http.Flusher) type assertions to fail in the claudebox
sidecar's streaming endpoint, returning 500 "streaming not supported".
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The `sdlc merge` command reads the Branch field from the feature manifest
on main, but `sdlc branch create` was only committing that state to the
feature branch (via the executor's CommitAndPush). This caused merge to
fail with "feature has no branch".
Two changes:
1. cmd/sdlc/cmd_branch.go: commit .sdlc/ state to main before
`git checkout -b`, ensuring Branch metadata is on main where merge
reads it.
2. internal/worker/sdlc_executor.go: reset workspace to main
(`git fetch && git checkout main && git reset --hard origin/main`)
before each SDLC task, preventing cross-task branch contamination
from commands that switch branches.
Also updates foundary cookbook with architect fallback pattern and
on_error: continue for steps that may fail during early lifecycle.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Woodpecker CI was timing out when watching deployment rollout status
due to missing RBAC permissions. The deployments were succeeding but
CI couldn't verify completion.
Changes:
- Add 'watch' verb to woodpecker-deployer Role
- Add threesix/default service account to RoleBinding
- Consolidate woodpecker-deployer RBAC into base/rbac.yaml
This resolves the "Failed to watch: deployments.apps is forbidden"
errors in CI logs while maintaining successful deployment rollouts.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Implements all 5 phases of Foundary Studio backend:
Phase 1: Chat Persistence (8 API endpoints)
- Conversations and messages with proper cascading deletes
- PostgreSQL schema with auto-update triggers
- Full CRUD operations with structured logging
Phase 2: Blueprint Entity (5 API endpoints)
- JSONB spec storage with GIN indexes
- Flexible structured data for project specifications
- Version-controlled blueprint management
Phase 3: Architect Service (3 API endpoints)
- Conversational AI orchestration with Claude
- Multi-turn dialogue with context building
- Blueprint spec extraction from conversations
Phase 4: Work Queue Integration
- Verified existing endpoint compatibility
Phase 5: Structured Questions (6 API endpoints)
- Four question types: text, choice, multichoice, yesno
- Answer validation with proper constraints
- Conversation-linked Q&A flow
Architecture:
- Textbook hexagonal architecture (domain → port → adapter → service → handler)
- Zero external dependencies in domain layer
- Consistent error handling with proper wrapping
- Auth scopes on all routes (projects:read, projects:execute)
- Structured logging with operation context and duration tracking
- NULL-safe DTO converters throughout
Database:
- 3 new migrations (019, 020, 021)
- UUIDs for all primary keys
- Proper foreign key constraints with ON DELETE CASCADE
- Optimized indexes including partial index for unanswered questions
- Auto-update triggers for timestamps
OpenAPI Documentation:
- Complete API documentation under 'Foundary' tag
- 22 new endpoints documented with examples
- Request/response schemas for all operations
Logging Improvements:
- Added operation field to all service logs
- Added duration_ms tracking for performance monitoring
- Log response_length instead of full response content
- Consistent use of logging field constants
- Execute-then-log pattern for delete operations
Files: 32 changed, 2800+ lines added
- 7 domain models
- 3 database migrations
- 3 port interfaces
- 3 postgres adapters
- 4 services (conversation, blueprint, question, architect)
- 4 handlers with DTOs
- OpenAPI documentation
- Integration in main.go
🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
Allow transitioning to the current phase (no-op success) instead of
rejecting it as a "backward" transition. This fixes issues where
external systems retry transition commands.
Before: draft -> draft returned error
After: draft -> draft returns nil (already there)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
SDLCGenerateHandler was using r.Route() to create a sub-router at
/projects/{id}/sdlc/features/{slug}, which shadowed SDLCHandler's
nested routes like /features/{slug}/artifacts/{type}/approve.
Changed to direct route registration to avoid chi route conflicts.
This fixes 404 errors on SDLC feature and artifact endpoints.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Root cause of DIGEST_INVALID errors was registry disk exhaustion.
Project teardown wasn't cleaning up container images, causing the
registry PVC to fill up over time.
Changes:
- Add RegistryProvider port interface for registry operations
- Extend zot.Client with DeleteProjectRepositories method
- Wire registry provider into ProjectInfraService
- Delete images during DeleteProject cleanup (step 4)
The zot client uses the OCI distribution API:
- Lists all repos, filters by project prefix
- Gets manifest digests via HEAD request
- Deletes manifests by digest to trigger GC
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>