fix: resolve 7 root causes causing cookbook deployment failures
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
RC-1: Gitea org fallback already removed (no-op, confirmed) RC-3: Push/pull now explicitly target origin main (HEAD:main) in both pod_git_operations.go and claudebox/git.go — fixes Woodpecker webhook trigger by ensuring pushes always land on the main branch RC-4: wait_for_pipeline records baseline pipeline number before polling; only returns success when a NEWER pipeline completes — prevents false positive when a prior pipeline was already success RC-5: Redis WRONGPASS fixed on live persona-community-5 instance; platform gap noted (no reprovision endpoint for Redis ACL drift) RC-6: Removed on_error:continue from all infra provisioning steps (add-db, add-redis) across persona-community, slackpath-2/3/4/5 trees — infra failures now fail the tree instead of silently continuing to a crash RC-7: Added .pnpm-store/ to skeleton .gitignore — prevents thousands of cache files being committed by agents after pnpm install RC-2: Updated all 12 cookbook trees — git_clone_url jordan/ → threesix/ (24 occurrences across all slackpath, aeries, full-stack, genkit trees) Also: strings.Cut and strings.SplitSeq lint fixes in pod_git_operations.go and claudebox/git.go Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
3dbde72966
commit
62a9bbb237
@ -156,14 +156,21 @@ wait_for_build() {
|
|||||||
wait_for_pipeline() {
|
wait_for_pipeline() {
|
||||||
local project_id="$1"
|
local project_id="$1"
|
||||||
local max_attempts="${2:-120}" # 10 minutes default
|
local max_attempts="${2:-120}" # 10 minutes default
|
||||||
local poll_interval="${3:-5}"
|
local poll_interval="${3:-10}"
|
||||||
local attempt=0
|
local attempt=0
|
||||||
local tracked_pipeline="" # Track specific pipeline once found
|
local tracked_pipeline="" # Track specific pipeline once found
|
||||||
|
|
||||||
echo -e "${CYAN}Waiting for CI pipeline...${NC}"
|
echo -e "${CYAN}Waiting for new CI pipeline...${NC}"
|
||||||
|
|
||||||
# Wait a bit for pipeline to be created
|
# Record the current latest pipeline number BEFORE waiting
|
||||||
sleep 5
|
# so we only track pipelines triggered AFTER this point
|
||||||
|
local baseline_number=0
|
||||||
|
local initial_result
|
||||||
|
initial_result=$(api_call GET "/projects/$project_id/pipelines" 2>/dev/null)
|
||||||
|
if echo "$initial_result" | jq -e '.data[0]' >/dev/null 2>&1; then
|
||||||
|
baseline_number=$(echo "$initial_result" | jq -r '.data[0].number // 0')
|
||||||
|
echo " Baseline pipeline: #$baseline_number — waiting for a newer one"
|
||||||
|
fi
|
||||||
|
|
||||||
while [[ $attempt -lt $max_attempts ]]; do
|
while [[ $attempt -lt $max_attempts ]]; do
|
||||||
local result
|
local result
|
||||||
@ -180,16 +187,23 @@ wait_for_pipeline() {
|
|||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Get latest pipeline status
|
# Get latest pipeline number and status
|
||||||
local status
|
local pipeline_number status
|
||||||
|
pipeline_number=$(echo "$result" | jq -r '.data[0].number // 0')
|
||||||
status=$(echo "$result" | jq -r '.data[0].status // "unknown"')
|
status=$(echo "$result" | jq -r '.data[0].status // "unknown"')
|
||||||
local pipeline_number
|
|
||||||
pipeline_number=$(echo "$result" | jq -r '.data[0].number // "?"')
|
|
||||||
|
|
||||||
# Track the pipeline we're monitoring
|
# Skip any pipeline that is not newer than our baseline
|
||||||
|
if [[ "$pipeline_number" -le "$baseline_number" ]]; then
|
||||||
|
echo " Waiting for new pipeline (latest is #$pipeline_number, baseline #$baseline_number)... (attempt $((attempt + 1))/$max_attempts)"
|
||||||
|
sleep "$poll_interval"
|
||||||
|
((attempt++))
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# A new pipeline exists — track it
|
||||||
if [[ -z "$tracked_pipeline" ]]; then
|
if [[ -z "$tracked_pipeline" ]]; then
|
||||||
tracked_pipeline="$pipeline_number"
|
tracked_pipeline="$pipeline_number"
|
||||||
echo " Tracking pipeline #$pipeline_number"
|
echo " Tracking new pipeline #$tracked_pipeline"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
case "$status" in
|
case "$status" in
|
||||||
@ -235,7 +249,7 @@ wait_for_pipeline() {
|
|||||||
((attempt++))
|
((attempt++))
|
||||||
done
|
done
|
||||||
|
|
||||||
echo -e "${YELLOW}Timeout waiting for pipeline to complete${NC}"
|
echo -e "${YELLOW}Timeout waiting for new pipeline${NC}"
|
||||||
# On timeout, still run diagnostics to help debug
|
# On timeout, still run diagnostics to help debug
|
||||||
if [[ -n "$tracked_pipeline" ]]; then
|
if [[ -n "$tracked_pipeline" ]]; then
|
||||||
diagnose_pipeline_failure "$project_id"
|
diagnose_pipeline_failure "$project_id"
|
||||||
|
|||||||
@ -68,7 +68,7 @@ steps:
|
|||||||
prompt: "/spec-feature {{ .vars.feature_slug }}"
|
prompt: "/spec-feature {{ .vars.feature_slug }}"
|
||||||
auto_commit: true
|
auto_commit: true
|
||||||
auto_push: true
|
auto_push: true
|
||||||
git_clone_url: "https://git.threesix.ai/jordan/{{ .outputs.create-project.project_id }}.git"
|
git_clone_url: "https://git.threesix.ai/threesix/{{ .outputs.create-project.project_id }}.git"
|
||||||
outputs:
|
outputs:
|
||||||
- build_id: .data.task_id
|
- build_id: .data.task_id
|
||||||
|
|
||||||
@ -90,7 +90,7 @@ steps:
|
|||||||
prompt: "/implement-feature {{ .vars.feature_slug }} --scope backend --requirements 'Use pkg/api. DB Table: agents. Fields: id, name, personality_prompt, created_at.'"
|
prompt: "/implement-feature {{ .vars.feature_slug }} --scope backend --requirements 'Use pkg/api. DB Table: agents. Fields: id, name, personality_prompt, created_at.'"
|
||||||
auto_commit: true
|
auto_commit: true
|
||||||
auto_push: true
|
auto_push: true
|
||||||
git_clone_url: "https://git.threesix.ai/jordan/{{ .outputs.create-project.project_id }}.git"
|
git_clone_url: "https://git.threesix.ai/threesix/{{ .outputs.create-project.project_id }}.git"
|
||||||
outputs:
|
outputs:
|
||||||
- build_id: .data.task_id
|
- build_id: .data.task_id
|
||||||
|
|
||||||
|
|||||||
@ -24,7 +24,7 @@ steps:
|
|||||||
prompt: "/extract-service core-api/internal/domain/agent_logic simulation-svc --pattern strangler"
|
prompt: "/extract-service core-api/internal/domain/agent_logic simulation-svc --pattern strangler"
|
||||||
auto_commit: true
|
auto_commit: true
|
||||||
auto_push: true
|
auto_push: true
|
||||||
git_clone_url: "https://git.threesix.ai/jordan/{{ .vars.project_id }}.git"
|
git_clone_url: "https://git.threesix.ai/threesix/{{ .vars.project_id }}.git"
|
||||||
outputs:
|
outputs:
|
||||||
- build_id: .data.task_id
|
- build_id: .data.task_id
|
||||||
|
|
||||||
|
|||||||
@ -38,7 +38,7 @@ steps:
|
|||||||
prompt: "/implement-feature {{ .vars.feature_slug }} --requirements 'Simulation SVC publishes agent moves to Redis. Spatial SVC tracks proximity. If two agents are near, Core UI shows a chat bubble.'"
|
prompt: "/implement-feature {{ .vars.feature_slug }} --requirements 'Simulation SVC publishes agent moves to Redis. Spatial SVC tracks proximity. If two agents are near, Core UI shows a chat bubble.'"
|
||||||
auto_commit: true
|
auto_commit: true
|
||||||
auto_push: true
|
auto_push: true
|
||||||
git_clone_url: "https://git.threesix.ai/jordan/{{ .vars.project_id }}.git"
|
git_clone_url: "https://git.threesix.ai/threesix/{{ .vars.project_id }}.git"
|
||||||
outputs:
|
outputs:
|
||||||
- build_id: .data.task_id
|
- build_id: .data.task_id
|
||||||
|
|
||||||
|
|||||||
@ -62,7 +62,7 @@ steps:
|
|||||||
prompt: "/spec-feature {{ .vars.feature_slug }}"
|
prompt: "/spec-feature {{ .vars.feature_slug }}"
|
||||||
auto_commit: true
|
auto_commit: true
|
||||||
auto_push: true
|
auto_push: true
|
||||||
git_clone_url: "https://git.threesix.ai/jordan/{{ .outputs.create-project.project_id }}.git"
|
git_clone_url: "https://git.threesix.ai/threesix/{{ .outputs.create-project.project_id }}.git"
|
||||||
outputs:
|
outputs:
|
||||||
- build_id: .data.task_id
|
- build_id: .data.task_id
|
||||||
|
|
||||||
|
|||||||
@ -62,7 +62,7 @@ steps:
|
|||||||
prompt: "/spec-feature {{ .vars.feature_slug }}"
|
prompt: "/spec-feature {{ .vars.feature_slug }}"
|
||||||
auto_commit: true
|
auto_commit: true
|
||||||
auto_push: true
|
auto_push: true
|
||||||
git_clone_url: "https://git.threesix.ai/jordan/{{ .outputs.create-project.project_id }}.git"
|
git_clone_url: "https://git.threesix.ai/threesix/{{ .outputs.create-project.project_id }}.git"
|
||||||
outputs:
|
outputs:
|
||||||
- build_id: .data.task_id
|
- build_id: .data.task_id
|
||||||
|
|
||||||
@ -92,7 +92,7 @@ steps:
|
|||||||
prompt: "/design-feature {{ .vars.feature_slug }}"
|
prompt: "/design-feature {{ .vars.feature_slug }}"
|
||||||
auto_commit: true
|
auto_commit: true
|
||||||
auto_push: true
|
auto_push: true
|
||||||
git_clone_url: "https://git.threesix.ai/jordan/{{ .outputs.create-project.project_id }}.git"
|
git_clone_url: "https://git.threesix.ai/threesix/{{ .outputs.create-project.project_id }}.git"
|
||||||
outputs:
|
outputs:
|
||||||
- build_id: .data.task_id
|
- build_id: .data.task_id
|
||||||
|
|
||||||
@ -135,7 +135,7 @@ steps:
|
|||||||
prompt: "/implement-task {{ .vars.feature_slug }} {{ .outputs.create-task-backend.task_id }}"
|
prompt: "/implement-task {{ .vars.feature_slug }} {{ .outputs.create-task-backend.task_id }}"
|
||||||
auto_commit: true
|
auto_commit: true
|
||||||
auto_push: true
|
auto_push: true
|
||||||
git_clone_url: "https://git.threesix.ai/jordan/{{ .outputs.create-project.project_id }}.git"
|
git_clone_url: "https://git.threesix.ai/threesix/{{ .outputs.create-project.project_id }}.git"
|
||||||
outputs:
|
outputs:
|
||||||
- build_id: .data.task_id
|
- build_id: .data.task_id
|
||||||
|
|
||||||
|
|||||||
@ -92,7 +92,7 @@ steps:
|
|||||||
'
|
'
|
||||||
auto_commit: true
|
auto_commit: true
|
||||||
auto_push: true
|
auto_push: true
|
||||||
git_clone_url: "https://git.threesix.ai/jordan/{{ .outputs.create-project.project_id }}.git"
|
git_clone_url: "https://git.threesix.ai/threesix/{{ .outputs.create-project.project_id }}.git"
|
||||||
outputs:
|
outputs:
|
||||||
- build_id: .data.task_id
|
- build_id: .data.task_id
|
||||||
|
|
||||||
|
|||||||
@ -24,16 +24,14 @@ steps:
|
|||||||
add-db:
|
add-db:
|
||||||
description: CockroachDB for persona + media persistence
|
description: CockroachDB for persona + media persistence
|
||||||
depends_on: [create-project]
|
depends_on: [create-project]
|
||||||
on_error: continue
|
|
||||||
action: api
|
action: api
|
||||||
method: POST
|
method: POST
|
||||||
endpoint: "/projects/{{ .outputs.create-project.project_id }}/components"
|
endpoint: "/projects/{{ .outputs.create-project.project_id }}/components"
|
||||||
body: { type: postgres, name: "persona-db" }
|
body: { type: postgres, name: "persona-db" }
|
||||||
|
|
||||||
add-redis:
|
add-redis:
|
||||||
description: Redis for SSE pub/sub and generation job queue (may already be provisioned by skeleton)
|
description: Redis for SSE pub/sub and generation job queue
|
||||||
depends_on: [create-project]
|
depends_on: [create-project]
|
||||||
on_error: continue
|
|
||||||
action: api
|
action: api
|
||||||
method: POST
|
method: POST
|
||||||
endpoint: "/projects/{{ .outputs.create-project.project_id }}/components"
|
endpoint: "/projects/{{ .outputs.create-project.project_id }}/components"
|
||||||
@ -98,7 +96,7 @@ steps:
|
|||||||
prompt: "/implement-feature persona-model --requirements 'DB migration in persona-api: CREATE TABLE personas (id UUID PRIMARY KEY DEFAULT gen_random_uuid(), name TEXT NOT NULL, handle TEXT UNIQUE NOT NULL, gender TEXT NOT NULL, description TEXT NOT NULL, tags TEXT[] NOT NULL DEFAULT ARRAY[]::TEXT[], spec_json JSONB, anchor_url TEXT, avatar_url TEXT, banner_url TEXT, image_urls TEXT[] NOT NULL DEFAULT ARRAY[]::TEXT[], video_urls TEXT[] NOT NULL DEFAULT ARRAY[]::TEXT[], status TEXT NOT NULL DEFAULT ''pending'', created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()). Domain model Persona with same fields. PersonaService: Create(ctx, description, gender, customName string) (*Persona, error) — saves row then enqueues a generate_spec job. GetByID(ctx, id). List(ctx, limit, offset int) ([]*Persona, error). Queue job type PersonaGenerateJob {PersonaID string, Stage string} where Stage values are: spec, anchor, avatar, banner, gallery_batch, video. Endpoints all under /api/persona-api: POST /personas (body: {description, gender, custom_name?}, returns 202 with persona), GET /personas (query: limit=20, offset=0), GET /personas/{id}. All routes require JWT auth (use skeleton auth.Middleware). After any persona field update publish SSE event persona_updated to channel:personas via the SSE hub.'"
|
prompt: "/implement-feature persona-model --requirements 'DB migration in persona-api: CREATE TABLE personas (id UUID PRIMARY KEY DEFAULT gen_random_uuid(), name TEXT NOT NULL, handle TEXT UNIQUE NOT NULL, gender TEXT NOT NULL, description TEXT NOT NULL, tags TEXT[] NOT NULL DEFAULT ARRAY[]::TEXT[], spec_json JSONB, anchor_url TEXT, avatar_url TEXT, banner_url TEXT, image_urls TEXT[] NOT NULL DEFAULT ARRAY[]::TEXT[], video_urls TEXT[] NOT NULL DEFAULT ARRAY[]::TEXT[], status TEXT NOT NULL DEFAULT ''pending'', created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()). Domain model Persona with same fields. PersonaService: Create(ctx, description, gender, customName string) (*Persona, error) — saves row then enqueues a generate_spec job. GetByID(ctx, id). List(ctx, limit, offset int) ([]*Persona, error). Queue job type PersonaGenerateJob {PersonaID string, Stage string} where Stage values are: spec, anchor, avatar, banner, gallery_batch, video. Endpoints all under /api/persona-api: POST /personas (body: {description, gender, custom_name?}, returns 202 with persona), GET /personas (query: limit=20, offset=0), GET /personas/{id}. All routes require JWT auth (use skeleton auth.Middleware). After any persona field update publish SSE event persona_updated to channel:personas via the SSE hub.'"
|
||||||
auto_commit: true
|
auto_commit: true
|
||||||
auto_push: true
|
auto_push: true
|
||||||
git_clone_url: "https://git.threesix.ai/jordan/{{ .outputs.create-project.project_id }}.git"
|
git_clone_url: "https://git.threesix.ai/threesix/{{ .outputs.create-project.project_id }}.git"
|
||||||
outputs:
|
outputs:
|
||||||
- build_id: .data.task_id
|
- build_id: .data.task_id
|
||||||
|
|
||||||
@ -120,7 +118,7 @@ steps:
|
|||||||
prompt: "/implement-feature persona-generation --requirements 'Implement the generation pipeline in media-worker using the skeleton ai-client package (LAOZHANG_API_KEY for images, GEMINI_API_KEY for video). Worker consumes PersonaGenerateJob from queue and executes the stage: STAGE spec — call LLM to generate a PersonaSpec JSON: {name, handle, gender, tags:[8 strings], personality_archetype, appearance:{hair, eyes, skin, body_type, distinctive_features}, style_signature, image_matrix:[{position:int, pose:string, clothing:string, background:string} x 30]}. Save full spec to spec_json, update name/handle/tags on the persona row. Then enqueue stage=anchor job. STAGE anchor — generate one reference face-front portrait image from appearance description. Save URL to anchor_url. Then enqueue stage=avatar AND stage=banner as parallel jobs. STAGE avatar — generate circular portrait derived from anchor appearance. Save to avatar_url. STAGE banner — generate 3:1 landscape lifestyle image derived from anchor. Save to banner_url. STAGE gallery_batch — generate 10 images using next 10 unfilled positions from image_matrix. Append URLs to image_urls array. If fewer than 30 images exist, enqueue another gallery_batch job. STAGE video — generate the next missing video from: [smile_reveal, personality_moment, lifestyle, invitation]. Append URL to video_urls. If fewer than 4 videos, enqueue next video job. After each stage: update persona row, publish job_update SSE event {persona_id, stage, status:complete|error, progress} to channel:personas.'"
|
prompt: "/implement-feature persona-generation --requirements 'Implement the generation pipeline in media-worker using the skeleton ai-client package (LAOZHANG_API_KEY for images, GEMINI_API_KEY for video). Worker consumes PersonaGenerateJob from queue and executes the stage: STAGE spec — call LLM to generate a PersonaSpec JSON: {name, handle, gender, tags:[8 strings], personality_archetype, appearance:{hair, eyes, skin, body_type, distinctive_features}, style_signature, image_matrix:[{position:int, pose:string, clothing:string, background:string} x 30]}. Save full spec to spec_json, update name/handle/tags on the persona row. Then enqueue stage=anchor job. STAGE anchor — generate one reference face-front portrait image from appearance description. Save URL to anchor_url. Then enqueue stage=avatar AND stage=banner as parallel jobs. STAGE avatar — generate circular portrait derived from anchor appearance. Save to avatar_url. STAGE banner — generate 3:1 landscape lifestyle image derived from anchor. Save to banner_url. STAGE gallery_batch — generate 10 images using next 10 unfilled positions from image_matrix. Append URLs to image_urls array. If fewer than 30 images exist, enqueue another gallery_batch job. STAGE video — generate the next missing video from: [smile_reveal, personality_moment, lifestyle, invitation]. Append URL to video_urls. If fewer than 4 videos, enqueue next video job. After each stage: update persona row, publish job_update SSE event {persona_id, stage, status:complete|error, progress} to channel:personas.'"
|
||||||
auto_commit: true
|
auto_commit: true
|
||||||
auto_push: true
|
auto_push: true
|
||||||
git_clone_url: "https://git.threesix.ai/jordan/{{ .outputs.create-project.project_id }}.git"
|
git_clone_url: "https://git.threesix.ai/threesix/{{ .outputs.create-project.project_id }}.git"
|
||||||
outputs:
|
outputs:
|
||||||
- build_id: .data.task_id
|
- build_id: .data.task_id
|
||||||
|
|
||||||
@ -150,7 +148,7 @@ steps:
|
|||||||
prompt: "/implement-feature community-ui --requirements 'Build the React community UI using skeleton packages (@project/ui, @project/auth, @project/layout, @project/realtime, @project/api-client). App uses DashboardShell layout from @project/layout. Routes: /login (public) and / and /personas/:id (protected via ProtectedRoute from @project/auth). LOGIN PAGE: OTP flow — email input, Send Code button, then code input + Verify button. On success store token and redirect to /. COMMUNITY PAGE (/): Top bar has Create Persona button. Main area is a responsive grid of PersonaCard components. PersonaCard shows: banner_url as card background image, avatar_url as circular overlay (bottom-left), name + handle + up to 4 tags as pills, image/video count badges. While status=pending|generating show a subtle pulsing overlay with current stage label (Crafting identity... / Generating anchor... etc). Click card → navigate to /personas/:id. CREATE PANEL: slides in from right, contains: description textarea (placeholder: Describe your persona...), gender pill selector (Female / Male / Non-binary), optional Name field, Create button. On submit POST /api/persona-api/personas — new card appears immediately in grid with generating state. DETAIL PAGE (/personas/:id): full-width banner header, large circular avatar centered, name + handle + all tags. Image gallery grid (masonry or even grid, click any image to open fullscreen lightbox with arrow nav). Videos section shows 4 video players (smile reveal, personality moment, lifestyle, invitation) with labels. REALTIME: useEventChannel from @project/realtime subscribed to channel:personas. On persona_updated event refresh that card data. On job_update event show stage progress on the card overlay.'"
|
prompt: "/implement-feature community-ui --requirements 'Build the React community UI using skeleton packages (@project/ui, @project/auth, @project/layout, @project/realtime, @project/api-client). App uses DashboardShell layout from @project/layout. Routes: /login (public) and / and /personas/:id (protected via ProtectedRoute from @project/auth). LOGIN PAGE: OTP flow — email input, Send Code button, then code input + Verify button. On success store token and redirect to /. COMMUNITY PAGE (/): Top bar has Create Persona button. Main area is a responsive grid of PersonaCard components. PersonaCard shows: banner_url as card background image, avatar_url as circular overlay (bottom-left), name + handle + up to 4 tags as pills, image/video count badges. While status=pending|generating show a subtle pulsing overlay with current stage label (Crafting identity... / Generating anchor... etc). Click card → navigate to /personas/:id. CREATE PANEL: slides in from right, contains: description textarea (placeholder: Describe your persona...), gender pill selector (Female / Male / Non-binary), optional Name field, Create button. On submit POST /api/persona-api/personas — new card appears immediately in grid with generating state. DETAIL PAGE (/personas/:id): full-width banner header, large circular avatar centered, name + handle + all tags. Image gallery grid (masonry or even grid, click any image to open fullscreen lightbox with arrow nav). Videos section shows 4 video players (smile reveal, personality moment, lifestyle, invitation) with labels. REALTIME: useEventChannel from @project/realtime subscribed to channel:personas. On persona_updated event refresh that card data. On job_update event show stage progress on the card overlay.'"
|
||||||
auto_commit: true
|
auto_commit: true
|
||||||
auto_push: true
|
auto_push: true
|
||||||
git_clone_url: "https://git.threesix.ai/jordan/{{ .outputs.create-project.project_id }}.git"
|
git_clone_url: "https://git.threesix.ai/threesix/{{ .outputs.create-project.project_id }}.git"
|
||||||
outputs:
|
outputs:
|
||||||
- build_id: .data.task_id
|
- build_id: .data.task_id
|
||||||
|
|
||||||
|
|||||||
@ -93,7 +93,7 @@ steps:
|
|||||||
prompt: "/implement-feature {{ .vars.feature_slug }} --requirements 'User model with email/password. POST /register, POST /login (returns JWT). Middleware that checks Authorization header. GET /me returns user profile.'"
|
prompt: "/implement-feature {{ .vars.feature_slug }} --requirements 'User model with email/password. POST /register, POST /login (returns JWT). Middleware that checks Authorization header. GET /me returns user profile.'"
|
||||||
auto_commit: true
|
auto_commit: true
|
||||||
auto_push: true
|
auto_push: true
|
||||||
git_clone_url: "https://git.threesix.ai/jordan/{{ .outputs.create-project.project_id }}.git"
|
git_clone_url: "https://git.threesix.ai/threesix/{{ .outputs.create-project.project_id }}.git"
|
||||||
outputs:
|
outputs:
|
||||||
- build_id: .data.task_id
|
- build_id: .data.task_id
|
||||||
|
|
||||||
|
|||||||
@ -20,9 +20,8 @@ steps:
|
|||||||
- domain: .data.domain
|
- domain: .data.domain
|
||||||
|
|
||||||
add-redis:
|
add-redis:
|
||||||
description: Add Redis for job queue (may already exist from skeleton)
|
description: Add Redis for job queue
|
||||||
depends_on: [create-project]
|
depends_on: [create-project]
|
||||||
on_error: continue
|
|
||||||
action: api
|
action: api
|
||||||
method: POST
|
method: POST
|
||||||
endpoint: "/projects/{{ .outputs.create-project.project_id }}/components"
|
endpoint: "/projects/{{ .outputs.create-project.project_id }}/components"
|
||||||
@ -59,7 +58,7 @@ steps:
|
|||||||
prompt: "/implement-feature {{ .vars.feature_slug }} --requirements 'API: POST /jobs pushes JSON to Redis. Worker: Pops from Redis, simulates work, updates status. API: GET /jobs/{id} returns status.'"
|
prompt: "/implement-feature {{ .vars.feature_slug }} --requirements 'API: POST /jobs pushes JSON to Redis. Worker: Pops from Redis, simulates work, updates status. API: GET /jobs/{id} returns status.'"
|
||||||
auto_commit: true
|
auto_commit: true
|
||||||
auto_push: true
|
auto_push: true
|
||||||
git_clone_url: "https://git.threesix.ai/jordan/{{ .outputs.create-project.project_id }}.git"
|
git_clone_url: "https://git.threesix.ai/threesix/{{ .outputs.create-project.project_id }}.git"
|
||||||
outputs:
|
outputs:
|
||||||
- build_id: .data.task_id
|
- build_id: .data.task_id
|
||||||
|
|
||||||
|
|||||||
@ -20,9 +20,8 @@ steps:
|
|||||||
- domain: .data.domain
|
- domain: .data.domain
|
||||||
|
|
||||||
add-redis:
|
add-redis:
|
||||||
description: Add Redis for Pub/Sub (may already exist from skeleton)
|
description: Add Redis for Pub/Sub
|
||||||
depends_on: [create-project]
|
depends_on: [create-project]
|
||||||
on_error: continue
|
|
||||||
action: api
|
action: api
|
||||||
method: POST
|
method: POST
|
||||||
endpoint: "/projects/{{ .outputs.create-project.project_id }}/components"
|
endpoint: "/projects/{{ .outputs.create-project.project_id }}/components"
|
||||||
@ -56,7 +55,7 @@ steps:
|
|||||||
prompt: "/implement-feature {{ .vars.feature_slug }} --requirements 'GET /ws upgrades to websocket. Incoming messages are published to Redis channel. Redis subscriber broadcasts to all connected clients.'"
|
prompt: "/implement-feature {{ .vars.feature_slug }} --requirements 'GET /ws upgrades to websocket. Incoming messages are published to Redis channel. Redis subscriber broadcasts to all connected clients.'"
|
||||||
auto_commit: true
|
auto_commit: true
|
||||||
auto_push: true
|
auto_push: true
|
||||||
git_clone_url: "https://git.threesix.ai/jordan/{{ .outputs.create-project.project_id }}.git"
|
git_clone_url: "https://git.threesix.ai/threesix/{{ .outputs.create-project.project_id }}.git"
|
||||||
outputs:
|
outputs:
|
||||||
- build_id: .data.task_id
|
- build_id: .data.task_id
|
||||||
|
|
||||||
|
|||||||
@ -20,9 +20,8 @@ steps:
|
|||||||
- domain: .data.domain
|
- domain: .data.domain
|
||||||
|
|
||||||
add-db:
|
add-db:
|
||||||
description: Add CockroachDB for user/auth storage (may already exist from skeleton)
|
description: Add CockroachDB for user/auth storage
|
||||||
depends_on: [create-project]
|
depends_on: [create-project]
|
||||||
on_error: continue
|
|
||||||
action: api
|
action: api
|
||||||
method: POST
|
method: POST
|
||||||
endpoint: "/projects/{{ .outputs.create-project.project_id }}/components"
|
endpoint: "/projects/{{ .outputs.create-project.project_id }}/components"
|
||||||
@ -31,9 +30,8 @@ steps:
|
|||||||
name: "main-db"
|
name: "main-db"
|
||||||
|
|
||||||
add-redis:
|
add-redis:
|
||||||
description: Add Redis for job queue and pub/sub (may already exist from skeleton)
|
description: Add Redis for job queue and pub/sub
|
||||||
depends_on: [create-project]
|
depends_on: [create-project]
|
||||||
on_error: continue
|
|
||||||
action: api
|
action: api
|
||||||
method: POST
|
method: POST
|
||||||
endpoint: "/projects/{{ .outputs.create-project.project_id }}/components"
|
endpoint: "/projects/{{ .outputs.create-project.project_id }}/components"
|
||||||
@ -97,7 +95,7 @@ steps:
|
|||||||
prompt: "/implement-feature {{ .vars.feature_slug }} --requirements 'Chat Service must call http://auth-svc/validate to check tokens. Chat Service must push to Redis queue for Worker. Worker must process tasks.'"
|
prompt: "/implement-feature {{ .vars.feature_slug }} --requirements 'Chat Service must call http://auth-svc/validate to check tokens. Chat Service must push to Redis queue for Worker. Worker must process tasks.'"
|
||||||
auto_commit: true
|
auto_commit: true
|
||||||
auto_push: true
|
auto_push: true
|
||||||
git_clone_url: "https://git.threesix.ai/jordan/{{ .outputs.create-project.project_id }}.git"
|
git_clone_url: "https://git.threesix.ai/threesix/{{ .outputs.create-project.project_id }}.git"
|
||||||
outputs:
|
outputs:
|
||||||
- build_id: .data.task_id
|
- build_id: .data.task_id
|
||||||
|
|
||||||
|
|||||||
@ -25,7 +25,6 @@ steps:
|
|||||||
add-db:
|
add-db:
|
||||||
description: Add database for preferences storage
|
description: Add database for preferences storage
|
||||||
depends_on: [create-project]
|
depends_on: [create-project]
|
||||||
on_error: continue
|
|
||||||
action: api
|
action: api
|
||||||
method: POST
|
method: POST
|
||||||
endpoint: "/projects/{{ .outputs.create-project.project_id }}/components"
|
endpoint: "/projects/{{ .outputs.create-project.project_id }}/components"
|
||||||
@ -95,7 +94,7 @@ steps:
|
|||||||
prompt: "/spec-feature {{ .vars.feature_slug }} --requirements 'CRUD API for user preferences. GET/PUT /preferences/{user_id}. Preferences are key-value pairs stored in DB. Support theme, language, notifications settings.'"
|
prompt: "/spec-feature {{ .vars.feature_slug }} --requirements 'CRUD API for user preferences. GET/PUT /preferences/{user_id}. Preferences are key-value pairs stored in DB. Support theme, language, notifications settings.'"
|
||||||
auto_commit: true
|
auto_commit: true
|
||||||
auto_push: true
|
auto_push: true
|
||||||
git_clone_url: "https://git.threesix.ai/jordan/{{ .outputs.create-project.project_id }}.git"
|
git_clone_url: "https://git.threesix.ai/threesix/{{ .outputs.create-project.project_id }}.git"
|
||||||
outputs:
|
outputs:
|
||||||
- build_id: .data.task_id
|
- build_id: .data.task_id
|
||||||
|
|
||||||
@ -140,7 +139,7 @@ steps:
|
|||||||
prompt: "/design-feature {{ .vars.feature_slug }}"
|
prompt: "/design-feature {{ .vars.feature_slug }}"
|
||||||
auto_commit: true
|
auto_commit: true
|
||||||
auto_push: true
|
auto_push: true
|
||||||
git_clone_url: "https://git.threesix.ai/jordan/{{ .outputs.create-project.project_id }}.git"
|
git_clone_url: "https://git.threesix.ai/threesix/{{ .outputs.create-project.project_id }}.git"
|
||||||
outputs:
|
outputs:
|
||||||
- build_id: .data.task_id
|
- build_id: .data.task_id
|
||||||
|
|
||||||
@ -170,7 +169,7 @@ steps:
|
|||||||
prompt: "/breakdown-feature {{ .vars.feature_slug }}"
|
prompt: "/breakdown-feature {{ .vars.feature_slug }}"
|
||||||
auto_commit: true
|
auto_commit: true
|
||||||
auto_push: true
|
auto_push: true
|
||||||
git_clone_url: "https://git.threesix.ai/jordan/{{ .outputs.create-project.project_id }}.git"
|
git_clone_url: "https://git.threesix.ai/threesix/{{ .outputs.create-project.project_id }}.git"
|
||||||
outputs:
|
outputs:
|
||||||
- build_id: .data.task_id
|
- build_id: .data.task_id
|
||||||
|
|
||||||
@ -200,7 +199,7 @@ steps:
|
|||||||
prompt: "/create-qa-plan {{ .vars.feature_slug }}"
|
prompt: "/create-qa-plan {{ .vars.feature_slug }}"
|
||||||
auto_commit: true
|
auto_commit: true
|
||||||
auto_push: true
|
auto_push: true
|
||||||
git_clone_url: "https://git.threesix.ai/jordan/{{ .outputs.create-project.project_id }}.git"
|
git_clone_url: "https://git.threesix.ai/threesix/{{ .outputs.create-project.project_id }}.git"
|
||||||
outputs:
|
outputs:
|
||||||
- build_id: .data.task_id
|
- build_id: .data.task_id
|
||||||
|
|
||||||
@ -269,7 +268,7 @@ steps:
|
|||||||
prompt: "/implement-feature {{ .vars.feature_slug }}"
|
prompt: "/implement-feature {{ .vars.feature_slug }}"
|
||||||
auto_commit: true
|
auto_commit: true
|
||||||
auto_push: true
|
auto_push: true
|
||||||
git_clone_url: "https://git.threesix.ai/jordan/{{ .outputs.create-project.project_id }}.git"
|
git_clone_url: "https://git.threesix.ai/threesix/{{ .outputs.create-project.project_id }}.git"
|
||||||
outputs:
|
outputs:
|
||||||
- build_id: .data.task_id
|
- build_id: .data.task_id
|
||||||
|
|
||||||
@ -312,7 +311,7 @@ steps:
|
|||||||
prompt: "/review-feature {{ .vars.feature_slug }}"
|
prompt: "/review-feature {{ .vars.feature_slug }}"
|
||||||
auto_commit: true
|
auto_commit: true
|
||||||
auto_push: true
|
auto_push: true
|
||||||
git_clone_url: "https://git.threesix.ai/jordan/{{ .outputs.create-project.project_id }}.git"
|
git_clone_url: "https://git.threesix.ai/threesix/{{ .outputs.create-project.project_id }}.git"
|
||||||
outputs:
|
outputs:
|
||||||
- build_id: .data.task_id
|
- build_id: .data.task_id
|
||||||
|
|
||||||
@ -355,7 +354,7 @@ steps:
|
|||||||
prompt: "/audit-feature {{ .vars.feature_slug }}"
|
prompt: "/audit-feature {{ .vars.feature_slug }}"
|
||||||
auto_commit: true
|
auto_commit: true
|
||||||
auto_push: true
|
auto_push: true
|
||||||
git_clone_url: "https://git.threesix.ai/jordan/{{ .outputs.create-project.project_id }}.git"
|
git_clone_url: "https://git.threesix.ai/threesix/{{ .outputs.create-project.project_id }}.git"
|
||||||
outputs:
|
outputs:
|
||||||
- build_id: .data.task_id
|
- build_id: .data.task_id
|
||||||
|
|
||||||
@ -398,7 +397,7 @@ steps:
|
|||||||
prompt: "/run-qa {{ .vars.feature_slug }}"
|
prompt: "/run-qa {{ .vars.feature_slug }}"
|
||||||
auto_commit: true
|
auto_commit: true
|
||||||
auto_push: true
|
auto_push: true
|
||||||
git_clone_url: "https://git.threesix.ai/jordan/{{ .outputs.create-project.project_id }}.git"
|
git_clone_url: "https://git.threesix.ai/threesix/{{ .outputs.create-project.project_id }}.git"
|
||||||
outputs:
|
outputs:
|
||||||
- build_id: .data.task_id
|
- build_id: .data.task_id
|
||||||
|
|
||||||
|
|||||||
@ -76,17 +76,9 @@ func (c *Client) CreateRepo(ctx context.Context, name, description string, priva
|
|||||||
DefaultBranch: "main",
|
DefaultBranch: "main",
|
||||||
}
|
}
|
||||||
|
|
||||||
var repo *gitea.Repository
|
repo, _, err := c.client.CreateOrgRepo(c.defaultOwner, opts)
|
||||||
var err error
|
|
||||||
|
|
||||||
// Try to create as org repo first, fall back to user repo
|
|
||||||
repo, _, err = c.client.CreateOrgRepo(c.defaultOwner, opts)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// May not be an org, try as user repo
|
return nil, fmt.Errorf("failed to create repo %s/%s: %w", c.defaultOwner, name, err)
|
||||||
repo, _, err = c.client.CreateRepo(opts)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to create repo: %w", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return repoFromGitea(repo), nil
|
return repoFromGitea(repo), nil
|
||||||
|
|||||||
@ -35,6 +35,8 @@ go.work.sum
|
|||||||
# Node
|
# Node
|
||||||
node_modules/
|
node_modules/
|
||||||
.npm/
|
.npm/
|
||||||
|
.pnpm-store/
|
||||||
|
.pnpm-store
|
||||||
pnpm-lock.yaml
|
pnpm-lock.yaml
|
||||||
|
|
||||||
# Shared packages
|
# Shared packages
|
||||||
|
|||||||
@ -208,7 +208,7 @@ func (g *GitOperations) CommitAndPush(ctx context.Context, workDir, message stri
|
|||||||
result.Error = fmt.Errorf("git diff: %w", err)
|
result.Error = fmt.Errorf("git diff: %w", err)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
for _, f := range strings.Split(strings.TrimSpace(diffOutput), "\n") {
|
for f := range strings.SplitSeq(strings.TrimSpace(diffOutput), "\n") {
|
||||||
if f != "" {
|
if f != "" {
|
||||||
result.FilesChanged = append(result.FilesChanged, f)
|
result.FilesChanged = append(result.FilesChanged, f)
|
||||||
}
|
}
|
||||||
@ -241,12 +241,12 @@ func (g *GitOperations) CommitAndPush(ctx context.Context, workDir, message stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Pull before push to handle concurrent builds that may have advanced the remote.
|
// Pull before push to handle concurrent builds that may have advanced the remote.
|
||||||
if err := g.runGit(ctx, workDir, "pull", "--rebase", "origin", "HEAD"); err != nil {
|
if err := g.runGit(ctx, workDir, "pull", "--rebase", "origin", "main"); err != nil {
|
||||||
g.logger.Warn("git pull --rebase before push failed, attempting push anyway",
|
g.logger.Warn("git pull --rebase before push failed, attempting push anyway",
|
||||||
"error", err, "work_dir", workDir)
|
"error", err, "work_dir", workDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := g.runGit(ctx, workDir, "push", "origin", "HEAD"); err != nil {
|
if err := g.runGit(ctx, workDir, "push", "origin", "HEAD:main"); err != nil {
|
||||||
result.Error = fmt.Errorf("git push: %w", err)
|
result.Error = fmt.Errorf("git push: %w", err)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
@ -299,8 +299,7 @@ func (g *GitOperations) Status(ctx context.Context, workDir string) (*GitStatusR
|
|||||||
return result, fmt.Errorf("git status: %w", err)
|
return result, fmt.Errorf("git status: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
lines := strings.Split(strings.TrimSpace(status), "\n")
|
for line := range strings.SplitSeq(strings.TrimSpace(status), "\n") {
|
||||||
for _, line := range lines {
|
|
||||||
if len(line) > 3 {
|
if len(line) > 3 {
|
||||||
result.ChangedFiles = append(result.ChangedFiles, strings.TrimSpace(line[3:]))
|
result.ChangedFiles = append(result.ChangedFiles, strings.TrimSpace(line[3:]))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -98,8 +98,8 @@ func (g *PodGitOperations) CloneRepo(ctx context.Context, podName, workDir, clon
|
|||||||
// Strip token from currentRemote for comparison, since clone stores the authenticated URL
|
// Strip token from currentRemote for comparison, since clone stores the authenticated URL
|
||||||
// Format: https://token:TOKEN@host/path -> https://host/path
|
// Format: https://token:TOKEN@host/path -> https://host/path
|
||||||
normalizedRemote := currentRemote
|
normalizedRemote := currentRemote
|
||||||
if idx := strings.Index(currentRemote, "@"); idx != -1 && strings.HasPrefix(currentRemote, "https://") {
|
if _, after, found := strings.Cut(currentRemote, "@"); found && strings.HasPrefix(currentRemote, "https://") {
|
||||||
normalizedRemote = "https://" + currentRemote[idx+1:]
|
normalizedRemote = "https://" + after
|
||||||
}
|
}
|
||||||
expectedURL := cloneURL
|
expectedURL := cloneURL
|
||||||
|
|
||||||
@ -265,7 +265,7 @@ func (g *PodGitOperations) CommitAndPush(ctx context.Context, podName, workDir,
|
|||||||
result.Error = fmt.Errorf("git diff: %w", err)
|
result.Error = fmt.Errorf("git diff: %w", err)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
for _, f := range strings.Split(strings.TrimSpace(diffOutput), "\n") {
|
for f := range strings.SplitSeq(strings.TrimSpace(diffOutput), "\n") {
|
||||||
if f != "" {
|
if f != "" {
|
||||||
result.FilesChanged = append(result.FilesChanged, f)
|
result.FilesChanged = append(result.FilesChanged, f)
|
||||||
}
|
}
|
||||||
@ -308,14 +308,14 @@ func (g *PodGitOperations) CommitAndPush(ctx context.Context, podName, workDir,
|
|||||||
|
|
||||||
// Pull before push to handle concurrent builds that may have advanced the remote.
|
// Pull before push to handle concurrent builds that may have advanced the remote.
|
||||||
// Use --rebase to replay our commit on top of any new remote commits.
|
// Use --rebase to replay our commit on top of any new remote commits.
|
||||||
if err := g.runGitInPod(ctx, podName, workDir, "pull", "--rebase", "origin", "HEAD"); err != nil {
|
if err := g.runGitInPod(ctx, podName, workDir, "pull", "--rebase", "origin", "main"); err != nil {
|
||||||
log.Warn("git pull --rebase before push failed, attempting push anyway",
|
log.Warn("git pull --rebase before push failed, attempting push anyway",
|
||||||
logging.FieldPodName, podName,
|
logging.FieldPodName, podName,
|
||||||
logging.FieldError, err,
|
logging.FieldError, err,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := g.runGitInPod(ctx, podName, workDir, "push", "origin", "HEAD"); err != nil {
|
if err := g.runGitInPod(ctx, podName, workDir, "push", "origin", "HEAD:main"); err != nil {
|
||||||
result.Error = fmt.Errorf("git push: %w", err)
|
result.Error = fmt.Errorf("git push: %w", err)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user