fix(architect): add pod_name to agent requests, rewrite foundary cookbook
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
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>
This commit is contained in:
parent
a9ad3d8304
commit
c68fadbccd
@ -70,6 +70,7 @@ When discussing code: "add to **platform**" = edit rdev; "add to **skeleton**" =
|
||||
- **Migrations:** NEVER modify committed migrations. Create NEW ones.
|
||||
- **500-line limit:** Files exceeding 500 lines must be split
|
||||
- **Tests:** All handlers and services require tests
|
||||
- **No fallbacks:** NEVER design "try X, fall back to Y" flows — fix X. Fallbacks hide errors and deliver inferior experiences.
|
||||
- **Multi-step ops:** NEVER log-and-continue after partial failure. Rollback or document partial state.
|
||||
- **Logging:** Use `logging.FromContext(ctx)` or injected `*slog.Logger`. NEVER `fmt.Println`, `log.Fatal`, `log.Printf`, or bare `slog.Info()`. Error key is ALWAYS `"error"` (not `"err"`). Use field constants from `internal/logging/fields.go` (e.g., `logging.FieldProjectID`, `logging.FieldError`). Log once at boundary (handlers/workers log, services return errors). Sensitive data (passwords, tokens, keys) is auto-redacted.
|
||||
- **HTTP clients:** NEVER create `&http.Client{}` without a `Timeout` field. All HTTP clients must have explicit timeouts (30s standard, 5s for health checks). A bare client can hang indefinitely.
|
||||
|
||||
@ -276,6 +276,7 @@ func main() {
|
||||
blueprintService,
|
||||
agentRegistry,
|
||||
projectRepo,
|
||||
nil, // uses defaults: claudebox-0, rdev namespace
|
||||
)
|
||||
|
||||
// Create question service (for Foundary structured questions)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -12,12 +12,20 @@ import (
|
||||
"github.com/orchard9/rdev/internal/port"
|
||||
)
|
||||
|
||||
// ArchitectServiceConfig holds configuration for the architect service.
|
||||
type ArchitectServiceConfig struct {
|
||||
DefaultPodName string // Default pod for agent execution (e.g., "claudebox-0")
|
||||
Namespace string // Kubernetes namespace (e.g., "rdev")
|
||||
}
|
||||
|
||||
// ArchitectService orchestrates conversational project design with Claude.
|
||||
type ArchitectService struct {
|
||||
conversationService *ConversationService
|
||||
blueprintService *BlueprintService
|
||||
agentRegistry port.CodeAgentRegistry
|
||||
projectRepo port.ProjectRepository
|
||||
defaultPodName string
|
||||
namespace string
|
||||
}
|
||||
|
||||
// NewArchitectService creates a new architect service.
|
||||
@ -26,12 +34,21 @@ func NewArchitectService(
|
||||
blueprintService *BlueprintService,
|
||||
agentRegistry port.CodeAgentRegistry,
|
||||
projectRepo port.ProjectRepository,
|
||||
cfg *ArchitectServiceConfig,
|
||||
) *ArchitectService {
|
||||
if cfg == nil {
|
||||
cfg = &ArchitectServiceConfig{
|
||||
DefaultPodName: "claudebox-0",
|
||||
Namespace: "rdev",
|
||||
}
|
||||
}
|
||||
return &ArchitectService{
|
||||
conversationService: conversationService,
|
||||
blueprintService: blueprintService,
|
||||
agentRegistry: agentRegistry,
|
||||
projectRepo: projectRepo,
|
||||
defaultPodName: cfg.DefaultPodName,
|
||||
namespace: cfg.Namespace,
|
||||
}
|
||||
}
|
||||
|
||||
@ -158,6 +175,12 @@ 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,
|
||||
@ -165,6 +188,8 @@ Current conversation context:`
|
||||
Metadata: map[string]string{
|
||||
"conversation_id": string(conversationID),
|
||||
"purpose": "architect",
|
||||
"pod_name": podName,
|
||||
"namespace": s.namespace,
|
||||
},
|
||||
}
|
||||
|
||||
@ -242,12 +267,20 @@ 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,
|
||||
Timeout: 2 * time.Minute,
|
||||
Metadata: map[string]string{
|
||||
"purpose": "spec-extraction",
|
||||
"pod_name": podName,
|
||||
"namespace": s.namespace,
|
||||
},
|
||||
}
|
||||
|
||||
@ -283,20 +316,7 @@ Return ONLY the JSON, no other text.`, transcript)
|
||||
|
||||
var spec map[string]any
|
||||
if err := json.Unmarshal([]byte(response), &spec); err != nil {
|
||||
// Fallback: create basic spec if parsing fails
|
||||
log := logging.FromContext(ctx)
|
||||
log.Warn("failed to parse agent spec extraction, using fallback",
|
||||
logging.FieldError, err,
|
||||
logging.FieldOperation, "extract_blueprint_spec",
|
||||
"response_length", len(response),
|
||||
"message_count", len(messages),
|
||||
)
|
||||
spec = map[string]any{
|
||||
"version": "1.0",
|
||||
"generated_at": time.Now().Format(time.RFC3339),
|
||||
"message_count": len(messages),
|
||||
"extraction_failed": true,
|
||||
}
|
||||
return nil, fmt.Errorf("parse spec extraction response: %w", err)
|
||||
}
|
||||
|
||||
return spec, nil
|
||||
|
||||
Loading…
Reference in New Issue
Block a user