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
|
local action description
|
||||||
action=$(echo "$step" | jq -r '.action // "unknown"')
|
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"
|
echo -e "${CYAN}Teardown $i:${NC} $description"
|
||||||
|
|
||||||
|
local response=""
|
||||||
case "$action" in
|
case "$action" in
|
||||||
api)
|
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)
|
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"
|
print_warning "Skipping unknown action: $action"
|
||||||
|
|||||||
@ -36,9 +36,31 @@ steps:
|
|||||||
max_attempts: 720
|
max_attempts: 720
|
||||||
poll_interval: 5
|
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:
|
add-components:
|
||||||
description: "Add React frontend, API service, and Postgres database"
|
description: "Add React frontend, API service, and Postgres database"
|
||||||
depends_on: [wait-bootstrap]
|
depends_on: [wait-setup-hooks]
|
||||||
action: api
|
action: api
|
||||||
method: POST
|
method: POST
|
||||||
endpoint: "/projects/{{ .outputs.create-project.project_id }}/components/batch"
|
endpoint: "/projects/{{ .outputs.create-project.project_id }}/components/batch"
|
||||||
@ -255,13 +277,68 @@ steps:
|
|||||||
project_id: "{{ .outputs.create-project.project_id }}"
|
project_id: "{{ .outputs.create-project.project_id }}"
|
||||||
max_attempts: 720
|
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
|
# SECTION 3: VERIFY
|
||||||
# Confirm site is live and API responds
|
# Confirm site is live and API responds
|
||||||
# ============================================================
|
# ============================================================
|
||||||
verify-site-live:
|
verify-site-live:
|
||||||
description: "Verify site is live after all builds"
|
description: "Verify site is live after all builds"
|
||||||
depends_on: [wait-deploy-polish]
|
depends_on: [wait-deploy-final]
|
||||||
action: wait_site
|
action: wait_site
|
||||||
domain: "{{ .outputs.create-project.domain }}"
|
domain: "{{ .outputs.create-project.domain }}"
|
||||||
max_attempts: 120
|
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 { DashboardShell, Sidebar, Header, type NavItem } from '@{{PROJECT_NAME}}/layout';
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
@ -14,13 +15,239 @@ import {
|
|||||||
} from '@{{PROJECT_NAME}}/ui';
|
} from '@{{PROJECT_NAME}}/ui';
|
||||||
|
|
||||||
const navItems: NavItem[] = [
|
const navItems: NavItem[] = [
|
||||||
{ label: 'Dashboard', href: '/', icon: Home, active: true },
|
{ label: 'Dashboard', href: '/', icon: Home },
|
||||||
{ label: 'Analytics', href: '/analytics', icon: BarChart3 },
|
{ label: 'Analytics', href: '/analytics', icon: BarChart3 },
|
||||||
{ label: 'Users', href: '/users', icon: Users, badge: '12' },
|
{ label: 'Users', href: '/users', icon: Users, badge: '12' },
|
||||||
{ label: 'Settings', href: '/settings', icon: Settings },
|
{ 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() {
|
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 (
|
return (
|
||||||
<DashboardShell
|
<DashboardShell
|
||||||
sidebar={
|
sidebar={
|
||||||
@ -28,7 +255,8 @@ function App() {
|
|||||||
logo={
|
logo={
|
||||||
<span className="font-semibold text-lg">{{PROJECT_NAME}}</span>
|
<span className="font-semibold text-lg">{{PROJECT_NAME}}</span>
|
||||||
}
|
}
|
||||||
items={navItems}
|
items={itemsWithActive}
|
||||||
|
onNavigate={(href) => navigate(href)}
|
||||||
footer={
|
footer={
|
||||||
<div className="text-sm text-[var(--text-muted)]">
|
<div className="text-sm text-[var(--text-muted)]">
|
||||||
v0.0.1
|
v0.0.1
|
||||||
@ -38,74 +266,18 @@ function App() {
|
|||||||
}
|
}
|
||||||
header={
|
header={
|
||||||
<Header
|
<Header
|
||||||
title="Dashboard"
|
title={pageTitle}
|
||||||
showSearch
|
showSearch
|
||||||
searchPlaceholder="Search..."
|
searchPlaceholder="Search..."
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div className="space-y-6">
|
<Routes>
|
||||||
{/* Welcome card */}
|
<Route path="/" element={<DashboardPage />} />
|
||||||
<Card>
|
<Route path="/users" element={<UsersPage />} />
|
||||||
<CardHeader>
|
<Route path="/analytics" element={<AnalyticsPage />} />
|
||||||
<CardTitle>Welcome to {{COMPONENT_NAME}}</CardTitle>
|
<Route path="/settings" element={<SettingsPage />} />
|
||||||
<CardDescription>
|
</Routes>
|
||||||
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>
|
|
||||||
</DashboardShell>
|
</DashboardShell>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,14 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom/client';
|
import ReactDOM from 'react-dom/client';
|
||||||
|
import { BrowserRouter } from 'react-router-dom';
|
||||||
import App from './App.tsx';
|
import App from './App.tsx';
|
||||||
import './index.css';
|
import './index.css';
|
||||||
import './lib/logger';
|
import './lib/logger';
|
||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById('root')!).render(
|
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<App />
|
<BrowserRouter>
|
||||||
|
<App />
|
||||||
|
</BrowserRouter>
|
||||||
</React.StrictMode>
|
</React.StrictMode>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import (
|
|||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
"github.com/orchard9/rdev/internal/auth"
|
"github.com/orchard9/rdev/internal/auth"
|
||||||
"github.com/orchard9/rdev/internal/domain"
|
"github.com/orchard9/rdev/internal/domain"
|
||||||
|
"github.com/orchard9/rdev/internal/logging"
|
||||||
"github.com/orchard9/rdev/internal/service"
|
"github.com/orchard9/rdev/internal/service"
|
||||||
"github.com/orchard9/rdev/internal/validate"
|
"github.com/orchard9/rdev/internal/validate"
|
||||||
"github.com/orchard9/rdev/pkg/api"
|
"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)
|
conv, err := h.architectService.StartConversation(r.Context(), projectID, req.Prompt)
|
||||||
if err != nil {
|
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")
|
api.WriteInternalError(w, r, "failed to start conversation")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -100,6 +103,8 @@ func (h *ArchitectHandler) ContinueConversation(w http.ResponseWriter, r *http.R
|
|||||||
api.WriteNotFound(w, r, "conversation not found")
|
api.WriteNotFound(w, r, "conversation not found")
|
||||||
return
|
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")
|
api.WriteInternalError(w, r, "failed to continue conversation")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -138,6 +143,8 @@ func (h *ArchitectHandler) GenerateBlueprint(w http.ResponseWriter, r *http.Requ
|
|||||||
api.WriteNotFound(w, r, "conversation not found")
|
api.WriteNotFound(w, r, "conversation not found")
|
||||||
return
|
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")
|
api.WriteInternalError(w, r, "failed to generate blueprint")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@ -150,9 +150,20 @@ func (s *ArchitectService) askArchitect(ctx context.Context, projectID string, c
|
|||||||
return "", fmt.Errorf("no agent available")
|
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))
|
project, err := s.projectRepo.Get(ctx, domain.ProjectID(projectID))
|
||||||
if err != nil {
|
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
|
// Prepare architect-specific system prompt
|
||||||
@ -175,15 +186,9 @@ Current conversation context:`
|
|||||||
|
|
||||||
fullPrompt := systemPrompt + "\n\n" + prompt
|
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{
|
agentReq := &domain.AgentRequest{
|
||||||
Prompt: fullPrompt,
|
Prompt: fullPrompt,
|
||||||
ProjectID: project.ID,
|
ProjectID: domain.ProjectID(projectID),
|
||||||
Timeout: 2 * time.Minute,
|
Timeout: 2 * time.Minute,
|
||||||
Metadata: map[string]string{
|
Metadata: map[string]string{
|
||||||
"conversation_id": string(conversationID),
|
"conversation_id": string(conversationID),
|
||||||
@ -224,9 +229,17 @@ func (s *ArchitectService) extractSpecFromMessages(ctx context.Context, projectI
|
|||||||
return nil, fmt.Errorf("no agent available")
|
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))
|
project, err := s.projectRepo.Get(ctx, domain.ProjectID(projectID))
|
||||||
if err != nil {
|
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
|
// 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)
|
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{
|
agentReq := &domain.AgentRequest{
|
||||||
Prompt: extractionPrompt,
|
Prompt: extractionPrompt,
|
||||||
ProjectID: project.ID,
|
ProjectID: domain.ProjectID(projectID),
|
||||||
Timeout: 2 * time.Minute,
|
Timeout: 2 * time.Minute,
|
||||||
Metadata: map[string]string{
|
Metadata: map[string]string{
|
||||||
"purpose": "spec-extraction",
|
"purpose": "spec-extraction",
|
||||||
|
|||||||
@ -691,8 +691,9 @@ func (s *ProjectInfraService) DeleteProject(ctx context.Context, projectID strin
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1. Undeploy if deployed
|
// 1. Undeploy all K8s resources (always attempt — component deployments
|
||||||
if s.deployer != nil && status.DeploymentStatus != "none" {
|
// may exist even when the main deployment status is "none")
|
||||||
|
if s.deployer != nil {
|
||||||
if err := s.deployer.UndeployAll(ctx, projectID); err != nil {
|
if err := s.deployer.UndeployAll(ctx, projectID); err != nil {
|
||||||
log.Warn("failed to undeploy", logging.FieldError, err)
|
log.Warn("failed to undeploy", logging.FieldError, err)
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user