diff --git a/CLAUDE.md b/CLAUDE.md index 1eedaf5..bd271f7 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -35,6 +35,7 @@ Run Claude Code instances in isolated Kubernetes pods with REST API control. Ena | **DNS / Cloudflare** | [services/dns-cloudflare.md](.claude/guides/services/dns-cloudflare.md) | | **Network policies / internal routing** | [ops/networking.md](.claude/guides/ops/networking.md) | | **SDLC orchestration** | [services/sdlc.md](.claude/guides/services/sdlc.md) | +| **Visual verification (Playwright)** | [services/visual-verification.md](.claude/guides/services/visual-verification.md) | ## Critical Rules @@ -171,6 +172,7 @@ cookbooks/ # End-to-end workflow guides | Build Orchestration | Planned | Structured build specs via API | | SDLC Orchestration | **Done** | Deterministic feature lifecycle with classifier engine, API, orchestrator, and 15 skeleton commands | | Composable Monorepo Templates | **Done** | Monorepo skeleton + component templates (service, worker, app-astro, app-react, cli) | +| Visual Verification | Planned | Playwright screenshots/video + AI evaluation for feature completeness | **Current Version:** v0.10.25 diff --git a/cmd/sdlc/cmd_artifact.go b/cmd/sdlc/cmd_artifact.go index 6376f79..3d9e8c4 100644 --- a/cmd/sdlc/cmd_artifact.go +++ b/cmd/sdlc/cmd_artifact.go @@ -151,6 +151,147 @@ var artifactRejectCmd = &cobra.Command{ }, } +var artifactPassCmd = &cobra.Command{ + Use: "pass ", + Short: "Mark an artifact as passed (for execution artifacts: review, audit, qa_results)", + Args: cobra.ExactArgs(2), + RunE: func(_ *cobra.Command, args []string) error { + root := mustResolveRoot() + slug, artTypeStr := args[0], args[1] + artType := sdlc.ArtifactType(artTypeStr) + + f, err := sdlc.LoadFeature(root, slug) + if err != nil { + return err + } + + art := f.GetArtifact(artType) + if art == nil { + return sdlc.ErrArtifactNotFound + } + + art.MarkPassed() + if err := f.Save(root); err != nil { + return err + } + + // Record in state + state, err := sdlc.LoadState(root) + if err != nil { + return err + } + state.RecordAction("PASS_ARTIFACT", slug, "user", "success") + if err := state.Save(root); err != nil { + return err + } + + if jsonOutput { + return printJSON(map[string]string{ + "feature": slug, + "artifact": string(artType), + "status": "passed", + }) + } + + fmt.Printf("Passed: %s/%s\n", slug, artType) + return nil + }, +} + +var artifactFailCmd = &cobra.Command{ + Use: "fail ", + Short: "Mark an artifact as failed (for execution artifacts: review, audit, qa_results)", + Args: cobra.ExactArgs(2), + RunE: func(_ *cobra.Command, args []string) error { + root := mustResolveRoot() + slug, artTypeStr := args[0], args[1] + artType := sdlc.ArtifactType(artTypeStr) + + f, err := sdlc.LoadFeature(root, slug) + if err != nil { + return err + } + + art := f.GetArtifact(artType) + if art == nil { + return sdlc.ErrArtifactNotFound + } + + art.MarkFailed() + if err := f.Save(root); err != nil { + return err + } + + // Record in state + state, err := sdlc.LoadState(root) + if err != nil { + return err + } + state.RecordAction("FAIL_ARTIFACT", slug, "user", "success") + if err := state.Save(root); err != nil { + return err + } + + if jsonOutput { + return printJSON(map[string]string{ + "feature": slug, + "artifact": string(artType), + "status": "failed", + }) + } + + fmt.Printf("Failed: %s/%s\n", slug, artType) + return nil + }, +} + +var artifactNeedsFixCmd = &cobra.Command{ + Use: "needs-fix ", + Short: "Mark an artifact as needing fixes (for execution artifacts: review, audit)", + Args: cobra.ExactArgs(2), + RunE: func(_ *cobra.Command, args []string) error { + root := mustResolveRoot() + slug, artTypeStr := args[0], args[1] + artType := sdlc.ArtifactType(artTypeStr) + + f, err := sdlc.LoadFeature(root, slug) + if err != nil { + return err + } + + art := f.GetArtifact(artType) + if art == nil { + return sdlc.ErrArtifactNotFound + } + + art.MarkNeedsFix() + if err := f.Save(root); err != nil { + return err + } + + // Record in state + state, err := sdlc.LoadState(root) + if err != nil { + return err + } + state.RecordAction("NEEDS_FIX_ARTIFACT", slug, "user", "success") + if err := state.Save(root); err != nil { + return err + } + + if jsonOutput { + return printJSON(map[string]string{ + "feature": slug, + "artifact": string(artType), + "status": "needs_fix", + }) + } + + fmt.Printf("Needs fix: %s/%s\n", slug, artType) + return nil + }, +} + var artifactStatusCmd = &cobra.Command{ Use: "status ", Short: "Show all artifact statuses for a feature", @@ -185,6 +326,9 @@ func init() { artifactCreateCmd, artifactApproveCmd, artifactRejectCmd, + artifactPassCmd, + artifactFailCmd, + artifactNeedsFixCmd, artifactStatusCmd, ) rootCmd.AddCommand(artifactCmd) diff --git a/internal/adapter/kubernetes/sdlc_executor.go b/internal/adapter/kubernetes/sdlc_executor.go index 8b3d3cd..c7c12e8 100644 --- a/internal/adapter/kubernetes/sdlc_executor.go +++ b/internal/adapter/kubernetes/sdlc_executor.go @@ -210,6 +210,21 @@ func (e *SDLCExecutor) RejectArtifact(ctx context.Context, podName, slug string, return e.execSDLCNoOutput(ctx, podName, "artifact", "reject", slug, string(artType)) } +// PassArtifact marks a feature artifact as passed. +func (e *SDLCExecutor) PassArtifact(ctx context.Context, podName, slug string, artType sdlc.ArtifactType) error { + return e.execSDLCNoOutput(ctx, podName, "artifact", "pass", slug, string(artType)) +} + +// FailArtifact marks a feature artifact as failed. +func (e *SDLCExecutor) FailArtifact(ctx context.Context, podName, slug string, artType sdlc.ArtifactType) error { + return e.execSDLCNoOutput(ctx, podName, "artifact", "fail", slug, string(artType)) +} + +// NeedsFixArtifact marks a feature artifact as needing fixes. +func (e *SDLCExecutor) NeedsFixArtifact(ctx context.Context, podName, slug string, artType sdlc.ArtifactType) error { + return e.execSDLCNoOutput(ctx, podName, "artifact", "needs-fix", slug, string(artType)) +} + // ListTasks returns all tasks for a feature. func (e *SDLCExecutor) ListTasks(ctx context.Context, podName, slug string) ([]sdlc.Task, error) { out, err := e.execSDLC(ctx, podName, "task", "list", slug) diff --git a/internal/adapter/templates/templates/skeleton/.claude/commands/audit-feature.md b/internal/adapter/templates/templates/skeleton/.claude/commands/audit-feature.md index c20bef3..4a87402 100644 --- a/internal/adapter/templates/templates/skeleton/.claude/commands/audit-feature.md +++ b/internal/adapter/templates/templates/skeleton/.claude/commands/audit-feature.md @@ -88,16 +88,32 @@ Write to `.sdlc/features/$ARGUMENTS/audit.md`: [Ordered list of actions to take] ``` -### 7. Register the Artifact +### 7. Register and Evaluate the Artifact + +Create the artifact: ```bash sdlc artifact create $ARGUMENTS audit ``` +Then evaluate the audit results and set the appropriate status: + +- If the audit has **no critical or high findings**: mark as passed + ```bash + sdlc artifact pass $ARGUMENTS audit + ``` +- If the audit has **critical or high findings**: mark as needs-fix + ```bash + sdlc artifact needs-fix $ARGUMENTS audit + ``` + +This status drives the SDLC classifier to either advance to QA or trigger remediate-audit. + ## Critical Rules - NEVER skip OWASP checks -- even if the feature seems low-risk - ALWAYS check for hardcoded secrets, tokens, and credentials - ALWAYS verify authentication and authorization boundaries -- NEVER pass an audit with critical or high severity findings unresolved +- NEVER mark an audit as passed if it has unresolved critical or high findings - ALWAYS run static analysis tools before manual review +- ALWAYS set the artifact status (pass or needs-fix) after writing the audit diff --git a/internal/adapter/templates/templates/skeleton/.claude/commands/fix-qa-failures.md b/internal/adapter/templates/templates/skeleton/.claude/commands/fix-qa-failures.md index b71d478..6aa125c 100644 --- a/internal/adapter/templates/templates/skeleton/.claude/commands/fix-qa-failures.md +++ b/internal/adapter/templates/templates/skeleton/.claude/commands/fix-qa-failures.md @@ -55,9 +55,22 @@ Update `.sdlc/features/$ARGUMENTS/qa-results.md`: - Add a remediation note explaining what was fixed - Verify the overall status (PASS only if all scenarios pass) -### 8. Report +### 8. Update Artifact Status -Summarize: failures fixed, root causes, regression status (all previously passing tests still pass). +After all fixes are applied and tests re-run: + +- If **all scenarios now pass**: mark as passed + ```bash + sdlc artifact pass $ARGUMENTS qa_results + ``` +- If **failures remain**: keep as failed + ```bash + sdlc artifact fail $ARGUMENTS qa_results + ``` + +### 9. Report + +Summarize: failures fixed, root causes, regression status (all previously passing tests still pass), and artifact status. ## Critical Rules @@ -66,3 +79,4 @@ Summarize: failures fixed, root causes, regression status (all previously passin - NEVER fix tests to match broken behavior -- fix the implementation - ALWAYS keep passing tests passing -- no regressions - ALWAYS update the QA results document with resolution details +- ALWAYS set the artifact status after fixing (pass if all scenarios now pass) diff --git a/internal/adapter/templates/templates/skeleton/.claude/commands/fix-review-issues.md b/internal/adapter/templates/templates/skeleton/.claude/commands/fix-review-issues.md index 663f927..c8291e1 100644 --- a/internal/adapter/templates/templates/skeleton/.claude/commands/fix-review-issues.md +++ b/internal/adapter/templates/templates/skeleton/.claude/commands/fix-review-issues.md @@ -55,9 +55,22 @@ go test ./... 2>/dev/null || true All tests must pass after all fixes are applied. -### 8. Report +### 8. Update Artifact Status -Summarize: findings fixed by severity, files modified, test results. +After all fixes are applied and tests pass, re-evaluate the review: + +- If **all blockers are resolved** and tests pass: mark as passed + ```bash + sdlc artifact pass $ARGUMENTS review + ``` +- If **blockers remain unresolved**: keep as needs-fix + ```bash + sdlc artifact needs-fix $ARGUMENTS review + ``` + +### 9. Report + +Summarize: findings fixed by severity, files modified, test results, and artifact status. ## Critical Rules @@ -66,3 +79,4 @@ Summarize: findings fixed by severity, files modified, test results. - NEVER close a finding without actually fixing it - NEVER introduce new issues while fixing existing ones - ALWAYS update the review report with resolution notes +- ALWAYS set the artifact status after fixing (pass if all blockers resolved) diff --git a/internal/adapter/templates/templates/skeleton/.claude/commands/remediate-audit.md b/internal/adapter/templates/templates/skeleton/.claude/commands/remediate-audit.md index e1a9fe2..d15dd9a 100644 --- a/internal/adapter/templates/templates/skeleton/.claude/commands/remediate-audit.md +++ b/internal/adapter/templates/templates/skeleton/.claude/commands/remediate-audit.md @@ -56,9 +56,22 @@ Update `.sdlc/features/$ARGUMENTS/audit.md` with remediation notes: | [description] | CRITICAL | REMEDIATED | [what was done] | ``` -### 8. Report +### 8. Update Artifact Status -Summarize: findings remediated by severity, remaining items, verification results. +After all remediations are applied and security checks pass, re-evaluate the audit: + +- If **all critical and high findings are remediated**: mark as passed + ```bash + sdlc artifact pass $ARGUMENTS audit + ``` +- If **critical or high findings remain**: keep as needs-fix + ```bash + sdlc artifact needs-fix $ARGUMENTS audit + ``` + +### 9. Report + +Summarize: findings remediated by severity, remaining items, verification results, and artifact status. ## Critical Rules @@ -68,3 +81,4 @@ Summarize: findings remediated by severity, remaining items, verification result - NEVER fix security issues with workarounds -- address root causes - ALWAYS update the audit report with remediation details - NEVER remove security findings from the report -- mark them as remediated +- ALWAYS set the artifact status after remediation (pass if all critical/high resolved) diff --git a/internal/adapter/templates/templates/skeleton/.claude/commands/review-feature.md b/internal/adapter/templates/templates/skeleton/.claude/commands/review-feature.md index 5381dec..756bbee 100644 --- a/internal/adapter/templates/templates/skeleton/.claude/commands/review-feature.md +++ b/internal/adapter/templates/templates/skeleton/.claude/commands/review-feature.md @@ -70,16 +70,32 @@ Write to `.sdlc/features/$ARGUMENTS/review.md`: [Which acceptance criteria have tests? Which are missing?] ``` -### 6. Register the Artifact +### 6. Register and Evaluate the Artifact + +Create the artifact: ```bash sdlc artifact create $ARGUMENTS review ``` +Then evaluate the review results and set the appropriate status: + +- If the review has **NO blockers**: mark as passed + ```bash + sdlc artifact pass $ARGUMENTS review + ``` +- If the review has **blockers**: mark as needs-fix + ```bash + sdlc artifact needs-fix $ARGUMENTS review + ``` + +This status drives the SDLC classifier to either advance to audit or trigger fix-review-issues. + ## Critical Rules - ALWAYS read spec and design before reviewing code - NEVER skip the security review dimension - ALWAYS check test coverage against acceptance criteria - ALWAYS provide actionable findings with file locations -- NEVER approve a review with unresolved blockers +- NEVER mark a review as passed if it has unresolved blockers +- ALWAYS set the artifact status (pass or needs-fix) after writing the review diff --git a/internal/adapter/templates/templates/skeleton/.claude/commands/run-qa.md b/internal/adapter/templates/templates/skeleton/.claude/commands/run-qa.md index 6a698d4..1afcb8f 100644 --- a/internal/adapter/templates/templates/skeleton/.claude/commands/run-qa.md +++ b/internal/adapter/templates/templates/skeleton/.claude/commands/run-qa.md @@ -77,12 +77,27 @@ Write to `.sdlc/features/$ARGUMENTS/qa-results.md`: [Detailed description of each failure with reproduction steps] ``` -### 6. Register the Artifact +### 6. Register and Evaluate the Artifact + +Create the artifact: ```bash sdlc artifact create $ARGUMENTS qa_results ``` +Then evaluate the QA results and set the appropriate status: + +- If **all scenarios pass** and all acceptance criteria are covered: mark as passed + ```bash + sdlc artifact pass $ARGUMENTS qa_results + ``` +- If **any scenario fails** or acceptance criteria have gaps: mark as failed + ```bash + sdlc artifact fail $ARGUMENTS qa_results + ``` + +This status drives the SDLC classifier to either advance to merge or trigger fix-qa-failures. + ## Critical Rules - ALWAYS execute every scenario from the QA plan -- no skipping @@ -90,3 +105,4 @@ sdlc artifact create $ARGUMENTS qa_results - ALWAYS document ALL results, including passing scenarios - ALWAYS verify acceptance criteria coverage explicitly - NEVER fabricate test evidence -- run the actual tests +- ALWAYS set the artifact status (pass or fail) after writing QA results diff --git a/internal/handlers/sdlc.go b/internal/handlers/sdlc.go index 4b7159c..854e2da 100644 --- a/internal/handlers/sdlc.go +++ b/internal/handlers/sdlc.go @@ -55,6 +55,9 @@ func (h *SDLCHandler) Mount(r api.Router) { // Artifacts - write r.With(auth.RequireScope(auth.ScopeProjectsExecute, auth.ScopeAdmin)).Post("/features/{slug}/artifacts/{type}/approve", h.ApproveArtifact) r.With(auth.RequireScope(auth.ScopeProjectsExecute, auth.ScopeAdmin)).Post("/features/{slug}/artifacts/{type}/reject", h.RejectArtifact) + r.With(auth.RequireScope(auth.ScopeProjectsExecute, auth.ScopeAdmin)).Post("/features/{slug}/artifacts/{type}/pass", h.PassArtifact) + r.With(auth.RequireScope(auth.ScopeProjectsExecute, auth.ScopeAdmin)).Post("/features/{slug}/artifacts/{type}/fail", h.FailArtifact) + r.With(auth.RequireScope(auth.ScopeProjectsExecute, auth.ScopeAdmin)).Post("/features/{slug}/artifacts/{type}/needs-fix", h.NeedsFixArtifact) // Tasks - read r.With(auth.RequireScope(auth.ScopeProjectsRead, auth.ScopeAdmin)).Get("/features/{slug}/tasks", h.ListTasks) diff --git a/internal/handlers/sdlc_artifacts.go b/internal/handlers/sdlc_artifacts.go index b416837..7e147ff 100644 --- a/internal/handlers/sdlc_artifacts.go +++ b/internal/handlers/sdlc_artifacts.go @@ -82,3 +82,87 @@ func (h *SDLCHandler) RejectArtifact(w http.ResponseWriter, r *http.Request) { "status": "rejected", }) } + +// PassArtifact marks a feature artifact as passed (for execution artifacts). +// POST /projects/{id}/sdlc/features/{slug}/artifacts/{type}/pass +func (h *SDLCHandler) PassArtifact(w http.ResponseWriter, r *http.Request) { + projectID := chi.URLParam(r, "id") + slug := chi.URLParam(r, "slug") + artTypeStr := chi.URLParam(r, "type") + + artType := sdlc.ArtifactType(artTypeStr) + if !sdlc.IsValidArtifactType(artType) { + api.WriteBadRequest(w, r, "invalid artifact type: "+artTypeStr) + return + } + + ctx, cancel := context.WithTimeout(r.Context(), TimeoutHeavyWrite) + defer cancel() + + if err := h.sdlcService.PassArtifact(ctx, projectID, slug, artType); err != nil { + writeSDLCError(w, r, err) + return + } + + api.WriteSuccess(w, r, map[string]any{ + "feature": slug, + "artifact": artTypeStr, + "status": "passed", + }) +} + +// FailArtifact marks a feature artifact as failed (for execution artifacts). +// POST /projects/{id}/sdlc/features/{slug}/artifacts/{type}/fail +func (h *SDLCHandler) FailArtifact(w http.ResponseWriter, r *http.Request) { + projectID := chi.URLParam(r, "id") + slug := chi.URLParam(r, "slug") + artTypeStr := chi.URLParam(r, "type") + + artType := sdlc.ArtifactType(artTypeStr) + if !sdlc.IsValidArtifactType(artType) { + api.WriteBadRequest(w, r, "invalid artifact type: "+artTypeStr) + return + } + + ctx, cancel := context.WithTimeout(r.Context(), TimeoutHeavyWrite) + defer cancel() + + if err := h.sdlcService.FailArtifact(ctx, projectID, slug, artType); err != nil { + writeSDLCError(w, r, err) + return + } + + api.WriteSuccess(w, r, map[string]any{ + "feature": slug, + "artifact": artTypeStr, + "status": "failed", + }) +} + +// NeedsFixArtifact marks a feature artifact as needing fixes. +// POST /projects/{id}/sdlc/features/{slug}/artifacts/{type}/needs-fix +func (h *SDLCHandler) NeedsFixArtifact(w http.ResponseWriter, r *http.Request) { + projectID := chi.URLParam(r, "id") + slug := chi.URLParam(r, "slug") + artTypeStr := chi.URLParam(r, "type") + + artType := sdlc.ArtifactType(artTypeStr) + if !sdlc.IsValidArtifactType(artType) { + api.WriteBadRequest(w, r, "invalid artifact type: "+artTypeStr) + return + } + + ctx, cancel := context.WithTimeout(r.Context(), TimeoutHeavyWrite) + defer cancel() + + if err := h.sdlcService.NeedsFixArtifact(ctx, projectID, slug, artType); err != nil { + writeSDLCError(w, r, err) + return + } + + api.WriteSuccess(w, r, map[string]any{ + "feature": slug, + "artifact": artTypeStr, + "status": "needs_fix", + }) +} diff --git a/internal/handlers/sdlc_test.go b/internal/handlers/sdlc_test.go index a7f634a..28b1355 100644 --- a/internal/handlers/sdlc_test.go +++ b/internal/handlers/sdlc_test.go @@ -64,6 +64,15 @@ func (m *testSDLCExecutor) ApproveArtifact(_ context.Context, _, _ string, _ sdl func (m *testSDLCExecutor) RejectArtifact(_ context.Context, _, _ string, _ sdlc.ArtifactType) error { return m.err } +func (m *testSDLCExecutor) PassArtifact(_ context.Context, _, _ string, _ sdlc.ArtifactType) error { + return m.err +} +func (m *testSDLCExecutor) FailArtifact(_ context.Context, _, _ string, _ sdlc.ArtifactType) error { + return m.err +} +func (m *testSDLCExecutor) NeedsFixArtifact(_ context.Context, _, _ string, _ sdlc.ArtifactType) error { + return m.err +} func (m *testSDLCExecutor) ListTasks(_ context.Context, _, _ string) ([]sdlc.Task, error) { return m.tasks, m.err } diff --git a/internal/port/sdlc_executor.go b/internal/port/sdlc_executor.go index f6ca632..526bed6 100644 --- a/internal/port/sdlc_executor.go +++ b/internal/port/sdlc_executor.go @@ -46,6 +46,15 @@ type SDLCExecutor interface { // RejectArtifact rejects a feature artifact. RejectArtifact(ctx context.Context, podName, slug string, artType sdlc.ArtifactType) error + // PassArtifact marks a feature artifact as passed (for execution artifacts). + PassArtifact(ctx context.Context, podName, slug string, artType sdlc.ArtifactType) error + + // FailArtifact marks a feature artifact as failed (for execution artifacts). + FailArtifact(ctx context.Context, podName, slug string, artType sdlc.ArtifactType) error + + // NeedsFixArtifact marks a feature artifact as needing fixes. + NeedsFixArtifact(ctx context.Context, podName, slug string, artType sdlc.ArtifactType) error + // ListTasks returns all tasks for a feature. ListTasks(ctx context.Context, podName, slug string) ([]sdlc.Task, error) diff --git a/internal/service/sdlc_service.go b/internal/service/sdlc_service.go index 03279e3..d21b2d9 100644 --- a/internal/service/sdlc_service.go +++ b/internal/service/sdlc_service.go @@ -186,6 +186,45 @@ func (s *SDLCService) RejectArtifact(ctx context.Context, projectID, slug string return nil } +// PassArtifact marks a feature artifact as passed. +func (s *SDLCService) PassArtifact(ctx context.Context, projectID, slug string, artType sdlc.ArtifactType) error { + podName, err := s.resolveProjectPod(ctx, projectID) + if err != nil { + return err + } + if err := s.sdlcExec.PassArtifact(ctx, podName, slug, artType); err != nil { + return err + } + s.logger.Info("artifact passed", "project", projectID, "feature", slug, "artifact", string(artType)) + return nil +} + +// FailArtifact marks a feature artifact as failed. +func (s *SDLCService) FailArtifact(ctx context.Context, projectID, slug string, artType sdlc.ArtifactType) error { + podName, err := s.resolveProjectPod(ctx, projectID) + if err != nil { + return err + } + if err := s.sdlcExec.FailArtifact(ctx, podName, slug, artType); err != nil { + return err + } + s.logger.Info("artifact failed", "project", projectID, "feature", slug, "artifact", string(artType)) + return nil +} + +// NeedsFixArtifact marks a feature artifact as needing fixes. +func (s *SDLCService) NeedsFixArtifact(ctx context.Context, projectID, slug string, artType sdlc.ArtifactType) error { + podName, err := s.resolveProjectPod(ctx, projectID) + if err != nil { + return err + } + if err := s.sdlcExec.NeedsFixArtifact(ctx, podName, slug, artType); err != nil { + return err + } + s.logger.Info("artifact needs fix", "project", projectID, "feature", slug, "artifact", string(artType)) + return nil +} + // ListTasks returns all tasks for a feature. func (s *SDLCService) ListTasks(ctx context.Context, projectID, slug string) ([]sdlc.Task, error) { podName, err := s.resolveProjectPod(ctx, projectID) diff --git a/internal/service/sdlc_service_test.go b/internal/service/sdlc_service_test.go index cde0dde..b73c168 100644 --- a/internal/service/sdlc_service_test.go +++ b/internal/service/sdlc_service_test.go @@ -24,6 +24,9 @@ type mockSDLCExecutor struct { getArtifactStatusFn func(ctx context.Context, podName, slug string) (map[sdlc.ArtifactType]*sdlc.Artifact, error) approveArtifactFn func(ctx context.Context, podName, slug string, artType sdlc.ArtifactType) error rejectArtifactFn func(ctx context.Context, podName, slug string, artType sdlc.ArtifactType) error + passArtifactFn func(ctx context.Context, podName, slug string, artType sdlc.ArtifactType) error + failArtifactFn func(ctx context.Context, podName, slug string, artType sdlc.ArtifactType) error + needsFixArtifactFn func(ctx context.Context, podName, slug string, artType sdlc.ArtifactType) error listTasksFn func(ctx context.Context, podName, slug string) ([]sdlc.Task, error) addTaskFn func(ctx context.Context, podName, slug, title string) (*sdlc.Task, error) startTaskFn func(ctx context.Context, podName, slug, taskID string) error @@ -123,6 +126,27 @@ func (m *mockSDLCExecutor) RejectArtifact(ctx context.Context, podName, slug str return nil } +func (m *mockSDLCExecutor) PassArtifact(ctx context.Context, podName, slug string, artType sdlc.ArtifactType) error { + if m.passArtifactFn != nil { + return m.passArtifactFn(ctx, podName, slug, artType) + } + return nil +} + +func (m *mockSDLCExecutor) FailArtifact(ctx context.Context, podName, slug string, artType sdlc.ArtifactType) error { + if m.failArtifactFn != nil { + return m.failArtifactFn(ctx, podName, slug, artType) + } + return nil +} + +func (m *mockSDLCExecutor) NeedsFixArtifact(ctx context.Context, podName, slug string, artType sdlc.ArtifactType) error { + if m.needsFixArtifactFn != nil { + return m.needsFixArtifactFn(ctx, podName, slug, artType) + } + return nil +} + func (m *mockSDLCExecutor) ListTasks(ctx context.Context, podName, slug string) ([]sdlc.Task, error) { if m.listTasksFn != nil { return m.listTasksFn(ctx, podName, slug)