feat: add feature development E2E test and SDLC handler fixes
- Add feature-dev-test.sh: full 10-step E2E test for SDLC + Claude Code workflow - Update feature-development.md cookbook with complete workflow documentation - Fix SDLC orchestrator and project management handler improvements - Update scaffold-test.sh with minor fixes Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
fc2cfa139c
commit
64ccf0b85d
@ -269,8 +269,10 @@ POST /project/landing/build
|
|||||||
| Script | Description |
|
| Script | Description |
|
||||||
|--------|-------------|
|
|--------|-------------|
|
||||||
| `scripts/landing-test.sh` | Landing page E2E test |
|
| `scripts/landing-test.sh` | Landing page E2E test |
|
||||||
| `scripts/feature-test.sh` | Feature development E2E test |
|
| `scripts/feature-dev-test.sh` | Feature development E2E test (Claude + SDLC flow) |
|
||||||
|
| `scripts/scaffold-test.sh` | Scaffold validation E2E test (patterns/components) |
|
||||||
| `scripts/composable-test.sh` | Composable monorepo E2E test |
|
| `scripts/composable-test.sh` | Composable monorepo E2E test |
|
||||||
|
| `scripts/sdlc-test.sh` | SDLC API endpoint validation |
|
||||||
| `scripts/template-validation.sh` | Template validation checks |
|
| `scripts/template-validation.sh` | Template validation checks |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@ -4,15 +4,208 @@
|
|||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
This cookbook documents the end-to-end workflow for developing features in a composable monorepo project. It assumes you have:
|
This cookbook documents the end-to-end workflow for developing features in a composable monorepo project. There are two approaches:
|
||||||
|
|
||||||
- An rdev project with monorepo skeleton (created via `POST /project`)
|
1. **SDLC-Driven** (Recommended) - Structured workflow using the SDLC system and Claude Code
|
||||||
- At least one API service component (`services/api`)
|
2. **Manual** - Traditional development with manual planning and implementation
|
||||||
- A Next.js or React app component (`apps/dashboard`)
|
|
||||||
|
The SDLC-driven approach uses skeleton commands that Claude executes inside the project pod, producing structured artifacts (spec, design, tasks) that can be approved and tracked.
|
||||||
|
|
||||||
```
|
```
|
||||||
Feature Development Workflow
|
SDLC Feature Development Workflow
|
||||||
────────────────────────────
|
──────────────────────────────────
|
||||||
|
1. Create Feature → POST /sdlc/features
|
||||||
|
2. Generate Spec → Claude runs /spec-feature
|
||||||
|
3. Approve Spec → POST /sdlc/features/{slug}/artifacts/spec/approve
|
||||||
|
4. Generate Design → Claude runs /design-feature
|
||||||
|
5. Approve Design → POST /sdlc/features/{slug}/artifacts/design/approve
|
||||||
|
6. Break Down Tasks → Claude runs /breakdown-feature
|
||||||
|
7. Approve Tasks → POST /sdlc/features/{slug}/artifacts/tasks/approve
|
||||||
|
8. Implement Tasks → Claude runs /implement-task for each
|
||||||
|
9. Review & Merge → Claude runs /review-feature, /merge-feature
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quick Start: API-Driven Feature Development
|
||||||
|
|
||||||
|
This section shows the minimal API calls to develop a feature using Claude Code and the SDLC system.
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export RDEV_API_URL="https://rdev.masq-ops.orchard9.ai"
|
||||||
|
export RDEV_API_KEY="<your-api-key>"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1. Create Feature
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST "$RDEV_API_URL/projects/myapp/sdlc/features" \
|
||||||
|
-H "X-API-Key: $RDEV_API_KEY" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"slug": "add-hello", "title": "Add /hello endpoint"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Generate Spec with Claude
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST "$RDEV_API_URL/projects/myapp/builds" \
|
||||||
|
-H "X-API-Key: $RDEV_API_KEY" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"prompt": "/spec-feature add-hello",
|
||||||
|
"auto_commit": true,
|
||||||
|
"auto_push": true,
|
||||||
|
"git_clone_url": "https://git.threesix.ai/jordan/myapp.git"
|
||||||
|
}'
|
||||||
|
# Returns: {"data": {"task_id": "abc123", ...}}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Check Build Status
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl "$RDEV_API_URL/builds/abc123" \
|
||||||
|
-H "X-API-Key: $RDEV_API_KEY"
|
||||||
|
# Wait for status: "completed"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Check Classifier for Next Action
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl "$RDEV_API_URL/projects/myapp/sdlc/next?feature=add-hello" \
|
||||||
|
-H "X-API-Key: $RDEV_API_KEY"
|
||||||
|
# Returns: {"action": "AWAIT_APPROVAL", "artifact": "spec", ...}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Approve Spec
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST "$RDEV_API_URL/projects/myapp/sdlc/features/add-hello/artifacts/spec/approve" \
|
||||||
|
-H "X-API-Key: $RDEV_API_KEY" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. Continue Through Phases
|
||||||
|
|
||||||
|
Repeat the pattern: **Generate artifact** → **Check classifier** → **Approve** → **Next phase**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Generate design
|
||||||
|
curl -X POST "$RDEV_API_URL/projects/myapp/builds" \
|
||||||
|
-H "X-API-Key: $RDEV_API_KEY" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"prompt": "/design-feature add-hello", "auto_commit": true, "auto_push": true, "git_clone_url": "https://git.threesix.ai/jordan/myapp.git"}'
|
||||||
|
|
||||||
|
# Approve design
|
||||||
|
curl -X POST "$RDEV_API_URL/projects/myapp/sdlc/features/add-hello/artifacts/design/approve" \
|
||||||
|
-H "X-API-Key: $RDEV_API_KEY" -H "Content-Type: application/json" -d '{}'
|
||||||
|
|
||||||
|
# Generate tasks
|
||||||
|
curl -X POST "$RDEV_API_URL/projects/myapp/builds" \
|
||||||
|
-H "X-API-Key: $RDEV_API_KEY" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"prompt": "/breakdown-feature add-hello", "auto_commit": true, "auto_push": true, "git_clone_url": "https://git.threesix.ai/jordan/myapp.git"}'
|
||||||
|
|
||||||
|
# Approve tasks
|
||||||
|
curl -X POST "$RDEV_API_URL/projects/myapp/sdlc/features/add-hello/artifacts/tasks/approve" \
|
||||||
|
-H "X-API-Key: $RDEV_API_KEY" -H "Content-Type: application/json" -d '{}'
|
||||||
|
|
||||||
|
# Implement first task
|
||||||
|
curl -X POST "$RDEV_API_URL/projects/myapp/builds" \
|
||||||
|
-H "X-API-Key: $RDEV_API_KEY" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"prompt": "/implement-task add-hello T1", "auto_commit": true, "auto_push": true, "git_clone_url": "https://git.threesix.ai/jordan/myapp.git"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### E2E Test Script
|
||||||
|
|
||||||
|
Run the full workflow automatically:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./cookbooks/scripts/feature-dev-test.sh run my-test-project
|
||||||
|
./cookbooks/scripts/feature-dev-test.sh status my-test-project
|
||||||
|
./cookbooks/scripts/feature-dev-test.sh teardown my-test-project
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Skeleton Commands Reference
|
||||||
|
|
||||||
|
These commands are installed in every project's `.claude/commands/` directory and can be invoked via the `/builds` endpoint.
|
||||||
|
|
||||||
|
| Command | Description | Artifacts |
|
||||||
|
|---------|-------------|-----------|
|
||||||
|
| `/spec-feature <slug>` | Create specification document | `.sdlc/features/<slug>/spec.md` |
|
||||||
|
| `/design-feature <slug>` | Create technical design | `.sdlc/features/<slug>/design.md` |
|
||||||
|
| `/breakdown-feature <slug>` | Break feature into tasks | `.sdlc/features/<slug>/tasks.md` |
|
||||||
|
| `/implement-task <slug> <task-id>` | Implement a specific task | Code changes |
|
||||||
|
| `/create-qa-plan <slug>` | Create QA test plan | `.sdlc/features/<slug>/qa-plan.md` |
|
||||||
|
| `/run-qa <slug>` | Execute QA tests | Test results |
|
||||||
|
| `/review-feature <slug>` | Code review all changes | Review report |
|
||||||
|
| `/audit-feature <slug>` | Audit feature for compliance | Audit report |
|
||||||
|
| `/fix-review-issues <slug>` | Fix issues from review | Code changes |
|
||||||
|
| `/remediate-audit <slug>` | Fix audit findings | Code changes |
|
||||||
|
| `/fix-qa-failures <slug>` | Fix failing QA tests | Code changes |
|
||||||
|
| `/merge-feature <slug>` | Merge feature to main | Merged branch |
|
||||||
|
| `/archive-feature <slug>` | Archive completed feature | Archived state |
|
||||||
|
| `/next <slug>` | Get classifier recommendation | Next action |
|
||||||
|
| `/deliver <slug>` | Run full delivery pipeline | All artifacts |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## SDLC API Reference
|
||||||
|
|
||||||
|
### Feature Management
|
||||||
|
|
||||||
|
| Endpoint | Method | Description |
|
||||||
|
|----------|--------|-------------|
|
||||||
|
| `/projects/{id}/sdlc/state` | GET | Get SDLC state for project |
|
||||||
|
| `/projects/{id}/sdlc/features` | GET | List all features |
|
||||||
|
| `/projects/{id}/sdlc/features` | POST | Create new feature |
|
||||||
|
| `/projects/{id}/sdlc/features/{slug}` | GET | Get feature details |
|
||||||
|
| `/projects/{id}/sdlc/features/{slug}` | DELETE | Delete feature |
|
||||||
|
|
||||||
|
### Artifacts
|
||||||
|
|
||||||
|
| Endpoint | Method | Description |
|
||||||
|
|----------|--------|-------------|
|
||||||
|
| `/projects/{id}/sdlc/features/{slug}/artifacts` | GET | List artifact status |
|
||||||
|
| `/projects/{id}/sdlc/features/{slug}/artifacts/{type}/approve` | POST | Approve artifact |
|
||||||
|
|
||||||
|
### Tasks
|
||||||
|
|
||||||
|
| Endpoint | Method | Description |
|
||||||
|
|----------|--------|-------------|
|
||||||
|
| `/projects/{id}/sdlc/features/{slug}/tasks` | GET | List tasks |
|
||||||
|
| `/projects/{id}/sdlc/features/{slug}/tasks` | POST | Add task |
|
||||||
|
|
||||||
|
### Workflow Control
|
||||||
|
|
||||||
|
| Endpoint | Method | Description |
|
||||||
|
|----------|--------|-------------|
|
||||||
|
| `/projects/{id}/sdlc/features/{slug}/block` | POST | Block feature |
|
||||||
|
| `/projects/{id}/sdlc/features/{slug}/unblock` | POST | Unblock feature |
|
||||||
|
| `/projects/{id}/sdlc/next` | GET | Get classifier recommendation |
|
||||||
|
|
||||||
|
### Queries
|
||||||
|
|
||||||
|
| Endpoint | Method | Description |
|
||||||
|
|----------|--------|-------------|
|
||||||
|
| `/projects/{id}/sdlc/query/blocked` | GET | List blocked features |
|
||||||
|
| `/projects/{id}/sdlc/query/ready` | GET | List ready features |
|
||||||
|
| `/projects/{id}/sdlc/query/needs-approval` | GET | List features awaiting approval |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Manual Development Workflow
|
||||||
|
|
||||||
|
For cases where you want direct control without the SDLC system, you can follow the traditional development workflow below.
|
||||||
|
|
||||||
|
```
|
||||||
|
Manual Feature Development Workflow
|
||||||
|
────────────────────────────────────
|
||||||
1. Planning → Define requirements, break into tasks
|
1. Planning → Define requirements, break into tasks
|
||||||
2. API Dev → Bind + Wrap patterns, OpenAPI annotations
|
2. API Dev → Bind + Wrap patterns, OpenAPI annotations
|
||||||
3. Frontend Dev → Auth hooks, design system, API client
|
3. Frontend Dev → Auth hooks, design system, API client
|
||||||
@ -21,17 +214,14 @@ Feature Development Workflow
|
|||||||
6. Deployment → Git push, Woodpecker CI, verify
|
6. Deployment → Git push, Woodpecker CI, verify
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
This approach assumes you have:
|
||||||
|
|
||||||
## Prerequisites
|
- An rdev project with monorepo skeleton (created via `POST /project`)
|
||||||
|
- At least one API service component (`services/api`)
|
||||||
### API Access
|
- A Next.js or React app component (`apps/dashboard`)
|
||||||
```bash
|
|
||||||
export RDEV_API_URL="https://rdev.masq-ops.orchard9.ai"
|
|
||||||
export RDEV_API_KEY="<your-api-key>"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Project Structure
|
### Project Structure
|
||||||
|
|
||||||
Your composable monorepo should have:
|
Your composable monorepo should have:
|
||||||
|
|
||||||
```
|
```
|
||||||
@ -728,24 +918,41 @@ import { DashboardShell, Sidebar, Header } from '@my-project/layout';
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## E2E Test Script
|
## E2E Test Scripts
|
||||||
|
|
||||||
Validate the complete feature development flow:
|
### Feature Development (Claude + SDLC)
|
||||||
|
|
||||||
|
Test the complete feature development flow with Claude Code:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Create test project with components
|
# Run full SDLC feature development flow
|
||||||
./cookbooks/scripts/feature-test.sh run test-feature
|
./cookbooks/scripts/feature-dev-test.sh run test-feature
|
||||||
|
|
||||||
# Check status
|
# Check status
|
||||||
./cookbooks/scripts/feature-test.sh status test-feature
|
./cookbooks/scripts/feature-dev-test.sh status test-feature
|
||||||
|
|
||||||
# Cleanup
|
# Cleanup
|
||||||
./cookbooks/scripts/feature-test.sh teardown test-feature
|
./cookbooks/scripts/feature-dev-test.sh teardown test-feature
|
||||||
|
```
|
||||||
|
|
||||||
|
### Scaffold Validation
|
||||||
|
|
||||||
|
Test that project scaffolding creates correct patterns:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create project and verify patterns
|
||||||
|
./cookbooks/scripts/scaffold-test.sh run test-scaffold
|
||||||
|
|
||||||
|
# Verify specific patterns
|
||||||
|
./cookbooks/scripts/scaffold-test.sh verify-patterns test-scaffold
|
||||||
|
|
||||||
|
# Cleanup
|
||||||
|
./cookbooks/scripts/scaffold-test.sh teardown test-scaffold
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Troubleshooting
|
## Manual Troubleshooting
|
||||||
|
|
||||||
### Handler returns 500 instead of proper error
|
### Handler returns 500 instead of proper error
|
||||||
Check that you're using `httperror.*` functions, not raw `errors.New()`.
|
Check that you're using `httperror.*` functions, not raw `errors.New()`.
|
||||||
@ -824,9 +1031,80 @@ All use CSS custom properties: `var(--background)`, `var(--accent)`, `var(--bord
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## SDLC Troubleshooting
|
||||||
|
|
||||||
|
### Build returns "completed" but no artifacts created
|
||||||
|
|
||||||
|
The skeleton command may have failed silently. Check the build output:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl "$RDEV_API_URL/builds/<task-id>" -H "X-API-Key: $RDEV_API_KEY" | jq '.data.result'
|
||||||
|
```
|
||||||
|
|
||||||
|
Common causes:
|
||||||
|
- Feature doesn't exist (create it first via `/sdlc/features`)
|
||||||
|
- Previous artifacts missing (spec must exist before design)
|
||||||
|
- SDLC CLI not installed in pod
|
||||||
|
|
||||||
|
### Classifier returns AWAIT_ARTIFACT but artifact exists
|
||||||
|
|
||||||
|
The artifact may exist in the file system but not registered with SDLC:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Register artifact manually
|
||||||
|
curl -X POST "$RDEV_API_URL/projects/$PROJECT/builds" \
|
||||||
|
-H "X-API-Key: $RDEV_API_KEY" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"prompt": "sdlc artifact create <slug> <type>", "auto_commit": true, "auto_push": true, "git_clone_url": "<url>"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Task implementation doesn't commit changes
|
||||||
|
|
||||||
|
Check that `auto_commit` and `auto_push` are set to `true`, and `git_clone_url` is provided:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST "$RDEV_API_URL/projects/$PROJECT/builds" \
|
||||||
|
-H "X-API-Key: $RDEV_API_KEY" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"prompt": "/implement-task <slug> <task-id>",
|
||||||
|
"auto_commit": true,
|
||||||
|
"auto_push": true,
|
||||||
|
"git_clone_url": "https://git.threesix.ai/jordan/<project>.git"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Build times out
|
||||||
|
|
||||||
|
Builds have a default timeout of 10 minutes. Complex implementations may need longer. Check the build status periodically:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Poll status
|
||||||
|
while true; do
|
||||||
|
status=$(curl -s "$RDEV_API_URL/builds/<task-id>" -H "X-API-Key: $RDEV_API_KEY" | jq -r '.data.status // .status')
|
||||||
|
echo "Status: $status"
|
||||||
|
[[ "$status" == "completed" || "$status" == "failed" ]] && break
|
||||||
|
sleep 10
|
||||||
|
done
|
||||||
|
```
|
||||||
|
|
||||||
|
### Feature stuck in wrong phase
|
||||||
|
|
||||||
|
Check the classifier recommendation to understand what's blocking:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl "$RDEV_API_URL/projects/$PROJECT/sdlc/next?feature=<slug>" \
|
||||||
|
-H "X-API-Key: $RDEV_API_KEY" | jq '.'
|
||||||
|
```
|
||||||
|
|
||||||
|
If needed, you can manually advance the phase by approving the pending artifact.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Related
|
## Related
|
||||||
|
|
||||||
- [Composable App Cookbook](./composable-app.md) - Creating projects with components
|
- [Composable App Cookbook](./composable-app.md) - Creating projects with components
|
||||||
- [Landing Page Cookbook](./landing-page.md) - Simple single-component sites
|
- [Landing Page Cookbook](./landing-page.md) - Simple single-component sites
|
||||||
- [Composable Monorepo Guide](../.claude/guides/services/composable-monorepo.md)
|
- [Composable Monorepo Guide](../.claude/guides/services/composable-monorepo.md)
|
||||||
- [API Framework Guide](../.claude/guides/packages/api-framework.md)
|
- [API Framework Guide](../.claude/guides/packages/api-framework.md)
|
||||||
|
- [SDLC Guide](../.claude/guides/services/sdlc.md) - Full SDLC orchestration documentation
|
||||||
|
|||||||
493
cookbooks/scripts/feature-dev-test.sh
Executable file
493
cookbooks/scripts/feature-dev-test.sh
Executable file
@ -0,0 +1,493 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Feature Development E2E Test Script
|
||||||
|
# Tests the complete Claude Code + SDLC feature development workflow:
|
||||||
|
# 1. Create composable project with skeleton
|
||||||
|
# 2. Add service component
|
||||||
|
# 3. Create SDLC feature via API
|
||||||
|
# 4. Invoke Claude to generate spec (via /builds with skeleton command)
|
||||||
|
# 5. Approve spec via API
|
||||||
|
# 6. Invoke Claude to generate design
|
||||||
|
# 7. Approve design via API
|
||||||
|
# 8. Invoke Claude to break down tasks
|
||||||
|
# 9. Approve tasks via API
|
||||||
|
# 10. Invoke Claude to implement first task
|
||||||
|
# 11. Verify code was generated
|
||||||
|
# 12. Teardown
|
||||||
|
#
|
||||||
|
# Usage: ./cookbooks/scripts/feature-dev-test.sh <command> <project-name>
|
||||||
|
# Commands: run, status, teardown
|
||||||
|
#
|
||||||
|
# This is the definitive E2E test for the SDLC + Claude Code integration.
|
||||||
|
# It exercises the full feature development workflow from project creation
|
||||||
|
# to implemented code.
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
source "$SCRIPT_DIR/common.sh"
|
||||||
|
|
||||||
|
# Parse --auto-teardown flag from args
|
||||||
|
ARGS=("$@")
|
||||||
|
for i in "${!ARGS[@]}"; do
|
||||||
|
if [[ "${ARGS[$i]}" == "--auto-teardown" ]]; then
|
||||||
|
AUTO_TEARDOWN="true"
|
||||||
|
unset 'ARGS[$i]'
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
ARGS=("${ARGS[@]}") # Re-index array
|
||||||
|
|
||||||
|
COMMAND="${ARGS[0]:-}"
|
||||||
|
PROJECT_NAME="${ARGS[1]:-}"
|
||||||
|
FEATURE_SLUG="add-hello-endpoint"
|
||||||
|
|
||||||
|
# Register cleanup trap for auto-teardown
|
||||||
|
register_cleanup_trap
|
||||||
|
|
||||||
|
if [[ -z "$COMMAND" || -z "$PROJECT_NAME" ]]; then
|
||||||
|
echo "Feature Development E2E Test Script"
|
||||||
|
echo ""
|
||||||
|
echo "Tests the complete Claude Code + SDLC feature development workflow."
|
||||||
|
echo ""
|
||||||
|
echo "Usage: $0 <command> <project-name> [--auto-teardown]"
|
||||||
|
echo ""
|
||||||
|
echo "Commands:"
|
||||||
|
echo " run - Execute full feature development flow"
|
||||||
|
echo " status - Check project and SDLC status"
|
||||||
|
echo " teardown - Delete the project"
|
||||||
|
echo ""
|
||||||
|
echo "Examples:"
|
||||||
|
echo " $0 run feature-dev-test"
|
||||||
|
echo " $0 run test-feat --auto-teardown"
|
||||||
|
echo " $0 status feature-dev-test"
|
||||||
|
echo " $0 teardown feature-dev-test"
|
||||||
|
echo ""
|
||||||
|
echo "Environment Variables:"
|
||||||
|
echo " RDEV_API_URL - rdev API base URL"
|
||||||
|
echo " RDEV_API_KEY - rdev API key"
|
||||||
|
echo " GITEA_TOKEN - Gitea API token (optional, for repo verification)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check response for success/error
|
||||||
|
check_response() {
|
||||||
|
local response="$1"
|
||||||
|
local step="$2"
|
||||||
|
|
||||||
|
local error
|
||||||
|
error=$(echo "$response" | jq -r '.error // ""')
|
||||||
|
if [[ -n "$error" && "$error" != "" && "$error" != "null" ]]; then
|
||||||
|
print_error "$step failed: $error"
|
||||||
|
echo "$response" | jq '.'
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Wait for a build to complete
|
||||||
|
# Arguments: task_id step_name [max_attempts]
|
||||||
|
wait_for_build_step() {
|
||||||
|
local task_id="$1"
|
||||||
|
local step_name="$2"
|
||||||
|
local max_attempts="${3:-120}" # 10 minutes default (5s * 120)
|
||||||
|
local attempt=0
|
||||||
|
|
||||||
|
echo " Waiting for build to complete (task: $task_id)..."
|
||||||
|
|
||||||
|
while [[ $attempt -lt $max_attempts ]]; do
|
||||||
|
local result
|
||||||
|
result=$(api_call GET "/builds/$task_id")
|
||||||
|
local status
|
||||||
|
status=$(echo "$result" | jq -r '.data.status // .status // "unknown"')
|
||||||
|
|
||||||
|
case "$status" in
|
||||||
|
completed)
|
||||||
|
local success
|
||||||
|
success=$(echo "$result" | jq -r '.data.result.success // .result.success // false')
|
||||||
|
if [[ "$success" == "true" ]]; then
|
||||||
|
print_success "$step_name completed successfully"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
print_error "$step_name completed but failed:"
|
||||||
|
echo "$result" | jq '.data.result // .result'
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
failed)
|
||||||
|
print_error "$step_name failed:"
|
||||||
|
echo "$result" | jq '.'
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
running)
|
||||||
|
echo " Build running... (attempt $((attempt + 1))/$max_attempts)"
|
||||||
|
;;
|
||||||
|
pending)
|
||||||
|
echo " Build pending... (attempt $((attempt + 1))/$max_attempts)"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo " Unknown status: $status (attempt $((attempt + 1))/$max_attempts)"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
sleep 5
|
||||||
|
((attempt++))
|
||||||
|
done
|
||||||
|
|
||||||
|
print_error "Timeout waiting for $step_name to complete"
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
|
||||||
|
# Submit a build and wait for completion
|
||||||
|
# Arguments: project_id prompt step_name
|
||||||
|
submit_build_and_wait() {
|
||||||
|
local project_id="$1"
|
||||||
|
local prompt="$2"
|
||||||
|
local step_name="$3"
|
||||||
|
|
||||||
|
local git_owner
|
||||||
|
git_owner=$(get_git_owner)
|
||||||
|
local git_clone_url="https://git.threesix.ai/${git_owner}/${project_id}.git"
|
||||||
|
|
||||||
|
echo " Submitting build: $prompt"
|
||||||
|
|
||||||
|
local payload
|
||||||
|
payload=$(jq -n \
|
||||||
|
--arg prompt "$prompt" \
|
||||||
|
--arg git_clone_url "$git_clone_url" \
|
||||||
|
'{
|
||||||
|
prompt: $prompt,
|
||||||
|
auto_commit: true,
|
||||||
|
auto_push: true,
|
||||||
|
git_clone_url: $git_clone_url
|
||||||
|
}')
|
||||||
|
|
||||||
|
local result
|
||||||
|
result=$(api_call POST "/projects/$project_id/builds" "$payload")
|
||||||
|
|
||||||
|
if ! check_response "$result" "Submit build"; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local task_id
|
||||||
|
task_id=$(echo "$result" | jq -r '.data.task_id // .task_id // ""')
|
||||||
|
|
||||||
|
if [[ -z "$task_id" ]]; then
|
||||||
|
print_error "No task_id in response"
|
||||||
|
echo "$result" | jq '.'
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
wait_for_build_step "$task_id" "$step_name"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Approve an SDLC artifact
|
||||||
|
# Arguments: project_id feature_slug artifact_type
|
||||||
|
approve_artifact() {
|
||||||
|
local project_id="$1"
|
||||||
|
local feature_slug="$2"
|
||||||
|
local artifact_type="$3"
|
||||||
|
|
||||||
|
echo " Approving $artifact_type artifact..."
|
||||||
|
|
||||||
|
local result
|
||||||
|
result=$(api_call POST "/projects/$project_id/sdlc/features/$feature_slug/artifacts/$artifact_type/approve" "{}")
|
||||||
|
|
||||||
|
if check_response "$result" "Approve $artifact_type"; then
|
||||||
|
print_success "$artifact_type artifact approved"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Add a component to the project
|
||||||
|
add_component() {
|
||||||
|
local comp_type="$1"
|
||||||
|
local comp_name="$2"
|
||||||
|
|
||||||
|
echo "Adding $comp_type component: $comp_name"
|
||||||
|
|
||||||
|
local payload
|
||||||
|
payload=$(jq -n \
|
||||||
|
--arg type "$comp_type" \
|
||||||
|
--arg name "$comp_name" \
|
||||||
|
'{type: $type, name: $name}')
|
||||||
|
|
||||||
|
local result
|
||||||
|
result=$(api_call POST "/projects/$PROJECT_NAME/components" "$payload")
|
||||||
|
|
||||||
|
local path
|
||||||
|
path=$(echo "$result" | jq -r '.data.path // .path // ""')
|
||||||
|
|
||||||
|
if [[ -z "$path" ]]; then
|
||||||
|
print_error "Failed to add component"
|
||||||
|
echo "$result" | jq '.'
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
print_success "Added $comp_type/$comp_name at $path"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Verify the feature artifacts exist in Gitea
|
||||||
|
verify_artifacts() {
|
||||||
|
local project_id="$1"
|
||||||
|
local feature_slug="$2"
|
||||||
|
|
||||||
|
local git_owner
|
||||||
|
git_owner=$(get_git_owner)
|
||||||
|
|
||||||
|
print_header "Verifying Feature Artifacts"
|
||||||
|
|
||||||
|
local artifacts=("spec.md" "design.md" "tasks.md")
|
||||||
|
local all_found=true
|
||||||
|
|
||||||
|
for artifact in "${artifacts[@]}"; do
|
||||||
|
local path=".sdlc/features/$feature_slug/$artifact"
|
||||||
|
echo " Checking $path..."
|
||||||
|
|
||||||
|
local check
|
||||||
|
check=$(curl -s "https://git.threesix.ai/api/v1/repos/$git_owner/$project_id/contents/$path" \
|
||||||
|
-H "Authorization: token ${GITEA_TOKEN:-}" 2>/dev/null | jq -r '.name // "not found"')
|
||||||
|
|
||||||
|
if [[ "$check" == "$artifact" ]]; then
|
||||||
|
print_success " $artifact exists"
|
||||||
|
else
|
||||||
|
print_warning " $artifact not found"
|
||||||
|
all_found=false
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ "$all_found" == "true" ]]; then
|
||||||
|
print_success "All artifacts verified"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
print_warning "Some artifacts missing (may still be propagating)"
|
||||||
|
return 0 # Don't fail on verification, may be timing
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main run flow
|
||||||
|
run_flow() {
|
||||||
|
print_header "Feature Development E2E Test"
|
||||||
|
echo "Project: $PROJECT_NAME"
|
||||||
|
echo "Feature: $FEATURE_SLUG"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# =========================================================================
|
||||||
|
# Step 1: Create project skeleton
|
||||||
|
# =========================================================================
|
||||||
|
print_header "Step 1: Creating project skeleton"
|
||||||
|
|
||||||
|
local create_payload
|
||||||
|
create_payload=$(jq -n \
|
||||||
|
--arg name "$PROJECT_NAME" \
|
||||||
|
--arg desc "Feature development E2E test" \
|
||||||
|
'{name: $name, description: $desc}')
|
||||||
|
|
||||||
|
local create_result
|
||||||
|
create_result=$(api_call POST "/project" "$create_payload")
|
||||||
|
echo "$create_result" | jq '.'
|
||||||
|
|
||||||
|
local domain
|
||||||
|
domain=$(echo "$create_result" | jq -r '.data.domain // .domain // ""')
|
||||||
|
|
||||||
|
if [[ -z "$domain" ]]; then
|
||||||
|
print_error "Failed to create project"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Track project for auto-cleanup
|
||||||
|
CLEANUP_PROJECT="$PROJECT_NAME"
|
||||||
|
|
||||||
|
print_success "Project created with domain: $domain"
|
||||||
|
|
||||||
|
# =========================================================================
|
||||||
|
# Step 2: Add service component
|
||||||
|
# =========================================================================
|
||||||
|
print_header "Step 2: Adding service component"
|
||||||
|
|
||||||
|
if ! add_component "service" "api"; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Wait for git sync
|
||||||
|
echo " Waiting for git sync..."
|
||||||
|
sleep 10
|
||||||
|
|
||||||
|
# =========================================================================
|
||||||
|
# Step 3: Create SDLC feature via build (using worker pod)
|
||||||
|
# =========================================================================
|
||||||
|
# Note: Composable projects don't have dedicated pods, so we use builds
|
||||||
|
# to execute SDLC commands on worker pods that clone the repo.
|
||||||
|
print_header "Step 3: Creating SDLC feature via build"
|
||||||
|
|
||||||
|
# Initialize SDLC and create feature in one build
|
||||||
|
if ! submit_build_and_wait "$PROJECT_NAME" \
|
||||||
|
"Initialize SDLC for this project and create a feature with slug '$FEATURE_SLUG' and title 'Add /hello endpoint to API service'. Use the sdlc CLI: 'sdlc init' then 'sdlc feature create $FEATURE_SLUG \"Add /hello endpoint to API service\"'" \
|
||||||
|
"SDLC initialization"; then
|
||||||
|
print_error "SDLC initialization failed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# =========================================================================
|
||||||
|
# Step 4: Generate spec via Claude
|
||||||
|
# =========================================================================
|
||||||
|
print_header "Step 4: Claude generates spec"
|
||||||
|
|
||||||
|
if ! submit_build_and_wait "$PROJECT_NAME" "/spec-feature $FEATURE_SLUG" "Spec generation"; then
|
||||||
|
print_error "Spec generation failed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# =========================================================================
|
||||||
|
# Step 5: Approve spec via build
|
||||||
|
# =========================================================================
|
||||||
|
print_header "Step 5: Approve spec"
|
||||||
|
|
||||||
|
if ! submit_build_and_wait "$PROJECT_NAME" \
|
||||||
|
"Approve the spec artifact for feature $FEATURE_SLUG using: sdlc artifact approve $FEATURE_SLUG spec" \
|
||||||
|
"Spec approval"; then
|
||||||
|
print_warning "Spec approval may have failed, continuing..."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# =========================================================================
|
||||||
|
# Step 6: Generate design via Claude
|
||||||
|
# =========================================================================
|
||||||
|
print_header "Step 6: Claude generates design"
|
||||||
|
|
||||||
|
if ! submit_build_and_wait "$PROJECT_NAME" "/design-feature $FEATURE_SLUG" "Design generation"; then
|
||||||
|
print_error "Design generation failed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# =========================================================================
|
||||||
|
# Step 7: Approve design via build
|
||||||
|
# =========================================================================
|
||||||
|
print_header "Step 7: Approve design"
|
||||||
|
|
||||||
|
if ! submit_build_and_wait "$PROJECT_NAME" \
|
||||||
|
"Approve the design artifact for feature $FEATURE_SLUG using: sdlc artifact approve $FEATURE_SLUG design" \
|
||||||
|
"Design approval"; then
|
||||||
|
print_warning "Design approval may have failed, continuing..."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# =========================================================================
|
||||||
|
# Step 8: Break down into tasks via Claude
|
||||||
|
# =========================================================================
|
||||||
|
print_header "Step 8: Claude breaks down into tasks"
|
||||||
|
|
||||||
|
if ! submit_build_and_wait "$PROJECT_NAME" "/breakdown-feature $FEATURE_SLUG" "Task breakdown"; then
|
||||||
|
print_error "Task breakdown failed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# =========================================================================
|
||||||
|
# Step 9: Approve tasks via build
|
||||||
|
# =========================================================================
|
||||||
|
print_header "Step 9: Approve tasks"
|
||||||
|
|
||||||
|
if ! submit_build_and_wait "$PROJECT_NAME" \
|
||||||
|
"Approve the tasks artifact for feature $FEATURE_SLUG using: sdlc artifact approve $FEATURE_SLUG tasks" \
|
||||||
|
"Tasks approval"; then
|
||||||
|
print_warning "Tasks approval may have failed, continuing..."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# =========================================================================
|
||||||
|
# Step 10: Implement first task via Claude
|
||||||
|
# =========================================================================
|
||||||
|
print_header "Step 10: Claude implements first task"
|
||||||
|
|
||||||
|
# First task is typically T1
|
||||||
|
local first_task_id="T1"
|
||||||
|
echo " First task ID: $first_task_id"
|
||||||
|
|
||||||
|
if ! submit_build_and_wait "$PROJECT_NAME" "/implement-task $FEATURE_SLUG $first_task_id" "Task implementation"; then
|
||||||
|
print_error "Task implementation failed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# =========================================================================
|
||||||
|
# Step 11: Verify artifacts exist
|
||||||
|
# =========================================================================
|
||||||
|
verify_artifacts "$PROJECT_NAME" "$FEATURE_SLUG"
|
||||||
|
|
||||||
|
# =========================================================================
|
||||||
|
# Summary
|
||||||
|
# =========================================================================
|
||||||
|
print_header "E2E Test Results"
|
||||||
|
print_success "Feature development flow completed!"
|
||||||
|
echo ""
|
||||||
|
echo " Project: $PROJECT_NAME"
|
||||||
|
echo " Feature: $FEATURE_SLUG"
|
||||||
|
echo " Domain: https://$domain"
|
||||||
|
echo " Git: https://git.threesix.ai/$(get_git_owner)/$PROJECT_NAME"
|
||||||
|
echo " CI: https://ci.threesix.ai/$(get_git_owner)/$PROJECT_NAME"
|
||||||
|
echo ""
|
||||||
|
echo "Workflow completed:"
|
||||||
|
echo " 1. Project skeleton created"
|
||||||
|
echo " 2. Service component added"
|
||||||
|
echo " 3. SDLC initialized + feature created (via build)"
|
||||||
|
echo " 4. Claude generated spec"
|
||||||
|
echo " 5. Spec approved"
|
||||||
|
echo " 6. Claude generated design"
|
||||||
|
echo " 7. Design approved"
|
||||||
|
echo " 8. Claude broke down into tasks"
|
||||||
|
echo " 9. Tasks approved"
|
||||||
|
echo " 10. Claude implemented first task"
|
||||||
|
echo ""
|
||||||
|
echo "View the feature artifacts at:"
|
||||||
|
echo " https://git.threesix.ai/$(get_git_owner)/$PROJECT_NAME/src/branch/main/.sdlc/features/$FEATURE_SLUG"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Show status
|
||||||
|
check_status() {
|
||||||
|
print_header "Project Status: $PROJECT_NAME"
|
||||||
|
|
||||||
|
# Get project info
|
||||||
|
echo "Project:"
|
||||||
|
api_call GET "/project/$PROJECT_NAME" | jq '.data // .'
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Get components
|
||||||
|
echo "Components:"
|
||||||
|
api_call GET "/projects/$PROJECT_NAME/components" | jq '.data // .'
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Get recent builds
|
||||||
|
print_header "Recent Builds"
|
||||||
|
api_call GET "/projects/$PROJECT_NAME/builds?limit=10" | jq '.data.builds[:10] // .builds[:10] // []'
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Note about SDLC status
|
||||||
|
echo "Note: SDLC status is stored in the git repo at .sdlc/"
|
||||||
|
echo "View at: https://git.threesix.ai/$(get_git_owner)/$PROJECT_NAME/src/branch/main/.sdlc"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Teardown
|
||||||
|
teardown() {
|
||||||
|
print_header "Tearing down: $PROJECT_NAME"
|
||||||
|
|
||||||
|
local result
|
||||||
|
result=$(api_call DELETE "/project/$PROJECT_NAME")
|
||||||
|
echo "$result" | jq '.'
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
print_success "Project deleted. Gitea repo preserved."
|
||||||
|
}
|
||||||
|
|
||||||
|
# Dispatch
|
||||||
|
case "$COMMAND" in
|
||||||
|
run)
|
||||||
|
run_flow
|
||||||
|
;;
|
||||||
|
status)
|
||||||
|
check_status
|
||||||
|
;;
|
||||||
|
teardown)
|
||||||
|
teardown
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unknown command: $COMMAND"
|
||||||
|
echo "Valid commands: run, status, teardown"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
@ -1,8 +1,8 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
# Feature Development E2E Test Script
|
# Scaffold Validation E2E Test Script
|
||||||
# Tests the complete feature development workflow:
|
# Tests the composable monorepo scaffold creation:
|
||||||
# 1. Create composable project with skeleton
|
# 1. Create composable project with skeleton
|
||||||
# 2. Add service component
|
# 2. Add service component
|
||||||
# 3. Add app-nextjs component
|
# 3. Add app-nextjs component
|
||||||
@ -11,7 +11,10 @@ set -euo pipefail
|
|||||||
# 6. Test auth integration
|
# 6. Test auth integration
|
||||||
# 7. Verify CI pipeline
|
# 7. Verify CI pipeline
|
||||||
#
|
#
|
||||||
# Usage: ./cookbooks/scripts/feature-test.sh <command> <project-name>
|
# NOTE: This tests scaffold creation and patterns, NOT feature development.
|
||||||
|
# For feature development E2E tests, see feature-dev-test.sh instead.
|
||||||
|
#
|
||||||
|
# Usage: ./cookbooks/scripts/scaffold-test.sh <command> <project-name>
|
||||||
# Commands: run, status, verify-patterns, teardown
|
# Commands: run, status, verify-patterns, teardown
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
|||||||
@ -56,12 +56,10 @@ func (h *ProjectManagementHandler) Mount(r api.Router) {
|
|||||||
Get("/{name}", h.Status)
|
Get("/{name}", h.Status)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Bulk operations on projects (admin only)
|
// Bulk operations - cleanup test projects (admin only)
|
||||||
r.Route("/projects", func(r chi.Router) {
|
// Note: Uses direct route to avoid conflict with /projects in projects.go
|
||||||
// Cleanup test projects - admin only for safety
|
|
||||||
r.With(auth.RequireScope(auth.ScopeAdmin)).
|
r.With(auth.RequireScope(auth.ScopeAdmin)).
|
||||||
Delete("/cleanup", h.CleanupTestProjects)
|
Delete("/projects/cleanup", h.CleanupTestProjects)
|
||||||
})
|
|
||||||
|
|
||||||
// Template endpoints (read-only)
|
// Template endpoints (read-only)
|
||||||
r.With(auth.RequireScope(auth.ScopeProjectsRead, auth.ScopeAdmin)).
|
r.With(auth.RequireScope(auth.ScopeProjectsRead, auth.ScopeAdmin)).
|
||||||
|
|||||||
@ -29,13 +29,15 @@ func NewSDLCOrchestratorHandler(orchestrator *service.SDLCOrchestratorService, l
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Mount registers orchestration routes under /projects/{id}/sdlc/.
|
// Mount registers orchestration routes under /projects/{id}/sdlc/.
|
||||||
|
// Note: Uses direct routes to avoid conflict with /projects/{id}/sdlc in sdlc.go
|
||||||
func (h *SDLCOrchestratorHandler) Mount(r api.Router) {
|
func (h *SDLCOrchestratorHandler) Mount(r api.Router) {
|
||||||
r.Route("/projects/{id}/sdlc", func(r chi.Router) {
|
|
||||||
// All orchestration operations are write operations
|
// All orchestration operations are write operations
|
||||||
r.With(auth.RequireScope(auth.ScopeProjectsExecute, auth.ScopeAdmin)).Post("/execute", h.Execute)
|
r.With(auth.RequireScope(auth.ScopeProjectsExecute, auth.ScopeAdmin)).
|
||||||
r.With(auth.RequireScope(auth.ScopeProjectsExecute, auth.ScopeAdmin)).Post("/resolve", h.Resolve)
|
Post("/projects/{id}/sdlc/execute", h.Execute)
|
||||||
r.With(auth.RequireScope(auth.ScopeProjectsExecute, auth.ScopeAdmin)).Post("/commit", h.Commit)
|
r.With(auth.RequireScope(auth.ScopeProjectsExecute, auth.ScopeAdmin)).
|
||||||
})
|
Post("/projects/{id}/sdlc/resolve", h.Resolve)
|
||||||
|
r.With(auth.RequireScope(auth.ScopeProjectsExecute, auth.ScopeAdmin)).
|
||||||
|
Post("/projects/{id}/sdlc/commit", h.Commit)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute runs the next classifier-recommended action.
|
// Execute runs the next classifier-recommended action.
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user