feat: add artifact pass/fail/needs-fix lifecycle for SDLC execution phases
- Add pass/fail/needs-fix CLI commands to cmd/sdlc/cmd_artifact.go
- Add 3 new methods to SDLCExecutor interface in internal/port
- Implement methods in kubernetes adapter
- Add service methods to SDLCService
- Add HTTP handlers for POST .../artifacts/{type}/pass|fail|needs-fix
- Update 6 skeleton commands to evaluate and set artifact status
- Update test mocks
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
64ccf0b85d
commit
6e8f5821af
@ -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
|
||||
|
||||
|
||||
@ -151,6 +151,147 @@ var artifactRejectCmd = &cobra.Command{
|
||||
},
|
||||
}
|
||||
|
||||
var artifactPassCmd = &cobra.Command{
|
||||
Use: "pass <feature> <type>",
|
||||
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 <feature> <type>",
|
||||
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 <feature> <type>",
|
||||
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 <feature>",
|
||||
Short: "Show all artifact statuses for a feature",
|
||||
@ -185,6 +326,9 @@ func init() {
|
||||
artifactCreateCmd,
|
||||
artifactApproveCmd,
|
||||
artifactRejectCmd,
|
||||
artifactPassCmd,
|
||||
artifactFailCmd,
|
||||
artifactNeedsFixCmd,
|
||||
artifactStatusCmd,
|
||||
)
|
||||
rootCmd.AddCommand(artifactCmd)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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",
|
||||
})
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user