fix(architect): handle missing projects in repo, add cookbook hooks/validation
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
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>
This commit is contained in:
parent
c68fadbccd
commit
542bc722ab
@ -847,16 +847,34 @@ cmd_teardown() {
|
||||
|
||||
local action description
|
||||
action=$(echo "$step" | jq -r '.action // "unknown"')
|
||||
description=$(echo "$step" | jq -r '.description // "Teardown step $i"')
|
||||
description=$(echo "$step" | jq -r ".description // \"Teardown step $i\"")
|
||||
|
||||
echo -e "${CYAN}Teardown $i:${NC} $description"
|
||||
|
||||
local response=""
|
||||
case "$action" in
|
||||
api)
|
||||
execute_api_step "$step" > /dev/null && print_success "Done" || print_warning "Failed (continuing)"
|
||||
if response=$(execute_api_step "$step" 2>/dev/null); then
|
||||
# Check for error in response body
|
||||
local api_error
|
||||
api_error=$(echo "$response" | jq -r '.error // empty' 2>/dev/null)
|
||||
if [[ -n "$api_error" ]]; then
|
||||
print_warning "API error (continuing): $api_error"
|
||||
else
|
||||
local api_status
|
||||
api_status=$(echo "$response" | jq -r '.data.status // "ok"' 2>/dev/null)
|
||||
print_success "Done ($api_status)"
|
||||
fi
|
||||
else
|
||||
print_warning "Failed (continuing)"
|
||||
fi
|
||||
;;
|
||||
shell)
|
||||
execute_shell_step "$step" > /dev/null && print_success "Done" || print_warning "Failed (continuing)"
|
||||
if response=$(execute_shell_step "$step" 2>/dev/null); then
|
||||
print_success "Done"
|
||||
else
|
||||
print_warning "Failed (continuing)"
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
print_warning "Skipping unknown action: $action"
|
||||
|
||||
@ -36,9 +36,31 @@ steps:
|
||||
max_attempts: 720
|
||||
poll_interval: 5
|
||||
|
||||
setup-hooks:
|
||||
description: "Configure git hooks in project workspace"
|
||||
depends_on: [wait-bootstrap]
|
||||
action: api
|
||||
method: POST
|
||||
endpoint: "/projects/{{ .outputs.create-project.project_id }}/builds"
|
||||
body:
|
||||
prompt: "Run ./scripts/setup-hooks.sh to configure git hooks. Then verify with: git config core.hooksPath"
|
||||
auto_commit: false
|
||||
auto_push: false
|
||||
git_clone_url: "{{ .outputs.create-project.git_clone_http }}"
|
||||
outputs:
|
||||
- build_id: .data.task_id
|
||||
|
||||
wait-setup-hooks:
|
||||
description: "Wait for git hooks setup to complete"
|
||||
depends_on: [setup-hooks]
|
||||
action: wait_build
|
||||
build_id: "{{ .outputs.setup-hooks.build_id }}"
|
||||
max_attempts: 120
|
||||
poll_interval: 5
|
||||
|
||||
add-components:
|
||||
description: "Add React frontend, API service, and Postgres database"
|
||||
depends_on: [wait-bootstrap]
|
||||
depends_on: [wait-setup-hooks]
|
||||
action: api
|
||||
method: POST
|
||||
endpoint: "/projects/{{ .outputs.create-project.project_id }}/components/batch"
|
||||
@ -255,13 +277,68 @@ steps:
|
||||
project_id: "{{ .outputs.create-project.project_id }}"
|
||||
max_attempts: 720
|
||||
|
||||
# --- Commit QA artifacts and validate pre-merge hooks ---
|
||||
commit-after-qa:
|
||||
description: "Commit any remaining changes after QA"
|
||||
depends_on: [wait-deploy-polish]
|
||||
action: api
|
||||
method: POST
|
||||
endpoint: "/projects/{{ .outputs.create-project.project_id }}/builds"
|
||||
body:
|
||||
prompt: '/commit-all "chore: commit QA artifacts and fixes"'
|
||||
auto_commit: false
|
||||
auto_push: false
|
||||
git_clone_url: "{{ .outputs.create-project.git_clone_http }}"
|
||||
outputs:
|
||||
- build_id: .data.task_id
|
||||
|
||||
wait-commit-after-qa:
|
||||
description: "Wait for QA commit to complete"
|
||||
depends_on: [commit-after-qa]
|
||||
action: wait_build
|
||||
build_id: "{{ .outputs.commit-after-qa.build_id }}"
|
||||
max_attempts: 120
|
||||
poll_interval: 5
|
||||
|
||||
pre-merge-validate:
|
||||
description: "Run pre-commit hooks and fix any failures"
|
||||
depends_on: [wait-commit-after-qa]
|
||||
action: api
|
||||
method: POST
|
||||
endpoint: "/projects/{{ .outputs.create-project.project_id }}/builds"
|
||||
body:
|
||||
prompt: |
|
||||
Run ./.githooks/pre-commit to check code quality.
|
||||
If it fails, run /fix-all to fix all issues, then re-run
|
||||
./.githooks/pre-commit. Repeat until it passes.
|
||||
auto_commit: true
|
||||
auto_push: true
|
||||
git_clone_url: "{{ .outputs.create-project.git_clone_http }}"
|
||||
outputs:
|
||||
- build_id: .data.task_id
|
||||
|
||||
wait-pre-merge-validate:
|
||||
description: "Wait for pre-merge validation to complete"
|
||||
depends_on: [pre-merge-validate]
|
||||
action: wait_build
|
||||
build_id: "{{ .outputs.pre-merge-validate.build_id }}"
|
||||
max_attempts: 720
|
||||
poll_interval: 5
|
||||
|
||||
wait-deploy-final:
|
||||
description: "Wait for final deployment pipeline after validation fixes"
|
||||
depends_on: [wait-pre-merge-validate]
|
||||
action: wait_pipeline
|
||||
project_id: "{{ .outputs.create-project.project_id }}"
|
||||
max_attempts: 720
|
||||
|
||||
# ============================================================
|
||||
# SECTION 3: VERIFY
|
||||
# Confirm site is live and API responds
|
||||
# ============================================================
|
||||
verify-site-live:
|
||||
description: "Verify site is live after all builds"
|
||||
depends_on: [wait-deploy-polish]
|
||||
depends_on: [wait-deploy-final]
|
||||
action: wait_site
|
||||
domain: "{{ .outputs.create-project.domain }}"
|
||||
max_attempts: 120
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { Routes, Route, useLocation, useNavigate } from 'react-router-dom';
|
||||
import { DashboardShell, Sidebar, Header, type NavItem } from '@{{PROJECT_NAME}}/layout';
|
||||
import {
|
||||
Button,
|
||||
@ -14,13 +15,239 @@ import {
|
||||
} from '@{{PROJECT_NAME}}/ui';
|
||||
|
||||
const navItems: NavItem[] = [
|
||||
{ label: 'Dashboard', href: '/', icon: Home, active: true },
|
||||
{ label: 'Dashboard', href: '/', icon: Home },
|
||||
{ label: 'Analytics', href: '/analytics', icon: BarChart3 },
|
||||
{ label: 'Users', href: '/users', icon: Users, badge: '12' },
|
||||
{ label: 'Settings', href: '/settings', icon: Settings },
|
||||
];
|
||||
|
||||
const pageTitles: Record<string, string> = {
|
||||
'/': 'Dashboard',
|
||||
'/analytics': 'Analytics',
|
||||
'/users': 'Users',
|
||||
'/settings': 'Settings',
|
||||
};
|
||||
|
||||
function DashboardPage() {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Welcome to {{COMPONENT_NAME}}</CardTitle>
|
||||
<CardDescription>
|
||||
This is part of the{' '}
|
||||
<code className="bg-[var(--surface-200)] px-1.5 py-0.5 rounded text-sm">
|
||||
{{PROJECT_NAME}}
|
||||
</code>{' '}
|
||||
monorepo, using the shared UI library and layout components.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex gap-3">
|
||||
<Button>Get Started</Button>
|
||||
<Button variant="outline">Documentation</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
<CardDescription>Total Users</CardDescription>
|
||||
<CardTitle className="text-3xl">1,234</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Badge variant="success">+12% from last month</Badge>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
<CardDescription>Active Sessions</CardDescription>
|
||||
<CardTitle className="text-3xl">567</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Badge variant="info">Live</Badge>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
<CardDescription>API Requests</CardDescription>
|
||||
<CardTitle className="text-3xl">89.2k</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Badge variant="warning">High traffic</Badge>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<p className="text-sm text-[var(--text-muted)]">
|
||||
Edit this file at{' '}
|
||||
<code className="bg-[var(--surface-200)] px-1.5 py-0.5 rounded">
|
||||
apps/{{COMPONENT_NAME}}/src/App.tsx
|
||||
</code>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function UsersPage() {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold text-[var(--text-primary)]">All Users</h2>
|
||||
<p className="text-sm text-[var(--text-muted)]">Manage your team members and their roles.</p>
|
||||
</div>
|
||||
<Button>Add User</Button>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{[
|
||||
{ name: 'Alice Chen', role: 'Admin', status: 'Active' },
|
||||
{ name: 'Bob Martinez', role: 'Editor', status: 'Active' },
|
||||
{ name: 'Carol Singh', role: 'Viewer', status: 'Invited' },
|
||||
].map((user) => (
|
||||
<Card key={user.name}>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-base">{user.name}</CardTitle>
|
||||
<CardDescription>{user.role}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Badge variant={user.status === 'Active' ? 'success' : 'info'}>
|
||||
{user.status}
|
||||
</Badge>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function AnalyticsPage() {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
<CardDescription>Page Views</CardDescription>
|
||||
<CardTitle className="text-3xl">24.5k</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Badge variant="success">+8% this week</Badge>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
<CardDescription>Bounce Rate</CardDescription>
|
||||
<CardTitle className="text-3xl">32%</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Badge variant="success">-3% improvement</Badge>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
<CardDescription>Avg. Session</CardDescription>
|
||||
<CardTitle className="text-3xl">4m 12s</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Badge variant="info">Stable</Badge>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Traffic Sources</CardTitle>
|
||||
<CardDescription>Where your visitors are coming from this month.</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-3">
|
||||
{[
|
||||
{ source: 'Direct', visits: '8,421', pct: 34 },
|
||||
{ source: 'Search', visits: '6,312', pct: 26 },
|
||||
{ source: 'Social', visits: '5,105', pct: 21 },
|
||||
{ source: 'Referral', visits: '4,662', pct: 19 },
|
||||
].map((row) => (
|
||||
<div key={row.source} className="flex items-center justify-between">
|
||||
<span className="text-sm font-medium text-[var(--text-primary)]">{row.source}</span>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-32 h-2 rounded-full bg-[var(--surface-200)] overflow-hidden">
|
||||
<div
|
||||
className="h-full rounded-full bg-[var(--accent)]"
|
||||
style={{ width: `${row.pct}%` }}
|
||||
/>
|
||||
</div>
|
||||
<span className="text-sm text-[var(--text-muted)] w-16 text-right">{row.visits}</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function SettingsPage() {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>General</CardTitle>
|
||||
<CardDescription>Manage your application settings and preferences.</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-4">
|
||||
{[
|
||||
{ label: 'Application Name', value: '{{PROJECT_NAME}}' },
|
||||
{ label: 'Environment', value: 'Production' },
|
||||
{ label: 'Region', value: 'US West' },
|
||||
].map((setting) => (
|
||||
<div key={setting.label} className="flex items-center justify-between py-2 border-b border-[var(--border-muted)] last:border-0">
|
||||
<span className="text-sm font-medium text-[var(--text-primary)]">{setting.label}</span>
|
||||
<Badge variant="outline">{setting.value}</Badge>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Danger Zone</CardTitle>
|
||||
<CardDescription>Irreversible actions for your application.</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-[var(--text-primary)]">Delete Application</p>
|
||||
<p className="text-sm text-[var(--text-muted)]">Permanently remove this application and all its data.</p>
|
||||
</div>
|
||||
<Button variant="destructive">Delete</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function App() {
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const itemsWithActive = navItems.map((item) => ({
|
||||
...item,
|
||||
active: location.pathname === item.href,
|
||||
}));
|
||||
|
||||
const pageTitle = pageTitles[location.pathname] || 'Dashboard';
|
||||
|
||||
return (
|
||||
<DashboardShell
|
||||
sidebar={
|
||||
@ -28,7 +255,8 @@ function App() {
|
||||
logo={
|
||||
<span className="font-semibold text-lg">{{PROJECT_NAME}}</span>
|
||||
}
|
||||
items={navItems}
|
||||
items={itemsWithActive}
|
||||
onNavigate={(href) => navigate(href)}
|
||||
footer={
|
||||
<div className="text-sm text-[var(--text-muted)]">
|
||||
v0.0.1
|
||||
@ -38,74 +266,18 @@ function App() {
|
||||
}
|
||||
header={
|
||||
<Header
|
||||
title="Dashboard"
|
||||
title={pageTitle}
|
||||
showSearch
|
||||
searchPlaceholder="Search..."
|
||||
/>
|
||||
}
|
||||
>
|
||||
<div className="space-y-6">
|
||||
{/* Welcome card */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Welcome to {{COMPONENT_NAME}}</CardTitle>
|
||||
<CardDescription>
|
||||
This is part of the{' '}
|
||||
<code className="bg-[var(--surface-200)] px-1.5 py-0.5 rounded text-sm">
|
||||
{{PROJECT_NAME}}
|
||||
</code>{' '}
|
||||
monorepo, using the shared UI library and layout components.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex gap-3">
|
||||
<Button>Get Started</Button>
|
||||
<Button variant="outline">Documentation</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Stats cards */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
<CardDescription>Total Users</CardDescription>
|
||||
<CardTitle className="text-3xl">1,234</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Badge variant="success">+12% from last month</Badge>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
<CardDescription>Active Sessions</CardDescription>
|
||||
<CardTitle className="text-3xl">567</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Badge variant="info">Live</Badge>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
<CardDescription>API Requests</CardDescription>
|
||||
<CardTitle className="text-3xl">89.2k</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Badge variant="warning">High traffic</Badge>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Edit hint */}
|
||||
<p className="text-sm text-[var(--text-muted)]">
|
||||
Edit this file at{' '}
|
||||
<code className="bg-[var(--surface-200)] px-1.5 py-0.5 rounded">
|
||||
apps/{{COMPONENT_NAME}}/src/App.tsx
|
||||
</code>
|
||||
</p>
|
||||
</div>
|
||||
<Routes>
|
||||
<Route path="/" element={<DashboardPage />} />
|
||||
<Route path="/users" element={<UsersPage />} />
|
||||
<Route path="/analytics" element={<AnalyticsPage />} />
|
||||
<Route path="/settings" element={<SettingsPage />} />
|
||||
</Routes>
|
||||
</DashboardShell>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,11 +1,14 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import App from './App.tsx';
|
||||
import './index.css';
|
||||
import './lib/logger';
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
<BrowserRouter>
|
||||
<App />
|
||||
</BrowserRouter>
|
||||
</React.StrictMode>
|
||||
);
|
||||
|
||||
@ -7,6 +7,7 @@ import (
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/orchard9/rdev/internal/auth"
|
||||
"github.com/orchard9/rdev/internal/domain"
|
||||
"github.com/orchard9/rdev/internal/logging"
|
||||
"github.com/orchard9/rdev/internal/service"
|
||||
"github.com/orchard9/rdev/internal/validate"
|
||||
"github.com/orchard9/rdev/pkg/api"
|
||||
@ -62,6 +63,8 @@ func (h *ArchitectHandler) StartConversation(w http.ResponseWriter, r *http.Requ
|
||||
|
||||
conv, err := h.architectService.StartConversation(r.Context(), projectID, req.Prompt)
|
||||
if err != nil {
|
||||
log := logging.FromContext(r.Context()).WithHandler("StartConversation")
|
||||
log.Error("failed to start conversation", logging.FieldError, err, logging.FieldProjectID, projectID)
|
||||
api.WriteInternalError(w, r, "failed to start conversation")
|
||||
return
|
||||
}
|
||||
@ -100,6 +103,8 @@ func (h *ArchitectHandler) ContinueConversation(w http.ResponseWriter, r *http.R
|
||||
api.WriteNotFound(w, r, "conversation not found")
|
||||
return
|
||||
}
|
||||
log := logging.FromContext(r.Context()).WithHandler("ContinueConversation")
|
||||
log.Error("failed to continue conversation", logging.FieldError, err, "conversation_id", conversationID)
|
||||
api.WriteInternalError(w, r, "failed to continue conversation")
|
||||
return
|
||||
}
|
||||
@ -138,6 +143,8 @@ func (h *ArchitectHandler) GenerateBlueprint(w http.ResponseWriter, r *http.Requ
|
||||
api.WriteNotFound(w, r, "conversation not found")
|
||||
return
|
||||
}
|
||||
log := logging.FromContext(r.Context()).WithHandler("GenerateBlueprint")
|
||||
log.Error("failed to generate blueprint", logging.FieldError, err, "conversation_id", conversationID)
|
||||
api.WriteInternalError(w, r, "failed to generate blueprint")
|
||||
return
|
||||
}
|
||||
|
||||
@ -150,9 +150,20 @@ func (s *ArchitectService) askArchitect(ctx context.Context, projectID string, c
|
||||
return "", fmt.Errorf("no agent available")
|
||||
}
|
||||
|
||||
// Resolve project pod name. The in-memory project repo may not have the
|
||||
// project (it discovers pods in the rdev namespace, but projects deploy to
|
||||
// the projects namespace). Fall back to defaults when not found.
|
||||
podName := s.defaultPodName
|
||||
project, err := s.projectRepo.Get(ctx, domain.ProjectID(projectID))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("resolve project: %w", err)
|
||||
log := logging.FromContext(ctx)
|
||||
log.Warn("project not found in repo, using default pod",
|
||||
logging.FieldProjectID, projectID,
|
||||
"default_pod", podName,
|
||||
logging.FieldError, err,
|
||||
)
|
||||
} else if project.PodName != "" {
|
||||
podName = project.PodName
|
||||
}
|
||||
|
||||
// Prepare architect-specific system prompt
|
||||
@ -175,15 +186,9 @@ Current conversation context:`
|
||||
|
||||
fullPrompt := systemPrompt + "\n\n" + prompt
|
||||
|
||||
// Resolve pod: use project's pod if set, otherwise fall back to default.
|
||||
podName := project.PodName
|
||||
if podName == "" {
|
||||
podName = s.defaultPodName
|
||||
}
|
||||
|
||||
agentReq := &domain.AgentRequest{
|
||||
Prompt: fullPrompt,
|
||||
ProjectID: project.ID,
|
||||
ProjectID: domain.ProjectID(projectID),
|
||||
Timeout: 2 * time.Minute,
|
||||
Metadata: map[string]string{
|
||||
"conversation_id": string(conversationID),
|
||||
@ -224,9 +229,17 @@ func (s *ArchitectService) extractSpecFromMessages(ctx context.Context, projectI
|
||||
return nil, fmt.Errorf("no agent available")
|
||||
}
|
||||
|
||||
// Resolve project pod name with fallback (same as askArchitect).
|
||||
podName := s.defaultPodName
|
||||
project, err := s.projectRepo.Get(ctx, domain.ProjectID(projectID))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("resolve project: %w", err)
|
||||
log := logging.FromContext(ctx)
|
||||
log.Warn("project not found in repo for spec extraction, using default pod",
|
||||
logging.FieldProjectID, projectID,
|
||||
logging.FieldError, err,
|
||||
)
|
||||
} else if project.PodName != "" {
|
||||
podName = project.PodName
|
||||
}
|
||||
|
||||
// Build conversation transcript
|
||||
@ -267,15 +280,9 @@ Extract and return ONLY a valid JSON object with this structure:
|
||||
|
||||
Return ONLY the JSON, no other text.`, transcript)
|
||||
|
||||
// Resolve pod: use project's pod if set, otherwise fall back to default.
|
||||
podName := project.PodName
|
||||
if podName == "" {
|
||||
podName = s.defaultPodName
|
||||
}
|
||||
|
||||
agentReq := &domain.AgentRequest{
|
||||
Prompt: extractionPrompt,
|
||||
ProjectID: project.ID,
|
||||
ProjectID: domain.ProjectID(projectID),
|
||||
Timeout: 2 * time.Minute,
|
||||
Metadata: map[string]string{
|
||||
"purpose": "spec-extraction",
|
||||
|
||||
@ -691,8 +691,9 @@ func (s *ProjectInfraService) DeleteProject(ctx context.Context, projectID strin
|
||||
return err
|
||||
}
|
||||
|
||||
// 1. Undeploy if deployed
|
||||
if s.deployer != nil && status.DeploymentStatus != "none" {
|
||||
// 1. Undeploy all K8s resources (always attempt — component deployments
|
||||
// may exist even when the main deployment status is "none")
|
||||
if s.deployer != nil {
|
||||
if err := s.deployer.UndeployAll(ctx, projectID); err != nil {
|
||||
log.Warn("failed to undeploy", logging.FieldError, err)
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user